// Copyright 2019 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/ash/settings/pages/people/os_sync_handler.h"

#include "ash/constants/ash_pref_names.h"
#include "ash/constants/chrome_webui_url_constants.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/ui/webui/ash/settings/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/sync/base/features.h"
#include "components/sync/base/pref_names.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_ui.h"
#include "url/gurl.h"

using syncer::SyncService;
using syncer::SyncUserSettings;
using syncer::UserSelectableOsType;
using syncer::UserSelectableOsTypeSet;

namespace ash {

namespace {
const char kWallpaperEnabledKey[] = "wallpaperEnabled";
}  // namespace

OSSyncHandler::OSSyncHandler(Profile* profile) : profile_(profile) {
  DCHECK(profile_);
}

OSSyncHandler::~OSSyncHandler() {
  RemoveSyncServiceObserver();
}

void OSSyncHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback(
      "DidNavigateToOsSyncPage",
      base::BindRepeating(&OSSyncHandler::HandleDidNavigateToOsSyncPage,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "DidNavigateAwayFromOsSyncPage",
      base::BindRepeating(&OSSyncHandler::HandleDidNavigateAwayFromOsSyncPage,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "OsSyncPrefsDispatch",
      base::BindRepeating(&OSSyncHandler::HandleOsSyncPrefsDispatch,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "SetOsSyncDatatypes",
      base::BindRepeating(&OSSyncHandler::HandleSetOsSyncDatatypes,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "OpenBrowserSyncSettings",
      base::BindRepeating(&OSSyncHandler::HandleOpenBrowserSyncSettings,
                          base::Unretained(this)));
}

void OSSyncHandler::OnJavascriptAllowed() {
  AddSyncServiceObserver();
}

void OSSyncHandler::OnJavascriptDisallowed() {
  RemoveSyncServiceObserver();
}

void OSSyncHandler::OnStateChanged(syncer::SyncService* service) {
  PushSyncPrefs();
}

void OSSyncHandler::OnSyncShutdown(syncer::SyncService* service) {
  // Unreachable, since this class is tied to UI which gets destroyed before the
  // Profile and its KeyedServices.
  NOTREACHED();
}

void OSSyncHandler::HandleDidNavigateToOsSyncPage(const base::ListValue& args) {
  syncer::SyncService* service = GetSyncService();
  if (service && !service->HasSyncConsent() &&
      syncer::IsReplaceSyncPromosWithSignInPromosEnabled()) {
    // For signed-in non-syncing users, clearing this flag acts solely to
    // dismiss the system/sync error notification without other side effects.
    // Since the dashboard reset previously disabled all individual OS types,
    // the user must still manually re-enable the toggles they want to sync.
    service->GetUserSettings()->ClearSyncFeatureDisabledViaDashboard();
  }
  HandleOsSyncPrefsDispatch(args);
}

void OSSyncHandler::HandleOsSyncPrefsDispatch(const base::ListValue& args) {
  AllowJavascript();

  PushSyncPrefs();
}

void OSSyncHandler::HandleDidNavigateAwayFromOsSyncPage(
    const base::ListValue& args) {
  // TODO(https://crbug.com/1278325): Remove this.
}

void OSSyncHandler::HandleOpenBrowserSyncSettings(const base::ListValue& args) {
  const GURL settings_url(ash::chrome_urls::kChromeUISettingsURL);
  ash::NewWindowDelegate::GetInstance()->OpenUrl(
      IdentityManagerFactory::GetForProfile(profile_)->HasPrimaryAccount(
          signin::ConsentLevel::kSync) ||
              !syncer::IsReplaceSyncPromosWithSignInPromosEnabled()
          ? settings_url.Resolve(ash::chrome_urls::kSyncSetupSubPage)
          : settings_url.Resolve(ash::chrome_urls::kAccountSubPage),
      ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
      ash::NewWindowDelegate::Disposition::kSwitchToTab);
}

void OSSyncHandler::HandleSetOsSyncDatatypes(const base::ListValue& args) {
  CHECK_EQ(1u, args.size());
  const base::Value& result_value = args[0];
  CHECK(result_value.is_dict());
  const base::DictValue& result = result_value.GetDict();

  // Wallpaper sync status is stored directly to the profile's prefs.
  bool wallpaper_synced = result.FindBool(kWallpaperEnabledKey).value();
  profile_->GetPrefs()->SetBoolean(ash::prefs::kSyncOsWallpaper,
                                   wallpaper_synced);

  // Start configuring the SyncService using the configuration passed to us from
  // the JS layer.
  syncer::SyncService* service = GetSyncService();

  // If the sync engine has shutdown for some reason, just stop.
  if (!service || !service->IsEngineInitialized()) {
    return;
  }

  bool sync_all_os_types = result.FindBool("syncAllOsTypes").value();

  UserSelectableOsTypeSet selected_types;
  for (UserSelectableOsType type : UserSelectableOsTypeSet::All()) {
    std::string key =
        syncer::GetUserSelectableOsTypeName(type) + std::string("Synced");
    std::optional<bool> sync_value = result.FindBool(key);
    CHECK(sync_value.has_value()) << key;
    if (sync_value.value()) {
      selected_types.Put(type);
    }
  }

  // Filter out any non-registered types. The WebUI may echo back values from
  // toggles for in-development features hidden by feature flags.
  SyncUserSettings* settings = service->GetUserSettings();
  selected_types.RetainAll(settings->GetRegisteredSelectableOsTypes());

  settings->SetSelectedOsTypes(sync_all_os_types, selected_types);

  // TODO(jamescook): Add metrics for selected types.
}

void OSSyncHandler::PushSyncPrefs() {
  syncer::SyncService* service = GetSyncService();
  // The sync service may be nullptr if it has been just disabled by policy.
  if (!service || !service->IsEngineInitialized()) {
    return;
  }

  base::DictValue args;
  SyncUserSettings* user_settings = service->GetUserSettings();
  // Tell the UI layer which data types are registered/enabled by the user.
  args.Set("syncAllOsTypes", user_settings->IsSyncAllOsTypesEnabled());
  UserSelectableOsTypeSet registered_types =
      user_settings->GetRegisteredSelectableOsTypes();
  UserSelectableOsTypeSet selected_types = user_settings->GetSelectedOsTypes();

  for (UserSelectableOsType type : UserSelectableOsTypeSet::All()) {
    std::string type_name = syncer::GetUserSelectableOsTypeName(type);
    args.SetByDottedPath(type_name + "Registered", registered_types.Has(type));
    args.SetByDottedPath(type_name + "Synced", selected_types.Has(type));
  }

  // Wallpaper sync status is fetched from prefs and is considered enabled if
  // all OS types are enabled; this mimics behavior of GetSelectedOsTypes().
  args.Set(kWallpaperEnabledKey,
           user_settings->IsSyncAllOsTypesEnabled() ||
               profile_->GetPrefs()->GetBoolean(ash::prefs::kSyncOsWallpaper));

  FireWebUIListener("os-sync-prefs-changed", args);
}

syncer::SyncService* OSSyncHandler::GetSyncService() const {
  const bool is_sync_allowed = SyncServiceFactory::IsSyncAllowed(profile_);
  return is_sync_allowed ? SyncServiceFactory::GetForProfile(profile_)
                         : nullptr;
}

void OSSyncHandler::AddSyncServiceObserver() {
  // Observe even if sync isn't allowed. IsSyncAllowed() can change mid-session.
  SyncService* service = SyncServiceFactory::GetForProfile(profile_);
  if (service) {
    service->AddObserver(this);
  }
}

void OSSyncHandler::RemoveSyncServiceObserver() {
  SyncService* service = SyncServiceFactory::GetForProfile(profile_);
  if (service) {
    service->RemoveObserver(this);
  }
}

}  // namespace ash
