// 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/preferences_merge_helper.h"

#include <algorithm>

#include "base/check_is_test.h"
#include "base/notreached.h"
#include "components/sync_preferences/pref_model_associator_client.h"
#include "components/sync_preferences/syncable_prefs_database.h"

namespace sync_preferences::helper {

namespace {

MergeBehavior GetMergeBehavior(const PrefModelAssociatorClient& client,
                               std::string_view pref_name) {
  const SyncablePrefMetadata* metadata =
      client.GetSyncablePrefsDatabase().GetSyncablePrefMetadata(pref_name);
  CHECK(metadata);
  return metadata->merge_behavior();
}

}  // namespace

base::ListValue MergeListValues(const base::ListValue& local_value,
                                const base::ListValue& server_value) {
  base::ListValue result = server_value.Clone();
  for (const auto& value : local_value) {
    if (!std::ranges::contains(result, value)) {
      result.Append(value.Clone());
    }
  }

  return result;
}

base::DictValue MergeDictionaryValues(const base::DictValue& local_value,
                                      const base::DictValue& server_value) {
  base::DictValue result = server_value.Clone();

  for (auto it : local_value) {
    // It's not clear whether using a C++17 structured binding here would cause
    // a copy of the value or not, so in doubt unpack the old way.
    const base::Value* local_key_value = &it.second;
    base::Value* server_key_value = result.Find(it.first);
    if (server_key_value) {
      if (local_key_value->is_dict() && server_key_value->is_dict()) {
        *server_key_value = base::Value(MergeDictionaryValues(
            local_key_value->GetDict(), server_key_value->GetDict()));
      }
      // Note that for all other types we want to preserve the "server"
      // values so we do nothing here.
    } else {
      result.Set(it.first, local_key_value->Clone());
    }
  }
  return result;
}

base::Value MergePreference(const PrefModelAssociatorClient* client,
                            std::string_view pref_name,
                            const base::Value& local_value,
                            const base::Value& server_value) {
  if (!client) {
    CHECK_IS_TEST();
    // No client was registered. Directly let server value win.
    return server_value.Clone();
  }

  switch (GetMergeBehavior(*client, pref_name)) {
    case MergeBehavior::kMergeableDict:
      if (!server_value.is_dict()) {
        // Server value is corrupt or missing, keep pref value unchanged.
        // TODO(crbug.com/40901973): Investigate in which scenarios can the
        // value be corrupt.
        return local_value.Clone();
      }
      // TODO(crbug.com/40933499): Investigate if this is valid or if this
      // should be a CHECK instead.
      if (!local_value.is_dict()) {
        return server_value.Clone();
      }
      return base::Value(
          MergeDictionaryValues(local_value.GetDict(), server_value.GetDict()));
    case MergeBehavior::kMergeableListWithRewriteOnUpdate:
      if (!server_value.is_list()) {
        // Server value is corrupt or missing, keep pref value unchanged.
        // TODO(crbug.com/40901973): Investigate in which scenarios can the
        // value be corrupt.
        return local_value.Clone();
      }
      // TODO(crbug.com/40933499): Investigate if this is valid or if this
      // should be a CHECK instead.
      if (!local_value.is_list()) {
        return server_value.Clone();
      }
      return base::Value(
          MergeListValues(local_value.GetList(), server_value.GetList()));
    case MergeBehavior::kCustom:
      if (base::Value merged_value = client->MaybeMergePreferenceValues(
              pref_name, local_value, server_value);
          !merged_value.is_none()) {
        return merged_value;
      }
      [[fallthrough]];
    case MergeBehavior::kNone:
      // If this is not a specially handled preference, server wins.
      return server_value.Clone();
  }
  NOTREACHED();
}

std::pair<base::DictValue, base::DictValue> UnmergeDictionaryValues(
    base::DictValue new_dict,
    const base::DictValue& original_local_dict,
    const base::DictValue& original_account_dict) {
  base::DictValue new_local_dict;
  base::DictValue new_account_dict;

  // Keep only keys that exist in the `new_dict`.
  for (auto [k, v] : original_local_dict) {
    if (new_dict.contains(k)) {
      new_local_dict.Set(k, v.Clone());
    }
  }
  for (auto [k, v] : original_account_dict) {
    if (new_dict.contains(k)) {
      new_account_dict.Set(k, v.Clone());
    }
  }

  // Add or update individual keys.
  for (auto [k, new_dict_value] : new_dict) {
    // If contained value is again a dict, recursively un-merge.
    if (new_dict_value.is_dict()) {
      base::Value local_dict_value(base::Value::Type::DICT);
      if (base::DictValue* local_dict_value_dict = new_local_dict.FindDict(k)) {
        local_dict_value = base::Value(std::move(*local_dict_value_dict));
      }
      base::Value account_dict_value(base::Value::Type::DICT);
      if (base::DictValue* account_dict_value_dict =
              new_account_dict.FindDict(k)) {
        account_dict_value = base::Value(std::move(*account_dict_value_dict));
      }
      // Note: Passing empty dict values in case the key does not exist or has a
      // different type.
      auto [local_v, account_v] = UnmergeDictionaryValues(
          std::move(new_dict_value).TakeDict(), local_dict_value.GetDict(),
          account_dict_value.GetDict());
      // If the new unmerged dict value is empty, remove the key.
      if (!local_v.empty()) {
        new_local_dict.Set(k, std::move(local_v));
      } else {
        new_local_dict.Remove(k);
      }
      // If the new unmerged dict value is empty, remove the key.
      if (!account_v.empty()) {
        new_account_dict.Set(k, std::move(account_v));
      } else {
        new_account_dict.Remove(k);
      }
    } else if (base::Value* account_dict_value = new_account_dict.Find(k)) {
      // The key already existed in the account store. Copy to the local store
      // if its value changed.
      if (*account_dict_value != new_dict_value) {
        new_local_dict.Set(k, new_dict_value.Clone());
        *account_dict_value = std::move(new_dict_value);
      }
    } else if (base::Value* local_dict_value = new_local_dict.Find(k)) {
      // The key already existed in the local store. Copy to the account store
      // if its value changed.
      if (*local_dict_value != new_dict_value) {
        new_account_dict.Set(k, new_dict_value.Clone());
        *local_dict_value = std::move(new_dict_value);
      }
    } else {
      // New dict entry - the key didn't exist before. Add it to both the
      // stores.
      new_account_dict.Set(k, new_dict_value.Clone());
      new_local_dict.Set(k, std::move(new_dict_value));
    }
  }
  return {std::move(new_local_dict), std::move(new_account_dict)};
}

}  // namespace sync_preferences::helper
