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

#include "components/sync_preferences/dual_layer_user_pref_store.h"

#include <optional>
#include <string>
#include <string_view>
#include <utility>

#include "base/auto_reset.h"
#include "base/barrier_closure.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/observer_list.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "components/sync/base/features.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_user_settings.h"
#include "components/sync_preferences/features.h"
#include "components/sync_preferences/pref_model_associator_client.h"
#include "components/sync_preferences/preferences_merge_helper.h"
#include "components/sync_preferences/syncable_prefs_database.h"

namespace sync_preferences {
namespace {

// This is the set of user selectable types that are relevant to
// `DualLayerUserPrefStore`. This is used to detect no-op changes to the user
// selected types efficiently.
constexpr syncer::UserSelectableTypeSet kInterestingUserSelectableTypes = {
    syncer::UserSelectableType::kPreferences,
    syncer::UserSelectableType::kHistory};

#if BUILDFLAG(IS_CHROMEOS)
// This is the set of user selectable OS types that are relevant to
// `DualLayerUserPrefStore`. This is used to detect no-op changes to the user
// selected types efficiently.
constexpr syncer::UserSelectableOsTypeSet kInterestingUserSelectableOsTypes = {
    syncer::UserSelectableOsType::kOsPreferences};
#endif  // BUILDFLAG(IS_CHROMEOS)

// The name of the pref storing the set of user selected types on the local pref
// store.
constexpr std::string_view kUserSelectedTypesPrefName =
    "dual_layer_user_pref_store.user_selected_sync_types";

#if BUILDFLAG(IS_CHROMEOS)
// The name of the pref storing the set of user selected os types on the local
// pref store.
constexpr std::string_view kUserSelectedOsTypesPrefName =
    "dual_layer_user_pref_store.user_selected_os_sync_types";
#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace

DualLayerUserPrefStore::UnderlyingPrefStoreObserver::
    UnderlyingPrefStoreObserver(DualLayerUserPrefStore* outer,
                                bool is_account_store)
    : outer_(outer), is_account_store_(is_account_store) {
  DCHECK(outer_);
}

void DualLayerUserPrefStore::UnderlyingPrefStoreObserver::OnPrefValueChanged(
    std::string_view key) {
  // Ignore this notification if it originated from the outer store - in that
  // case, `DualLayerUserPrefStore` itself will send notifications as
  // appropriate. This avoids dual notifications even though there are dual
  // writes.
  if (outer_->is_setting_prefs_) {
    return;
  }
  // Otherwise: This must've been a write directly to the underlying store, so
  // notify any observers.
  // Note: Observers should only be notified if the effective value of a pref
  // changes.
  if (outer_->IsPrefKeyMergeable(key)) {
    if (base::FeatureList::IsEnabled(
            features::kOptimizeDualLayerUserPrefStoreMaybeMerge) &&
        !outer_->UpdateMergedPrefCacheIfMergeable(key)) {
      // The effective value of the mergeable pref did not change.
      return;
    }
  }
  // The effective value will not change if this is a write to the local store,
  // but the account store has a value that overrides it.
  else if (!is_account_store_ &&
           outer_->GetAccountPrefStore()->GetValue(key, nullptr)) {
    return;
  }

  for (PrefStore::Observer& observer : outer_->observers_) {
    observer.OnPrefValueChanged(key);
  }
}

void DualLayerUserPrefStore::UnderlyingPrefStoreObserver::
    OnInitializationCompleted(bool succeeded) {
  initialization_succeeded_ = succeeded;

  // Notify observers only after all underlying PrefStores are initialized.
  if (!outer_->IsInitializationComplete()) {
    return;
  }

  // Forward error if any of the underlying store reported error upon
  // ReadPrefsAsync().
  if (outer_->read_error_delegate_.has_value() &&
      outer_->read_error_delegate_.value()) {
    if (auto read_error = outer_->GetReadError();
        read_error != PersistentPrefStore::PREF_READ_ERROR_NONE) {
      outer_->read_error_delegate_.value()->OnError(read_error);
    }
  }

  for (auto& observer : outer_->observers_) {
    observer.OnInitializationCompleted(outer_->IsInitializationSuccessful());
  }
}

bool DualLayerUserPrefStore::UnderlyingPrefStoreObserver::
    initialization_succeeded() const {
  CHECK(outer_->IsInitializationComplete());
  return initialization_succeeded_;
}

DualLayerUserPrefStore::DualLayerUserPrefStore(
    scoped_refptr<PersistentPrefStore> local_pref_store,
    scoped_refptr<PersistentPrefStore> account_pref_store,
    scoped_refptr<PrefModelAssociatorClient> pref_model_associator_client)
    : local_pref_store_(std::move(local_pref_store)),
      account_pref_store_(std::move(account_pref_store)),
      local_pref_store_observer_(this, /*is_account_store=*/false),
      account_pref_store_observer_(this, /*is_account_store=*/true),
      pref_model_associator_client_(pref_model_associator_client) {
  local_pref_store_->AddObserver(&local_pref_store_observer_);
  account_pref_store_->AddObserver(&account_pref_store_observer_);
}

DualLayerUserPrefStore::~DualLayerUserPrefStore() {
  account_pref_store_->RemoveObserver(&account_pref_store_observer_);
  local_pref_store_->RemoveObserver(&local_pref_store_observer_);
}

scoped_refptr<PersistentPrefStore> DualLayerUserPrefStore::GetLocalPrefStore() {
  return local_pref_store_;
}

scoped_refptr<WriteablePrefStore>
DualLayerUserPrefStore::GetAccountPrefStore() {
  return account_pref_store_;
}

void DualLayerUserPrefStore::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void DualLayerUserPrefStore::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

bool DualLayerUserPrefStore::HasObservers() const {
  return !observers_.empty();
}

bool DualLayerUserPrefStore::IsInitializationComplete() const {
  return local_pref_store_->IsInitializationComplete() &&
         account_pref_store_->IsInitializationComplete();
}

bool DualLayerUserPrefStore::GetValue(std::string_view key,
                                      const base::Value** result) const {
  const bool in_account = ShouldGetValueFromAccountStore(key);
  const bool in_local = ShouldGetValueFromLocalStore(key);

  if (!in_account && !in_local) {
    return false;
  }

  if (!in_account) {
    return local_pref_store_->GetValue(key, result);
  }

  if (!in_local) {
    return account_pref_store_->GetValue(key, result);
  }

  const base::Value* account_value = nullptr;
  const base::Value* local_value = nullptr;

  account_pref_store_->GetValue(key, &account_value);
  local_pref_store_->GetValue(key, &local_value);

  if (!account_value && !local_value) {
    // Pref doesn't exist.
    return false;
  }

  if (result) {
    // Merge pref if `key` exists in both the stores.
    if (account_value && local_value) {
      *result = MaybeMerge(key, *local_value, *account_value);
      CHECK(*result);
    } else if (account_value) {
      *result = account_value;
    } else {
      *result = local_value;
    }
  }
  return true;
}

base::DictValue DualLayerUserPrefStore::GetValues() const {
  base::DictValue values = local_pref_store_->GetValues();

  if (base::FeatureList::IsEnabled(features::kAccountScopedPrefs)) {
    for (const std::string& pref_name :
         GetSyncablePrefNamesInStore(local_pref_store_.get())) {
      if (!ShouldGetValueFromLocalStore(pref_name)) {
        values.RemoveByDottedPath(pref_name);
      }
    }
  }

  for (const std::string& pref_name :
       GetSyncablePrefNamesInStore(account_pref_store_.get())) {
    // Filter out prefs which should not be queried from the account store, for
    // example, prefs requiring history opt-in if history sync is off.
    if (ShouldGetValueFromAccountStore(pref_name)) {
      const base::Value* value = nullptr;
      // GetValue() will merge the value if needed.
      GetValue(pref_name, &value);
      CHECK(value);
      values.SetByDottedPath(pref_name, value->Clone());
    }
  }
  return values;
}

void DualLayerUserPrefStore::SetValue(std::string_view key,
                                      base::Value value,
                                      uint32_t flags) {
  const base::Value* initial_value = nullptr;
  // Only notify if something actually changed.
  // Note: `value` is still added to the stores in case `key` was missing from
  // any or had a different value.
  const bool should_notify =
      !GetValue(key, &initial_value) || (*initial_value != value);

  DoSetValue(key, std::move(value), flags, /*notify=*/true);

  if (should_notify) {
    observers_.NotifyAllowReentrancy(&PrefStore::Observer::OnPrefValueChanged,
                                     key);
  }
}

void DualLayerUserPrefStore::RemoveValue(std::string_view key, uint32_t flags) {
  // Only proceed if the pref exists.
  if (!GetValue(key, nullptr)) {
    return;
  }

  {
    base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);
    if (ShouldSetValueInLocalStore(key)) {
      local_pref_store_->RemoveValue(key, flags);
    }
    if (ShouldSetValueInAccountStore(key)) {
      account_pref_store_->RemoveValue(key, flags);
    }
  }

  // Remove from the list of merge prefs if exists.
  merged_prefs_.RemoveValue(key);

  observers_.NotifyAllowReentrancy(&PrefStore::Observer::OnPrefValueChanged,
                                   key);
}

bool DualLayerUserPrefStore::GetMutableValue(std::string_view key,
                                             base::Value** result) {
  const bool in_account = ShouldGetValueFromAccountStore(key);
  const bool in_local = ShouldGetValueFromLocalStore(key);

  if (!in_account && !in_local) {
    return false;
  }

  if (!in_account) {
    return local_pref_store_->GetMutableValue(key, result);
  }

  if (!in_local) {
    return account_pref_store_->GetMutableValue(key, result);
  }

  base::Value* local_value = nullptr;
  local_pref_store_->GetMutableValue(key, &local_value);
  base::Value* account_value = nullptr;
  account_pref_store_->GetMutableValue(key, &account_value);

  if (!account_value && !local_value) {
    // Pref doesn't exist.
    return false;
  }
  if (result) {
    // Note: Any changes to the returned Value will only directly take effect
    // in the underlying store. However, callers of this method are required to
    // call ReportValueChanged() once they're done modifying it, and that
    // propagates the change to all the underlying stores.

    // If pref exists it both stores, create a merged pref.
    if (account_value && local_value) {
      *result = MaybeMerge(key, *local_value, *account_value);
      CHECK(*result);
    } else if (account_value) {
      *result = account_value;
    } else {
      *result = local_value;
    }
  }
  return true;
}

void DualLayerUserPrefStore::ReportValueChanged(std::string_view key,
                                                uint32_t flags) {
  {
    base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);
    const bool in_account = ShouldSetValueInAccountStore(key);
    const bool in_local = ShouldSetValueInLocalStore(key);
    if (in_account && in_local) {
      const base::Value* new_value = nullptr;
      // In case a merged value was updated, it would exist in `merged_prefs_`.
      // Else, get the new value from whichever store has it and copy it to the
      // other one.
      if (merged_prefs_.GetValue(key, &new_value)) {
        auto [new_local_value, new_account_value] =
            UnmergeValue(key, new_value->Clone(), flags);
        account_pref_store_->SetValueSilently(key, std::move(new_account_value),
                                              flags);
        local_pref_store_->SetValueSilently(key, std::move(new_local_value),
                                            flags);
      } else if (account_pref_store_->GetValue(key, &new_value)) {
        local_pref_store_->SetValueSilently(key, new_value->Clone(), flags);
      } else if (local_pref_store_->GetValue(key, &new_value)) {
        account_pref_store_->SetValueSilently(key, new_value->Clone(), flags);
      }
      // It is possible that the pref just doesn't exist (anymore).
    }
    // Forward the ReportValueChanged() call to the underlying stores, so they
    // can notify their own observers.
    if (in_local) {
      local_pref_store_->ReportValueChanged(key, flags);
    }
    if (in_account) {
      account_pref_store_->ReportValueChanged(key, flags);
    }
  }

  // Update the cached merged value.
  UpdateMergedPrefCacheIfMergeable(key);

  observers_.NotifyAllowReentrancy(&PrefStore::Observer::OnPrefValueChanged,
                                   key);
}

void DualLayerUserPrefStore::DoSetValue(std::string_view key,
                                        base::Value value,
                                        uint32_t flags,
                                        bool notify) {
  base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);

  auto set_value_fn = notify ? &WriteablePrefStore::SetValue
                             : &WriteablePrefStore::SetValueSilently;

  const bool in_account = ShouldSetValueInAccountStore(key);
  const bool in_local = ShouldSetValueInLocalStore(key);

  if (in_local && in_account) {
    if (IsPrefKeyMergeable(key)) {
      auto [new_local_value, new_account_value] =
          UnmergeValue(key, std::move(value), flags);
      (account_pref_store_.get()->*set_value_fn)(
          key, std::move(new_account_value), flags);
      (local_pref_store_.get()->*set_value_fn)(key, std::move(new_local_value),
                                               flags);
    } else {
      (account_pref_store_.get()->*set_value_fn)(key, value.Clone(), flags);
      (local_pref_store_.get()->*set_value_fn)(key, std::move(value), flags);
    }
  } else if (in_local) {
    (local_pref_store_.get()->*set_value_fn)(key, std::move(value), flags);
  } else if (in_account) {
    (account_pref_store_.get()->*set_value_fn)(key, std::move(value), flags);
  }

  // Update the cached merged value.
  UpdateMergedPrefCacheIfMergeable(key);
}

void DualLayerUserPrefStore::SetValueSilently(std::string_view key,
                                              base::Value value,
                                              uint32_t flags) {
  DoSetValue(key, std::move(value), flags, /*notify=*/false);
}

void DualLayerUserPrefStore::RemoveValuesByPrefixSilently(
    std::string_view prefix) {
  local_pref_store_->RemoveValuesByPrefixSilently(prefix);

  // RemoveValuesByPrefixSilently() is not used for the account store since it
  // will remove values which are not being synced yet(for e.g. prefs behind
  // history opt-in). Instead, each pref in the account store is checked and
  // removed if it is writeable right now.
  {
    base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);
    // Clear all synced preferences with the prefix from the account store.
    for (const std::string& pref_name :
         GetSyncablePrefNamesInStore(account_pref_store_.get())) {
      if (base::StartsWith(pref_name, prefix) &&
          ShouldSetValueInAccountStore(pref_name)) {
        account_pref_store_->RemoveValue(
            pref_name, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
      }
    }
  }

  // Remove from the list of merged prefs if exists.
  merged_prefs_.ClearWithPrefix(prefix);
}

bool DualLayerUserPrefStore::ReadOnly() const {
  return local_pref_store_->ReadOnly() || account_pref_store_->ReadOnly();
}

PersistentPrefStore::PrefReadError DualLayerUserPrefStore::GetReadError()
    const {
  if (auto local_prefs_read_error = local_pref_store_->GetReadError();
      local_prefs_read_error != PersistentPrefStore::PREF_READ_ERROR_NONE) {
    return local_prefs_read_error;
  }
  return account_pref_store_->GetReadError();
}

PersistentPrefStore::PrefReadError DualLayerUserPrefStore::ReadPrefs() {
  // Call ReadPrefs() on both stores before reporting error.
  auto local_prefs_read_error = local_pref_store_->ReadPrefs();
  auto account_prefs_read_error = account_pref_store_->ReadPrefs();

  if (local_prefs_read_error != PersistentPrefStore::PREF_READ_ERROR_NONE) {
    return local_prefs_read_error;
  }
  return account_prefs_read_error;
}

void DualLayerUserPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
  // The store is expected to take ownership of `error_delegate`, thus it's not
  // valid to forward the same to the two underlying stores. Instead, if any
  // error occurs, it's reported in OnInitializationCompleted() handle.
  read_error_delegate_.emplace(error_delegate);
  local_pref_store_->ReadPrefsAsync(nullptr);
  account_pref_store_->ReadPrefsAsync(nullptr);
}

void DualLayerUserPrefStore::CommitPendingWrite(
    base::OnceClosure reply_callback,
    base::OnceClosure synchronous_done_callback) {
  // A BarrierClosure will run its callback wherever the last instance of the
  // returned wrapper is invoked. As such it is guaranteed to respect the reply
  // vs synchronous semantics assuming `local_pref_store_` and
  // `account_pref_store_` honor it.

  static constexpr int kNumStores = 2;

  base::RepeatingClosure reply_callback_wrapper =
      reply_callback
          ? base::BarrierClosure(kNumStores, std::move(reply_callback))
          : base::RepeatingClosure();

  base::RepeatingClosure synchronous_callback_wrapper =
      synchronous_done_callback
          ? base::BarrierClosure(kNumStores,
                                 std::move(synchronous_done_callback))
          : base::RepeatingClosure();

  local_pref_store_->CommitPendingWrite(reply_callback_wrapper,
                                        synchronous_callback_wrapper);
  account_pref_store_->CommitPendingWrite(reply_callback_wrapper,
                                          synchronous_callback_wrapper);
}

void DualLayerUserPrefStore::SchedulePendingLossyWrites() {
  local_pref_store_->SchedulePendingLossyWrites();
  account_pref_store_->SchedulePendingLossyWrites();
}

void DualLayerUserPrefStore::OnStoreDeletionFromDisk() {
  local_pref_store_->OnStoreDeletionFromDisk();
  account_pref_store_->OnStoreDeletionFromDisk();
}

bool DualLayerUserPrefStore::ShouldSetValueInAccountStore(
    std::string_view key) const {
  // A preference `key` is added to account store only if it is syncable,  the
  // corresponding pref type is active, and falls under the current user
  // consent, i.e. "privacy-sensitive" prefs require history opt-in.

  // Never write to the account store if it's not read from the account store.
  if (!ShouldGetValueFromAccountStore(key)) {
    return false;
  }
  const SyncablePrefMetadata* metadata =
      pref_model_associator_client_->GetSyncablePrefsDatabase()
          .GetSyncablePrefMetadata(key);
  CHECK(metadata);
  bool is_pref_type_enabled = false;
  if (base::FeatureList::IsEnabled(syncer::kSyncPreferencesUseSelectedTypes)) {
    if (metadata->data_type() == syncer::PREFERENCES ||
        metadata->data_type() == syncer::PRIORITY_PREFERENCES) {
      is_pref_type_enabled = GetInterestingUserSelectedTypes().Has(
          syncer::UserSelectableType::kPreferences);
    }
#if BUILDFLAG(IS_CHROMEOS)
    if (metadata->data_type() == syncer::OS_PREFERENCES ||
        metadata->data_type() == syncer::OS_PRIORITY_PREFERENCES) {
      is_pref_type_enabled = GetInterestingUserSelectedOsTypes().Has(
          syncer::UserSelectableOsType::kOsPreferences);
    }
#endif  // BUILDFLAG(IS_CHROMEOS)
  } else {
    is_pref_type_enabled = active_types_.contains(metadata->data_type());
  }
  if (!is_pref_type_enabled &&
      // Checks if the pref already exists in the account store.
      // This is to handle cases where a pref might pre-exist before
      // `is_pref_type_enabled` can be correctly computed and the type is
      // considered enabled.
      !account_pref_store_->GetValue(key, nullptr)) {
    return false;
  }
  return true;
}

bool DualLayerUserPrefStore::ShouldGetValueFromAccountStore(
    std::string_view key) const {
  // A preference `key` is queried from account store only if it is syncable and
  // falls under the current user consent, i.e. "privacy-sensitive" prefs
  // require history opt-in.
  // Note: There is no check if the pref type is active because they are
  // determined only after the Sync machinery is initialized, but account-store
  // values need to be applied even before that.

  if (!pref_model_associator_client_) {
    // Safer this way.
    return false;
  }
  const SyncablePrefMetadata* metadata =
      pref_model_associator_client_->GetSyncablePrefsDatabase()
          .GetSyncablePrefMetadata(key);
  // Checks if the pref is a syncable pref.
  if (!metadata) {
    return false;
  }
  // Checks if the pref requires a history opt-in.
  if (metadata->is_history_opt_in_required() && !IsHistorySyncEnabled()) {
    return false;
  }
  // Priority pref type is always active. This adds check to avoid syncing them
  // if the user toggle is off. This however skips all the allowlisted priority
  // prefs.
  if (metadata->data_type() == syncer::PRIORITY_PREFERENCES &&
      !GetInterestingUserSelectedTypes().Has(
          syncer::UserSelectableType::kPreferences) &&
      !pref_model_associator_client_->GetSyncablePrefsDatabase()
           .IsPreferenceAlwaysSyncing(key)) {
    return false;
  }
  return true;
}

void DualLayerUserPrefStore::EnableType(syncer::DataType data_type) {
  CHECK(data_type == syncer::PREFERENCES ||
        data_type == syncer::PRIORITY_PREFERENCES
#if BUILDFLAG(IS_CHROMEOS)
        || data_type == syncer::OS_PREFERENCES ||
        data_type == syncer::OS_PRIORITY_PREFERENCES
#endif  // BUILDFLAG(IS_CHROMEOS)
  );
  active_types_.insert(data_type);
}

bool DualLayerUserPrefStore::ShouldSetValueInLocalStore(
    std::string_view key) const {
  if (!base::FeatureList::IsEnabled(features::kAccountScopedPrefs)) {
    // A preference `key` should always be writable to the local store.
    return true;
  }
  if (!pref_model_associator_client_) {
    // Safer this way.
    return true;
  }
  const SyncablePrefMetadata* metadata =
      pref_model_associator_client_->GetSyncablePrefsDatabase()
          .GetSyncablePrefMetadata(key);
  // Prefs are written to the local store by default, unless explicitly tagged
  // as account-only.
  return !metadata ||
         metadata->write_behavior() != WriteBehavior::kWriteToAccountOnly;
}

bool DualLayerUserPrefStore::ShouldGetValueFromLocalStore(
    std::string_view key) const {
  if (!base::FeatureList::IsEnabled(features::kAccountScopedPrefs)) {
    // A preference `key` should always be readable from the local store.
    return true;
  }
  if (!pref_model_associator_client_) {
    // Safer this way.
    return true;
  }
  const SyncablePrefMetadata* metadata =
      pref_model_associator_client_->GetSyncablePrefsDatabase()
          .GetSyncablePrefMetadata(key);
  // Prefs are read from the local store by default, unless explicitly tagged
  // as account-only.
  return !metadata ||
         // Account-only prefs should ideally not exist in the local store,
         // except in case of a bug somewhere.
         metadata->write_behavior() != WriteBehavior::kWriteToAccountOnly;
}

void DualLayerUserPrefStore::DisableTypeAndClearAccountStore(
    syncer::DataType data_type) {
  CHECK(data_type == syncer::PREFERENCES ||
        data_type == syncer::PRIORITY_PREFERENCES
#if BUILDFLAG(IS_CHROMEOS)
        || data_type == syncer::OS_PREFERENCES ||
        data_type == syncer::OS_PRIORITY_PREFERENCES
#endif  // BUILDFLAG(IS_CHROMEOS)
  );
  active_types_.erase(data_type);

  if (!pref_model_associator_client_) {
    // No pref is treated as syncable in this case. No need to clear the account
    // store.
    return;
  }

  // Clear all synced preferences from the account store.
  for (const std::string& pref_name :
       GetSyncablePrefNamesInStore(account_pref_store_.get())) {
    const SyncablePrefMetadata* metadata =
        pref_model_associator_client_->GetSyncablePrefsDatabase()
            .GetSyncablePrefMetadata(pref_name);
    CHECK(metadata);
    if (metadata->data_type() != data_type) {
      continue;
    }
    const base::Value* value = nullptr;
    // Should only notify observers if the effective value changes.
    // Note: A notification is still sent if a pref goes from an
    // explicitly-set value to an equal default value.
    // Note: If the pref requires history opt-in, but history sync is
    // disabled, GetValue() will not return the account value, and in case
    // no value for the pref exists in the local store, no notification should
    // be sent out.
    bool should_notify = GetValue(pref_name, &value);
    if (const base::Value* local_value = nullptr;
        value && local_pref_store_->GetValue(pref_name, &local_value)) {
      should_notify = (*local_value != *value);
    }
    {
      base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);
      // The write flags only affect persistence, and the default flag is the
      // safer choice.
      account_pref_store_->RemoveValue(
          pref_name, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
      merged_prefs_.RemoveValue(pref_name);
    }
    if (should_notify) {
      observers_.NotifyAllowReentrancy(&PrefStore::Observer::OnPrefValueChanged,
                                       pref_name);
    }
  }

  if (active_types_.empty()) {
    // Clear the account store of any garbage value without notifications. This
    // can happen if a previously syncable pref was persisted to the account
    // store but is no longer syncable.
    // TODO(crbug.com/40067768): Look into if the garbage values can cleared on
    // browser startup.

    // Since there's no direct way to clear the pref store or get a list of all
    // keys (because of the dotted paths) and `RemoveValuesByPrefixSilently("")`
    // is disallowed, the following workaround is used to clear the store.
    for (auto [key, value] : account_pref_store_->GetValues()) {
      account_pref_store_->RemoveValuesByPrefixSilently(key);
    }
    merged_prefs_.Clear();
    // Clear the user selected types pref in the local store.
    // TODO(crbug.com/464008640): Consider removing this, since the selected
    // types should get cleared anyway when sync is disabled.
    SetInterestingUserSelectedTypes(syncer::UserSelectableTypeSet());
#if BUILDFLAG(IS_CHROMEOS)
    SetInterestingUserSelectedOsTypes(syncer::UserSelectableOsTypeSet());
#endif  // BUILDFLAG(IS_CHROMEOS)
    // Clear any account-only prefs from the local store. There shouldn't be any
    // such prefs in the local store, but if there are due to some bug, this
    // will clear them.
    if (base::FeatureList::IsEnabled(features::kAccountScopedPrefs)) {
      for (const std::string& pref_name :
           GetSyncablePrefNamesInStore(local_pref_store_.get())) {
        if (!ShouldSetValueInLocalStore(pref_name)) {
          local_pref_store_->RemoveValue(
              pref_name, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
        }
      }
    }
  }
}

bool DualLayerUserPrefStore::IsPrefKeyMergeable(std::string_view key) const {
  if (!pref_model_associator_client_) {
    return false;
  }
  const auto& syncable_prefs_database =
      pref_model_associator_client_->GetSyncablePrefsDatabase();
  return syncable_prefs_database.IsPreferenceSyncable(key) &&
         syncable_prefs_database.IsPreferenceMergeable(key);
}

const base::Value* DualLayerUserPrefStore::MaybeMerge(
    std::string_view pref_name,
    const base::Value& local_value,
    const base::Value& account_value) const {
  // Return the account value if `pref_name` is not mergeable.
  if (!IsPrefKeyMergeable(pref_name)) {
    return &account_value;
  }

  const base::Value* merged_pref = nullptr;
  if (base::FeatureList::IsEnabled(
          features::kOptimizeDualLayerUserPrefStoreMaybeMerge) &&
      merged_prefs_.GetValue(pref_name, &merged_pref)) {
    return merged_pref;
  }

  base::Value merged_value =
      helper::MergePreference(pref_model_associator_client_.get(), pref_name,
                              local_value, account_value);

  merged_prefs_.SetValue(pref_name, std::move(merged_value));

  merged_prefs_.GetValue(pref_name, &merged_pref);
  DCHECK(merged_pref);
  return merged_pref;
}

base::Value* DualLayerUserPrefStore::MaybeMerge(std::string_view pref_name,
                                                base::Value& local_value,
                                                base::Value& account_value) {
  // Doing const_cast should be safe as ultimately the value being pointed to is
  // a non-const object.
  return const_cast<base::Value*>(
      std::as_const(*this).MaybeMerge(pref_name, local_value, account_value));
}

bool DualLayerUserPrefStore::UpdateMergedPrefCacheIfMergeable(
    std::string_view key) const {
  if (!base::FeatureList::IsEnabled(
          features::kOptimizeDualLayerUserPrefStoreMaybeMerge) ||
      !IsPrefKeyMergeable(key)) {
    return false;
  }

  const base::Value* local_value = nullptr;
  local_pref_store_->GetValue(key, &local_value);
  const base::Value* account_value = nullptr;
  account_pref_store_->GetValue(key, &account_value);

  if (local_value && account_value) {
    base::Value merged_value = helper::MergePreference(
        pref_model_associator_client_.get(), key, *local_value, *account_value);
    if (base::Value* original_value = nullptr;
        !merged_prefs_.GetValue(key, &original_value) ||
        *original_value != merged_value) {
      merged_prefs_.SetValue(key, std::move(merged_value));
      return true;
    }
    return false;
  }

  merged_prefs_.RemoveValue(key);
  return true;
}

std::pair<base::Value, base::Value> DualLayerUserPrefStore::UnmergeValue(
    std::string_view pref_name,
    base::Value value,
    uint32_t flags) const {
  CHECK(ShouldSetValueInAccountStore(pref_name));

  // Note: There is no "standard" unmerging logic for list or scalar prefs.
  // TODO(crbug.com/40256874): Allow support for custom unmerge logic.
  if (pref_model_associator_client_->GetSyncablePrefsDatabase()
          .GetSyncablePrefMetadata(pref_name)
          ->merge_behavior() == MergeBehavior::kMergeableDict) {
    // Per crbug.com/1430854, it is possible for the value to not be of dict
    // type. However, in this case, whatever is the type of `value` it's bound
    // to be correct, as UnmergeValue() is called by setters which in turn are
    // only called after a type check.
    if (value.is_dict()) {
      base::DictValue local_dict;
      if (const base::Value* local_dict_value = nullptr;
          local_pref_store_->GetValue(pref_name, &local_dict_value) &&
          // Very unlikely, but if the value is of incorrect type, ignore it
          // since the new value will overwrite it anyway (if the new value is
          // different from the account value).
          local_dict_value->is_dict()) {
        local_dict = local_dict_value->GetDict().Clone();
      }
      base::DictValue account_dict;
      if (const base::Value* account_dict_value = nullptr;
          account_pref_store_->GetValue(pref_name, &account_dict_value) &&
          // Very unlikely, but if the value is of incorrect type, ignore it
          // since the new value will overwrite it anyway (if the new value is
          // different from the local value).
          account_dict_value->is_dict()) {
        account_dict = account_dict_value->GetDict().Clone();
      }
      auto [new_local_dict, new_account_dict] = helper::UnmergeDictionaryValues(
          std::move(value).TakeDict(), local_dict, account_dict);
      // Note: This would still return an empty dict even if the pref didn't
      // exist in either of the stores. This should however be okay since no
      // actual pref value is leaked to the other.
      return {base::Value(std::move(new_local_dict)),
              base::Value(std::move(new_account_dict))};
    } else {
      DLOG(ERROR) << pref_name
                  << " marked as a mergeable dict pref but is of type "
                  << value.type();
    }
  }

  // Directly pass the new value as both the local value and the account value
  // for prefs with no specific merge logic.
  base::Value new_account_value(value.Clone());
  base::Value new_local_value(std::move(value));
  return {std::move(new_local_value), std::move(new_account_value)};
}

bool DualLayerUserPrefStore::IsInitializationSuccessful() const {
  return local_pref_store_observer_.initialization_succeeded() &&
         account_pref_store_observer_.initialization_succeeded();
}

std::vector<std::string> DualLayerUserPrefStore::GetSyncablePrefNamesInStore(
    const PersistentPrefStore* store) const {
  std::vector<std::string> keys;

  if (!pref_model_associator_client_) {
    return keys;
  }

  // GetValues() returns a dict which is set using SetByDottedPaths(). That
  // means, a key "a.b.c" is presented as: `{'a': {'b': {'c': ... }}}`. This
  // util recurses over the nested dicts with keys being joined with a dot, till
  // the string forms a valid pref name, for eg. it will recurse with keys, "a",
  // "a.b", and then "a.b.c" which was the original key.
  auto recurse_and_insert = [&](const std::string& key,
                                const base::Value& value,
                                auto& recurse_and_insert_ref) -> void {
    // Checks if `key` is a pref name using syncable pref database. This is
    // different from ShouldSetValueInAccountStore() which checks whether or not
    // a pref should synced right now based on enabled DataTypes.
    if (pref_model_associator_client_->GetSyncablePrefsDatabase()
            .IsPreferenceSyncable(key)) {
      keys.push_back(key);
    } else if (value.is_dict()) {
      for (auto [k, v] : value.GetDict()) {
        recurse_and_insert_ref(key + "." + k, v, recurse_and_insert_ref);
      }
    }
  };

  for (auto [key, value] : store->GetValues()) {
    recurse_and_insert(key, value, recurse_and_insert);
  }

  return keys;
}

base::flat_set<syncer::DataType> DualLayerUserPrefStore::GetActiveTypesForTest()
    const {
  return active_types_;
}

bool DualLayerUserPrefStore::IsHistorySyncEnabled() const {
  return GetInterestingUserSelectedTypes().Has(
      syncer::UserSelectableType::kHistory);
}

bool DualLayerUserPrefStore::IsHistorySyncEnabledForTest() const {
  return IsHistorySyncEnabled();
}

void DualLayerUserPrefStore::SetIsHistorySyncEnabledForTest(
    bool is_history_sync_enabled) {
  syncer::UserSelectableTypeSet user_selected_types =
      GetInterestingUserSelectedTypes();
  if (is_history_sync_enabled) {
    user_selected_types.Put(syncer::UserSelectableType::kHistory);
  } else {
    user_selected_types.Remove(syncer::UserSelectableType::kHistory);
  }
  SetUserSelectedTypesForTest(user_selected_types);
}

syncer::UserSelectableTypeSet
DualLayerUserPrefStore::GetUserSelectedTypesForTest() const {
  return GetInterestingUserSelectedTypes();
}

void DualLayerUserPrefStore::SetUserSelectedTypesForTest(
    syncer::UserSelectableTypeSet user_selected_types) {
  SetInterestingUserSelectedTypes(user_selected_types);
}

#if BUILDFLAG(IS_CHROMEOS)
syncer::UserSelectableOsTypeSet
DualLayerUserPrefStore::GetUserSelectedOsTypesForTest() const {
  return GetInterestingUserSelectedOsTypes();
}

void DualLayerUserPrefStore::SetUserSelectedOsTypesForTest(
    syncer::UserSelectableOsTypeSet user_selected_os_types) {
  SetInterestingUserSelectedOsTypes(user_selected_os_types);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

void DualLayerUserPrefStore::SetInterestingUserSelectedTypes(
    syncer::UserSelectableTypeSet user_selected_types) {
  // This is stored in the local pref store for early availability to be able to
  // decide whether to expose values of account priority prefs and
  // history-scoped prefs.
  CHECK(kInterestingUserSelectableTypes.HasAll(user_selected_types));
  local_pref_store_->SetValueSilently(
      kUserSelectedTypesPrefName,
      base::Value(
          syncer::UserSelectableTypeSetToValueList(user_selected_types)),
      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
}

syncer::UserSelectableTypeSet
DualLayerUserPrefStore::GetInterestingUserSelectedTypes() const {
  const base::Value* value = nullptr;
  // Load the user selected types from the local pref store. This allows for
  // persistence and early availability.
  if (!local_pref_store_->GetValue(kUserSelectedTypesPrefName, &value) ||
      !value->is_list()) {
    return syncer::UserSelectableTypeSet();
  }
  return base::Intersection(
      syncer::ValueListToUserSelectableTypeSet(value->GetList()),
      kInterestingUserSelectableTypes);
}

#if BUILDFLAG(IS_CHROMEOS)
void DualLayerUserPrefStore::SetInterestingUserSelectedOsTypes(
    syncer::UserSelectableOsTypeSet user_selected_types) {
  // This is stored in the local pref store for early availability to be able to
  // decide whether to expose values of account priority prefs and
  // history-scoped prefs.
  CHECK(kInterestingUserSelectableOsTypes.HasAll(user_selected_types));
  local_pref_store_->SetValueSilently(
      kUserSelectedOsTypesPrefName,
      base::Value(syncer::UserSelectableOsTypeSetToValueList(user_selected_types)),
      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
}

syncer::UserSelectableOsTypeSet
DualLayerUserPrefStore::GetInterestingUserSelectedOsTypes() const {
  const base::Value* value = nullptr;
  // Load the user selected types from the local pref store. This allows for
  // persistence and early availability.
  if (!local_pref_store_->GetValue(kUserSelectedOsTypesPrefName, &value) ||
      !value->is_list()) {
    return syncer::UserSelectableOsTypeSet();
  }
  return base::Intersection(
      syncer::ValueListToUserSelectableOsTypeSet(value->GetList()),
      kInterestingUserSelectableOsTypes);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

void DualLayerUserPrefStore::OnSyncServiceInitialized(
    syncer::SyncService* sync_service) {
  sync_service->AddObserver(this);
  // `sync_service` init should be considered as a state change.
  OnStateChanged(sync_service);
}

void DualLayerUserPrefStore::OnStateChanged(syncer::SyncService* sync_service) {
  syncer::UserSelectableTypeSet user_selected_types =
      sync_service->GetUserSettings()->GetSelectedTypes();
  // Only retain the concerning types.
  user_selected_types.RetainAll(kInterestingUserSelectableTypes);

#if BUILDFLAG(IS_CHROMEOS)
  syncer::UserSelectableOsTypeSet user_selected_os_types =
      sync_service->GetUserSettings()->GetSelectedOsTypes();
  // Only retain the concerning types.
  user_selected_os_types.RetainAll(kInterestingUserSelectableOsTypes);
#endif  // BUILDFLAG(IS_CHROMEOS)

  if (user_selected_types == GetInterestingUserSelectedTypes()
#if BUILDFLAG(IS_CHROMEOS)
      && user_selected_os_types == GetInterestingUserSelectedOsTypes()
#endif  // BUILDFLAG(IS_CHROMEOS)
  ) {
    return;
  }

  if (!pref_model_associator_client_) {
    SetInterestingUserSelectedTypes(user_selected_types);
#if BUILDFLAG(IS_CHROMEOS)
    SetInterestingUserSelectedOsTypes(user_selected_os_types);
#endif  // BUILDFLAG(IS_CHROMEOS)
    return;
  }

  // Store the old values for account prefs in a map and only inform the
  // observers if the effective values change.
  // Note: std::optional is used as the value type since it makes the
  // comparison with the new values easier.
  std::map<std::string, std::optional<base::Value>> old_values;
  for (const std::string& pref_name :
       GetSyncablePrefNamesInStore(account_pref_store_.get())) {
    const SyncablePrefMetadata* metadata =
        pref_model_associator_client_->GetSyncablePrefsDatabase()
            .GetSyncablePrefMetadata(pref_name);
    CHECK(metadata);
    if (const base::Value* value = nullptr; GetValue(pref_name, &value)) {
      old_values.emplace(pref_name, value->Clone());
    } else {
      // Put in std::nullopt to mark pref not existing in the store. This
      // helps avoid an extra call to GetPrefNamesInAccount() later.
      old_values.emplace(pref_name, std::nullopt);
    }
  }

  SetInterestingUserSelectedTypes(user_selected_types);
#if BUILDFLAG(IS_CHROMEOS)
  SetInterestingUserSelectedOsTypes(user_selected_os_types);
#endif  // BUILDFLAG(IS_CHROMEOS)

  // The sync state change might have changed the effective value. Compare the
  // old and new values and notify the observers if they change.
  for (const auto& [pref_name, old_value] : old_values) {
    std::optional<base::Value> new_value;
    if (const base::Value* value = nullptr; GetValue(pref_name, &value)) {
      new_value = value->Clone();
    }

    // Only notify the observers if the effective value is changing.
    if (old_value != new_value) {
      observers_.NotifyAllowReentrancy(&PrefStore::Observer::OnPrefValueChanged,
                                       pref_name);
    }
  }
}

void DualLayerUserPrefStore::OnSyncShutdown(syncer::SyncService* sync_service) {
  // Pref service and hence the pref store outlives sync service.
  sync_service->RemoveObserver(this);
}

void DualLayerUserPrefStore::SetValueInAccountStoreOnly(std::string_view key,
                                                        base::Value value,
                                                        uint32_t flags) {
  const base::Value* initial_value = nullptr;
  // Only notify if the effective value actually changes.
  bool should_notify =
      !GetValue(key, &initial_value) || (*initial_value != value);
  {
    base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);
    account_pref_store_->SetValue(key, std::move(value), flags);
  }

  // Update the cached merged value.
  UpdateMergedPrefCacheIfMergeable(key);

  if (should_notify) {
    observers_.NotifyAllowReentrancy(&PrefStore::Observer::OnPrefValueChanged,
                                     key);
  }
}

bool DualLayerUserPrefStore::HasReadErrorDelegate() const {
  return read_error_delegate_.has_value();
}

}  // namespace sync_preferences
