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

// TODO(crbug.com/549792645): Remove VLOG level override once Synced Setup
// launches.
#undef ENABLED_VLOG_LEVEL
#define ENABLED_VLOG_LEVEL 1

#include "chrome/browser/sync/chrome_sync_controller_builder.h"

#include <memory>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/security_events/security_event_recorder.h"
#include "chrome/browser/spellchecker/spellcheck_service.h"
#include "chrome/browser/themes/theme_local_data_batch_uploader.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_syncable_service.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/channel_info.h"
#include "components/desks_storage/core/desk_sync_service.h"
#include "components/spellcheck/spellcheck_buildflags.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/features.h"
#include "components/sync/base/pref_names.h"
#include "components/sync/base/report_unrecoverable_error.h"
#include "components/sync/model/data_type_controller_delegate.h"
#include "components/sync/model/data_type_store.h"
#include "components/sync/model/data_type_store_service.h"
#include "components/sync/model/forwarding_data_type_controller_delegate.h"
#include "components/sync/service/data_type_controller.h"
#include "components/sync/service/syncable_service_based_data_type_controller.h"
#include "components/sync_preferences/features.h"
#include "components/themes/cross_device/features.h"
#include "content/public/browser/browser_thread.h"

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "chrome/browser/extensions/api/storage/settings_sync_util.h"  // nogncheck
#include "chrome/browser/extensions/sync/extension_local_data_batch_uploader.h"  // nogncheck
#include "chrome/browser/extensions/sync/extension_sync_service.h"  // nogncheck
#include "chrome/browser/sync/glue/extension_data_type_controller.h"
#include "chrome/browser/sync/glue/extension_setting_data_type_controller.h"
#endif

#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service.h"
#include "chrome/browser/ash/app_list/arc/arc_package_sync_data_type_controller.h"
#include "chrome/browser/ash/app_list/arc/arc_package_syncable_service.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/floating_sso/cookie_sync_data_type_controller.h"
#include "chrome/browser/ash/floating_sso/floating_sso_service.h"
#include "chrome/browser/ash/printing/oauth2/authorization_zones_manager.h"
#include "chrome/browser/ash/printing/printers_sync_bridge.h"
#include "chrome/browser/ash/printing/synced_printers_manager.h"
#include "chromeos/ash/components/sync_wifi/wifi_configuration_sync_service.h"
#include "chromeos/ash/experiences/arc/arc_util.h"
#include "components/sync_preferences/pref_service_syncable.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/android/webapk/webapk_sync_service.h"
#include "chrome/browser/ntp_customization/ntp_android_custom_background_service.h"
#endif  // BUILDFLAG(IS_ANDROID)

ChromeSyncControllerBuilder::ChromeSyncControllerBuilder() = default;

ChromeSyncControllerBuilder::~ChromeSyncControllerBuilder() = default;

void ChromeSyncControllerBuilder::SetCrossDeviceThemeTracker(
    themes::CrossDeviceThemeTracker<LocalThemeSpecifics>*
        cross_device_theme_tracker) {
  cross_device_theme_tracker_.Set(cross_device_theme_tracker);
}

void ChromeSyncControllerBuilder::SetDataTypeStoreService(
    syncer::DataTypeStoreService* data_type_store_service) {
  data_type_store_service_.Set(data_type_store_service);
}

void ChromeSyncControllerBuilder::SetSecurityEventRecorder(
    SecurityEventRecorder* security_event_recorder) {
  security_event_recorder_.Set(security_event_recorder);
}

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
void ChromeSyncControllerBuilder::SetExtensionSyncService(
    ExtensionSyncService* extension_sync_service) {
  extension_sync_service_.Set(extension_sync_service);
}

void ChromeSyncControllerBuilder::SetExtensionSystemProfile(Profile* profile) {
  extension_system_profile_.Set(profile);
}
#endif  // BUILDFLAG(ENABLE_EXTENSIONS_CORE)

#if BUILDFLAG(ENABLE_EXTENSIONS)
void ChromeSyncControllerBuilder::SetThemeService(ThemeService* theme_service) {
  theme_service_.Set(theme_service);
}

void ChromeSyncControllerBuilder::SetWebAppProvider(
    web_app::WebAppProvider* web_app_provider) {
  web_app_provider_.Set(web_app_provider);
}
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

#if BUILDFLAG(ENABLE_SPELLCHECK)
void ChromeSyncControllerBuilder::SetSpellcheckService(
    SpellcheckService* spellcheck_service) {
  spellcheck_service_.Set(spellcheck_service);
}
#endif  // BUILDFLAG(ENABLE_SPELLCHECK)

#if BUILDFLAG(IS_ANDROID)
void ChromeSyncControllerBuilder::SetNtpAndroidCustomBackgroundService(
    NtpAndroidCustomBackgroundService* ntp_android_custom_background_service) {
  ntp_android_custom_background_service_.Set(
      ntp_android_custom_background_service);
}

void ChromeSyncControllerBuilder::SetWebApkSyncService(
    webapk::WebApkSyncService* web_apk_sync_service) {
  web_apk_sync_service_.Set(web_apk_sync_service);
}
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(IS_CHROMEOS)
void ChromeSyncControllerBuilder::SetAppListSyncableService(
    app_list::AppListSyncableService* app_list_syncable_service) {
  app_list_syncable_service_.Set(app_list_syncable_service);
}

void ChromeSyncControllerBuilder::SetAuthorizationZonesManager(
    ash::printing::oauth2::AuthorizationZonesManager*
        authorization_zones_manager) {
  authorization_zones_manager_.Set(authorization_zones_manager);
}

void ChromeSyncControllerBuilder::SetArcPackageSyncableService(
    arc::ArcPackageSyncableService* arc_package_syncable_service,
    Profile* arc_package_profile) {
  arc_package_syncable_service_.Set(arc_package_syncable_service);
  arc_package_profile_.Set(arc_package_profile);
}

void ChromeSyncControllerBuilder::SetDeskSyncService(
    desks_storage::DeskSyncService* desk_sync_service) {
  desk_sync_service_.Set(desk_sync_service);
}

void ChromeSyncControllerBuilder::SetFloatingSsoService(
    ash::floating_sso::FloatingSsoService* floating_sso_service) {
  floating_sso_service_.Set(floating_sso_service);
}

void ChromeSyncControllerBuilder::SetOsPrefServiceSyncable(
    sync_preferences::PrefServiceSyncable* os_pref_service_syncable) {
  os_pref_service_syncable_.Set(os_pref_service_syncable);
}

void ChromeSyncControllerBuilder::SetPrefService(PrefService* pref_service) {
  pref_service_.Set(pref_service);
}

void ChromeSyncControllerBuilder::SetSyncedPrintersManager(
    ash::SyncedPrintersManager* synced_printer_manager) {
  synced_printer_manager_.Set(synced_printer_manager);
}

void ChromeSyncControllerBuilder::SetWifiConfigurationSyncService(
    ash::sync_wifi::WifiConfigurationSyncService*
        wifi_configuration_sync_service) {
  wifi_configuration_sync_service_.Set(wifi_configuration_sync_service);
}
#endif  // BUILDFLAG(IS_CHROMEOS)


std::vector<std::unique_ptr<syncer::DataTypeController>>
ChromeSyncControllerBuilder::Build(syncer::SyncService* sync_service) {
  std::vector<std::unique_ptr<syncer::DataTypeController>> controllers;

  const base::RepeatingClosure dump_stack = base::BindRepeating(
      &syncer::ReportUnrecoverableError, chrome::GetChannel());

  syncer::RepeatingDataTypeStoreFactory data_type_store_factory =
      data_type_store_service_.value()->GetStoreFactory();

    syncer::DataTypeControllerDelegate* security_events_delegate =
        security_event_recorder_.value()->GetControllerDelegate().get();
    // Forward both full-sync and transport-only modes to the same delegate,
    // since behavior for SECURITY_EVENTS does not differ.
    controllers.push_back(std::make_unique<syncer::DataTypeController>(
        syncer::SECURITY_EVENTS,
        /*delegate_for_full_sync_mode=*/
        std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
            security_events_delegate),
        /*delegate_for_transport_mode=*/
        std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
            security_events_delegate)));

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
    if (extension_sync_service_.value()) {
      // TODO(crbug.com/425381293): Add an ExtensionLocalDataBatchUploader once
      // its implementation is complete.
      controllers.push_back(
          std::make_unique<browser_sync::ExtensionDataTypeController>(
              syncer::EXTENSIONS, data_type_store_factory,
              extension_sync_service_.value()->AsWeakPtr(), dump_stack,
              browser_sync::ExtensionDataTypeController::DelegateMode::
                  kTransportModeWithSingleModel,
              extension_system_profile_.value(),
              std::make_unique<extensions::ExtensionLocalDataBatchUploader>(
                  extension_system_profile_.value())));

      controllers.push_back(
          std::make_unique<browser_sync::ExtensionSettingDataTypeController>(
              syncer::EXTENSION_SETTINGS, data_type_store_factory,
              extensions::settings_sync_util::GetSyncableServiceProvider(
                  extension_system_profile_.value(),
                  syncer::EXTENSION_SETTINGS),
              dump_stack,
              browser_sync::ExtensionSettingDataTypeController::DelegateMode::
                  kTransportModeWithSingleModel,
              extension_system_profile_.value()));
    }
#endif  // BUILDFLAG(ENABLE_EXTENSIONS_CORE)

#if BUILDFLAG(ENABLE_EXTENSIONS)
    if (extension_sync_service_.value()) {
      controllers.push_back(
          std::make_unique<browser_sync::ExtensionDataTypeController>(
              syncer::APPS, data_type_store_factory,
              extension_sync_service_.value()->AsWeakPtr(), dump_stack,
#if BUILDFLAG(IS_CHROMEOS)
              base::FeatureList::IsEnabled(
                  syncer::kReplaceSyncPromosWithSignInPromos)
                  ? browser_sync::ExtensionDataTypeController::DelegateMode::
                        kTransportModeWithSingleModel
                  : browser_sync::ExtensionDataTypeController::DelegateMode::
                        kLegacyFullSyncModeOnly,
#else
              browser_sync::ExtensionDataTypeController::DelegateMode::
                  kLegacyFullSyncModeOnly,
#endif  // BUILDFLAG(IS_CHROMEOS)
              extension_system_profile_.value()));

      controllers.push_back(
          std::make_unique<browser_sync::ExtensionSettingDataTypeController>(
              syncer::APP_SETTINGS, data_type_store_factory,
              extensions::settings_sync_util::GetSyncableServiceProvider(
                  extension_system_profile_.value(), syncer::APP_SETTINGS),
              dump_stack,
#if BUILDFLAG(IS_CHROMEOS)
              base::FeatureList::IsEnabled(
                  syncer::kReplaceSyncPromosWithSignInPromos)
                  ? browser_sync::ExtensionSettingDataTypeController::
                        DelegateMode::kTransportModeWithSingleModel
                  : browser_sync::ExtensionSettingDataTypeController::
                        DelegateMode::kLegacyFullSyncModeOnly,
#else
              browser_sync::ExtensionSettingDataTypeController::DelegateMode::
                  kLegacyFullSyncModeOnly,
#endif  // BUILDFLAG(IS_CHROMEOS)
              extension_system_profile_.value()));
    }

    if (theme_service_.value()) {
      controllers.push_back(
          std::make_unique<browser_sync::ExtensionDataTypeController>(
              syncer::THEMES, data_type_store_factory,
              theme_service_.value()->GetThemeSyncableService()->AsWeakPtr(),
              dump_stack,
              base::FeatureList::IsEnabled(
                  syncer::kSeparateLocalAndAccountThemes)
                  ? browser_sync::ExtensionDataTypeController::DelegateMode::
                        kTransportModeWithSingleModel
                  : browser_sync::ExtensionDataTypeController::DelegateMode::
                        kLegacyFullSyncModeOnly,
              extension_system_profile_.value(),
              // SyncService depends on ThemeService. So the
              // ThemeSyncableService instance should outlive the controller.
              std::make_unique<ThemeLocalDataBatchUploader>(
                  theme_service_.value()->GetThemeSyncableService())));
    }

    if (web_app_provider_.value()) {
      syncer::DataTypeControllerDelegate* delegate =
          web_app_provider_.value()
              ->sync_bridge_unsafe()
              .change_processor()
              ->GetControllerDelegate()
              .get();

      controllers.push_back(std::make_unique<syncer::DataTypeController>(
          syncer::WEB_APPS,
          /*delegate_for_full_sync_mode=*/
          std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
              delegate),
          /*delegate_for_transport_mode=*/
          std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
              delegate)
          ));
    }
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

#if BUILDFLAG(IS_ANDROID)
    if (ntp_android_custom_background_service_.value()) {
      syncer::DataTypeControllerDelegate* delegate =
          ntp_android_custom_background_service_.value()
              ->GetSyncControllerDelegate()
              .get();
      if (delegate) {
        controllers.push_back(std::make_unique<syncer::DataTypeController>(
            syncer::THEMES_ANDROID,
            /*delegate_for_full_sync_mode=*/
            std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
                delegate),
            /*delegate_for_transport_mode=*/
            std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
                delegate)));
      }
    }

    if (web_apk_sync_service_.value()) {
      syncer::DataTypeControllerDelegate* delegate =
          web_apk_sync_service_.value()->GetDataTypeControllerDelegate().get();
      controllers.push_back(std::make_unique<syncer::DataTypeController>(
          syncer::WEB_APKS,
          /*delegate_for_full_sync_mode=*/
          std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
              delegate),
          /*delegate_for_transport_mode=*/
          std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
              delegate)));
    }
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(ENABLE_SPELLCHECK)
    // Chrome prefers OS provided spell checkers where they exist. So only sync
    // the custom dictionary on platforms that typically don't provide one.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
    // Dictionary sync is enabled by default.
    if (spellcheck_service_.value()) {
      controllers.push_back(
          std::make_unique<syncer::SyncableServiceBasedDataTypeController>(
              syncer::DICTIONARY, data_type_store_factory,
              spellcheck_service_.value()->GetCustomDictionary()->AsWeakPtr(),
              dump_stack,
              base::FeatureList::IsEnabled(
                  syncer::kSpellcheckSeparateLocalAndAccountDictionaries)
                  ? syncer::SyncableServiceBasedDataTypeController::
                        DelegateMode::kTransportModeWithSingleModel
                  : syncer::SyncableServiceBasedDataTypeController::
                        DelegateMode::kLegacyFullSyncModeOnly));
    }
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
#endif  // BUILDFLAG(ENABLE_SPELLCHECK)

#if BUILDFLAG(IS_CHROMEOS)
    CHECK(os_pref_service_syncable_.value());
    controllers.push_back(
        std::make_unique<syncer::SyncableServiceBasedDataTypeController>(
            syncer::OS_PREFERENCES, data_type_store_factory,
            os_pref_service_syncable_.value()
                ->GetSyncableService(syncer::OS_PREFERENCES)
                ->AsWeakPtr(),
            dump_stack,
            syncer::SyncableServiceBasedDataTypeController::DelegateMode::
                kTransportModeWithSingleModel));
    controllers.push_back(
        std::make_unique<syncer::SyncableServiceBasedDataTypeController>(
            syncer::OS_PRIORITY_PREFERENCES, data_type_store_factory,
            os_pref_service_syncable_.value()
                ->GetSyncableService(syncer::OS_PRIORITY_PREFERENCES)
                ->AsWeakPtr(),
            dump_stack,
            syncer::SyncableServiceBasedDataTypeController::DelegateMode::
                kTransportModeWithSingleModel));

    CHECK(synced_printer_manager_.value());
    syncer::DataTypeControllerDelegate* printers_delegate =
        synced_printer_manager_.value()
            ->GetSyncBridge()
            ->change_processor()
            ->GetControllerDelegate()
            .get();
    controllers.push_back(std::make_unique<syncer::DataTypeController>(
        syncer::PRINTERS,
        /*delegate_for_full_sync_mode=*/
        std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
            printers_delegate),
        /*delegate_for_transport_mode=*/
        base::FeatureList::IsEnabled(syncer::kReplaceSyncPromosWithSignInPromos)
            ? std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
                  printers_delegate)
            : nullptr));

    // Some profile types (e.g. sign-in screen) don't support app list.
    // Temporarily Disable AppListSyncableService for tablet form factor
    // devices. See crbug.com/40652815 for details.
    if (app_list_syncable_service_.value() &&
        !ash::switches::IsTabletFormFactor()) {
      // Runs in sync transport-mode and full-sync mode.
      controllers.push_back(
          std::make_unique<syncer::SyncableServiceBasedDataTypeController>(
              syncer::APP_LIST, data_type_store_factory,
              app_list_syncable_service_.value()->AsWeakPtr(), dump_stack,
              syncer::SyncableServiceBasedDataTypeController::DelegateMode::
                  kTransportModeWithSingleModel));
    }

    if (arc_package_syncable_service_.value()) {
      controllers.push_back(std::make_unique<ArcPackageSyncDataTypeController>(
          data_type_store_factory,
          arc_package_syncable_service_.value()->AsWeakPtr(), dump_stack,
          sync_service, arc_package_profile_.value()));
    }

    if (wifi_configuration_sync_service_.value()) {
      syncer::DataTypeControllerDelegate* wifi_configurations_delegate =
          wifi_configuration_sync_service_.value()
              ->GetControllerDelegate()
              .get();
      controllers.push_back(std::make_unique<syncer::DataTypeController>(
          syncer::WIFI_CONFIGURATIONS,
          /*delegate_for_full_sync_mode=*/
          std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
              wifi_configurations_delegate),
          /*delegate_for_transport_mode=*/
          base::FeatureList::IsEnabled(
              syncer::kReplaceSyncPromosWithSignInPromos)
              ? std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
                    wifi_configurations_delegate)
              : nullptr));
    }

    CHECK(desk_sync_service_.value());
    controllers.push_back(std::make_unique<syncer::DataTypeController>(
        syncer::WORKSPACE_DESK,
        /*delegate_for_full_sync_mode=*/
        std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
            desk_sync_service_.value()->GetControllerDelegate().get()),
        /*delegate_for_transport_mode=*/
        base::FeatureList::IsEnabled(syncer::kReplaceSyncPromosWithSignInPromos)
            ? std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
                  desk_sync_service_.value()->GetControllerDelegate().get())
            : nullptr));

    if (authorization_zones_manager_.value()) {
      syncer::DataTypeControllerDelegate*
          printers_authorization_servers_delegate =
              authorization_zones_manager_.value()
                  ->GetDataTypeSyncBridge()
                  ->change_processor()
                  ->GetControllerDelegate()
                  .get();
      controllers.push_back(std::make_unique<syncer::DataTypeController>(
          syncer::PRINTERS_AUTHORIZATION_SERVERS,
          /*delegate_for_full_sync_mode=*/
          std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
              printers_authorization_servers_delegate),
          /*delegate_for_transport_mode=*/
          base::FeatureList::IsEnabled(
              syncer::kReplaceSyncPromosWithSignInPromos)
              ? std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
                    printers_authorization_servers_delegate)
              : nullptr));
    }

    if (floating_sso_service_.value()) {
      syncer::DataTypeControllerDelegate* delegate =
          floating_sso_service_.value()->GetControllerDelegate().get();
      controllers.push_back(
          std::make_unique<ash::floating_sso::CookieSyncDataTypeController>(
              /*delegate_for_full_sync_mode=*/
              std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
                  delegate),
              /*delegate_for_transport_mode=*/
              base::FeatureList::IsEnabled(
                  syncer::kReplaceSyncPromosWithSignInPromos)
                  ? std::make_unique<
                        syncer::ForwardingDataTypeControllerDelegate>(delegate)
                  : nullptr,
              sync_service, pref_service_.value()));
    }
#endif  // BUILDFLAG(IS_CHROMEOS)

    if (auto tracker = cross_device_theme_tracker_.value();
        tracker &&
        base::FeatureList::IsEnabled(themes::kCrossDeviceThemeTracker)) {
      // TODO(crbug.com/549792645): Remove debug logs once Synced Setup
      // launches.
      VLOG_IF(1,
              base::FeatureList::IsEnabled(
                  sync_preferences::features::kCrossDevicePrefTrackerExtraLogs))
          << "XplatSyncedSetup, " << __func__ << ": tracker present";
      // CrossDeviceThemeTracker tracks remote themes across devices (e.g.
      // Desktop and iOS themes on Android, or Android and iOS themes on
      // Desktop) for Synced Set Up import. Continuous 2-way sync for Android's
      // own NTP customizations (syncer::THEMES_ANDROID) is managed separately
      // above by NtpAndroidCustomBackgroundService (guarded by
      // syncer::kNewTabPageCustomizationThemeSync).
      for (syncer::DataType type :
           {syncer::THEMES, syncer::THEMES_ANDROID, syncer::THEMES_IOS}) {
        if (auto* delegate = tracker->GetSyncDelegateForType(type).get()) {
          VLOG_IF(
              1,
              base::FeatureList::IsEnabled(
                  sync_preferences::features::kCrossDevicePrefTrackerExtraLogs))
              << "XplatSyncedSetup, " << __func__ << ": "
              << syncer::DataTypeToDebugString(type) << " delegate present";
          controllers.push_back(std::make_unique<syncer::DataTypeController>(
              type,
              /*delegate_for_full_sync_mode=*/
              std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
                  delegate),
              /*delegate_for_transport_mode=*/
              std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
                  delegate)));
        }
      }
    }

    return controllers;
}
