// 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.

#ifndef COMPONENTS_SYNC_PREFERENCES_DUAL_LAYER_USER_PREF_STORE_H_
#define COMPONENTS_SYNC_PREFERENCES_DUAL_LAYER_USER_PREF_STORE_H_

#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <utility>

#include "base/containers/flat_set.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "components/prefs/persistent_pref_store.h"
#include "components/prefs/value_map_pref_store.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/sync_service_observer.h"

namespace syncer {
class SyncService;
}  // namespace syncer

namespace sync_preferences {

class PrefModelAssociatorClient;

// A two-layer user PrefStore that combines local preferences (scoped to this
// profile) with account-scoped preferences (scoped to the user's signed-in
// account).
// * Account takes precedence: If a pref has a value in both stores, then
//   typically the account-scoped one takes precedence. However, for some prefs,
//   the two values may be merged.
// * Dual writes: Any changes made to prefs *on this device* are written to both
//   stores. However, incoming changes made on other devices only go into the
//   account store.
class DualLayerUserPrefStore : public PersistentPrefStore,
                               public syncer::SyncServiceObserver {
 public:
  DualLayerUserPrefStore(
      scoped_refptr<PersistentPrefStore> local_pref_store,
      scoped_refptr<PersistentPrefStore> account_pref_store,
      scoped_refptr<PrefModelAssociatorClient> pref_model_associator_client);

  DualLayerUserPrefStore(const DualLayerUserPrefStore&) = delete;
  DualLayerUserPrefStore& operator=(const DualLayerUserPrefStore&) = delete;

  // Marks `data_type` as enabled for account storage. This should be called
  // when a data type starts syncing.
  // TODO(crbug.com/464008640): Rename this to `MarkTypeActive` or similar.
  void EnableType(syncer::DataType data_type);
  // Unmarks `data_type` as enabled for account storage and removes all
  // corresponding preference entries(belonging to this type) from account
  // storage. This should be called when a data type stops syncing.
  void DisableTypeAndClearAccountStore(syncer::DataType data_type);

  // Sets value for preference `key` only in the account store. This is meant
  // for use by sync components (specifically PrefModelAssociator) to insert
  // value in the account store directly but only leads to notifications for
  // DualLayerUserPrefStore observers if the effective value changes.
  // Note: This does not do any merge/unmerge and does not check whether the
  // pref `key` is syncable.
  // TODO(crbug.com/40277783): Implement a better way to handle this usage by
  // the sync components.
  void SetValueInAccountStoreOnly(std::string_view key,
                                  base::Value value,
                                  uint32_t flags);

  void OnSyncServiceInitialized(syncer::SyncService* sync_service);

  scoped_refptr<PersistentPrefStore> GetLocalPrefStore();
  scoped_refptr<WriteablePrefStore> GetAccountPrefStore();

  // PrefStore implementation.
  void AddObserver(Observer* observer) override;
  void RemoveObserver(Observer* observer) override;
  bool HasObservers() const override;
  bool IsInitializationComplete() const override;
  bool GetValue(std::string_view key,
                const base::Value** result) const override;
  base::DictValue GetValues() const override;

  // WriteablePrefStore implementation.
  void SetValue(std::string_view key,
                base::Value value,
                uint32_t flags) override;
  void RemoveValue(std::string_view key, uint32_t flags) override;
  bool GetMutableValue(std::string_view key, base::Value** result) override;
  void ReportValueChanged(std::string_view key, uint32_t flags) override;
  void SetValueSilently(std::string_view key,
                        base::Value value,
                        uint32_t flags) override;
  void RemoveValuesByPrefixSilently(std::string_view prefix) override;

  // PersistentPrefStore implementation.
  bool ReadOnly() const override;
  PrefReadError GetReadError() const override;
  PrefReadError ReadPrefs() override;
  void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
  void CommitPendingWrite(
      base::OnceClosure reply_callback = base::OnceClosure(),
      base::OnceClosure synchronous_done_callback =
          base::OnceClosure()) override;
  void SchedulePendingLossyWrites() override;
  void OnStoreDeletionFromDisk() override;
  bool HasReadErrorDelegate() const override;

  // SyncServiceObserver implementation
  void OnStateChanged(syncer::SyncService* sync_service) override;
  void OnSyncShutdown(syncer::SyncService* sync_service) override;

  // Return the set of active pref types.
  base::flat_set<syncer::DataType> GetActiveTypesForTest() const;

  bool IsHistorySyncEnabledForTest() const;
  void SetIsHistorySyncEnabledForTest(bool is_history_sync_enabled);
  syncer::UserSelectableTypeSet GetUserSelectedTypesForTest() const;
  void SetUserSelectedTypesForTest(
      syncer::UserSelectableTypeSet user_selected_types);

#if BUILDFLAG(IS_CHROMEOS)
  syncer::UserSelectableOsTypeSet GetUserSelectedOsTypesForTest() const;
  void SetUserSelectedOsTypesForTest(
      syncer::UserSelectableOsTypeSet user_selected_types);
#endif  // BUILDFLAG(IS_CHROMEOS)

 protected:
  ~DualLayerUserPrefStore() override;

 private:
  // Forwards events from the underlying stores to the provided "outer"
  // `DualLayerUserPrefStore` to synthesize external events via `observers_`.
  class UnderlyingPrefStoreObserver : public PrefStore::Observer {
   public:
    explicit UnderlyingPrefStoreObserver(DualLayerUserPrefStore* outer,
                                         bool is_account_store);

    UnderlyingPrefStoreObserver(const UnderlyingPrefStoreObserver&) = delete;
    UnderlyingPrefStoreObserver& operator=(const UnderlyingPrefStoreObserver&) =
        delete;

    // PrefStore::Observer implementation.
    void OnPrefValueChanged(std::string_view key) override;
    void OnInitializationCompleted(bool succeeded) override;

    bool initialization_succeeded() const;

   private:
    const raw_ptr<DualLayerUserPrefStore> outer_;
    const bool is_account_store_;
    // Start with `initialization_succeeded_` as true as some persistent
    // stores do not issue OnInitializationCompleted().
    bool initialization_succeeded_ = true;
  };

  // Sets the value for `key` in the appropriate store(s). `notify` controls
  // whether observers of the underlying pref stores are notified of the
  // change (i.e. whether SetValue() or SetValueSilently() is called).
  void DoSetValue(std::string_view key,
                  base::Value value,
                  uint32_t flags,
                  bool notify);

  bool IsInitializationSuccessful() const;

  // Returns whether the pref with the given `key` should be inserted into the
  // account pref store. Note that the account store keeps in sync with the
  // account.
  bool ShouldSetValueInAccountStore(std::string_view key) const;
  // Returns whether the pref with the given `key` should be queried from the
  // account pref store. Note that the account store keeps in sync with the
  // account.
  bool ShouldGetValueFromAccountStore(std::string_view key) const;

  // Returns whether the pref with the given `key` should be inserted into the
  // local pref store.
  bool ShouldSetValueInLocalStore(std::string_view key) const;
  // Returns whether the pref with the given `key` should be queried from the
  // local pref store.
  bool ShouldGetValueFromLocalStore(std::string_view key) const;

  // Returns whether the pref with the given `key` is mergeable.
  bool IsPrefKeyMergeable(std::string_view key) const;

  // Produces a "merged" view of `account_value` and `local_value`. In case
  // `pref_name` is a mergeable pref, a new merged pref is returned, which is
  // owned by `merged_prefs_`. Else, it returns a pointer to the account value,
  // given that in this case the account value overrides the local value.
  const base::Value* MaybeMerge(std::string_view pref_name,
                                const base::Value& local_value,
                                const base::Value& account_value) const;
  base::Value* MaybeMerge(std::string_view pref_name,
                          base::Value& local_value,
                          base::Value& account_value);

  // Updates the merged pref cache for `key` if it is mergeable and exists in
  // both stores. Returns true if the merged value changed.
  bool UpdateMergedPrefCacheIfMergeable(std::string_view key) const;

  // Unmerges `value` and returns the new local value and the account value (in
  // that order).
  std::pair<base::Value, base::Value> UnmergeValue(std::string_view pref_name,
                                                   base::Value value,
                                                   uint32_t flags) const;

  // Get all syncable prefs currently present in `store`.
  // Note that this will also return prefs which can not be queried from the
  // `store`. For example, this method will return prefs requiring history
  // opt-in even if history sync is disabled. A GetValue() call for such a pref
  // will not query the account store. Thus it is the role of the callers to
  // check the history opt-in.
  std::vector<std::string> GetSyncablePrefNamesInStore(
      const PersistentPrefStore* store) const;

  // Returns whether the user has history sync turned on.
  bool IsHistorySyncEnabled() const;

  // Returns the subset of user selected types that are of relevance in
  // determining whether an account pref should be exposed. This is stored in
  // the local pref store for early availability.
  syncer::UserSelectableTypeSet GetInterestingUserSelectedTypes() const;
  // Sets the subset of user selected types that are of relevance in determining
  // whether an account pref should be exposed. These are set when the sync
  // service is initialized and/or when sync service state changes.
  void SetInterestingUserSelectedTypes(
      syncer::UserSelectableTypeSet user_selected_types);

#if BUILDFLAG(IS_CHROMEOS)
  // Returns the subset of user selected OS types that are of relevance in
  // determining whether an account pref should be exposed. This is stored in
  // the local pref store for early availability.
  syncer::UserSelectableOsTypeSet GetInterestingUserSelectedOsTypes() const;
  // Sets the subset of user selected OS types that are of relevance in
  // determining whether an account pref should be exposed. These are set when
  // the sync service is initialized and/or when sync service state changes.
  void SetInterestingUserSelectedOsTypes(
      syncer::UserSelectableOsTypeSet user_selected_types);
#endif  // BUILDFLAG(IS_CHROMEOS)

  // The two underlying pref stores, scoped to this device/profile and to the
  // user's signed-in account, respectively.
  const scoped_refptr<PersistentPrefStore> local_pref_store_;
  const scoped_refptr<PersistentPrefStore> account_pref_store_;

  // This stores the merged value of a mergeable pref, if required - i.e. if the
  // pref is queried while it exists on both the stores. This is needed to
  // tackle the issue regarding the ownership of the newly created merged values
  // on calls to GetValue().
  // Note: Marked as mutable to allow update by GetValue() method which is
  // const.
  mutable PrefValueMap merged_prefs_;

  // Observers for the two underlying pref stores, used to propagate pref-change
  // notifications the this class's own observers.
  UnderlyingPrefStoreObserver local_pref_store_observer_;
  UnderlyingPrefStoreObserver account_pref_store_observer_;

  // Optional so we can differentiate `nullopt` from `nullptr`.
  std::optional<std::unique_ptr<PersistentPrefStore::ReadErrorDelegate>>
      read_error_delegate_;

  // List of preference types currently syncing.
  base::flat_set<syncer::DataType> active_types_;

  // Set to true while this store is setting prefs in the underlying stores.
  // Used to avoid self-notifications.
  bool is_setting_prefs_ = false;

  base::ObserverList<PrefStore::Observer, true> observers_;

  const scoped_refptr<PrefModelAssociatorClient> pref_model_associator_client_;
};

}  // namespace sync_preferences

#endif  // COMPONENTS_SYNC_PREFERENCES_DUAL_LAYER_USER_PREF_STORE_H_
