// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/web/webui/web_ui_ios_data_source_impl.h"

#import <string>

#import "base/containers/map_util.h"
#import "base/functional/bind.h"
#import "base/memory/raw_ptr.h"
#import "base/memory/ref_counted_memory.h"
#import "base/strings/string_util.h"
#import "base/strings/utf_string_conversions.h"
#import "ios/web/public/web_client.h"
#import "ui/base/webui/jstemplate_builder.h"
#import "ui/base/webui/resource_path.h"
#import "ui/base/webui/web_ui_util.h"

namespace web {

// static
WebUIIOSDataSource* WebUIIOSDataSource::Create(std::string_view source_name) {
  return new WebUIIOSDataSourceImpl(source_name);
}

// static
void WebUIIOSDataSource::Add(BrowserState* browser_state,
                             WebUIIOSDataSource* source) {
  URLDataManagerIOS::AddWebUIIOSDataSource(browser_state, source);
}

// Internal class to hide the fact that WebUIIOSDataSourceImpl implements
// URLDataSourceIOS.
class WebUIIOSDataSourceImpl::InternalDataSource : public URLDataSourceIOS {
 public:
  InternalDataSource(WebUIIOSDataSourceImpl* parent) : parent_(parent) {}

  ~InternalDataSource() override {}

  // URLDataSourceIOS implementation.
  std::string GetSource() const override { return parent_->GetSource(); }
  std::string GetMimeType(std::string_view path) const override {
    return parent_->GetMimeType(path);
  }
  void StartDataRequest(std::string_view path,
                        URLDataSourceIOS::GotDataCallback callback) override {
    return parent_->StartDataRequest(path, std::move(callback));
  }
  bool ShouldReplaceExistingSource() const override {
    return parent_->replace_existing_source_;
  }
  bool ShouldReplaceI18nInJS() const override {
    return parent_->ShouldReplaceI18nInJS();
  }
  bool AllowCaching() const override { return false; }
  bool ShouldDenyXFrameOptions() const override {
    return parent_->deny_xframe_options_;
  }

 private:
  raw_ptr<WebUIIOSDataSourceImpl> parent_;
};

WebUIIOSDataSourceImpl::WebUIIOSDataSourceImpl(std::string_view source_name)
    : URLDataSourceIOSImpl(source_name, new InternalDataSource(this)),
      source_name_(source_name),
      default_resource_(-1),
      deny_xframe_options_(true),
      load_time_data_defaults_added_(false),
      replace_existing_source_(true),
      should_replace_i18n_in_js_(false) {}

WebUIIOSDataSourceImpl::~WebUIIOSDataSourceImpl() {}

void WebUIIOSDataSourceImpl::AddString(const std::string& name,
                                       const std::u16string& value) {
  localized_strings_.Set(name, value);
  replacements_[name] = base::UTF16ToUTF8(value);
}

void WebUIIOSDataSourceImpl::AddString(const std::string& name,
                                       const std::string& value) {
  localized_strings_.Set(name, value);
  replacements_[name] = value;
}

void WebUIIOSDataSourceImpl::AddLocalizedString(const std::string& name,
                                                int ids) {
  localized_strings_.Set(name, GetWebClient()->GetLocalizedString(ids));
  replacements_[name] =
      base::UTF16ToUTF8(GetWebClient()->GetLocalizedString(ids));
}

void WebUIIOSDataSourceImpl::AddLocalizedStrings(
    const base::DictValue& localized_strings) {
  localized_strings_.Merge(localized_strings.Clone());
  ui::TemplateReplacementsFromDictionaryValue(localized_strings,
                                              &replacements_);
}

void WebUIIOSDataSourceImpl::AddLocalizedStrings(
    base::span<const webui::LocalizedString> strings) {
  for (const auto& str : strings) {
    AddLocalizedString(str.name, str.id);
  }
}

void WebUIIOSDataSourceImpl::AddBoolean(const std::string& name, bool value) {
  localized_strings_.Set(name, value);
}

void WebUIIOSDataSourceImpl::UseStringsJs() {
  use_strings_js_ = true;
}

void WebUIIOSDataSourceImpl::EnableReplaceI18nInJS() {
  should_replace_i18n_in_js_ = true;
}

bool WebUIIOSDataSourceImpl::ShouldReplaceI18nInJS() const {
  return should_replace_i18n_in_js_;
}

void WebUIIOSDataSourceImpl::AddResourcePath(const std::string& path,
                                             int resource_id) {
  path_to_idr_map_[path] = resource_id;
}

void WebUIIOSDataSourceImpl::AddResourcePaths(
    base::span<const webui::ResourcePath> paths) {
  for (const auto& path : paths) {
    AddResourcePath(path.path, path.id);
  }
}

void WebUIIOSDataSourceImpl::SetDefaultResource(int resource_id) {
  default_resource_ = resource_id;
}

void WebUIIOSDataSourceImpl::DisableDenyXFrameOptions() {
  deny_xframe_options_ = false;
}

const ui::TemplateReplacements* WebUIIOSDataSourceImpl::GetReplacements()
    const {
  return &replacements_;
}

std::string WebUIIOSDataSourceImpl::GetSource() const {
  return source_name_;
}

std::string WebUIIOSDataSourceImpl::GetMimeType(std::string_view path) const {
  if (base::EndsWith(path, ".js", base::CompareCase::INSENSITIVE_ASCII)) {
    return "application/javascript";
  }

  if (base::EndsWith(path, ".json", base::CompareCase::INSENSITIVE_ASCII)) {
    return "application/json";
  }

  if (base::EndsWith(path, ".pdf", base::CompareCase::INSENSITIVE_ASCII)) {
    return "application/pdf";
  }

  if (base::EndsWith(path, ".css", base::CompareCase::INSENSITIVE_ASCII)) {
    return "text/css";
  }

  if (base::EndsWith(path, ".svg", base::CompareCase::INSENSITIVE_ASCII)) {
    return "image/svg+xml";
  }

  return "text/html";
}

void WebUIIOSDataSourceImpl::EnsureLoadTimeDataDefaultsAdded() {
  if (load_time_data_defaults_added_) {
    return;
  }

  load_time_data_defaults_added_ = true;
  base::DictValue defaults;
  webui::SetLoadTimeDataDefaults(web::GetWebClient()->GetApplicationLocale(),
                                 &defaults);
  AddLocalizedStrings(defaults);
}

void WebUIIOSDataSourceImpl::StartDataRequest(
    std::string_view path,
    URLDataSourceIOS::GotDataCallback callback) {
  EnsureLoadTimeDataDefaultsAdded();

  if (use_strings_js_) {
    bool from_js_module = path == "strings.m.js";
    if (from_js_module || path == "strings.js") {
      SendLocalizedStringsAsJSON(std::move(callback), from_js_module);
      return;
    }
  }

  int resource_id = PathToIdrOrDefault(path);
  DCHECK_NE(resource_id, -1);
  scoped_refptr<base::RefCountedMemory> response(
      GetWebClient()->GetDataResourceBytes(resource_id));
  std::move(callback).Run(response);
}

void WebUIIOSDataSourceImpl::SendLocalizedStringsAsJSON(
    URLDataSourceIOS::GotDataCallback callback,
    bool from_js_module) {
  std::string template_data;
  webui::AppendJsonJS(localized_strings_, &template_data, from_js_module);
  std::move(callback).Run(
      base::MakeRefCounted<base::RefCountedString>(std::move(template_data)));
}

int WebUIIOSDataSourceImpl::PathToIdrOrDefault(std::string_view path) const {
  const int* idr = base::FindOrNull(path_to_idr_map_, path);
  return idr ? *idr : default_resource_;
}

}  // namespace web
