// 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 "components/language/core/browser/language_prefs.h"

#include <algorithm>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/language/core/browser/incognito_language_list_map.h"
#include "components/language/core/browser/pref_names.h"
#include "components/language/core/common/language_util.h"
#include "components/language/core/common/locale_util.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/strings/grit/components_locale_settings.h"
#include "ui/base/l10n/l10n_util.h"

namespace language {

void LanguagePrefs::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterStringPref(language::prefs::kAcceptLanguages,
                               l10n_util::GetStringUTF8(IDS_ACCEPT_LANGUAGES),
                               user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);

  registry->RegisterStringPref(language::prefs::kSelectedLanguages,
                               std::string(),
                               user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);

  registry->RegisterListPref(language::prefs::kForcedLanguages);
#if BUILDFLAG(IS_CHROMEOS)
  registry->RegisterStringPref(language::prefs::kPreferredLanguages,
                               kFallbackInputMethodLocale);

  registry->RegisterStringPref(
      language::prefs::kPreferredLanguagesSyncable, "",
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
#endif
#if BUILDFLAG(IS_ANDROID)
  registry->RegisterBooleanPref(
      language::prefs::kAppLanguagePromptShown, false,
      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
  registry->RegisterListPref(language::prefs::kULPLanguages);
#endif
}

LanguagePrefs::LanguagePrefs(PrefService* user_prefs) : prefs_(user_prefs) {
  InitializeSelectedLanguagesPref();
  UpdateAcceptLanguagesPref();
  base::RepeatingClosure callback = base::BindRepeating(
      &LanguagePrefs::UpdateAcceptLanguagesPref, base::Unretained(this));
  pref_change_registrar_.Init(prefs_);
  pref_change_registrar_.Add(language::prefs::kForcedLanguages, callback);
  pref_change_registrar_.Add(language::prefs::kSelectedLanguages, callback);
}

LanguagePrefs::~LanguagePrefs() {
  pref_change_registrar_.RemoveAll();
}

void LanguagePrefs::GetAcceptLanguagesList(
    std::vector<std::string>* languages) const {
  DCHECK(languages);
  DCHECK(languages->empty());
#if BUILDFLAG(IS_CHROMEOS)
  const std::string& key = language::prefs::kPreferredLanguages;
#else
  const std::string& key = language::prefs::kAcceptLanguages;
#endif

  *languages = base::SplitString(prefs_->GetString(key), ",",
                                 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
}

void LanguagePrefs::GetUserSelectedLanguagesList(
    std::vector<std::string>* languages) const {
  DCHECK(languages);
  DCHECK(languages->empty());
  const std::string& key = language::prefs::kSelectedLanguages;
  *languages = base::SplitString(prefs_->GetString(key), ",",
                                 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
}

void LanguagePrefs::SetUserSelectedLanguagesList(
    const std::vector<std::string>& languages) {
  std::vector<std::string> filtered_languages =
      l10n_util::KeepAcceptedLanguages(languages);
  std::string languages_str = base::JoinString(filtered_languages, ",");
  prefs_->SetString(language::prefs::kSelectedLanguages, languages_str);
#if BUILDFLAG(IS_CHROMEOS)
  prefs_->SetString(language::prefs::kPreferredLanguages, languages_str);
#endif
}

void LanguagePrefs::GetDeduplicatedUserLanguages(
    std::string* deduplicated_languages_string) {
  std::vector<std::string> deduplicated_languages;
  forced_languages_set_.clear();

  // Add policy languages.
  for (const auto& language :
       prefs_->GetList(language::prefs::kForcedLanguages)) {
    if (forced_languages_set_.find(language.GetString()) ==
        forced_languages_set_.end()) {
      deduplicated_languages.emplace_back(language.GetString());
      forced_languages_set_.insert(language.GetString());
    }
  }

  // Add non-duplicate user-selected languages.
  for (auto& language :
       base::SplitString(prefs_->GetString(language::prefs::kSelectedLanguages),
                         ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
    if (forced_languages_set_.find(language) == forced_languages_set_.end())
      deduplicated_languages.emplace_back(std::move(language));
  }
  *deduplicated_languages_string =
      base::JoinString(deduplicated_languages, ",");
}

void LanguagePrefs::UpdateAcceptLanguagesPref() {
  std::string deduplicated_languages_string;
  GetDeduplicatedUserLanguages(&deduplicated_languages_string);
  if (deduplicated_languages_string !=
      prefs_->GetString(language::prefs::kAcceptLanguages))
    prefs_->SetString(language::prefs::kAcceptLanguages,
                      deduplicated_languages_string);
}

#if BUILDFLAG(IS_ANDROID)
std::vector<std::string> LanguagePrefs::GetULPLanguages() {
  std::vector<std::string> ulp_languages;
  for (const auto& language : prefs_->GetList(language::prefs::kULPLanguages)) {
    ulp_languages.push_back(language.GetString());
  }
  return ulp_languages;
}

void LanguagePrefs::SetULPLanguages(
    std::vector<base::i18n::LanguageTag> ulp_languages) {
  base::ListValue ulp_pref_list;
  ulp_pref_list.reserve(ulp_languages.size());
  for (const auto& language : ulp_languages) {
    ulp_pref_list.Append(std::string(language.tag_string()));
  }
  prefs_->SetList(language::prefs::kULPLanguages, std::move(ulp_pref_list));
}
#endif

bool LanguagePrefs::IsForcedLanguage(std::string_view language) {
  return forced_languages_set_.find(language) != forced_languages_set_.end();
}

void LanguagePrefs::InitializeSelectedLanguagesPref() {
  // Initializes user-selected languages if they're empty.
  // This is important so that previously saved languages aren't overwritten.
  if (prefs_->GetString(language::prefs::kSelectedLanguages).empty()) {
    prefs_->SetString(language::prefs::kSelectedLanguages,
                      prefs_->GetString(language::prefs::kAcceptLanguages));
  }
}

void ResetLanguagePrefs(PrefService* prefs) {
  prefs->ClearPref(language::prefs::kSelectedLanguages);
  prefs->ClearPref(language::prefs::kAcceptLanguages);
#if BUILDFLAG(IS_CHROMEOS)
  prefs->ClearPref(language::prefs::kPreferredLanguages);
  prefs->ClearPref(language::prefs::kPreferredLanguagesSyncable);
#endif
#if BUILDFLAG(IS_ANDROID)
  prefs->ClearPref(language::prefs::kULPLanguages);
#endif
}

std::string GetFirstLanguage(std::string_view language_list) {
  auto end = language_list.find(",");
  return std::string(language_list.substr(0, end));
}

namespace {

// Ensure at compile time that our fallback key exists in the generated map.
constexpr bool IncognitoMapContainsFallback() {
  return std::ranges::any_of(kIncognitoLanguageListMap, [](const auto& e) {
    return e.first == kFallbackInputMethodLocale;
  });
}
static_assert(
    IncognitoMapContainsFallback(),
    "kFallbackInputMethodLocale must exist in kIncognitoLanguageListMap");

}  // namespace

std::string GetIncognitoLanguageList(std::string_view language_list) {
  auto comma_pos = language_list.find(',');
  // <2 values suggests a user actively deleted other languages from their
  // settings, so prioritize keeping that user preference over the normal logic.
  if (comma_pos == std::string_view::npos) {
    return std::string(language_list);
  }

  std::string_view first_language = language_list.substr(0, comma_pos);

  // Look up in the generated map.
  auto it = kIncognitoLanguageListMap.find(first_language);
  if (it != kIncognitoLanguageListMap.end()) {
    return std::string(it->second);
  }

  // Fallback for simple (no regional subtags) or unrecognized languages, just
  // prepend the first language to global default (i.e., "en-US,en").
  return base::StrCat(
      {first_language, ",",
       kIncognitoLanguageListMap.at(kFallbackInputMethodLocale)});
}

}  // namespace language
