// 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/omnibox/browser/omnibox_metrics_provider.h"

#include <string>
#include <string_view>
#include <vector>

#include "base/containers/fixed_flat_map.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "components/metrics/metrics_log.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/autocomplete_result.h"
#include "components/omnibox/browser/cross_device_tab_provider.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/browser/omnibox_log.h"
#include "components/omnibox/common/logger.h"
#include "components/omnibox/common/omnibox_metrics_utils.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "third_party/metrics_proto/omnibox_input_type.pb.h"
#include "third_party/metrics_proto/omnibox_scoring_signals.pb.h"

using metrics::OmniboxEventProto;

namespace {

using ScoringSignals = ::metrics::OmniboxEventProto::Suggestion::ScoringSignals;
using OmniboxScoringSignals = ::metrics::OmniboxScoringSignals;

// Extracts the subset of signals which must be logged by the client in order to
// train the Omnibox ML Scoring model using server-side training logic.
void GetScoringSignalsForLogging(const OmniboxScoringSignals& scoring_signals,
                                 ScoringSignals& scoring_signals_for_logging) {
  // Keep consistent:
  // - omnibox_event.proto `ScoringSignals`
  // - omnibox_scoring_signals.proto `OmniboxScoringSignals`
  // - autocomplete_scoring_model_handler.cc
  //   `AutocompleteScoringModelHandler::ExtractInputFromScoringSignals()`
  // - autocomplete_match.cc `AutocompleteMatch::MergeScoringSignals()`
  // - autocomplete_controller.cc `RecordScoringSignalCoverageForProvider()`
  // - omnibox_metrics_provider.cc `GetScoringSignalsForLogging()`
  // - omnibox.mojom `struct Signals`
  // - omnibox_page_handler.cc
  //   `TypeConverter<AutocompleteMatch::ScoringSignals, mojom::SignalsPtr>`
  // - omnibox_page_handler.cc `TypeConverter<mojom::SignalsPtr,
  //   AutocompleteMatch::ScoringSignals>`
  // - omnibox_util.ts `signalNames`
  // - omnibox/histograms.xml
  //   `Omnibox.URLScoringModelExecuted.ScoringSignalCoverage`

  if (scoring_signals.has_typed_count()) {
    scoring_signals_for_logging.set_typed_count(scoring_signals.typed_count());
  }
  if (scoring_signals.has_visit_count()) {
    scoring_signals_for_logging.set_visit_count(scoring_signals.visit_count());
  }
  if (scoring_signals.has_elapsed_time_last_visit_secs()) {
    scoring_signals_for_logging.set_elapsed_time_last_visit_secs(
        scoring_signals.elapsed_time_last_visit_secs());
  }
  if (scoring_signals.has_shortcut_visit_count()) {
    scoring_signals_for_logging.set_shortcut_visit_count(
        scoring_signals.shortcut_visit_count());
  }
  if (scoring_signals.has_shortest_shortcut_len()) {
    scoring_signals_for_logging.set_shortest_shortcut_len(
        scoring_signals.shortest_shortcut_len());
  }
  if (scoring_signals.has_elapsed_time_last_shortcut_visit_sec()) {
    scoring_signals_for_logging.set_elapsed_time_last_shortcut_visit_sec(
        scoring_signals.elapsed_time_last_shortcut_visit_sec());
  }
  if (scoring_signals.has_is_host_only()) {
    scoring_signals_for_logging.set_is_host_only(
        scoring_signals.is_host_only());
  }
  if (scoring_signals.has_num_bookmarks_of_url()) {
    scoring_signals_for_logging.set_num_bookmarks_of_url(
        scoring_signals.num_bookmarks_of_url());
  }
  if (scoring_signals.has_first_bookmark_title_match_position()) {
    scoring_signals_for_logging.set_first_bookmark_title_match_position(
        scoring_signals.first_bookmark_title_match_position());
  }
  if (scoring_signals.has_total_bookmark_title_match_length()) {
    scoring_signals_for_logging.set_total_bookmark_title_match_length(
        scoring_signals.total_bookmark_title_match_length());
  }
  if (scoring_signals.has_num_input_terms_matched_by_bookmark_title()) {
    scoring_signals_for_logging.set_num_input_terms_matched_by_bookmark_title(
        scoring_signals.num_input_terms_matched_by_bookmark_title());
  }
  if (scoring_signals.has_first_url_match_position()) {
    scoring_signals_for_logging.set_first_url_match_position(
        scoring_signals.first_url_match_position());
  }
  if (scoring_signals.has_total_url_match_length()) {
    scoring_signals_for_logging.set_total_url_match_length(
        scoring_signals.total_url_match_length());
  }
  if (scoring_signals.has_host_match_at_word_boundary()) {
    scoring_signals_for_logging.set_host_match_at_word_boundary(
        scoring_signals.host_match_at_word_boundary());
  }
  if (scoring_signals.has_total_host_match_length()) {
    scoring_signals_for_logging.set_total_host_match_length(
        scoring_signals.total_host_match_length());
  }
  if (scoring_signals.has_total_path_match_length()) {
    scoring_signals_for_logging.set_total_path_match_length(
        scoring_signals.total_path_match_length());
  }
  if (scoring_signals.has_total_query_or_ref_match_length()) {
    scoring_signals_for_logging.set_total_query_or_ref_match_length(
        scoring_signals.total_query_or_ref_match_length());
  }
  if (scoring_signals.has_total_title_match_length()) {
    scoring_signals_for_logging.set_total_title_match_length(
        scoring_signals.total_title_match_length());
  }
  if (scoring_signals.has_has_non_scheme_www_match()) {
    scoring_signals_for_logging.set_has_non_scheme_www_match(
        scoring_signals.has_non_scheme_www_match());
  }
  if (scoring_signals.has_num_input_terms_matched_by_title()) {
    scoring_signals_for_logging.set_num_input_terms_matched_by_title(
        scoring_signals.num_input_terms_matched_by_title());
  }
  if (scoring_signals.has_num_input_terms_matched_by_url()) {
    scoring_signals_for_logging.set_num_input_terms_matched_by_url(
        scoring_signals.num_input_terms_matched_by_url());
  }
  if (scoring_signals.has_length_of_url()) {
    scoring_signals_for_logging.set_length_of_url(
        scoring_signals.length_of_url());
  }
  if (scoring_signals.has_site_engagement()) {
    scoring_signals_for_logging.set_site_engagement(
        scoring_signals.site_engagement());
  }
  if (scoring_signals.has_allowed_to_be_default_match()) {
    scoring_signals_for_logging.set_allowed_to_be_default_match(
        scoring_signals.allowed_to_be_default_match());
  }
  if (scoring_signals.has_search_suggest_relevance()) {
    scoring_signals_for_logging.set_search_suggest_relevance(
        scoring_signals.search_suggest_relevance());
  }
}

constexpr base::TimeDelta kDefaultTimeDelta = base::Milliseconds(-1);

}  // namespace

OmniboxMetricsProvider::OmniboxMetricsProvider()
    : subscription_(OmniboxEventGlobalTracker::GetInstance()->RegisterCallback(
          base::BindRepeating(&OmniboxMetricsProvider::OnURLOpenedFromOmnibox,
                              base::Unretained(this)))) {}

OmniboxMetricsProvider::~OmniboxMetricsProvider() = default;

void OmniboxMetricsProvider::OnRecordingEnabled() {
  metrics_recording_enabled_ = true;
}

void OmniboxMetricsProvider::OnRecordingDisabled() {
  metrics_recording_enabled_ = false;
}

void OmniboxMetricsProvider::ProvideCurrentSessionData(
    metrics::ChromeUserMetricsExtension* uma_proto) {
  uma_proto->mutable_omnibox_event()->Swap(
      omnibox_events_cache.mutable_omnibox_event());
}

// static
ClientSummarizedResultType
OmniboxMetricsProvider::GetClientSummarizedResultType(
    metrics::OmniboxEventProto::Suggestion::ResultType type) {
  static constexpr auto kResultTypesToClientSummarizedResultTypes =
      base::MakeFixedFlatMap<OmniboxEventProto::Suggestion::ResultType,
                             ClientSummarizedResultType>({
          {OmniboxEventProto::Suggestion::URL_WHAT_YOU_TYPED,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::HISTORY_URL,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::HISTORY_TITLE,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::HISTORY_BODY,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::HISTORY_KEYWORD,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::NAVSUGGEST,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::BOOKMARK_TITLE,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::NAVSUGGEST_PERSONALIZED,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::CLIPBOARD_URL,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::PHYSICAL_WEB,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::PHYSICAL_WEB_OVERFLOW,
           ClientSummarizedResultType::kUrl},  // More like a URL than a search.
          {OmniboxEventProto::Suggestion::DOCUMENT,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::SEARCH_WHAT_YOU_TYPED,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::SEARCH_HISTORY,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::SEARCH_SUGGEST,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::SEARCH_OTHER_ENGINE,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::SEARCH_SUGGEST_ENTITY,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::SEARCH_SUGGEST_TAIL,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::SEARCH_SUGGEST_PERSONALIZED,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::SEARCH_SUGGEST_PROFILE,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::SEARCH_SUGGEST_ANSWER,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::CALCULATOR,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::CLIPBOARD_TEXT,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::CLIPBOARD_IMAGE,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::EXTENSION_APP,
           ClientSummarizedResultType::kApp},
          {OmniboxEventProto::Suggestion::APP,
           ClientSummarizedResultType::kApp},
          {OmniboxEventProto::Suggestion::CONTACT,
           ClientSummarizedResultType::kContact},
          {OmniboxEventProto::Suggestion::APP_RESULT,
           ClientSummarizedResultType::kOnDevice},
          {OmniboxEventProto::Suggestion::LEGACY_ON_DEVICE,
           ClientSummarizedResultType::kOnDevice},
          {OmniboxEventProto::Suggestion::TILE_SUGGESTION,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::HISTORY_CLUSTER,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::OPEN_TAB,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::STARTER_PACK,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::TAB_SWITCH,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::PEDAL,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::HISTORY_EMBEDDINGS,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::FEATURED_ENTERPRISE_SEARCH,
           ClientSummarizedResultType::kSearch},
          {OmniboxEventProto::Suggestion::HISTORY_EMBEDDINGS_ANSWER,
           ClientSummarizedResultType::kUrl},
          {OmniboxEventProto::Suggestion::CROSS_DEVICE_TAB,
           ClientSummarizedResultType::kUrl},
      });

  const auto it = kResultTypesToClientSummarizedResultTypes.find(type);
  return it == kResultTypesToClientSummarizedResultTypes.cend()
             ? ClientSummarizedResultType::kUnknown
             : it->second;
}

void OmniboxMetricsProvider::OnURLOpenedFromOmnibox(OmniboxLog* log) {
  // Log search navigations to debug logs.
  if (log && log->selection.line >= 0 &&
      static_cast<size_t>(log->selection.line) < log->result->size()) {
    const AutocompleteMatch& match = log->result->match_at(log->selection.line);
    if (AutocompleteMatch::IsSearchType(match.type)) {
      OMNIBOX_LOG("search_nav") << log->final_destination_url.spec();
    }
  }

  // Record UMA/UKM metrics only when metrics recording is enabled.
  if (metrics_recording_enabled_) {
    RecordOmniboxEvent(*log);
    RecordMetrics(*log);
    RecordZeroPrefixPrecisionRecallUsage(*log);
    RecordContextualSearchPrecisionRecallUsage(*log);
  }
}

void OmniboxMetricsProvider::RecordOmniboxEvent(const OmniboxLog& log) {
  std::vector<std::u16string_view> terms =
      base::SplitStringPiece(log.text, base::kWhitespaceUTF16,
                             base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);

  OmniboxEventProto* omnibox_event = omnibox_events_cache.add_omnibox_event();
  omnibox_event->set_time_sec(metrics::MetricsLog::GetCurrentTime());
  if (log.tab_id.is_valid()) {
    // If we know what tab the autocomplete URL was opened in, log it.
    omnibox_event->set_tab_id(log.tab_id.id());
  }
  omnibox_event->set_typed_length(log.text.length());
  omnibox_event->set_just_deleted_text(log.just_deleted_text);
  omnibox_event->set_num_typed_terms(static_cast<int>(terms.size()));
  omnibox_event->set_selected_index(log.selection.line);
  omnibox_event->set_selected_tab_match(log.disposition ==
                                        WindowOpenDisposition::SWITCH_TO_TAB);
  if (log.completed_length != std::u16string::npos)
    omnibox_event->set_completed_length(log.completed_length);
  // Set the typing duration only if set/valid.
  if (log.elapsed_time_since_user_first_modified_omnibox != kDefaultTimeDelta) {
    omnibox_event->set_typing_duration_ms(
        log.elapsed_time_since_user_first_modified_omnibox.InMilliseconds());
  }
  // Set the time since the last change to default match only if set/valid.
  if (log.elapsed_time_since_last_change_to_default_match !=
      kDefaultTimeDelta) {
    omnibox_event->set_duration_since_last_default_match_update_ms(
        log.elapsed_time_since_last_change_to_default_match.InMilliseconds());
  }
  omnibox_event->set_current_page_classification(
      log.current_page_classification);
  omnibox_event->set_input_type(log.input_type);
  // We consider a paste-and-search/paste-and-go action to have a closed popup
  // (as explained in omnibox_event.proto) even if it was not, because such
  // actions ignore the contents of the popup so it doesn't matter that it was
  // open.
  omnibox_event->set_is_popup_open(log.is_popup_open && !log.is_paste_and_go);
  omnibox_event->set_is_paste_and_go(log.is_paste_and_go);

  if (log.steady_state_omnibox_position !=
      metrics::OmniboxEventProto::UNKNOWN_POSITION) {
    omnibox_event->set_steady_state_omnibox_position(
        log.steady_state_omnibox_position);
  }

  for (size_t i = 0; i < log.result->size(); i++) {
    const AutocompleteMatch& match = log.result->match_at(i);
    OmniboxEventProto::Suggestion* suggestion = omnibox_event->add_suggestion();

    const int action_index = log.selection.line == i && log.selection.IsAction()
                                 ? log.selection.action_index
                                 : -1;
    suggestion->set_provider(match.GetOmniboxEventProviderType(action_index));
    suggestion->set_result_type(match.GetOmniboxEventResultType(action_index));
    suggestion->set_relevance(match.relevance);
    if (match.typed_count != -1) {
      suggestion->set_typed_count(match.typed_count);
    }

    // TODO(crbug.com/40139076): send the entire set of subtypes.
    if (!match.subtypes.empty()) {
      suggestion->set_result_subtype_identifier(*match.subtypes.begin());
    }

    suggestion->set_has_tab_match(match.has_tab_match.value_or(false));
    suggestion->set_is_keyword_suggestion(match.from_keyword);

    // Scoring signals are not logged when the client is in incognito mode or
    // when the particular suggestion type is considered ineligible for signal
    // logging.
    if (OmniboxFieldTrial::IsReportingUrlScoringSignalsEnabled() &&
        !log.is_incognito && match.IsMlSignalLoggingEligible() &&
        match.scoring_signals) {
      ScoringSignals scoring_signals_for_logging;
      GetScoringSignalsForLogging(*match.scoring_signals,
                                  scoring_signals_for_logging);
      suggestion->mutable_scoring_signals()->CopyFrom(
          scoring_signals_for_logging);
    }
  }
  for (const auto& info : log.providers_info) {
    OmniboxEventProto::ProviderInfo* provider_info =
        omnibox_event->add_provider_info();
    provider_info->CopyFrom(info);
  }
  omnibox_event->set_in_keyword_mode(log.in_keyword_mode);
  if (log.in_keyword_mode)
    omnibox_event->set_keyword_mode_entry_method(log.keyword_mode_entry_method);
  if (log.is_query_started_from_tile)
    omnibox_event->set_is_query_started_from_tile(true);
  for (auto feature : log.features_triggered)
    omnibox_event->add_feature_triggered(feature);
  for (auto feature : log.features_triggered_in_session) {
    omnibox_event->add_feature_triggered_in_session(feature);
  }
}

void OmniboxMetricsProvider::RecordMetrics(const OmniboxLog& log) {
  if (log.selection.line < 0 || log.selection.line >= log.result->size() ||
      !log.session) {
    return;
  }

  auto autocomplete_match = log.result->match_at(log.selection.line);
  const int action_index =
      log.selection.IsAction() ? log.selection.action_index : -1;
  auto provider_type =
      autocomplete_match.GetOmniboxEventProviderType(action_index);
  auto omnibox_event_result_type =
      autocomplete_match.GetOmniboxEventResultType(action_index);
  auto client_summarized_result_type =
      GetClientSummarizedResultType(omnibox_event_result_type);

  const std::string page_context = OmniboxEventProto::PageClassification_Name(
      log.current_page_classification);

  // Log UMA histograms.
  base::UmaHistogramEnumeration(
      "Omnibox.SuggestionUsed.ClientSummarizedResultType",
      client_summarized_result_type);

  // Only log when an active tool or model is specified to avoid logging regular
  // searches.
  if (log.input_state.active_tool != omnibox::TOOL_MODE_UNSPECIFIED ||
      log.input_state.active_model != omnibox::MODEL_MODE_UNSPECIFIED) {
    bool has_non_trivial_match = false;
    for (const auto& match : *log.result) {
      if (!match.IsVerbatimType()) {
        has_non_trivial_match = true;
        break;
      }
    }

    std::string tool_str = GetToolModeString(log.input_state.active_tool);
    std::string model_str = GetModelModeString(log.input_state.active_model);

    bool tool_specified =
        log.input_state.active_tool != omnibox::TOOL_MODE_UNSPECIFIED;
    bool model_specified =
        log.input_state.active_model != omnibox::MODEL_MODE_UNSPECIFIED;

    if (tool_specified) {
      base::UmaHistogramBoolean(
          base::StrCat(
              {"Omnibox.NonTrivialSuggestions.Recall.ByToolType.", tool_str}),
          has_non_trivial_match);
    }
    if (model_specified) {
      base::UmaHistogramBoolean(
          base::StrCat(
              {"Omnibox.NonTrivialSuggestions.Recall.ByModel.", model_str}),
          has_non_trivial_match);
    }
    if (tool_specified && model_specified) {
      base::UmaHistogramBoolean(
          base::StrCat({"Omnibox.NonTrivialSuggestions.Recall.ByToolType.",
                        tool_str, ".ByModel.", model_str}),
          has_non_trivial_match);
    }

    if (has_non_trivial_match) {
      bool selected_non_trivial = !autocomplete_match.IsVerbatimType();

      if (tool_specified) {
        base::UmaHistogramBoolean(
            base::StrCat({"Omnibox.NonTrivialSuggestions.Precision.ByToolType.",
                          tool_str}),
            selected_non_trivial);
      }
      if (model_specified) {
        base::UmaHistogramBoolean(
            base::StrCat({"Omnibox.NonTrivialSuggestions.Precision.ByModel.",
                          model_str}),
            selected_non_trivial);
      }
      if (tool_specified && model_specified) {
        base::UmaHistogramBoolean(
            base::StrCat({"Omnibox.NonTrivialSuggestions.Precision.ByToolType.",
                          tool_str, ".ByModel.", model_str}),
            selected_non_trivial);
      }
    }
  }

  if (log.session->zero_prefix_search_suggestions_shown_in_session ||
      log.session->typed_search_suggestions_shown_in_session) {
    base::UmaHistogramEnumeration(
        "Omnibox.SuggestionShown.ClientSummarizedResultType",
        ClientSummarizedResultType::kSearch);
    base::UmaHistogramEnumeration(
        base::StrCat({"Omnibox.SuggestionShown.ClientSummarizedResultType."
                      "ByPageContext.",
                      page_context}),
        ClientSummarizedResultType::kSearch);
  }
  if (log.session->zero_prefix_url_suggestions_shown_in_session ||
      log.session->typed_url_suggestions_shown_in_session) {
    base::UmaHistogramEnumeration(
        "Omnibox.SuggestionShown.ClientSummarizedResultType",
        ClientSummarizedResultType::kUrl);
    base::UmaHistogramEnumeration(
        base::StrCat({"Omnibox.SuggestionShown.ClientSummarizedResultType."
                      "ByPageContext.",
                      page_context}),
        ClientSummarizedResultType::kUrl);
  }

  if (log.session->zero_prefix_search_suggestions_shown_in_session) {
    base::UmaHistogramEnumeration(
        "Omnibox.SuggestionShown.ZeroSuggest.ClientSummarizedResultType",
        ClientSummarizedResultType::kSearch);
    base::UmaHistogramEnumeration(
        base::StrCat({"Omnibox.SuggestionShown.ZeroSuggest."
                      "ClientSummarizedResultType.ByPageContext.",
                      page_context}),
        ClientSummarizedResultType::kSearch);
  }
  if (log.session->zero_prefix_url_suggestions_shown_in_session) {
    base::UmaHistogramEnumeration(
        "Omnibox.SuggestionShown.ZeroSuggest.ClientSummarizedResultType",
        ClientSummarizedResultType::kUrl);
    base::UmaHistogramEnumeration(
        base::StrCat({"Omnibox.SuggestionShown.ZeroSuggest."
                      "ClientSummarizedResultType.ByPageContext.",
                      page_context}),
        ClientSummarizedResultType::kUrl);
  }

  if (log.session->typed_search_suggestions_shown_in_session) {
    base::UmaHistogramEnumeration(
        "Omnibox.SuggestionShown.TypedSuggest.ClientSummarizedResultType",
        ClientSummarizedResultType::kSearch);
    base::UmaHistogramEnumeration(
        base::StrCat({"Omnibox.SuggestionShown.TypedSuggest."
                      "ClientSummarizedResultType.ByPageContext.",
                      page_context}),
        ClientSummarizedResultType::kSearch);
  }
  if (log.session->typed_url_suggestions_shown_in_session) {
    base::UmaHistogramEnumeration(
        "Omnibox.SuggestionShown.TypedSuggest.ClientSummarizedResultType",
        ClientSummarizedResultType::kUrl);
    base::UmaHistogramEnumeration(
        base::StrCat({"Omnibox.SuggestionShown.TypedSuggest."
                      "ClientSummarizedResultType.ByPageContext.",
                      page_context}),
        ClientSummarizedResultType::kUrl);
  }

  CrossDeviceTabProvider::RecordInteractionMetrics(log);

  // Log UKM event.
  if (log.ukm_source_id == ukm::kInvalidSourceId) {
    return;
  }
  ukm::builders::Omnibox_SuggestionUsed event(log.ukm_source_id);
  event.SetPageClassification(
      static_cast<int64_t>(log.current_page_classification));
  event.SetProviderType(static_cast<int64_t>(provider_type));
  event.SetResultType(static_cast<int64_t>(omnibox_event_result_type));
  event.SetResultTypeGroup(static_cast<int64_t>(client_summarized_result_type));
  event.SetSelectedIndex(log.selection.line);
  // Set the time since the user last focused the omnibox only if set/valid.
  if (log.elapsed_time_since_user_focused_omnibox != kDefaultTimeDelta) {
    event.SetTimeSinceLastFocusMs(ukm::GetExponentialBucketMinForUserTiming(
        log.elapsed_time_since_user_focused_omnibox.InMilliseconds()));
  }
  event.SetTypedLength(log.text.length());
  // Set the typing duration only if set/valid.
  if (log.elapsed_time_since_user_first_modified_omnibox != kDefaultTimeDelta) {
    event.SetTypingDurationMs(ukm::GetExponentialBucketMinForUserTiming(
        log.elapsed_time_since_user_first_modified_omnibox.InMilliseconds()));
  }
  event.SetZeroPrefixSearchShown(
      log.session->zero_prefix_search_suggestions_shown_in_session);
  event.SetZeroPrefixUrlShown(
      log.session->zero_prefix_url_suggestions_shown_in_session);
  event.SetZeroPrefixContextualSearchShown(
      log.session->contextual_search_suggestions_shown_in_session);
  event.SetZeroPrefixLensActionShown(log.session->lens_action_shown_in_session);
  event.Record(ukm::UkmRecorder::Get());
}

void OmniboxMetricsProvider::RecordZeroPrefixPrecisionRecallUsage(
    const OmniboxLog& log) {
  if (!log.session) {
    return;
  }

  const std::string page_context = OmniboxEventProto::PageClassification_Name(
      log.current_page_classification);

  bool zero_prefix_shown =
      log.session->zero_prefix_suggestions_shown_in_session;
  bool zero_prefix_selected = log.text.empty();

  if (zero_prefix_shown) {
    base::UmaHistogramBoolean("Omnibox.ZeroSuggest.Precision",
                              zero_prefix_selected);
    base::UmaHistogramBoolean(
        base::StrCat(
            {"Omnibox.ZeroSuggest.Precision.ByPageContext.", page_context}),
        zero_prefix_selected);
  }

  base::UmaHistogramBoolean("Omnibox.ZeroSuggest.Recall", zero_prefix_shown);
  base::UmaHistogramBoolean(
      base::StrCat({"Omnibox.ZeroSuggest.Recall.ByPageContext.", page_context}),
      zero_prefix_shown);

  base::UmaHistogramBoolean("Omnibox.ZeroSuggest.Usage", zero_prefix_selected);
  base::UmaHistogramBoolean(
      base::StrCat({"Omnibox.ZeroSuggest.Usage.ByPageContext.", page_context}),
      zero_prefix_selected);
}

void OmniboxMetricsProvider::RecordContextualSearchPrecisionRecallUsage(
    const OmniboxLog& log) {
  if (log.selection.line < 0 || log.selection.line >= log.result->size() ||
      !log.session) {
    return;
  }

  const auto& autocomplete_match = log.result->match_at(log.selection.line);

  bool contextual_search_selected =
      autocomplete_match.takeover_action &&
      autocomplete_match.takeover_action->ActionId() ==
          OmniboxActionId::CONTEXTUAL_SEARCH_FULFILLMENT;
  bool contextual_search_shown =
      log.session->contextual_search_suggestions_shown_in_session;

  if (contextual_search_shown) {
    base::UmaHistogramBoolean("Omnibox.ContextualSearchSuggestion.Precision",
                              contextual_search_selected);
  }
  base::UmaHistogramBoolean("Omnibox.ContextualSearchSuggestion.Recall",
                            contextual_search_shown);
  base::UmaHistogramBoolean("Omnibox.ContextualSearchSuggestion.Usage",
                            contextual_search_selected);

  bool lens_action_selected = autocomplete_match.takeover_action &&
                              autocomplete_match.takeover_action->ActionId() ==
                                  OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS;
  bool lens_action_shown = log.session->lens_action_shown_in_session;

  if (lens_action_shown) {
    base::UmaHistogramBoolean("Omnibox.LensAction.Precision",
                              lens_action_selected);
  }
  base::UmaHistogramBoolean("Omnibox.LensAction.Recall", lens_action_shown);
  base::UmaHistogramBoolean("Omnibox.LensAction.Usage", lens_action_selected);
}
