// Copyright 2014 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/translate/core/browser/translate_manager.h"

#include <map>
#include <memory>
#include <string_view>
#include <tuple>
#include <utility>

#include "base/command_line.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/i18n/language_tag.h"
#include "base/i18n/tag_converters.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/language/core/browser/language_model.h"
#include "components/language/core/common/language_experiments.h"
#include "components/language/core/common/locale_util.h"
#include "components/language_detection/core/constants.h"
#include "components/prefs/pref_service.h"
#include "components/translate/core/browser/language_state.h"
#include "components/translate/core/browser/page_translated_details.h"
#include "components/translate/core/browser/translate_browser_metrics.h"
#include "components/translate/core/browser/translate_client.h"
#include "components/translate/core/browser/translate_download_manager.h"
#include "components/translate/core/browser/translate_driver.h"
#include "components/translate/core/browser/translate_error_details.h"
#include "components/translate/core/browser/translate_init_details.h"
#include "components/translate/core/browser/translate_language_list.h"
#include "components/translate/core/browser/translate_metrics_logger.h"
#include "components/translate/core/browser/translate_metrics_logger_impl.h"
#include "components/translate/core/browser/translate_prefs.h"
#include "components/translate/core/browser/translate_ranker.h"
#include "components/translate/core/browser/translate_script.h"
#include "components/translate/core/browser/translate_trigger_decision.h"
#include "components/translate/core/browser/translate_url_util.h"
#include "components/translate/core/common/language_detection_details.h"
#include "components/translate/core/common/translate_features.h"
#include "components/translate/core/common/translate_language_matcher.h"
#include "components/translate/core/common/translate_switches.h"
#include "components/translate/core/common/translate_util.h"
#include "components/variations/variations_associated_data.h"
#include "google_apis/google_api_keys.h"
#include "net/base/mime_util.h"
#include "net/base/network_change_notifier.h"
#include "net/base/url_util.h"
#include "net/http/http_status_code.h"
#include "third_party/metrics_proto/translate_event.pb.h"

namespace translate {
namespace {

using ::base::i18n::GetKnownLanguageTag;
using ::base::i18n::GetLanguageTagFromString;
using ::base::i18n::LanguageTag;
using ::base::i18n::LanguageTagConverter;

// Callbacks for translate errors.
TranslateManager::TranslateErrorCallbackList* g_error_callback_list_ = nullptr;

// Callbacks for translate initializations.
TranslateManager::TranslateInitCallbackList* g_init_callback_list_ = nullptr;

// Callbacks for language detection.
TranslateManager::LanguageDetectedCallbackList* g_detection_callback_list_ =
    nullptr;

std::string GetForcedTranslateLanguage() {
  return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
      switches::kForcedTranslateLanguage);
}

LanguageTag GetTranslateLanguage(std::string_view tag) {
  std::optional<LanguageTag> parsed = GetLanguageTagFromString(tag);
  if (!parsed) {
    return GetKnownLanguageTag("und");
  }
  return GetTranslateLanguageMatcher().Match(*parsed).value_or(
      GetKnownLanguageTag("und"));
}

std::optional<LanguageTag> GetTranslateLanguageIfValid(std::string_view tag) {
  std::optional<LanguageTag> parsed_tag = GetLanguageTagFromString(tag);
  if (!parsed_tag) {
    return std::nullopt;
  }

  return GetTranslateLanguageMatcher().Match(*parsed_tag);
}

}  // namespace

TranslateManager::~TranslateManager() = default;

// static
base::CallbackListSubscription TranslateManager::RegisterTranslateErrorCallback(
    const TranslateManager::TranslateErrorCallback& callback) {
  if (!g_error_callback_list_)
    g_error_callback_list_ = new TranslateErrorCallbackList;
  return g_error_callback_list_->Add(callback);
}

// static
base::CallbackListSubscription TranslateManager::RegisterTranslateInitCallback(
    const TranslateManager::TranslateInitCallback& callback) {
  if (!g_init_callback_list_)
    g_init_callback_list_ = new TranslateInitCallbackList;
  return g_init_callback_list_->Add(callback);
}

// static
base::CallbackListSubscription
TranslateManager::RegisterLanguageDetectedCallback(
    const TranslateManager::LanguageDetectedCallback& callback) {
  if (!g_detection_callback_list_)
    g_detection_callback_list_ = new LanguageDetectedCallbackList;
  return g_detection_callback_list_->Add(callback);
}

TranslateManager::TranslateManager(TranslateClient* translate_client,
                                   TranslateRanker* translate_ranker,
                                   language::LanguageModel* language_model)
    : page_seq_no_(0),
      translate_client_(translate_client),
      translate_driver_(translate_client_->GetTranslateDriver()),
      translate_ranker_(translate_ranker),
      language_model_(language_model),
      null_translate_metrics_logger_(
          std::make_unique<NullTranslateMetricsLogger>()),
      language_state_(translate_driver_),
      translate_event_(std::make_unique<metrics::TranslateEventProto>()) {}

base::WeakPtr<TranslateManager> TranslateManager::GetWeakPtr() {
  return weak_method_factory_.GetWeakPtr();
}

void TranslateManager::InitiateTranslation(std::string_view page_lang) {
  std::unique_ptr<TranslatePrefs> translate_prefs(
      translate_client_->GetTranslatePrefs());
  std::string page_language_code =
      TranslateDownloadManager::GetLanguageCode(page_lang);
  TranslateBrowserMetrics::TargetLanguageOrigin target_language_origin =
      TranslateBrowserMetrics::TargetLanguageOrigin::kUninitialized;
  std::string target_lang =
      GetTargetLanguage(translate_prefs.get(), language_model_,
                        page_language_code, target_language_origin);

  // TODO(crbug.com/40610937): The ranker event shouldn't be a global on this
  // object. It should instead be passed around to code that uses it.
  InitTranslateEvent(page_language_code, target_lang, *translate_prefs);

  // Logs the initial source and target languages, as well as whether the
  // initial source language is blocked (i.e. on the never translate list).
  GetActiveTranslateMetricsLogger()->LogInitialSourceLanguage(
      page_language_code,
      translate_prefs->IsBlockedLanguage(page_language_code));
  GetActiveTranslateMetricsLogger()->LogTargetLanguage(target_lang,
                                                       target_language_origin);

  const TranslateTriggerDecision& decision = ComputePossibleOutcomes(
      translate_prefs.get(), page_language_code, target_lang);

  bool ui_shown = MaterializeDecision(decision, translate_prefs.get(),
                                      page_language_code, target_lang);
  MaybeShowOmniboxIcon(decision);

  NotifyTranslateInit(page_language_code, target_lang, decision, ui_shown);

  RecordDecisionMetrics(decision, page_language_code, ui_shown);
  RecordDecisionRankerEvent(decision, translate_prefs.get(), page_language_code,
                            target_lang);

  // Mark the current state as the initial state now that we are done
  // initializing Translate.
  GetActiveTranslateMetricsLogger()->LogInitialState();
}

bool TranslateManager::CanManuallyTranslate(bool menu_logging) {
  bool can_translate = true;

  if (net::NetworkChangeNotifier::IsOffline()) {
    if (!menu_logging) {
      return false;
    }
    TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
        TranslateBrowserMetrics::MenuTranslationUnavailableReason::
            kNetworkOffline);
    can_translate = false;
  }

  if (!ignore_missing_key_for_testing_ &&
      !::google_apis::HasAPIKeyConfigured()) {
    if (!menu_logging) {
      return false;
    }
    TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
        TranslateBrowserMetrics::MenuTranslationUnavailableReason::
            kApiKeysMissing);
    can_translate = false;
  }

  // not supported MIME type pages currently cannot be translated.
  // See bug: 217945, 1208340.
  if (!IsMimeTypeSupported(translate_driver_->GetContentsMimeType())) {
    if (!menu_logging) {
      return false;
    }
    TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
        TranslateBrowserMetrics::MenuTranslationUnavailableReason::
            kMIMETypeUnsupported);
    can_translate = false;
  }

  if (!translate_client_->IsTranslatableURL(
          translate_driver_->GetVisibleURL())) {
    if (!menu_logging) {
      return false;
    }
    TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
        TranslateBrowserMetrics::MenuTranslationUnavailableReason::
            kURLNotTranslatable);
    can_translate = false;
  }

  if (language_state_.pdf_translatability_status() ==
      LanguageState::PdfTranslatabilityStatus::kUntranslatable) {
    if (!menu_logging) {
      return false;
    }
    TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
        TranslateBrowserMetrics::MenuTranslationUnavailableReason::
            kURLNotTranslatable);
    can_translate = false;
  }

  const std::string source_language = language_state_.source_language();
  // The source language is empty when language detection has not finished
  // running. In this case, Android queues the translation and waits until the
  // source language has been determined. Android is the only platform that
  // supports manual translation in this case.
#if !BUILDFLAG(IS_ANDROID)
  if (source_language.empty()) {
    if (!menu_logging) {
      return false;
    }
    TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
        TranslateBrowserMetrics::MenuTranslationUnavailableReason::
            kSourceLangUnknown);
    can_translate = false;
  }
#endif

  std::unique_ptr<TranslatePrefs> translate_prefs(
      translate_client_->GetTranslatePrefs());
  if (!translate_prefs->IsTranslateAllowedByPolicy()) {
    if (!menu_logging) {
      return false;
    }
    TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
        TranslateBrowserMetrics::MenuTranslationUnavailableReason::
            kNotAllowedByPolicy);
    can_translate = false;
  }

  const std::string target_lang = GetTargetLanguage(
      translate_prefs.get(), language_model_,
      TranslateDownloadManager::GetLanguageCode(source_language));
  if (target_lang.empty()) {
    if (!menu_logging) {
      return false;
    }
    TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
        TranslateBrowserMetrics::MenuTranslationUnavailableReason::
            kTargetLangUnknown);
    can_translate = false;
  }

  if (menu_logging) {
    UMA_HISTOGRAM_BOOLEAN("Translate.MenuTranslation.IsAvailable",
                          can_translate);
  }

  return can_translate;
}

bool TranslateManager::CanPartiallyTranslateTargetLanguage() {
  std::unique_ptr<TranslatePrefs> translate_prefs(
      translate_client_->GetTranslatePrefs());
  const std::string& source_language = language_state_.source_language();
  const std::string target_lang = GetTargetLanguage(
      translate_prefs.get(), language_model_,
      TranslateDownloadManager::GetLanguageCode(source_language));

  if (target_lang.empty()) {
    return false;
  }
  return TranslateLanguageList::IsSupportedPartialTranslateLanguage(
      target_lang);
}

bool TranslateManager::IsMimeTypeSupported(std::string_view mime_type) {
  if (net::MatchesMimeType("image/*", mime_type))
    return false;
  if (mime_type == "multipart/related")
    return false;

  return true;
}

void TranslateManager::ShowTranslateUI(std::optional<std::string> source_code,
                                       std::optional<std::string> target_code,
                                       bool auto_translate,
                                       bool triggered_from_menu) {
  // If a translation is in progress, do nothing.
  if (language_state_.translation_pending()) {
    return;
  }
  std::unique_ptr<TranslatePrefs> translate_prefs(
      translate_client_->GetTranslatePrefs());

  // Get language codes.
  const LanguageTag translate_source_language_tag = GetTranslateLanguage(
      source_code.has_value() ? source_code.value()
                              : TranslateDownloadManager::GetLanguageCode(
                                    language_state_.source_language()));

  const LanguageTag translate_target_language_tag = GetTranslateLanguage(
      target_code.has_value() ? target_code.value()
                              : GetTargetLanguageForDisplay(
                                    translate_prefs.get(), language_model_));

  bool is_translated = language_state_.IsPageTranslated() &&
                       translate_target_language_tag.tag_string() ==
                           language_state_.current_language();

  language_state_.SetTranslateEnabled(true);
  const TranslateStep step = is_translated ? TRANSLATE_STEP_AFTER_TRANSLATE
                                           : TRANSLATE_STEP_BEFORE_TRANSLATE;
  // Translate the page if it has not been translated and manual translate
  // should trigger translation automatically. Otherwise, only show the infobar.
  if (auto_translate && !is_translated) {
    TranslatePage(
        translate_source_language_tag.tag_string(),
        translate_target_language_tag.tag_string(), triggered_from_menu,
        GetActiveTranslateMetricsLogger()->GetNextManualTranslationType(
            triggered_from_menu));
    return;
  }
  translate_client_->ShowTranslateUI(
      step, std::string(translate_source_language_tag.tag_string()),
      std::string(translate_target_language_tag.tag_string()),
      TranslateErrors::NONE, triggered_from_menu);
}

void TranslateManager::ShowTranslateUI(bool auto_translate,
                                       bool triggered_from_menu) {
  ShowTranslateUI(std::nullopt, std::nullopt, auto_translate,
                  triggered_from_menu);
}

void TranslateManager::TranslatePage(std::string_view original_source_lang,
                                     std::string_view target_lang,
                                     bool triggered_from_menu,
                                     TranslationType translation_type) {
  const GURL& page_url = translate_driver_->GetVisibleURL();
  language_state_.SetTranslationType(translation_type);
  // TODO(crbug.com/40898004): Very rarely, users can reach a state where this
  // Translate code is called on a page ineligible for translation. It is
  // unclear how this state is reached, but the crash rate is very low
  // (<0.1CPM) and this state is not breaking for the user. This crash rate
  // should be monitored and investigated if it increases.
  if (!translate_client_->IsTranslatableURL(page_url)) {
    base::debug::DumpWithoutCrashing();
    return;
  }

  translate_driver_->PrepareToTranslatePage(page_seq_no_, original_source_lang,
                                            target_lang, triggered_from_menu);

  // If the source language matches the UI language, it means the translation
  // prompt is being forced by an experiment. Report this so the count of how
  // often it happens can be decremented (meaning the user didn't decline or
  // ignore the prompt).
  if (original_source_lang ==
      TranslateDownloadManager::GetLanguageCode(
          TranslateDownloadManager::GetInstance()->application_locale())) {
    translate_client_->GetTranslatePrefs()
        ->ReportAcceptedAfterForceTriggerOnEnglishPages();
  }

  // If the target language isn't in the chrome://settings/languages list, add
  // it there. This way, it's obvious to the user that Chrome is remembering
  // their choice, they can remove it from the list, and they'll send that
  // language in the Accept-Language header, giving servers a chance to serve
  // them pages in that language.
  AddTargetLanguageToAcceptLanguages(target_lang);

  // Translation can be kicked by context menu against unsupported languages.
  // Unsupported language strings should be replaced with
  // kUnknownLanguageCode in order to send a translation request with enabling
  // server side auto language detection.
  std::string source_lang(original_source_lang);
  if (!TranslateDownloadManager::IsSupportedLanguage(source_lang))
    source_lang = std::string(language_detection::kUnknownLanguageCode);

  // Capture the translate event if we were triggered from the menu.
  if (triggered_from_menu) {
    RecordTranslateEvent(
        metrics::TranslateEventProto::USER_CONTEXT_MENU_TRANSLATE);
  }

  if (source_lang == target_lang) {
    // If the languages are the same, try the translation using the unknown
    // language code. The source and target languages should only be equal if
    // the translation was manually triggered by the user. Rather than show
    // them the error, we should attempt to send the page for translation. For
    // page with multiple languages we often detect same language, but the
    // Translation service is able to translate the various languages using it's
    // own language detection.
    source_lang = language_detection::kUnknownLanguageCode;
  }

  // Trigger the "translating now" UI.
  translate_client_->ShowTranslateUI(
      translate::TRANSLATE_STEP_TRANSLATING, std::string(source_lang),
      std::string(target_lang), TranslateErrors::NONE, triggered_from_menu);

  GetActiveTranslateMetricsLogger()->LogTranslationStarted(translation_type);

  TranslateScript* script = TranslateDownloadManager::GetInstance()->script();
  DCHECK(script != nullptr);

  script->ClearIfDataRegionChanged(translate_client_->GetPrefs());

  const std::string& script_data = script->data();
  if (!script_data.empty()) {
    DoTranslatePage(script_data, source_lang, target_lang);
    return;
  }

  // The script is not available yet.  Queue that request and query for the
  // script.  Once it is downloaded we'll do the translate.
  TranslateScript::RequestCallback callback = base::BindOnce(
      &TranslateManager::OnTranslateScriptFetchComplete, GetWeakPtr(),
      std::string(source_lang), std::string(target_lang));

  script->Request(std::move(callback), translate_driver_->IsIncognito(),
                  translate_client_->GetPrefs());
}

void TranslateManager::RevertTranslation() {
  // Do nothing if the page is not translated.
  if (!GetLanguageState()->IsPageTranslated()) {
    return;
  }
  // Capture the revert event in the translate metrics
  RecordTranslateEvent(metrics::TranslateEventProto::USER_REVERT);

  // Revert the translation.
  translate_driver_->RevertTranslation(page_seq_no_);
  language_state_.SetCurrentLanguage(language_state_.source_language());

  GetActiveTranslateMetricsLogger()->LogReversion();
}

void TranslateManager::DoTranslatePage(std::string_view translate_script,
                                       std::string_view source_lang,
                                       std::string_view target_lang) {
  language_state_.set_translation_pending(true);
  translate_driver_->TranslatePage(page_seq_no_, translate_script, source_lang,
                                   target_lang);
  if (translate_driver_->GetContentsMimeType() == "application/pdf" &&
      base::FeatureList::IsEnabled(translate::kEnableTranslatePdf)) {
    TranslateBrowserMetrics::ReportPdfSourceLanguage(source_lang);
    TranslateBrowserMetrics::ReportPdfTargetLanguage(target_lang);
  }
}

// Notifies |g_error_callback_list_| of translate errors.
void TranslateManager::NotifyTranslateError(TranslateErrors error_type) {
  if (!g_error_callback_list_ || error_type == TranslateErrors::NONE ||
      translate_driver_->IsIncognito()) {
    return;
  }

  TranslateErrorDetails error_details;
  error_details.time = base::Time::Now();
  error_details.url = translate_driver_->GetLastCommittedURL();
  error_details.error = error_type;
  g_error_callback_list_->Notify(error_details);
}

void TranslateManager::NotifyTranslateInit(std::string page_language_code,
                                           std::string target_lang,
                                           TranslateTriggerDecision decision,
                                           bool ui_shown) {
  if (!g_init_callback_list_ || translate_driver_->IsIncognito())
    return;

  TranslateInitDetails details;
  details.time = base::Time::Now();
  details.url = translate_driver_->GetLastCommittedURL();
  details.page_language_code = page_language_code;
  details.target_lang = target_lang;
  details.decision = decision;
  details.ui_shown = ui_shown;

  g_init_callback_list_->Notify(details);
}

void TranslateManager::NotifyLanguageDetected(
    const translate::LanguageDetectionDetails& details) {
  if (!g_detection_callback_list_)
    return;

  g_detection_callback_list_->Notify(details);
}

void TranslateManager::PageTranslated(std::string_view source_lang,
                                      std::string_view target_lang,
                                      TranslateErrors error_type) {
  if (error_type == TranslateErrors::NONE) {
    // The user could have updated the source language before translating, so
    // update the language state with both source and current.
    language_state_.SetSourceLanguage(source_lang);
    language_state_.SetCurrentLanguage(target_lang);
  }

  language_state_.set_translation_pending(false);
  language_state_.set_translation_error(error_type != TranslateErrors::NONE);

  if ((error_type == TranslateErrors::NONE) &&
      source_lang != language_detection::kUnknownLanguageCode &&
      !TranslateDownloadManager::IsSupportedLanguage(source_lang)) {
    error_type = TranslateErrors::UNSUPPORTED_LANGUAGE;
  }

  // Currently we only want to log any error happens during the translation
  // script initialization phase such as translation script failed because of
  // CSP issues (crbug.com/738277).
  // Note: NotifyTranslateError and ShowTranslateUI will not log the errors.
  if (error_type == TranslateErrors::INITIALIZATION_ERROR)
    RecordTranslateEvent(metrics::TranslateEventProto::INITIALIZATION_ERROR);

  translate_client_->ShowTranslateUI(
      translate::TRANSLATE_STEP_AFTER_TRANSLATE, std::string(source_lang),
      std::string(target_lang), error_type, false);

  language_state_.SetTranslationType(TranslationType::kUninitialized);
  NotifyTranslateError(error_type);
  GetActiveTranslateMetricsLogger()->LogTranslationFinished(
      error_type == TranslateErrors::NONE, error_type);
}

void TranslateManager::OnTranslateScriptFetchComplete(std::string source_lang,
                                                      std::string target_lang,
                                                      bool success) {
  if (!translate_driver_->HasCurrentPage())
    return;

  if (success) {
    // Translate the page.
    TranslateScript* translate_script =
        TranslateDownloadManager::GetInstance()->script();
    DCHECK(translate_script);
    DoTranslatePage(translate_script->data(), source_lang, target_lang);
  } else {
    translate_client_->ShowTranslateUI(
        translate::TRANSLATE_STEP_TRANSLATE_ERROR, source_lang, target_lang,
        TranslateErrors::NETWORK, false);
    NotifyTranslateError(TranslateErrors::NETWORK);
    GetActiveTranslateMetricsLogger()->LogTranslationFinished(
        false, TranslateErrors::NETWORK);
  }
}

std::string TranslateManager::GetTargetLanguageForDisplay(
    TranslatePrefs* prefs,
    language::LanguageModel* language_model) {
  // If the page is translated, always return the current target language. This
  // ensures that reshowing the UI on a translated page maintains the correct
  // target language that the page is currently translated into.
  if (language_state_.IsPageTranslated())
    return language_state_.current_language();

  return GetTargetLanguage(prefs, language_model,
                           TranslateDownloadManager::GetLanguageCode(
                               language_state_.source_language()));
}

// static
std::string TranslateManager::GetTargetLanguage(
    TranslatePrefs* prefs,
    language::LanguageModel* language_model,
    std::string_view source_lang_code,
    TranslateBrowserMetrics::TargetLanguageOrigin& target_language_origin) {
  DCHECK(prefs);

  // For callers with source language we want to check their auto translate
  // preference.
  // We don't enable auto translate feature in incognito profiles so this will
  // always be empty for incognito.
  if (source_lang_code != language_detection::kUnknownLanguageCode) {
    const std::string forced_lang = GetForcedTranslateLanguage();
    if (!forced_lang.empty() &&
        TranslateDownloadManager::IsSupportedLanguage(forced_lang)) {
      target_language_origin =
          TranslateBrowserMetrics::TargetLanguageOrigin::kForcedTranslateLanguage;
      return forced_lang;
    }

    const std::string auto_lang = GetAutoTargetLanguage(source_lang_code, prefs);
    if (!auto_lang.empty()) {
      target_language_origin =
          TranslateBrowserMetrics::TargetLanguageOrigin::kAutoTranslate;
      return auto_lang;
    }
  }

  // If we've recorded the most recent target language, use that.
  if (base::FeatureList::IsEnabled(kTranslateRecentTarget)) {
    const std::string recent_target = prefs->GetRecentTargetLanguage();
    if (!recent_target.empty() &&
        TranslateDownloadManager::IsSupportedLanguage(recent_target)) {
      target_language_origin =
          TranslateBrowserMetrics::TargetLanguageOrigin::kRecentTarget;
      return recent_target;
    }
  }

  if (language_model) {
    std::vector<std::string> language_codes;
    for (const auto& lang : language_model->GetLanguages()) {
      std::optional<LanguageTag> parsed_tag = GetTranslateLanguageIfValid(
          TranslateDownloadManager::GetLanguageCode(lang.lang_code));
      if (!parsed_tag) {
        continue;
      }
      if (TranslateDownloadManager::IsSupportedLanguage(
              parsed_tag->tag_string())) {
        language_codes.emplace_back(parsed_tag->tag_string());
      }
    }

    // If forcing triggering on English, skip English as the target language if
    // possible by moving it to the end of |language_codes|.
    if (prefs->ShouldForceTriggerTranslateOnEnglishPages() &&
        source_lang_code == "en") {
      std::stable_partition(
          language_codes.begin(), language_codes.end(),
          [&](const auto& lang) { return lang.compare("en") != 0; });
    }

    // Use the first language from the model that translate supports.
    if (!language_codes.empty()) {
      target_language_origin =
          TranslateBrowserMetrics::TargetLanguageOrigin::kLanguageModel;
      return language_codes[0];
    }
  }

  // Get the browser's user interface language.
  LanguageTag parsed_ui_language_tag =
      GetTranslateLanguage(TranslateDownloadManager::GetLanguageCode(
          TranslateDownloadManager::GetInstance()->application_locale()));
  if (TranslateDownloadManager::IsSupportedLanguage(
          parsed_ui_language_tag.tag_string())) {
    target_language_origin =
        TranslateBrowserMetrics::TargetLanguageOrigin::kApplicationUI;
    return std::string(parsed_ui_language_tag.tag_string());
  }

  // Get the first supported language on the Accept Languages list.
  for (const auto& lang : prefs->GetLanguageList()) {
    std::string lang_code =
        TranslateDownloadManager::GetLanguageCode(lang.tag_string());
    if (TranslateDownloadManager::IsSupportedLanguage(lang_code)) {
      target_language_origin =
          TranslateBrowserMetrics::TargetLanguageOrigin::kAcceptLanguages;
      return lang_code;
    }
  }

  // If there isn't a target language determined by the above logic, default to
  // English. Otherwise the user can get stuck not being able to translate. See
  // https://crbug.com/1041387.
  target_language_origin =
      TranslateBrowserMetrics::TargetLanguageOrigin::kDefaultEnglish;
  return std::string("en");
}

// static
std::string TranslateManager::GetTargetLanguage(
    TranslatePrefs* prefs,
    language::LanguageModel* language_model,
    std::string_view source_lang_code) {
  TranslateBrowserMetrics::TargetLanguageOrigin target_language_origin =
      TranslateBrowserMetrics::TargetLanguageOrigin::kUninitialized;
  return GetTargetLanguage(prefs, language_model, source_lang_code,
                           target_language_origin);
}

// static
std::string TranslateManager::GetAutoTargetLanguage(
    std::string_view source_language,
    TranslatePrefs* translate_prefs) {
  std::string auto_target_lang;
  if (translate_prefs->ShouldAutoTranslate(source_language,
                                           &auto_target_lang)) {
    // We need to confirm that the saved target language is still supported.
    // Also, GetLanguageCode will take care of removing country code if any.
    auto_target_lang =
        TranslateDownloadManager::GetLanguageCode(auto_target_lang);
    if (TranslateDownloadManager::IsSupportedLanguage(auto_target_lang))
      return auto_target_lang;
  }
  return std::string();
}

LanguageState* TranslateManager::GetLanguageState() {
  return &language_state_;
}

bool TranslateManager::ignore_missing_key_for_testing_ = false;

// static
void TranslateManager::SetIgnoreMissingKeyForTesting(bool ignore) {
  ignore_missing_key_for_testing_ = ignore;
}

// static
bool TranslateManager::IsAvailable(const TranslatePrefs* prefs) {
  // These conditions mirror the conditions in InitiateTranslation.
  return (ignore_missing_key_for_testing_ ||
          ::google_apis::HasAPIKeyConfigured()) &&
         prefs->IsOfferTranslateEnabled();
}

void TranslateManager::InitTranslateEvent(std::string_view src_lang,
                                          std::string_view dst_lang,
                                          const TranslatePrefs& prefs) {
  translate_event_->Clear();
  translate_event_->set_source_language(src_lang);
  translate_event_->set_target_language(dst_lang);
  translate_event_->set_country(prefs.GetCountry());
  translate_event_->set_accept_count(
      prefs.GetTranslationAcceptedCount(src_lang));
  translate_event_->set_decline_count(
      prefs.GetTranslationDeniedCount(src_lang));
  translate_event_->set_ignore_count(
      prefs.GetTranslationIgnoredCount(src_lang));
  translate_event_->set_ranker_response(
      metrics::TranslateEventProto::NOT_QUERIED);
  translate_event_->set_event_type(metrics::TranslateEventProto::UNKNOWN);
  // TODO(rogerm): Populate the language list.
}

void TranslateManager::RecordTranslateEvent(int event_type) {
  translate_ranker_->RecordTranslateEvent(
      event_type, translate_driver_->GetUkmSourceId(), translate_event_.get());
}

bool TranslateManager::ShouldSuppressBubbleUI(
    std::string_view target_language) {
  // Suppress the UI if the user navigates to a page with the same language as
  // the previous page, unless the page was loaded from a link click with
  // hrefTranslate attached that matches the target language, since in that case
  // the site might have a good reason to show the translate UI regardless of
  // the source language of the previous page. In the new UI, continue offering
  // translation after the user navigates to another page.
  DCHECK(!target_language.empty());
  if (language_state_.href_translate() == target_language ||
      language_state_.HasLanguageChanged()) {
    return false;
  }

  RecordTranslateEvent(metrics::TranslateEventProto::MATCHES_PREVIOUS_LANGUAGE);

  GetActiveTranslateMetricsLogger()->LogTriggerDecision(
      TriggerDecision::kDisabledMatchesPreviousLanguage);
  return true;
}

void TranslateManager::AddTargetLanguageToAcceptLanguages(
    std::string_view target_language_code) {
  auto prefs = translate_client_->GetTranslatePrefs();
  std::vector<base::i18n::LanguageTag> languages = prefs->GetLanguageList();

  std::string_view target_language, tail;
  // |target_language_code| should satisfy BCP47 and consist of a language code
  // and an optional region code joined by an hyphen.
  std::tie(target_language, tail) =
      language::SplitIntoMainAndTail(target_language_code);

  // Don't add the target language if it's redundant with another already in the
  // list.
  if (tail.empty()) {
    for (const auto& language : languages) {
      if (language.language_subtag() == target_language) {
        return;
      }
    }
  } else {
    for (const auto& language : languages) {
      if (language.tag_string() == target_language_code) {
        return;
      }
    }
  }

  // Only add the target language if it's not an automatic target (such as when
  // translation happens because of an hrefTranslate navigation).
  if (language_state_.AutoTranslateTo() != target_language_code &&
      language_state_.href_translate() != target_language_code) {
    if (std::optional<LanguageTag> parsed_tag =
            GetLanguageTagFromString(target_language_code)) {
      prefs->AddToLanguageList(*parsed_tag, /*force_blocked=*/false);
    }
  }
}

const TranslateTriggerDecision TranslateManager::ComputePossibleOutcomes(
    TranslatePrefs* translate_prefs,
    std::string_view page_language_code,
    std::string_view target_lang) {
  // This function looks at a bunch of signals and determines which of four
  // outcomes should be selected:
  // 1. Auto-translate the page
  // 2. Show translate UI
  // 3. Do nothing
  // 4. Force auto-translate
  // This is achieved by passing the |decision| object to the different Filter*
  // functions, which will mark certain outcomes as undesirable. This |decision|
  // object is then used to trigger the correct behavior, and finally record
  // corresponding metrics in InitiateTranslation.
  TranslateTriggerDecision decision;

  FilterIsTranslatePossible(&decision, translate_prefs, page_language_code,
                            target_lang);

  // Querying the ranker now, but not exiting immediately so that we may log
  // other potential suppression reasons.
  if (!translate_ranker_->ShouldOfferTranslation(
          translate_event_.get(), GetActiveTranslateMetricsLogger())) {
    decision.SuppressFromRanker();
  }

  FilterForUserPrefs(&decision, translate_prefs, page_language_code);

  if (decision.should_suppress_from_ranker()) {
    // Delay logging this until after FilterForUserPrefs because TriggerDecision
    // values from FilterForUserPrefs have higher priority.
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kDisabledByRanker);
  }

  FilterForForcedTranslateLanguage(page_language_code, &decision,
                                  translate_prefs);
  FilterAutoTranslate(&decision, translate_prefs, page_language_code);
  FilterForHrefTranslate(&decision, translate_prefs, page_language_code);
  FilterForPredefinedTarget(&decision, translate_prefs, page_language_code);

  return decision;
}

void TranslateManager::FilterForForcedTranslateLanguage(
    const std::string_view page_language_code,
    TranslateTriggerDecision* decision,
    TranslatePrefs* translate_prefs) {
  const std::string forced_lang = GetForcedTranslateLanguage();
  if (forced_lang.empty() ||
      !TranslateDownloadManager::IsSupportedLanguage(forced_lang)) {
    return;
  }

  // If the user has specified a target language via the command line,
  // automatically translate to that language.
  decision->ForceAutoTranslate();
  decision->auto_translate_target = forced_lang;
  GetActiveTranslateMetricsLogger()->LogTriggerDecision(
      TriggerDecision::kForcedTranslationByCommandline);
}

void TranslateManager::FilterIsTranslatePossible(
    TranslateTriggerDecision* decision,
    TranslatePrefs* translate_prefs,
    std::string_view page_language_code,
    std::string_view target_lang) {
  // Short-circuit out if not in a state where initiating translation makes
  // sense (this method may be called multiple times for a given page).
  if (!language_state_.page_level_translation_criteria_met() ||
      language_state_.translation_pending() ||
      language_state_.translation_declined() ||
      language_state_.IsPageTranslated()) {
    decision->PreventAllTriggering();
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kDisabledDoesntNeedTranslation);
  }

  // Also, skip if the connection is currently offline - initiation doesn't make
  // sense there, either.
  if (net::NetworkChangeNotifier::IsOffline()) {
    decision->PreventAllTriggering();
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kDisabledOffline);
  }

  if (!ignore_missing_key_for_testing_ &&
      !::google_apis::HasAPIKeyConfigured()) {
    // Without an API key, translate won't work, so don't offer to translate in
    // the first place. Leave kOfferTranslateEnabled on, though, because that
    // settings syncs and we don't want to turn off translate everywhere else.
    decision->PreventAllTriggering();
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kDisabledMissingAPIKey);
  }

  // not supported MIME type pages currently cannot be translated.
  // See bug: 217945, 1208340.
  if (!IsMimeTypeSupported(translate_driver_->GetContentsMimeType())) {
    decision->PreventAllTriggering();
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kDisabledMIMETypeNotSupported);
  }

  // Don't translate any Chrome specific page, e.g., New Tab Page, Download,
  // History, and so on.
  const GURL& page_url = translate_driver_->GetVisibleURL();
  if (!translate_client_->IsTranslatableURL(page_url)) {
    decision->PreventAllTriggering();
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kDisabledURLNotSupported);
  }

  if (!translate_prefs->IsOfferTranslateEnabled()) {
    decision->PreventAllTriggering();
    decision->ranker_events.push_back(
        metrics::TranslateEventProto::DISABLED_BY_PREF);
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kDisabledNeverOfferTranslations);
  }

  // Don't translate similar languages (ex: en-US to en).
  if (page_language_code == target_lang) {
    // This doesn't prevent *all* possible translate outcomes because some could
    // use a different target language, making this condition only relevant to
    // regular auto-translate/show UI.
    decision->PreventAutoTranslate();
    decision->PreventShowingUI();
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kDisabledSimilarLanguages);
  }

  // Nothing to do if either the language Chrome is in or the language of
  // the page is not supported by the translation server.
  if (target_lang.empty() ||
      !TranslateDownloadManager::IsSupportedLanguage(page_language_code)) {
    // This doesn't prevent *all* possible translate outcomes because some could
    // use a different target language, making this condition only relevant to
    // regular auto-translate/show UI.
    decision->PreventAutoTranslate();
    decision->PreventShowingUI();
    decision->ranker_events.push_back(
        metrics::TranslateEventProto::UNSUPPORTED_LANGUAGE);
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kDisabledUnsupportedLanguage);
  }
}

void TranslateManager::FilterAutoTranslate(
    TranslateTriggerDecision* decision,
    TranslatePrefs* translate_prefs,
    std::string_view page_language_code) {
  // If auto translate is not enabled for the translate manager, prevent auto
  // translation and return early.
  if (!enable_auto_translate_ || decision->will_force_auto_translate()) {
    decision->PreventAutoTranslate();
    return;
  }

  // Determine whether auto-translate is required, and if so for which target
  // language.
  std::string always_translate_target =
      GetAutoTargetLanguage(page_language_code, translate_prefs);
  std::string link_auto_translate_target = language_state_.AutoTranslateTo();

  if (!translate_driver_->IsIncognito() && !always_translate_target.empty()) {
    // If the user has previously selected "always translate" for this
    // language we automatically translate.  Note that in incognito mode we
    // disable that feature; the user will get an infobar, so they can control
    // whether the page's text is sent to the translate server.
    decision->auto_translate_target = always_translate_target;
    decision->ranker_events.push_back(
        metrics::TranslateEventProto::AUTO_TRANSLATION_BY_PREF);
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kAutomaticTranslationByPref);
  } else if (!link_auto_translate_target.empty()) {
    // This page was navigated through a click from a translated page.
    decision->auto_translate_target = link_auto_translate_target;
    decision->ranker_events.push_back(
        metrics::TranslateEventProto::AUTO_TRANSLATION_BY_LINK);
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kAutomaticTranslationByLink);
  }

  if (decision->auto_translate_target.empty()) {
    decision->PreventAutoTranslate();
  }
}

void TranslateManager::FilterForUserPrefs(TranslateTriggerDecision* decision,
                                          TranslatePrefs* translate_prefs,
                                          std::string_view page_language_code) {
  // Don't translate any user blocklisted languages.
  if (!translate_prefs->CanTranslateLanguage(page_language_code)) {
    decision->SetIsInLanguageBlocklist();

    decision->PreventAutoTranslate();
    decision->PreventShowingUI();

    // Disable showing the translate UI for a predefined target language,
    // although note that auto translation to a predefined target language is
    // still allowed to happen, since that auto-translation overrides the user's
    // language blocklist.
    decision->PreventShowingPredefinedLanguageTranslateUI();

    decision->ranker_events.push_back(
        metrics::TranslateEventProto::LANGUAGE_DISABLED_BY_USER_CONFIG);
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kDisabledNeverTranslateLanguage);
  }

  // Don't translate any user blocklisted URLs.
  const GURL& page_url = translate_driver_->GetVisibleURL();
  if (translate_prefs->IsSiteOnNeverPromptList(page_url.HostNoBrackets())) {
    decision->SetIsInSiteBlocklist();

    decision->PreventAutoTranslate();
    decision->PreventShowingUI();

    // The site blocklist isn't overridden for hrefTranslate.
    decision->PreventShowingHrefTranslateUI();
    decision->PreventAutoHrefTranslate();

    // The site blocklist isn't overridden for predefined target languages.
    decision->PreventShowingPredefinedLanguageTranslateUI();
    decision->PreventPredefinedLanguageAutoTranslate();

    decision->ranker_events.push_back(
        metrics::TranslateEventProto::URL_DISABLED_BY_USER_CONFIG);
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kDisabledNeverTranslateSite);
  }
}

void TranslateManager::FilterForHrefTranslate(
    TranslateTriggerDecision* decision,
    TranslatePrefs* translate_prefs,
    std::string_view page_language_code) {
  if (!language_state_.navigation_from_google() ||
      decision->will_force_auto_translate()) {
    decision->PreventAutoHrefTranslate();
  }

  decision->href_translate_source = page_language_code;
  decision->href_translate_target = language_state_.href_translate();

  if (language_state_.navigation_from_google()) {
    GetActiveTranslateMetricsLogger()->SetHasHrefTranslateTarget(
        !decision->href_translate_target.empty());
  }

  // Can't honor hrefTranslate if there's no specified target or the target
  // language isn't supported.
  if (decision->href_translate_target.empty() ||
      !TranslateDownloadManager::IsSupportedLanguage(
          decision->href_translate_target)) {
    decision->PreventAutoHrefTranslate();
    decision->PreventShowingHrefTranslateUI();
  }

  if (!TranslateDownloadManager::IsSupportedLanguage(page_language_code) ||
      page_language_code == decision->href_translate_target) {
    if (language_state_.navigation_from_google()) {
      // If hrefTranslate is present but the page language is unsupported,
      // unknown, or seems to match the hrefTranslate target language, then as a
      // last ditch effort assume that language detection was incorrect and send
      // "und" as the source language to make the translate service attempt to
      // detect the language as it processes the page content.
      decision->href_translate_source =
          language_detection::kUnknownLanguageCode;
    } else {
      decision->PreventAutoHrefTranslate();
      decision->PreventShowingHrefTranslateUI();
    }
  }
}

void TranslateManager::FilterForPredefinedTarget(
    TranslateTriggerDecision* decision,
    TranslatePrefs* translate_prefs,
    std::string_view page_language_code) {
  decision->predefined_translate_source = page_language_code;
  decision->predefined_translate_target =
      language_state_.GetPredefinedTargetLanguage();

  if (!language_state_.should_auto_translate_to_predefined_target_language()
           .has_value() ||
      decision->will_force_auto_translate()) {
    decision->PreventPredefinedLanguageAutoTranslate();
  }

  if (decision->predefined_translate_target.empty() ||
      !TranslateDownloadManager::IsSupportedLanguage(
          decision->predefined_translate_target)) {
    decision->PreventShowingPredefinedLanguageTranslateUI();
    decision->PreventPredefinedLanguageAutoTranslate();
    return;
  }

  if (!TranslateDownloadManager::IsSupportedLanguage(
          decision->predefined_translate_source) ||
      decision->predefined_translate_source ==
          decision->href_translate_target) {
    // If a predefined auto-translate target language is present but the page
    // language is unsupported, unknown, or seems to match this target language,
    // then as a last ditch effort assume that language detection was incorrect
    // and send "und" as the source language to make the translate service
    // attempt to detect the language as it processes the page content.
    decision->predefined_translate_source =
        language_detection::kUnknownLanguageCode;
  }
}

void TranslateManager::MaybeShowOmniboxIcon(
    const TranslateTriggerDecision& decision) {
  if (decision.IsTriggeringPossible()) {
    // Show the omnibox icon if any translate trigger is possible.
    language_state_.SetTranslateEnabled(true);
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kShowIcon);
  }
}

bool TranslateManager::MaterializeDecision(
    const TranslateTriggerDecision& decision,
    TranslatePrefs* translate_prefs,
    std::string_view page_language_code,
    std::string_view target_lang) {
  // Auto-translating always happens if it's still possible here.
  if (decision.can_auto_translate()) {
    TranslatePage(page_language_code, decision.auto_translate_target, false,
                  GetLanguageState()->InTranslateNavigation()
                      ? TranslationType::kAutomaticTranslationByLink
                      : TranslationType::kAutomaticTranslationByPref);
    return true;
  }

  if (decision.can_auto_href_translate()) {
    TranslatePage(decision.href_translate_source,
                  decision.href_translate_target, false,
                  TranslationType::kAutomaticTranslationByHref);
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kAutomaticTranslationByHref);
    return true;
  }

  if (decision.can_auto_translate_for_predefined_language()) {
    TranslatePage(decision.predefined_translate_source,
                  decision.predefined_translate_target, false,
                  TranslationType::kAutomaticTranslationToPredefinedTarget);
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kAutomaticTranslationToPredefinedTarget);
    return true;
  }

  // Auto-translate didn't happen, so check if the UI should be shown. It must
  // not be suppressed by preference, system state, or the Ranker.

  // Will be true if we've decided to show the infobar/bubble UI to the user.
  bool did_show_ui = false;

  // Check whether the target language is predefined. If it is predefined
  // trigger Translate UI even if it would not otherwise be triggered
  // or would be triggered with another language.
  if (decision.can_show_predefined_language_translate_ui()) {
    did_show_ui = translate_client_->ShowTranslateUI(
        translate::TRANSLATE_STEP_BEFORE_TRANSLATE,
        std::string(page_language_code), decision.predefined_translate_target,
        TranslateErrors::NONE, false);
  }

  if (!did_show_ui && decision.ShouldShowUI()) {
    // If the source language matches the UI language, it means the translation
    // prompt is being forced by an experiment. Report this so the count of how
    // often it happens can be tracked to suppress the experiment as necessary.
    if (page_language_code ==
        TranslateDownloadManager::GetLanguageCode(
            TranslateDownloadManager::GetInstance()->application_locale())) {
      translate_prefs->ReportForceTriggerOnEnglishPages();
    }

    // Prompts the user if they want the page translated.
    did_show_ui = translate_client_->ShowTranslateUI(
        translate::TRANSLATE_STEP_BEFORE_TRANSLATE,
        std::string(page_language_code), std::string(target_lang),
        TranslateErrors::NONE, false);
  }

  // Auto-translate didn't happen, and the UI wasn't shown so consider the
  // hrefTranslate attribute if it was present on the originating link.
  if (!did_show_ui && decision.can_show_href_translate_ui()) {
    did_show_ui = translate_client_->ShowTranslateUI(
        translate::TRANSLATE_STEP_BEFORE_TRANSLATE,
        decision.href_translate_source, decision.href_translate_target,
        TranslateErrors::NONE, false);
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kShowUIFromHref);
  }

  if (did_show_ui) {
    GetActiveTranslateMetricsLogger()->LogTriggerDecision(
        TriggerDecision::kShowUI);
  }

  return did_show_ui;
}

void TranslateManager::RecordDecisionMetrics(
    const TranslateTriggerDecision& decision,
    std::string_view page_language_code,
    bool ui_shown) {
  // For Google navigations, the hrefTranslate hint may trigger a translation
  // automatically. Record metrics if there is navigation from Google and a
  // |decision.href_translate_target|.
  if (language_state_.navigation_from_google() &&
      !decision.href_translate_target.empty()) {
    if (decision.can_auto_translate() || decision.can_auto_href_translate()) {
      if (decision.can_auto_translate() &&
          decision.auto_translate_target != decision.href_translate_target) {
        TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
            TranslateBrowserMetrics::HrefTranslateStatus::
                kAutoTranslatedDifferentTargetLanguage);
      } else {
        TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
            TranslateBrowserMetrics::HrefTranslateStatus::kAutoTranslated);
      }
    } else if (decision.can_show_href_translate_ui()) {
      TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
          TranslateBrowserMetrics::HrefTranslateStatus::
              kUiShownNotAutoTranslated);
    } else {
      TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
          TranslateBrowserMetrics::HrefTranslateStatus::
              kNoUiShownNotAutoTranslated);
    }
  }

  if (!decision.can_auto_translate() && !decision.can_auto_href_translate() &&
      (decision.can_auto_translate_for_predefined_language() ||
       decision.can_show_predefined_language_translate_ui())) {
    return;
  }
}

void TranslateManager::RecordDecisionRankerEvent(
    const TranslateTriggerDecision& decision,
    TranslatePrefs* translate_prefs,
    std::string_view page_language_code,
    std::string_view target_lang) {
  if (!decision.auto_translate_target.empty()) {
    translate_event_->set_modified_target_language(
        decision.auto_translate_target);
  }

  if (!decision.ranker_events.empty()) {
    auto event = decision.ranker_events[0];
    RecordTranslateEvent(event);
  }

  // Finally, if the decision was to show UI and ranker suppressed it, log that.
  if (!decision.can_auto_translate() && decision.can_show_ui() &&
      decision.should_suppress_from_ranker()) {
    RecordTranslateEvent(metrics::TranslateEventProto::DISABLED_BY_RANKER);
  }
}

void TranslateManager::SetPredefinedTargetLanguage(
    base::i18n::LanguageTag language,
    bool should_auto_translate) {
  language_state_.SetPredefinedTargetLanguage(language, should_auto_translate);
}

void TranslateManager::SetEnableAutoTranslate(bool enable_auto_translate) {
  enable_auto_translate_ = enable_auto_translate;
}

TranslateMetricsLogger* TranslateManager::GetActiveTranslateMetricsLogger() {
  // If |active_translate_metrics_logger_| is not null, return that. Otherwise
  // return |null_translate_metrics_logger_|. This way the callee doesn't have
  // to check if the returned value is null.
  return active_translate_metrics_logger_
             ? active_translate_metrics_logger_.get()
             : null_translate_metrics_logger_.get();
}

void TranslateManager::RegisterTranslateMetricsLogger(
    base::WeakPtr<TranslateMetricsLogger> translate_metrics_logger) {
  active_translate_metrics_logger_ = translate_metrics_logger;
}

}  // namespace translate
