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

#include "chrome/browser/ui/webui/signin/sync_confirmation_ui.h"

#include <string>

#include "base/check_deref.h"
#include "base/feature_list.h"
#include "base/json/json_writer.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/buildflag.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/regional_capabilities/regional_capabilities_service_factory.h"
#include "chrome/browser/signin/account_consistency_mode_manager.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/profiles/profile_colors_util.h"
#include "chrome/browser/ui/webui/signin/signin_url_utils.h"
#include "chrome/browser/ui/webui/signin/sync_confirmation_handler.h"
#include "chrome/common/themes/autogenerated_theme_util.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/signin_resources.h"
#include "components/regional_capabilities/regional_capabilities_service.h"
#include "components/signin/public/base/avatar_icon_util.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_user_settings.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/webui/resource_path.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/gfx/color_utils.h"
#include "ui/native_theme/native_theme.h"
#include "ui/webui/webui_util.h"

namespace {
const char kSyncBenefitAutofillStringName[] = "syncConfirmationAutofill";
const char kSyncBenefitBookmarksStringName[] = "syncConfirmationBookmarks";
const char kSyncBenefitReadingListStringName[] = "syncConfirmationReadingList";
const char kSyncBenefitExtensionsStringName[] = "syncConfirmationExtensions";
const char kSyncBenefitHistoryAndMoreStringName[] =
    "syncConfirmationHistoryAndMore";
const char kSyncBenefitIconNameKey[] = "iconName";
const char kSyncBenefitTitleKey[] = "title";

bool IsAnyTypeSyncable(const syncer::SyncService* sync_service,
                       syncer::UserSelectableTypeSet types) {
  if (!sync_service) {
    return false;
  }
  for (auto type : types) {
    if (!sync_service->GetUserSettings()->IsTypeManagedByPolicy(type)) {
      return true;
    }
  }
  return false;
}

}  // namespace

bool SyncConfirmationUIConfig::IsWebUIEnabled(
    content::BrowserContext* browser_context) {
  Profile* profile = Profile::FromBrowserContext(browser_context);
  return !profile->IsOffTheRecord();
}

// static
std::string SyncConfirmationUI::GetSyncBenefitsListJSON(
    const syncer::SyncService* sync_service) {
  using syncer::UserSelectableType;
  base::ListValue sync_benefits_list;

  if (IsAnyTypeSyncable(sync_service, {UserSelectableType::kBookmarks,
                                       UserSelectableType::kReadingList})) {
    std::string titleKey;
    if (IsAnyTypeSyncable(sync_service, {UserSelectableType::kBookmarks})) {
      titleKey = kSyncBenefitBookmarksStringName;
    } else {
      titleKey = kSyncBenefitReadingListStringName;
    }

    base::DictValue bookmarks;
    bookmarks.Set(kSyncBenefitTitleKey, titleKey);
    bookmarks.Set(kSyncBenefitIconNameKey,
                  (base::FeatureList::IsEnabled(features::kWebUIRoundedIcons)
                       ? "signin:star"
                       : "signin:star-outline-old"));
    sync_benefits_list.Append(std::move(bookmarks));
  }

  if (IsAnyTypeSyncable(sync_service, {UserSelectableType::kAutofill,
                                       UserSelectableType::kPasswords})) {
    base::DictValue autofill;
    autofill.Set(kSyncBenefitTitleKey, kSyncBenefitAutofillStringName);
    autofill.Set(kSyncBenefitIconNameKey,
                 (base::FeatureList::IsEnabled(features::kWebUIRoundedIcons)
                      ? "signin:assignment"
                      : "signin:assignment-outline-old"));
    sync_benefits_list.Append(std::move(autofill));
  }

  if (IsAnyTypeSyncable(sync_service, {UserSelectableType::kExtensions,
                                       UserSelectableType::kApps})) {
    base::DictValue extensions;
    extensions.Set(kSyncBenefitTitleKey, kSyncBenefitExtensionsStringName);
    extensions.Set(kSyncBenefitIconNameKey,
                   (base::FeatureList::IsEnabled(features::kWebUIRoundedIcons)
                        ? "signin:chrome-extension"
                        : "signin:extension-outline-old"));
    sync_benefits_list.Append(std::move(extensions));
  }

  // Even if no associated type is syncable, we still deliberately show "History
  // and more". So no need to check it.
  base::DictValue history_and_more;
  history_and_more.Set(kSyncBenefitTitleKey,
                       kSyncBenefitHistoryAndMoreStringName);
  history_and_more.Set(
      kSyncBenefitIconNameKey,
      (base::FeatureList::IsEnabled(features::kWebUIRoundedIcons)
           ? "signin:devices"
           : "signin:devices-old"));
  sync_benefits_list.Append(std::move(history_and_more));

  return base::WriteJson(sync_benefits_list).value_or("");
}

SyncConfirmationUI::SyncConfirmationUI(content::WebUI* web_ui)
    : SigninWebDialogUI(web_ui), profile_(Profile::FromWebUI(web_ui)) {
  const GURL& url = web_ui->GetWebContents()->GetVisibleURL();
  const bool is_sync_allowed = SyncServiceFactory::IsSyncAllowed(profile_);

  content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
      profile_, chrome::kChromeUISyncConfirmationHost);
  webui::SetJSModuleDefaults(source);
  webui::EnableTrustedTypesCSP(source);
  // Per https//issues.chromium.org/issues/40091019 this WebUI issues direct
  // network requests for images, so allow them from anywhere for this UI only.
  source->OverrideContentSecurityPolicy(
      network::mojom::CSPDirectiveName::ImgSrc,
      "img-src * data: blob: 'self';");

  static constexpr webui::ResourcePath kResources[] = {
      {"icons.html.js", IDR_SIGNIN_ICONS_HTML_JS},
      {"signin_shared.css.js", IDR_SIGNIN_SIGNIN_SHARED_CSS_JS},
      {"signin_vars.css.js", IDR_SIGNIN_SIGNIN_VARS_CSS_JS},
      {"tangible_sync_style_shared.css.js",
       IDR_SIGNIN_TANGIBLE_SYNC_STYLE_SHARED_CSS_JS},
      {"sync_confirmation_browser_proxy.js",
       IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_CONFIRMATION_BROWSER_PROXY_JS},
      {"sync_confirmation.js",
       IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_CONFIRMATION_JS},
      {"sync_confirmation_refresh.js",
       IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_CONFIRMATION_REFRESH_JS},
      {chrome::kChromeUISyncConfirmationLoadingPath,
       IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_LOADING_CONFIRMATION_HTML},
  };
  source->AddResourcePaths(kResources);

  AddStringResource(source, "syncLoadingConfirmationTitle",
                    IDS_SYNC_LOADING_CONFIRMATION_TITLE);

  bool is_first_run_desktop_refresh_enabled = false;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
  const bool is_in_search_engine_choice_region =
      CHECK_DEREF(regional_capabilities::RegionalCapabilitiesServiceFactory::
                      GetForProfile(profile_))
          .IsInSearchEngineChoiceScreenRegion();
  is_first_run_desktop_refresh_enabled =
      switches::IsFirstRunDesktopRefreshEnabled(
          is_in_search_engine_choice_region);
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
  source->AddBoolean("isFirstRunDesktopRefreshEnabled",
                     is_first_run_desktop_refresh_enabled);

  if (is_sync_allowed) {
    InitializeForSyncConfirmation(source, GetSyncConfirmationStyle(url),
                                  IsSyncConfirmationPromo(url),
                                  is_first_run_desktop_refresh_enabled);
  } else {
    InitializeForSyncDisabled(source);
  }

  base::DictValue strings;
  webui::SetLoadTimeDataDefaults(g_browser_process->GetApplicationLocale(),
                                 &strings);
  source->AddLocalizedStrings(strings);

  if (url.GetQuery().find("debug") != std::string::npos) {
    // Not intended to be hooked to anything. The dialog will not initialize it
    // so we force it here.
    InitializeMessageHandlerWithBrowser(nullptr);
  }
}

SyncConfirmationUI::~SyncConfirmationUI() = default;

void SyncConfirmationUI::InitializeMessageHandlerWithBrowser(
    BrowserWindowInterface* browser) {
  web_ui()->AddMessageHandler(std::make_unique<SyncConfirmationHandler>(
      profile_, js_localized_string_to_ids_map_, browser));
}

void SyncConfirmationUI::InitializeForSyncConfirmation(
    content::WebUIDataSource* source,
    SyncConfirmationStyle style,
    bool is_sync_promo,
    bool is_first_run_desktop_refresh_enabled) {
  int info_title_id = IDS_SYNC_CONFIRMATION_TANGIBLE_SYNC_INFO_TITLE;
  int info_desc_id = IDS_SYNC_CONFIRMATION_TANGIBLE_SYNC_INFO_DESC;
  int confirm_label_id = IDS_SYNC_CONFIRMATION_CONFIRM_BUTTON_LABEL;
  int undo_label_id = IDS_CANCEL;

  source->AddResourcePath("images/dialog_illustration.svg",
                          IDR_SIGNIN_IMAGES_SHARED_DIALOG_ILLUSTRATION_SVG);
  source->AddResourcePath(
      "images/dialog_illustration_dark.svg",
      IDR_SIGNIN_IMAGES_SHARED_DIALOG_ILLUSTRATION_DARK_SVG);
  source->AddResourcePath("images/window_left_illustration.svg",
                          IDR_SIGNIN_IMAGES_SHARED_LEFT_BANNER_SVG);
  source->AddResourcePath("images/window_left_illustration_dark.svg",
                          IDR_SIGNIN_IMAGES_SHARED_LEFT_BANNER_DARK_SVG);
  source->AddResourcePath("images/window_right_illustration.svg",
                          IDR_SIGNIN_IMAGES_SHARED_RIGHT_BANNER_SVG);
  source->AddResourcePath("images/window_right_illustration_dark.svg",
                          IDR_SIGNIN_IMAGES_SHARED_RIGHT_BANNER_DARK_SVG);
  source->AddResourcePath(
      "sync_confirmation_app.js",
      IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_CONFIRMATION_APP_JS);
  source->AddResourcePath(
      "sync_confirmation_app.css.js",
      IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_CONFIRMATION_APP_CSS_JS);
  source->AddResourcePath(
      "sync_confirmation_app.html.js",
      IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_CONFIRMATION_APP_HTML_JS);
  source->SetDefaultResource(
      IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_CONFIRMATION_HTML);

  if (is_first_run_desktop_refresh_enabled) {
    source->SetDefaultResource(
        IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_CONFIRMATION_REFRESH_HTML);
    source->AddResourcePath(
        "sync_confirmation_app_refresh.js",
        IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_CONFIRMATION_APP_REFRESH_JS);
    source->AddResourcePath(
        "sync_confirmation_app_refresh.css.js",
        IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_CONFIRMATION_APP_REFRESH_CSS_JS);
    source->AddResourcePath(
        "sync_confirmation_app_refresh.html.js",
        IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_CONFIRMATION_APP_REFRESH_HTML_JS);
    source->AddResourcePath(
        "images/shared_gradient_light_background.svg",
        IDR_SIGNIN_IMAGES_SHARED_GRADIENT_LIGHT_BACKGROUND_SVG);
    source->AddResourcePath(
        "images/shared_gradient_dark_background.svg",
        IDR_SIGNIN_IMAGES_SHARED_GRADIENT_DARK_BACKGROUND_SVG);
  }

  // TODO(crbug.com/40242558): Refactor SyncConfirmationStyle based on the
  // purpose instead of what kind of container the page is displayed in.
  bool is_modal_dialog;
  switch (style) {
    case SyncConfirmationStyle::kDefaultModal:
      is_modal_dialog = true;
      break;
    case SyncConfirmationStyle::kSigninInterceptModal:
      is_modal_dialog = true;
      break;
    case SyncConfirmationStyle::kWindow:
      is_modal_dialog = false;
      break;
  }

  bool is_signin_intercept_promo =
      style == SyncConfirmationStyle::kSigninInterceptModal;
  bool use_clickable_sync_info_desc = false;

  source->AddBoolean("isModalDialog", is_modal_dialog);

  source->AddString("accountPictureUrl",
                    profiles::GetPlaceholderAvatarIconUrl());

  source->AddString(
      "syncBenefitsList",
      GetSyncBenefitsListJSON(SyncServiceFactory::GetForProfile(profile_)));

  // Default overrides without placeholders
  if (is_signin_intercept_promo) {
    info_title_id =
        IDS_SYNC_CONFIRMATION_TANGIBLE_SYNC_INFO_TITLE_SIGNIN_INTERCEPT_V2;
    confirm_label_id = IDS_SYNC_CONFIRMATION_TURN_ON_SYNC_BUTTON_LABEL;
  }
  if (is_sync_promo) {
    undo_label_id = IDS_NO_THANKS;
  }

  // Registering and resolving the strings with placeholders
  if (is_signin_intercept_promo) {
    ProfileAttributesEntry* entry =
        g_browser_process->profile_manager()
            ->GetProfileAttributesStorage()
            .GetProfileAttributesWithPath(profile_->GetPath());
    DCHECK(entry);
    std::u16string gaia_name = entry->GetGAIANameToDisplay();
    if (gaia_name.empty()) {
      gaia_name = entry->GetLocalProfileName();
    }
    AddStringResourceWithPlaceholder(
        source, "syncConfirmationTitle",
        IDS_SYNC_CONFIRMATION_WELCOME_TITLE_SIGNIN_INTERCEPT, gaia_name);
  } else {
    AddStringResource(source, "syncConfirmationTitle",
                      IDS_SYNC_CONFIRMATION_TANGIBLE_SYNC_TITLE);
  }

  // Registering and resolving the strings without placeholders
  AddStringResource(source, "syncConfirmationSyncInfoTitle", info_title_id);
  AddStringResource(source, "syncConfirmationConfirmLabel", confirm_label_id);
  AddStringResource(source, "syncConfirmationUndoLabel", undo_label_id);
  AddStringResource(source, "syncConfirmationSyncInfoDesc", info_desc_id);
  AddStringResource(source, "syncConfirmationSettingsLabel",
                    IDS_SYNC_CONFIRMATION_SETTINGS_BUTTON_LABEL);
  AddStringResource(source, "syncConfirmationSettingsInfo",
                    IDS_SYNC_CONFIRMATION_SETTINGS_INFO);
  AddStringResource(source, kSyncBenefitBookmarksStringName,
                    IDS_SYNC_CONFIRMATION_TANGIBLE_SYNC_BOOKMARKS);
  AddStringResource(source, kSyncBenefitReadingListStringName,
                    IDS_SYNC_CONFIRMATION_TANGIBLE_SYNC_READING_LIST);
  AddStringResource(source, kSyncBenefitAutofillStringName,
                    IDS_SYNC_CONFIRMATION_TANGIBLE_SYNC_AUTOFILL);
  AddStringResource(source, kSyncBenefitExtensionsStringName,
                    IDS_SYNC_CONFIRMATION_TANGIBLE_SYNC_EXTENSIONS);
  AddStringResource(source, kSyncBenefitHistoryAndMoreStringName,
                    IDS_SYNC_CONFIRMATION_TANGIBLE_SYNC_HISTORY_AND_MORE);

  // Registering other variables that are computed above based on multiple
  // factors (e.g. platform).
  source->AddBoolean("useClickableSyncInfoDesc", use_clickable_sync_info_desc);
}

void SyncConfirmationUI::InitializeForSyncDisabled(
    content::WebUIDataSource* source) {
  source->SetDefaultResource(
      IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_DISABLED_CONFIRMATION_HTML);
  source->AddResourcePath(
      "sync_disabled_confirmation_app.js",
      IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_DISABLED_CONFIRMATION_APP_JS);
  source->AddResourcePath(
      "sync_disabled_confirmation_app.css.js",
      IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_DISABLED_CONFIRMATION_APP_CSS_JS);
  source->AddResourcePath(
      "sync_disabled_confirmation_app.html.js",
      IDR_SIGNIN_SYNC_CONFIRMATION_SYNC_DISABLED_CONFIRMATION_APP_HTML_JS);

  bool managed_account_signout_disallowed =
      enterprise_util::UserAcceptedAccountManagement(profile_);

  source->AddBoolean("signoutDisallowed", managed_account_signout_disallowed);
  AddStringResource(source, "syncDisabledConfirmationTitle",
                    IDS_SYNC_DISABLED_CONFIRMATION_CHROME_SYNC_TITLE);
  AddStringResource(source, "syncDisabledConfirmationDetails",
                    IDS_SYNC_DISABLED_CONFIRMATION_DETAILS);
  AddStringResource(
      source, "syncDisabledConfirmationConfirmLabel",
      managed_account_signout_disallowed
          ? IDS_SYNC_DISABLED_CONFIRMATION_CONFIRM_BUTTON_MANAGED_ACCOUNT_SIGNOUT_DISALLOWED_LABEL
          : IDS_SYNC_DISABLED_CONFIRMATION_CONFIRM_BUTTON_LABEL);
  AddStringResource(source, "syncDisabledConfirmationUndoLabel",
                    IDS_SYNC_DISABLED_CONFIRMATION_UNDO_BUTTON_LABEL);
}

void SyncConfirmationUI::AddStringResource(content::WebUIDataSource* source,
                                           const std::string& name,
                                           int ids) {
  source->AddLocalizedString(name, ids);
  AddLocalizedStringToIdsMap(l10n_util::GetStringUTF8(ids), ids);
}

void SyncConfirmationUI::AddStringResourceWithPlaceholder(
    content::WebUIDataSource* source,
    const std::string& name,
    int ids,
    const std::u16string& parameter) {
  std::string localized_string = l10n_util::GetStringFUTF8(ids, parameter);
  source->AddString(name, localized_string);
  AddLocalizedStringToIdsMap(localized_string, ids);
}

void SyncConfirmationUI::AddLocalizedStringToIdsMap(
    const std::string& localized_string,
    int ids) {
  js_localized_string_to_ids_map_[localized_string] = ids;
}
