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

#include "components/prefs/wrap_with_prefix_pref_store.h"

#include <string>
#include <string_view>

#include "base/strings/strcat.h"
#include "base/strings/string_util.h"

WrapWithPrefixPrefStore::WrapWithPrefixPrefStore(
    scoped_refptr<PersistentPrefStore> target_pref_store,
    std::string_view path_prefix)
    : target_pref_store_(std::move(target_pref_store)),
      dotted_prefix_(base::StrCat({path_prefix, "."})) {
  target_pref_store_->AddObserver(this);
}

WrapWithPrefixPrefStore::~WrapWithPrefixPrefStore() {
  target_pref_store_->RemoveObserver(this);
}

bool WrapWithPrefixPrefStore::GetValue(std::string_view key,
                                       const base::Value** value) const {
  return target_pref_store_->GetValue(AddDottedPrefix(key), value);
}

base::DictValue WrapWithPrefixPrefStore::GetValues() const {
  base::DictValue values = target_pref_store_->GetValues();
  std::string_view prefix(dotted_prefix_.c_str(), dotted_prefix_.size() - 1);
  if (base::DictValue* values_with_prefix =
          values.FindDictByDottedPath(prefix)) {
    return std::move(*values_with_prefix);
  }
  return {};
}

bool WrapWithPrefixPrefStore::GetMutableValue(std::string_view key,
                                              base::Value** value) {
  return target_pref_store_->GetMutableValue(AddDottedPrefix(key), value);
}

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

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

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

bool WrapWithPrefixPrefStore::IsInitializationComplete() const {
  return target_pref_store_->IsInitializationComplete();
}

void WrapWithPrefixPrefStore::SetValue(std::string_view key,
                                       base::Value value,
                                       uint32_t flags) {
  target_pref_store_->SetValue(AddDottedPrefix(key), std::move(value), flags);
}

void WrapWithPrefixPrefStore::SetValueSilently(std::string_view key,
                                               base::Value value,
                                               uint32_t flags) {
  target_pref_store_->SetValueSilently(AddDottedPrefix(key), std::move(value),
                                       flags);
}

void WrapWithPrefixPrefStore::RemoveValue(std::string_view key,
                                          uint32_t flags) {
  target_pref_store_->RemoveValue(AddDottedPrefix(key), flags);
}

void WrapWithPrefixPrefStore::RemoveValuesByPrefixSilently(
    std::string_view prefix) {
  target_pref_store_->RemoveValuesByPrefixSilently(AddDottedPrefix(prefix));
}

bool WrapWithPrefixPrefStore::ReadOnly() const {
  return target_pref_store_->ReadOnly();
}

PersistentPrefStore::PrefReadError WrapWithPrefixPrefStore::GetReadError()
    const {
  return target_pref_store_->GetReadError();
}

PersistentPrefStore::PrefReadError WrapWithPrefixPrefStore::ReadPrefs() {
  // The target pref store should have been initialized prior to calling
  // ReadPrefs() on this store.
  CHECK(target_pref_store_->IsInitializationComplete() ||
        // To catch case where target pref store initialization failed.
        target_pref_store_->GetReadError() !=
            PersistentPrefStore::PREF_READ_ERROR_NONE);
  return target_pref_store_->GetReadError();
}

void WrapWithPrefixPrefStore::ReadPrefsAsync(
    ReadErrorDelegate* error_delegate) {
  // The target pref store should either have been initialized or should have an
  // ongoing read.
  CHECK(IsInitializationComplete() ||
        // To catch case where target pref store initialization failed.
        GetReadError() != PersistentPrefStore::PREF_READ_ERROR_NONE ||
        // ReadPrefsAsync() was called but it's still ongoing.
        target_pref_store_->HasReadErrorDelegate());
  read_error_delegate_.emplace(error_delegate);
  if (PersistentPrefStore::PrefReadError read_error = GetReadError();
      read_error != PersistentPrefStore::PREF_READ_ERROR_NONE &&
      error_delegate) {
    error_delegate->OnError(read_error);
  }
}

void WrapWithPrefixPrefStore::SchedulePendingLossyWrites() {
  // This store is only a wrapper and relies on the target pref store being
  // independently notified of this.
}

void WrapWithPrefixPrefStore::OnStoreDeletionFromDisk() {
  // This store is only a wrapper and relies on the target pref store being
  // independently notified of this.
}

void WrapWithPrefixPrefStore::ReportValueChanged(std::string_view key,
                                                 uint32_t flags) {
  return target_pref_store_->ReportValueChanged(AddDottedPrefix(key), flags);
}

void WrapWithPrefixPrefStore::OnPrefValueChanged(std::string_view key) {
  if (!HasDottedPrefix(key)) {
    return;
  }
  std::string_view original_key(RemoveDottedPrefix(key));
  observers_.NotifyAllowReentrancy(&PrefStore::Observer::OnPrefValueChanged,
                                   original_key);
}

void WrapWithPrefixPrefStore::OnInitializationCompleted(bool succeeded) {
  if (PersistentPrefStore::PrefReadError read_error = GetReadError();
      read_error != PersistentPrefStore::PREF_READ_ERROR_NONE &&
      read_error_delegate_.has_value() && read_error_delegate_.value()) {
    read_error_delegate_.value()->OnError(read_error);
  }
  for (PrefStore::Observer& observer : observers_) {
    observer.OnInitializationCompleted(succeeded);
  }
}

std::string WrapWithPrefixPrefStore::AddDottedPrefix(
    std::string_view path) const {
  return base::StrCat({dotted_prefix_, path});
}

std::string_view WrapWithPrefixPrefStore::RemoveDottedPrefix(
    std::string_view path) const {
  CHECK(HasDottedPrefix(path));
  path.remove_prefix(dotted_prefix_.size());
  return path;
}

bool WrapWithPrefixPrefStore::HasDottedPrefix(std::string_view path) const {
  return base::StartsWith(path, dotted_prefix_);
}

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