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

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

#include "base/check_op.h"
#include "base/containers/fixed_flat_map.h"
#include "base/feature_list.h"
#include "base/hash/hash.h"
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/memory_usage_estimator.h"
#include "base/trace_event/trace_event.h"
#include "build/android_buildflags.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "components/history_embeddings/core/history_embeddings_features.h"
#include "components/omnibox/browser/actions/omnibox_action.h"
#include "components/omnibox/browser/actions/omnibox_action_concepts.h"
#include "components/omnibox/browser/actions/omnibox_action_in_suggest.h"
#include "components/omnibox/browser/autocomplete_enums.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/document_provider.h"
#include "components/omnibox/browser/inline_autocompletion_util.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/common/omnibox_feature_configs.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/search_engines/default_search_manager.h"
#include "components/search_engines/search_engine_type.h"
#include "components/search_engines/search_engine_utils.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_service.h"
#include "components/search_engines/template_url_starter_pack_data.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "third_party/omnibox_proto/answer_type.pb.h"
#include "third_party/omnibox_proto/groups.pb.h"
#include "third_party/omnibox_proto/suggest_template_info.pb.h"
#include "third_party/omnibox_proto/types.pb.h"
#include "ui/base/device_form_factor.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_features.h"
#include "ui/gfx/vector_icon_types.h"
#include "url/third_party/mozilla/url_parse.h"
#include "url/url_util.h"

#if (!BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !BUILDFLAG(IS_IOS)
#include "components/omnibox/browser/suggestion_answer.h"
#include "components/omnibox/browser/vector_icons.h"  // nogncheck
#include "components/vector_icons/vector_icons.h"     // nogncheck
#endif

constexpr bool kIsAndroid = BUILDFLAG(IS_ANDROID);
constexpr bool kIsDesktopAndroid = BUILDFLAG(IS_DESKTOP_ANDROID);
constexpr bool kIsDesktop =
    (!BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)) || kIsDesktopAndroid;

namespace {

bool IsTrivialClassification(const ACMatchClassifications& classifications) {
  return classifications.empty() ||
         ((classifications.size() == 1) &&
          (classifications.back().style == ACMatchClassification::NONE));
}

// Returns true if one of the |terms_prefixed_by_http_or_https| matches the
// beginning of the URL (sans scheme).  (Recall that
// |terms_prefixed_by_http_or_https|, for the input "http://a b" will be
// ["a"].)  This suggests that the user wants a particular URL with a scheme
// in mind, hence the caller should not consider another URL like this one
// but with a different scheme to be a duplicate.
bool WordMatchesURLContent(
    const std::vector<std::u16string>& terms_prefixed_by_http_or_https,
    const GURL& url) {
  size_t prefix_length =
      url.GetScheme().length() + strlen(url::kStandardSchemeSeparator);
  DCHECK_GE(url.spec().length(), prefix_length);
  const std::u16string formatted_url = url_formatter::FormatUrl(
      url, url_formatter::kFormatUrlOmitNothing, base::UnescapeRule::NORMAL,
      nullptr, nullptr, &prefix_length);
  if (prefix_length == std::u16string::npos)
    return false;
  const std::u16string_view formatted_url_without_scheme =
      std::u16string_view(formatted_url).substr(prefix_length);
  for (const auto& term : terms_prefixed_by_http_or_https) {
    if (base::StartsWith(formatted_url_without_scheme, term,
                         base::CompareCase::SENSITIVE))
      return true;
  }
  return false;
}

// Gives a basis for match comparison that prefers some providers over others
// while remaining neutral with a default score of zero for most providers.
int GetDeduplicationProviderPreferenceScore(
    const AutocompleteProvider* provider) {
  if (!provider) {
    return 0;
  }
  const AutocompleteProvider::Type type = provider->type();

  constexpr auto kProviderPrefMap =
      base::MakeFixedFlatMap<AutocompleteProvider::Type, int>({
          // Prefer live remote suggestions. We check provider type instead
          // of match type in order to distinguish live suggestions from the
          // from their stale counterparts from the shortcut, history, and
          // bookmark providers. The latter often have stale metadata such as
          // last access date.
          {AutocompleteProvider::TYPE_DOCUMENT, 2},
          {AutocompleteProvider::TYPE_ENTERPRISE_SEARCH_AGGREGATOR, 2},
          // Prefer bookmark suggestions, as:
          // 1) Their titles may be explicitly set.
          // 2) They may display enhanced information such as the bookmark
          //    folders path.
          {AutocompleteProvider::TYPE_BOOKMARK, 1},
          // Don't let bookmarks override builtins, as that interferes with
          // starter pack matches when user has bookmarked their destination.
          {AutocompleteProvider::TYPE_BUILTIN, kIsDesktop ? 1 : 0},
          // Prefer non-shorcut matches over shortcuts, the latter of which may
          // have stale or missing URL titles (the latter from what-you-typed
          // matches).
          {AutocompleteProvider::TYPE_SHORTCUTS, -1},
          // Prefer non-fuzzy matches over fuzzy matches.
          {AutocompleteProvider::TYPE_HISTORY_FUZZY, -2},
      });
  const auto it = kProviderPrefMap.find(type);
  return it != kProviderPrefMap.end() ? it->second : 0;
}

int GetOrderForActionType(scoped_refptr<OmniboxAction> action) {
  // Sort: Call -> Directions -> Reviews -> Tab Switch.
  auto* action_in_suggest = OmniboxActionInSuggest::FromAction(action.get());
  if (action_in_suggest == nullptr) {
    return 0;
  }
  switch (action_in_suggest->Type()) {
    case omnibox::SuggestTemplateInfo_TemplateAction_ActionType_CALL:
      return 1;
    case omnibox::SuggestTemplateInfo_TemplateAction_ActionType_DIRECTIONS:
      return 2;
    case omnibox::SuggestTemplateInfo_TemplateAction_ActionType_REVIEWS:
      return 3;
    case omnibox::
        SuggestTemplateInfo_TemplateAction_ActionType_CHROME_TAB_SWITCH:
      return 4;
    default:
      return 0;
  }
}

}  // namespace

template <typename... Args>
size_t ACMatchKeyHash<Args...>::operator()(
    const ACMatchKey<Args...>& key) const {
  size_t seed = 0;
  // Compute a hash by applying `HashCombine` to each element of the "key"
  // tuple.
  std::apply(
      [&seed](auto&&... args) { ((base::HashCombine(seed, args)), ...); }, key);
  return seed;
}

// This trick allows implementing `ACMatchKeyHash` in the implementation file.
// Every unique specialization of `ACMatchKey` should have a corresponding
// declaration here.
// Deduplication key hash for `AutocompleteResult`.
template struct ACMatchKeyHash<std::string,  // URL
                               AutocompleteMatchDedupeType>;

// RichAutocompletionParams ---------------------------------------------------

RichAutocompletionParams::RichAutocompletionParams()
    : enabled(OmniboxFieldTrial::IsRichAutocompletionEnabled()),
      autocomplete_titles_min_char(
          OmniboxFieldTrial::kRichAutocompletionAutocompleteTitlesMinChar
              .Get()),
      autocomplete_shortcut_text_min_char(
          OmniboxFieldTrial::kRichAutocompletionAutocompleteShortcutTextMinChar
              .Get()) {}

RichAutocompletionParams& RichAutocompletionParams::GetParams() {
  static RichAutocompletionParams params;
  return params;
}

void RichAutocompletionParams::ClearParamsForTesting() {
  GetParams() = {};
}

// SessionData ----------------------------------------------------------------

SessionData::SessionData() = default;

SessionData::SessionData(const SessionData& session_data) = default;

SessionData::~SessionData() = default;

SessionData& SessionData::operator=(const SessionData& match) = default;

// AutocompleteMatch ----------------------------------------------------------

// static
const char* AutocompleteMatch::DocumentTypeString(DocumentType type) {
  return kDocumentTypeStrings[static_cast<int>(type)];
}

// static
bool AutocompleteMatch::DocumentTypeFromInteger(int value,
                                                DocumentType* result) {
  DCHECK(result);

  // The resulting value may still be invalid after the static_cast.
  DocumentType document_type = static_cast<DocumentType>(value);
  if (document_type >= DocumentType::NONE &&
      document_type < DocumentType::DOCUMENT_TYPE_SIZE) {
    *result = document_type;
    return true;
  }

  return false;
}

// static
const char16_t AutocompleteMatch::kEllipsis[] = u"... ";

AutocompleteMatch::AutocompleteMatch()
    : transition(ui::PAGE_TRANSITION_GENERATED) {}

AutocompleteMatch::AutocompleteMatch(AutocompleteProvider* provider,
                                     int relevance,
                                     bool deletable,
                                     Type type)
    : provider(provider),
      relevance(relevance),
      deletable(deletable),
      type(type) {}

AutocompleteMatch::AutocompleteMatch(const AutocompleteMatch& match)
    : provider(match.provider),
      relevance(match.relevance),
      typed_count(match.typed_count),
      deletable(match.deletable),
      fill_into_edit(match.fill_into_edit),
      additional_text(match.additional_text),
      inline_autocompletion(match.inline_autocompletion),
      rich_autocompletion_triggered(match.rich_autocompletion_triggered),
      allowed_to_be_default_match(match.allowed_to_be_default_match),
      destination_url(match.destination_url),
      stripped_destination_url(match.stripped_destination_url),
      extra_headers(match.extra_headers),
      image_dominant_color(match.image_dominant_color),
      image_url(match.image_url),
      icon_url(match.icon_url),
      entity_id(match.entity_id),
      website_uri(match.website_uri),
      document_type(match.document_type),
      enterprise_search_aggregator_type(
          match.enterprise_search_aggregator_type),
      tail_suggest_common_prefix(match.tail_suggest_common_prefix),
      contents(match.contents),
      contents_class(match.contents_class),
      description(match.description),
      description_class(match.description_class),
      description_for_shortcuts(match.description_for_shortcuts),
      description_class_for_shortcuts(match.description_class_for_shortcuts),
      suggestion_group_id(match.suggestion_group_id),
      swap_contents_and_description(match.swap_contents_and_description),
      answer_template(match.answer_template),
      suggest_template(match.suggest_template),
      answer_type(match.answer_type),
      transition(match.transition),
      type(match.type),
      suggest_type(match.suggest_type),
      subtypes(match.subtypes),
      has_tab_match(match.has_tab_match),
#if BUILDFLAG(IS_ANDROID)
      android_tab_id(match.android_tab_id),
#endif
      associated_keyword(match.associated_keyword),
      keyword(match.keyword),
      from_keyword(match.from_keyword),
      actions(match.actions),
      takeover_action(match.takeover_action),
      from_previous(match.from_previous),
      session(match.session),
      search_terms_args(
          match.search_terms_args
              ? new TemplateURLRef::SearchTermsArgs(*match.search_terms_args)
              : nullptr),
      post_content(match.post_content
                       ? new TemplateURLRef::PostContent(*match.post_content)
                       : nullptr),
      additional_info(match.additional_info),
      duplicate_matches(match.duplicate_matches),
      suggest_tiles(match.suggest_tiles),
      scoring_signals(match.scoring_signals),
      culled_by_provider(match.culled_by_provider),
      shortcut_boosted(match.shortcut_boosted),
      iph_type(match.iph_type),
      iph_link_text(match.iph_link_text),
      iph_link_url(match.iph_link_url),
      history_embeddings_answer_header_text(
          match.history_embeddings_answer_header_text),
      history_embeddings_answer_header_loading(
          match.history_embeddings_answer_header_loading),
      feedback_type(match.feedback_type),
      matching_tab_group_uuid(match.matching_tab_group_uuid) {
}

AutocompleteMatch::AutocompleteMatch(AutocompleteMatch&& match) noexcept {
  *this = std::move(match);
}

AutocompleteMatch& AutocompleteMatch::operator=(
    AutocompleteMatch&& match) noexcept {
  provider = std::move(match.provider);
  relevance = std::move(match.relevance);
  typed_count = std::move(match.typed_count);
  deletable = std::move(match.deletable);
  fill_into_edit = std::move(match.fill_into_edit);
  additional_text = std::move(match.additional_text);
  inline_autocompletion = std::move(match.inline_autocompletion);
  rich_autocompletion_triggered =
      std::move(match.rich_autocompletion_triggered);
  allowed_to_be_default_match = std::move(match.allowed_to_be_default_match);
  destination_url = std::move(match.destination_url);
  stripped_destination_url = std::move(match.stripped_destination_url);
  extra_headers = std::move(match.extra_headers);
  image_dominant_color = std::move(match.image_dominant_color);
  image_url = std::move(match.image_url);
  icon_url = std::move(match.icon_url);
  entity_id = std::move(match.entity_id);
  website_uri = std::move(match.website_uri);
  document_type = std::move(match.document_type);
  enterprise_search_aggregator_type =
      std::move(match.enterprise_search_aggregator_type);
  tail_suggest_common_prefix = std::move(match.tail_suggest_common_prefix);
  contents = std::move(match.contents);
  contents_class = std::move(match.contents_class);
  description = std::move(match.description);
  description_class = std::move(match.description_class);
  description_for_shortcuts = std::move(match.description_for_shortcuts);
  description_class_for_shortcuts =
      std::move(match.description_class_for_shortcuts);
  suggestion_group_id = std::move(match.suggestion_group_id);
  swap_contents_and_description =
      std::move(match.swap_contents_and_description);
  answer_template = std::move(match.answer_template);
  suggest_template = std::move(match.suggest_template);
  answer_type = std::move(match.answer_type);
  transition = std::move(match.transition);
  type = std::move(match.type);
  suggest_type = std::move(match.suggest_type);
  subtypes = std::move(match.subtypes);
  has_tab_match = std::move(match.has_tab_match);
  associated_keyword = std::move(match.associated_keyword);
  keyword = std::move(match.keyword);
  from_keyword = std::move(match.from_keyword);
  actions = std::move(match.actions);
  takeover_action = std::move(match.takeover_action);
  from_previous = std::move(match.from_previous);
  session = std::move(match.session);
  search_terms_args = std::move(match.search_terms_args);
  post_content = std::move(match.post_content);
  additional_info = std::move(match.additional_info);
  duplicate_matches = std::move(match.duplicate_matches);
  suggest_tiles = std::move(match.suggest_tiles);
  scoring_signals = std::move(match.scoring_signals);
  culled_by_provider = std::move(match.culled_by_provider);
  shortcut_boosted = std::move(match.shortcut_boosted);
  iph_type = std::move(match.iph_type);
  iph_link_text = std::move(match.iph_link_text);
  iph_link_url = std::move(match.iph_link_url);
  history_embeddings_answer_header_text =
      std::move(match.history_embeddings_answer_header_text);
  history_embeddings_answer_header_loading =
      std::move(match.history_embeddings_answer_header_loading);
  feedback_type = std::move(match.feedback_type);
  matching_tab_group_uuid = std::move(match.matching_tab_group_uuid);
#if BUILDFLAG(IS_ANDROID)
  android_tab_id = std::move(match.android_tab_id);
  DestroyJavaObject();
  std::swap(java_match_, match.java_match_);
  UpdateJavaObjectNativeRef();
#endif
  return *this;
}

AutocompleteMatch::~AutocompleteMatch() {
#if BUILDFLAG(IS_ANDROID)
  DestroyJavaObject();
#endif
}

AutocompleteMatch& AutocompleteMatch::operator=(
    const AutocompleteMatch& match) {
  if (this == &match)
    return *this;

  provider = match.provider;
  relevance = match.relevance;
  typed_count = match.typed_count;
  deletable = match.deletable;
  fill_into_edit = match.fill_into_edit;
  additional_text = match.additional_text;
  inline_autocompletion = match.inline_autocompletion;
  rich_autocompletion_triggered = match.rich_autocompletion_triggered;
  allowed_to_be_default_match = match.allowed_to_be_default_match;
  destination_url = match.destination_url;
  stripped_destination_url = match.stripped_destination_url;
  extra_headers = match.extra_headers;
  image_dominant_color = match.image_dominant_color;
  image_url = match.image_url;
  icon_url = match.icon_url;
  entity_id = match.entity_id;
  website_uri = match.website_uri;
  document_type = match.document_type;
  enterprise_search_aggregator_type = match.enterprise_search_aggregator_type;
  tail_suggest_common_prefix = match.tail_suggest_common_prefix;
  contents = match.contents;
  contents_class = match.contents_class;
  description = match.description;
  description_class = match.description_class;
  description_for_shortcuts = match.description_for_shortcuts;
  description_class_for_shortcuts = match.description_class_for_shortcuts;
  suggestion_group_id = match.suggestion_group_id;
  swap_contents_and_description = match.swap_contents_and_description;
  answer_template = match.answer_template;
  suggest_template = match.suggest_template;
  answer_type = match.answer_type;
  transition = match.transition;
  type = match.type;
  suggest_type = match.suggest_type;
  subtypes = match.subtypes;
  has_tab_match = match.has_tab_match;
  associated_keyword = match.associated_keyword;
  keyword = match.keyword;
  from_keyword = match.from_keyword;
  actions = match.actions;
  takeover_action = match.takeover_action;
  from_previous = match.from_previous;
  session = match.session;
  search_terms_args.reset(
      match.search_terms_args
          ? new TemplateURLRef::SearchTermsArgs(*match.search_terms_args)
          : nullptr);
  post_content.reset(match.post_content
                         ? new TemplateURLRef::PostContent(*match.post_content)
                         : nullptr);
  additional_info = match.additional_info;
  duplicate_matches = match.duplicate_matches;
  suggest_tiles = match.suggest_tiles;
  scoring_signals = match.scoring_signals;
  culled_by_provider = match.culled_by_provider;
  shortcut_boosted = match.shortcut_boosted;
  iph_type = match.iph_type;
  iph_link_text = match.iph_link_text;
  iph_link_url = match.iph_link_url;
  history_embeddings_answer_header_text =
      match.history_embeddings_answer_header_text;
  history_embeddings_answer_header_loading =
      match.history_embeddings_answer_header_loading;
  feedback_type = match.feedback_type;
  matching_tab_group_uuid = match.matching_tab_group_uuid;

#if BUILDFLAG(IS_ANDROID)
  android_tab_id = match.android_tab_id;
  // In case the target element previously held a java object, release it.
  // This happens, when in an expression "match1 = match2;" match1 already
  // is initialized and linked to a Java object: we rewrite the contents of the
  // match1 object and it would be desired to either update its corresponding
  // Java element, or drop it and construct it lazily the next time it is
  // needed.
  // Note that because Java<>C++ AutocompleteMatch relation is 1:1, we do not
  // want to copy the object here.
  DestroyJavaObject();
#endif
  return *this;
}

#if (!BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !BUILDFLAG(IS_IOS)
// static
const gfx::VectorIcon& AutocompleteMatch::AnswerTypeToAnswerIcon(
    omnibox::AnswerType type) {
  switch (type) {
    case omnibox::ANSWER_TYPE_CURRENCY:
      return features::IsRoundedIconsEnabled()
                 ? omnibox::kAutorenewIcon
                 : omnibox::kAnswerCurrencyChromeRefreshOldIcon;
    case omnibox::ANSWER_TYPE_DICTIONARY:
      return features::IsRoundedIconsEnabled()
                 ? omnibox::kBookIcon
                 : omnibox::kAnswerDictionaryChromeRefreshOldIcon;
    case omnibox::ANSWER_TYPE_FINANCE:
      return features::IsRoundedIconsEnabled()
                 ? omnibox::kSwapVertIcon
                 : omnibox::kAnswerFinanceChromeRefreshOldIcon;
    case omnibox::ANSWER_TYPE_SUNRISE_SUNSET:
      return features::IsRoundedIconsEnabled()
                 ? omnibox::kWbSunnyIcon
                 : omnibox::kAnswerSunriseChromeRefreshOldIcon;
    case omnibox::ANSWER_TYPE_TRANSLATION:
      return features::IsRoundedIconsEnabled()
                 ? omnibox::kTranslateIcon
                 : omnibox::kAnswerTranslationChromeRefreshOldIcon;
    default:
      return omnibox::kAnswerDefaultIcon;
  }
}

const gfx::VectorIcon& AutocompleteMatch::GetVectorIcon(
    bool is_bookmark,
    const TemplateURL* turl) const {
  if (suggest_template.has_value() && suggest_template->has_type_icon()) {
    // Update this assertion and the switch below whenever values are added.
    static_assert(omnibox::SuggestTemplateInfo::IconType_MAX ==
                  omnibox::SuggestTemplateInfo::BOLT);
    switch (suggest_template->type_icon()) {
      case omnibox::SuggestTemplateInfo::ICON_TYPE_UNSPECIFIED:
        // When not specified, fall back on regular match icon logic below.
        break;
      case omnibox::SuggestTemplateInfo::HISTORY:
        return features::IsRoundedIconsEnabled()
                   ? vector_icons::kHistoryIcon
                   : vector_icons::kHistoryChromeRefreshOldIcon;
      case omnibox::SuggestTemplateInfo::NOTES_SPARK:
        return features::IsRoundedIconsEnabled() ? omnibox::kNotesSparkIcon
                                                 : omnibox::kNotesSparkOldIcon;
      case omnibox::SuggestTemplateInfo::SEARCH_LOOP:
        return features::IsRoundedIconsEnabled()
                   ? vector_icons::kSearchIcon
                   : vector_icons::kSearchChromeRefreshOldIcon;
      case omnibox::SuggestTemplateInfo::SEARCH_LOOP_WITH_SPARKLE:
        return features::IsRoundedIconsEnabled() ? omnibox::kSearchSparkIcon
                                                 : omnibox::kSearchSparkOldIcon;
      case omnibox::SuggestTemplateInfo::TRENDING:
        return features::IsRoundedIconsEnabled()
                   ? omnibox::kTrendingUpIcon
                   : omnibox::kTrendingUpChromeRefreshOldIcon;
      case omnibox::SuggestTemplateInfo::SUB_ARROW_RIGHT:
        return features::IsRoundedIconsEnabled()
                   ? omnibox::kSubdirectoryArrowRightIcon
                   : omnibox::kSubdirectoryArrowRightOldIcon;
      case omnibox::SuggestTemplateInfo::GLOBE_WITH_SEARCH_LOOP:
      case omnibox::SuggestTemplateInfo::BANANA:
      case omnibox::SuggestTemplateInfo::DRAFT_SPARK:
      case omnibox::SuggestTemplateInfo::LIGHTBULB:
      case omnibox::SuggestTemplateInfo::ATTACH_FILE:
      case omnibox::SuggestTemplateInfo::SCHOOL:
      case omnibox::SuggestTemplateInfo::INK_PEN:
      case omnibox::SuggestTemplateInfo::TAB:
      case omnibox::SuggestTemplateInfo::PHOTO_SPARK:
      case omnibox::SuggestTemplateInfo::BOLT:
      default:
        // Out of range value defaults to search loupe.
        return features::IsRoundedIconsEnabled()
                   ? vector_icons::kSearchIcon
                   : vector_icons::kSearchChromeRefreshOldIcon;
    }
  }

  // Some match types should retain their traditional icon even when bookmarked.
  if (is_bookmark && type != Type::HISTORY_EMBEDDINGS_ANSWER &&
      type != Type::STARTER_PACK) {
    return features::IsRoundedIconsEnabled()
               ? omnibox::kStarIcon
               : omnibox::kBookmarkChromeRefreshOldIcon;
  }

  if (answer_type != omnibox::ANSWER_TYPE_UNSPECIFIED) {
    return AnswerTypeToAnswerIcon(answer_type);
  }

  switch (type) {
    case Type::URL_WHAT_YOU_TYPED:
    case Type::HISTORY_URL:
    case Type::HISTORY_TITLE:
    case Type::HISTORY_BODY:
    case Type::HISTORY_KEYWORD:
    case Type::NAVSUGGEST:
    case Type::BOOKMARK_TITLE:
    case Type::NAVSUGGEST_PERSONALIZED:
    case Type::CLIPBOARD_URL:
    case Type::PHYSICAL_WEB_DEPRECATED:
    case Type::PHYSICAL_WEB_OVERFLOW_DEPRECATED:
    case Type::TAB_SEARCH_DEPRECATED:
    case Type::TILE_NAVSUGGEST:
    case Type::TILE_MOST_VISITED_SITE:
    case Type::OPEN_TAB:
    case Type::HISTORY_EMBEDDINGS:
      return features::IsRoundedIconsEnabled()
                 ? omnibox::kPublicIcon
                 : omnibox::kPageChromeRefreshOldIcon;

    case Type::SEARCH_SUGGEST:
      return IsTrendSuggestion()
                 ? features::IsRoundedIconsEnabled()
                       ? omnibox::kTrendingUpIcon
                       : omnibox::kTrendingUpChromeRefreshOldIcon
             : (IsContextualSearchSuggestion() &&
                omnibox_feature_configs::ContextualSearch::Get()
                    .contextual_zero_suggest_lens_fulfillment)
                 ? features::IsRoundedIconsEnabled()
                       ? omnibox::kSubdirectoryArrowRightIcon
                       : omnibox::kSubdirectoryArrowRightOldIcon
             : features::IsRoundedIconsEnabled()
                 ? vector_icons::kSearchIcon
                 : vector_icons::kSearchChromeRefreshOldIcon;

    case Type::PEDAL:
      return takeover_action ? takeover_action->GetVectorIcon()
             : features::IsRoundedIconsEnabled()
                 ? vector_icons::kSearchIcon
                 : vector_icons::kSearchChromeRefreshOldIcon;

    case Type::SEARCH_OTHER_ENGINE:
      if (provider != nullptr &&
          provider->type() == AutocompleteProvider::TYPE_UNSCOPED_EXTENSION) {
        return features::IsRoundedIconsEnabled()
                   ? omnibox::kExtensionFilledIcon
                   : omnibox::kExtensionAppOldIcon;
      }
      return features::IsRoundedIconsEnabled()
                 ? vector_icons::kSearchIcon
                 : vector_icons::kSearchChromeRefreshOldIcon;

    case Type::SEARCH_WHAT_YOU_TYPED:
    case Type::SEARCH_SUGGEST_ENTITY:
    case Type::SEARCH_SUGGEST_PROFILE:
    case Type::CONTACT_DEPRECATED:
    case Type::VOICE_SUGGEST:
    case Type::CLIPBOARD_TEXT:
    case Type::CLIPBOARD_IMAGE:
    case Type::TILE_SUGGESTION:
    case Type::TILE_REPEATABLE_QUERY:
      return features::IsRoundedIconsEnabled()
                 ? vector_icons::kSearchIcon
                 : vector_icons::kSearchChromeRefreshOldIcon;

    case Type::SEARCH_HISTORY:
    case Type::SEARCH_SUGGEST_PERSONALIZED:
      DCHECK(IsSearchHistoryType(type));
      return features::IsRoundedIconsEnabled()
                 ? vector_icons::kHistoryIcon
                 : vector_icons::kHistoryChromeRefreshOldIcon;

    case Type::EXTENSION_APP_DEPRECATED:
      return features::IsRoundedIconsEnabled() ? omnibox::kExtensionFilledIcon
                                               : omnibox::kExtensionAppOldIcon;

    case Type::CALCULATOR:
      return features::IsRoundedIconsEnabled()
                 ? omnibox::kEqualIcon
                 : omnibox::kCalculatorChromeRefreshOldIcon;

    case Type::NULL_RESULT_MESSAGE:
      // Select the icon according to the type of IPH. Otherwise (for No Results
      // Found), fallthrough to use the empty icon.
      switch (iph_type) {
        case IphType::kNone:
          return gfx::VectorIcon::EmptyIcon();
        case IphType::kGemini:
          return omnibox::kSparkIcon;
        case IphType::kFeaturedEnterpriseSiteSearch:
        case IphType::kEnterpriseSearchAggregator:
          return features::IsRoundedIconsEnabled()
                     ? omnibox::kDomainIcon
                     : omnibox::kEnterpriseOldIcon;
        case IphType::kHistoryEmbeddingsSettingsPromo:
          return omnibox::kSparkIcon;
        case IphType::kHistoryEmbeddingsDisclaimer:
          return gfx::VectorIcon::EmptyIcon();
        case IphType::kHistoryScopePromo:
          return features::IsRoundedIconsEnabled()
                     ? vector_icons::kHistoryIcon
                     : vector_icons::kHistoryChromeRefreshOldIcon;
        case IphType::kHistoryEmbeddingsScopePromo:
          return omnibox::kSparkIcon;
      }

    case Type::SEARCH_SUGGEST_TAIL:
    case Type::HISTORY_EMBEDDINGS_ANSWER:
      return gfx::VectorIcon::EmptyIcon();

    case Type::DOCUMENT_SUGGESTION:
      switch (document_type) {
        case DocumentType::DRIVE_DOCS:
          return omnibox::kDriveDocsCustomIcon;
        case DocumentType::DRIVE_FORMS:
          return omnibox::kDriveFormsCustomIcon;
        case DocumentType::DRIVE_SHEETS:
          return omnibox::kDriveSheetsCustomIcon;
        case DocumentType::DRIVE_SLIDES:
          return omnibox::kDriveSlidesCustomIcon;
        case DocumentType::DRIVE_IMAGE:
          return omnibox::kDriveImageCustomIcon;
        case DocumentType::DRIVE_PDF:
          return omnibox::kDrivePdfCustomIcon;
        case DocumentType::DRIVE_VIDEO:
          return omnibox::kDriveVideoCustomIcon;
        case DocumentType::DRIVE_FOLDER:
          return omnibox::kDriveFolderCustomIcon;
        case DocumentType::DRIVE_OTHER:
          return omnibox::kDriveLogoCustomIcon;
        default:
          return features::IsRoundedIconsEnabled()
                     ? omnibox::kPublicIcon
                     : omnibox::kPageChromeRefreshOldIcon;
      }

    case Type::HISTORY_CLUSTER:
      return features::IsRoundedIconsEnabled()
                 ? omnibox::kConversionPathIcon
                 : omnibox::kJourneysChromeRefreshOldIcon;

    case Type::STARTER_PACK:
      if (turl) {
        switch (turl->GetBuiltinEngineType()) {
          case KEYWORD_MODE_STARTER_PACK_BOOKMARKS:
            return features::IsRoundedIconsEnabled()
                       ? omnibox::kStarFilledIcon
                       : omnibox::kStarActiveChromeRefreshOldIcon;
          case KEYWORD_MODE_STARTER_PACK_HISTORY:
            return features::IsRoundedIconsEnabled()
                       ? vector_icons::kHistoryIcon
                       : vector_icons::kHistoryChromeRefreshOldIcon;
          case KEYWORD_MODE_STARTER_PACK_TABS:
            return features::IsRoundedIconsEnabled()
                       ? omnibox::kChromeProductIcon
                       : omnibox::kProductChromeRefreshOldIcon;
          case KEYWORD_MODE_STARTER_PACK_GEMINI:
            return omnibox::kSparkIcon;
          case KEYWORD_MODE_STARTER_PACK_AI_MODE:
            return features::IsRoundedIconsEnabled()
                       ? omnibox::kSearchSparkIcon
                       : omnibox::kSearchSparkOldIcon;
          default:
            break;
        }
      }
      return features::IsRoundedIconsEnabled()
                 ? omnibox::kChromeProductIcon
                 : omnibox::kProductChromeRefreshOldIcon;

    case Type::FEATURED_ENTERPRISE_SEARCH:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
      if (turl && turl->CreatedByEnterpriseSearchAggregatorPolicy()) {
        return base::FeatureList::IsEnabled(omnibox::kUseAgentspace25Logo)
                   ? vector_icons::kGoogleAgentspaceMonochromeLogo25Icon
                   : vector_icons::kGoogleAgentspaceMonochromeLogoIcon;
      }
#endif
      return features::IsRoundedIconsEnabled()
                 ? omnibox::kPublicIcon
                 : omnibox::kPageChromeRefreshOldIcon;

    case Type::NUM_TYPES:
      NOTREACHED() << "Unexpected AutocompleteMatchType value: "
                   << static_cast<int>(type);
    default:
      return features::IsRoundedIconsEnabled()
                 ? omnibox::kPublicIcon
                 : omnibox::kPageChromeRefreshOldIcon;
  }
}
#endif

// static
bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch& match1,
                                     const AutocompleteMatch& match2) {
  // For equal-relevance matches, we sort alphabetically, so that providers
  // who return multiple elements at the same priority get a "stable" sort
  // across multiple updates.
  return match1.relevance == match2.relevance
             ? match1.contents < match2.contents
             : match1.relevance > match2.relevance;
}

// static
bool AutocompleteMatch::BetterDuplicate(const AutocompleteMatch& match1,
                                        const AutocompleteMatch& match2) {
  if (kIsDesktop) {
    // Prefer featured Enterprise site search matches.
    if (match1.type == AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH &&
        match2.type != AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH) {
      return true;
    }
    if (match1.type != AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH &&
        match2.type == AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH) {
      return false;
    }

    // Prefer starter pack matches.
    if (match1.type == AutocompleteMatchType::STARTER_PACK &&
        match2.type != AutocompleteMatchType::STARTER_PACK) {
      return true;
    }
    if (match1.type != AutocompleteMatchType::STARTER_PACK &&
        match2.type == AutocompleteMatchType::STARTER_PACK) {
      return false;
    }
  }

  // Prefer entity and answer matches over non-entity & non-answer matches, if
  // they have the same `fill_into_edit` value.
  if (match1.fill_into_edit == match2.fill_into_edit) {
    bool rich1 = match1.type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY ||
                 match1.answer_type;
    bool rich2 = match2.type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY ||
                 match2.answer_type;
    if (rich1 && !rich2) {
      return true;
    }
    if (rich2 && !rich1) {
      return false;
    }
  }

  // Prefer open tab matches over other types of matches.
  if (match1.type == AutocompleteMatchType::OPEN_TAB &&
      match2.type != AutocompleteMatchType::OPEN_TAB) {
    return true;
  }
  if (match1.type != AutocompleteMatchType::OPEN_TAB &&
      match2.type == AutocompleteMatchType::OPEN_TAB) {
    return false;
  }

  // Prefer matches allowed to be the default match.
  if (match1.allowed_to_be_default_match && !match2.allowed_to_be_default_match)
    return true;
  if (!match1.allowed_to_be_default_match && match2.allowed_to_be_default_match)
    return false;

  // Prefer some providers above others according to score (default is zero).
  const int match1_score =
      GetDeduplicationProviderPreferenceScore(match1.provider);
  const int match2_score =
      GetDeduplicationProviderPreferenceScore(match2.provider);
  if (match1_score != match2_score) {
    return match1_score > match2_score;
  }

  // By default, simply prefer the more relevant match.
  return MoreRelevant(match1, match2);
}

// static
bool AutocompleteMatch::BetterDuplicateByIterator(
    const std::vector<AutocompleteMatch>::const_iterator it1,
    const std::vector<AutocompleteMatch>::const_iterator it2) {
  return BetterDuplicate(*it1, *it2);
}

// static
AutocompleteMatch::ACMatchClassifications
AutocompleteMatch::MergeClassifications(
    const ACMatchClassifications& classifications1,
    const ACMatchClassifications& classifications2) {
  // We must return the empty vector only if both inputs are truly empty.
  // The result of merging an empty vector with a single (0, NONE)
  // classification is the latter one-entry vector.
  if (IsTrivialClassification(classifications1))
    return classifications2.empty() ? classifications1 : classifications2;
  if (IsTrivialClassification(classifications2))
    return classifications1;

  ACMatchClassifications output;
  for (auto i = classifications1.begin(), j = classifications2.begin();
       i != classifications1.end();) {
    AutocompleteMatch::AddLastClassificationIfNecessary(
        &output, std::max(i->offset, j->offset), i->style | j->style);
    const size_t next_i_offset = (i + 1) == classifications1.end()
                                     ? static_cast<size_t>(-1)
                                     : (i + 1)->offset;
    const size_t next_j_offset = (j + 1) == classifications2.end()
                                     ? static_cast<size_t>(-1)
                                     : (j + 1)->offset;
    if (next_i_offset >= next_j_offset)
      ++j;
    if (next_j_offset >= next_i_offset)
      ++i;
  }

  return output;
}

// static
std::string AutocompleteMatch::ClassificationsToString(
    const ACMatchClassifications& classifications) {
  std::string serialized_classifications;
  for (size_t i = 0; i < classifications.size(); ++i) {
    base::StrAppend(
        &serialized_classifications,
        {i ? "," : "", base::NumberToString(classifications[i].offset), ",",
         base::NumberToString(classifications[i].style)});
  }
  return serialized_classifications;
}

// static
ACMatchClassifications AutocompleteMatch::ClassificationsFromString(
    std::string_view serialized_classifications) {
  ACMatchClassifications classifications;
  std::vector<std::string_view> tokens =
      base::SplitStringPiece(serialized_classifications, ",",
                             base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  DCHECK(!(tokens.size() & 1));  // The number of tokens should be even.
  for (size_t i = 0; i < tokens.size(); i += 2) {
    int classification_offset = 0;
    int classification_style = ACMatchClassification::NONE;
    if (!base::StringToInt(tokens[i], &classification_offset) ||
        !base::StringToInt(tokens[i + 1], &classification_style)) {
      NOTREACHED();
    }
    classifications.push_back(
        ACMatchClassification(classification_offset, classification_style));
  }
  return classifications;
}

// static
void AutocompleteMatch::AddLastClassificationIfNecessary(
    ACMatchClassifications* classifications,
    size_t offset,
    int style) {
  DCHECK(classifications);
  if (classifications->empty() || classifications->back().style != style) {
    DCHECK(classifications->empty() ||
           (offset > classifications->back().offset));
    classifications->push_back(ACMatchClassification(offset, style));
  }
}

// static
std::u16string AutocompleteMatch::SanitizeString(const std::u16string& text) {
  return AutocompleteInput::SanitizeString(text);
}

// static
bool AutocompleteMatch::IsFeaturedEnterpriseSearchType(Type type) {
  return type == AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH;
}

// static
bool AutocompleteMatch::IsFeaturedSearchType(Type type) {
  return IsStarterPackType(type) || IsFeaturedEnterpriseSearchType(type);
}

// static
bool AutocompleteMatch::IsPreconnectableType(Type type) {
  CHECK(base::FeatureList::IsEnabled(
      omnibox::kPreconnectNonSearchOmniboxSuggestions));
  return IsSearchType(type) ||
         type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
         type == AutocompleteMatchType::HISTORY_URL ||
         type == AutocompleteMatchType::HISTORY_TITLE ||
         type == AutocompleteMatchType::HISTORY_BODY ||
         type == AutocompleteMatchType::HISTORY_KEYWORD ||
         type == AutocompleteMatchType::NAVSUGGEST ||
         type == AutocompleteMatchType::BOOKMARK_TITLE ||
         type == AutocompleteMatchType::NAVSUGGEST_PERSONALIZED ||
         type == AutocompleteMatchType::CLIPBOARD_URL ||
         type == AutocompleteMatchType::DOCUMENT_SUGGESTION ||
         type == AutocompleteMatchType::TILE_NAVSUGGEST ||
         type == AutocompleteMatchType::OPEN_TAB ||
         type == AutocompleteMatchType::TILE_MOST_VISITED_SITE ||
         type == AutocompleteMatchType::HISTORY_EMBEDDINGS;
}

// static
bool AutocompleteMatch::IsSearchType(Type type) {
  return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
         type == AutocompleteMatchType::SEARCH_HISTORY ||
         type == AutocompleteMatchType::SEARCH_SUGGEST ||
         type == AutocompleteMatchType::SEARCH_OTHER_ENGINE ||
         type == AutocompleteMatchType::CALCULATOR ||
         type == AutocompleteMatchType::VOICE_SUGGEST ||
         type == AutocompleteMatchType::CLIPBOARD_TEXT ||
         type == AutocompleteMatchType::CLIPBOARD_IMAGE ||
         IsSpecializedSearchType(type);
}

// static
bool AutocompleteMatch::IsSpecializedSearchType(Type type) {
  return type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY ||
         type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL ||
         type == AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED ||
         type == AutocompleteMatchType::TILE_SUGGESTION ||
         type == AutocompleteMatchType::TILE_REPEATABLE_QUERY ||
         type == AutocompleteMatchType::SEARCH_SUGGEST_PROFILE;
}

// static
bool AutocompleteMatch::IsSearchHistoryType(Type type) {
  return type == AutocompleteMatchType::SEARCH_HISTORY ||
         type == AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED;
}

// static
bool AutocompleteMatch::IsStarterPackType(Type type) {
  return type == AutocompleteMatchType::STARTER_PACK;
}

// static
bool AutocompleteMatch::IsClipboardType(Type type) {
  return type == AutocompleteMatchType::CLIPBOARD_URL ||
         type == AutocompleteMatchType::CLIPBOARD_TEXT ||
         type == AutocompleteMatchType::CLIPBOARD_IMAGE;
}

// static
bool AutocompleteMatch::ShouldBeSkippedForGroupBySearchVsUrl(Type type) {
  return IsClipboardType(type) ||
         type == AutocompleteMatchType::TILE_NAVSUGGEST ||
         type == AutocompleteMatchType::TILE_MOST_VISITED_SITE ||
         type == AutocompleteMatchType::TILE_REPEATABLE_QUERY ||
         type == AutocompleteMatchType::TILE_SUGGESTION;
}

// static
omnibox::GroupId AutocompleteMatch::GetDefaultGroupId(Type type) {
  if (type == AutocompleteMatchType::TILE_NAVSUGGEST ||
      type == AutocompleteMatchType::TILE_SUGGESTION ||
      type == AutocompleteMatchType::TILE_MOST_VISITED_SITE ||
      type == AutocompleteMatchType::TILE_REPEATABLE_QUERY) {
    return omnibox::GROUP_MOBILE_MOST_VISITED;
  }

  if (type == AutocompleteMatchType::CLIPBOARD_URL ||
      type == AutocompleteMatchType::CLIPBOARD_TEXT ||
      type == AutocompleteMatchType::CLIPBOARD_IMAGE) {
    return omnibox::GROUP_MOBILE_CLIPBOARD;
  }

  // TODO(b/309458788): Create a separate group for Featured Enterprise search.
  //                    In the meantime, reusing the same group as starter pack
  //                    is OK, since they are shown together anyway.
  if (IsStarterPackType(type) || IsFeaturedEnterpriseSearchType(type)) {
    return omnibox::GROUP_STARTER_PACK;
  }

  if (IsSearchType(type))
    return omnibox::GROUP_SEARCH;

  if (type == AutocompleteMatchType::HISTORY_CLUSTER)
    return omnibox::GROUP_HISTORY_CLUSTER;

  if (type == AutocompleteMatchType::CROSS_DEVICE_TAB) {
    return omnibox::GROUP_CROSS_DEVICE_TABS;
  }

  return omnibox::GROUP_OTHER_NAVS;
}

// static
TemplateURL* AutocompleteMatch::GetTemplateURLWithKeyword(
    TemplateURLService* template_url_service,
    const std::u16string& keyword,
    const std::string& host) {
  return const_cast<TemplateURL*>(GetTemplateURLWithKeyword(
      static_cast<const TemplateURLService*>(template_url_service), keyword,
      host));
}

// static
const TemplateURL* AutocompleteMatch::GetTemplateURLWithKeyword(
    const TemplateURLService* template_url_service,
    const std::u16string& keyword,
    const std::string& host) {
  if (template_url_service == nullptr)
    return nullptr;
  const TemplateURL* template_url =
      keyword.empty() ? nullptr
                      : template_url_service->GetTemplateURLForKeyword(keyword);
  return (template_url || host.empty())
             ? template_url
             : template_url_service->GetTemplateURLForHost(host);
}

// static
GURL AutocompleteMatch::GURLToStrippedGURL(
    const GURL& url,
    const AutocompleteInput& input,
    const TemplateURLService* template_url_service,
    const std::u16string& keyword,
    const bool keep_search_intent_params) {
  if (!url.is_valid())
    return url;

  // Special-case canonicalization Docs URLs. This logic is self-contained and
  // will not participate in the TemplateURL canonicalization.
  GURL docs_url = DocumentProvider::GetURLForDeduping(url);
  if (docs_url.is_valid())
    return docs_url;

  GURL stripped_destination_url = url;

  // If the destination URL looks like it was generated from a TemplateURL,
  // remove all substitutions other than the search terms and optionally the
  // search intent params. This allows eliminating cases like past search URLs
  // from history that differ only by some obscure query param from each other
  // or from the search/keyword provider matches.
  const TemplateURL* template_url = GetTemplateURLWithKeyword(
      template_url_service, keyword, stripped_destination_url.GetHost());
  if (template_url && template_url->SupportsReplacement(
                          template_url_service->search_terms_data())) {
    using CacheKey = std::tuple<const TemplateURL*, GURL, bool>;
    static base::NoDestructor<base::LRUCache<CacheKey, GURL>> template_cache(
        30);
    const CacheKey cache_key = {template_url, url, keep_search_intent_params};
    const auto& cached = template_cache->Get(cache_key);
    if (cached != template_cache->end()) {
      stripped_destination_url = cached->second;
    } else if (template_url->KeepSearchTermsInURL(
                   url, template_url_service->search_terms_data(),
                   keep_search_intent_params,
                   /*normalize_search_terms=*/false,
                   &stripped_destination_url)) {
      template_cache->Put(cache_key, stripped_destination_url);
    }
  }

  // |replacements| keeps all the substitutions we're going to make to
  // from {destination_url} to {stripped_destination_url}.  |need_replacement|
  // is a helper variable that helps us keep track of whether we need
  // to apply the replacement.
  bool needs_replacement = false;
  GURL::Replacements replacements;

  // Remove the www. prefix from the host.
  static const char prefix[] = "www.";
  static const size_t prefix_len = std::size(prefix) - 1;
  std::string host = stripped_destination_url.GetHost();
  if (host.compare(0, prefix_len, prefix) == 0 && host.length() > prefix_len) {
    replacements.SetHostStr(std::string_view(host).substr(prefix_len));
    needs_replacement = true;
  }

  // Replace https protocol with http, as long as the user didn't explicitly
  // specify one of the two.
  if (stripped_destination_url.SchemeIs(url::kHttpsScheme) &&
      (input.terms_prefixed_by_http_or_https().empty() ||
       !WordMatchesURLContent(input.terms_prefixed_by_http_or_https(), url))) {
    replacements.SetSchemeStr(url::kHttpScheme);
    needs_replacement = true;
  }

  if (input.parts().ref.is_empty() && url.has_ref()) {
    replacements.ClearRef();
    needs_replacement = true;
  }

  if (needs_replacement)
    stripped_destination_url =
        stripped_destination_url.ReplaceComponents(replacements);
  return stripped_destination_url;
}

// static
void AutocompleteMatch::GetMatchComponents(
    const GURL& url,
    const std::vector<MatchPosition>& match_positions,
    bool* match_in_scheme,
    bool* match_in_subdomain) {
  DCHECK(match_in_scheme);
  DCHECK(match_in_subdomain);

  size_t domain_length =
      net::registry_controlled_domains::GetDomainAndRegistry(
          url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES)
          .size();
  const url::Parsed& parsed = url.parsed_for_possibly_invalid_spec();

  size_t host_pos = parsed.CountCharactersBefore(url::Parsed::HOST, false);

  bool has_subdomain = domain_length > 0 && domain_length < url.host().length();
  // Subtract an extra character from the domain start to exclude the '.'
  // delimiter between subdomain and domain.
  size_t subdomain_end =
      has_subdomain ? host_pos + url.host().length() - domain_length - 1
                    : std::string::npos;

  for (auto& position : match_positions) {
    // Only flag |match_in_scheme| if the match starts at the very beginning.
    if (position.first == 0 && parsed.scheme.is_nonempty())
      *match_in_scheme = true;

    // Subdomain matches must begin before the domain, and end somewhere within
    // the host or later.
    if (has_subdomain && position.first < subdomain_end &&
        position.second > host_pos && parsed.host.is_nonempty()) {
      *match_in_subdomain = true;
    }
  }
}

// static
url_formatter::FormatUrlTypes AutocompleteMatch::GetFormatTypes(
    bool preserve_scheme,
    bool preserve_subdomain) {
  auto format_types = url_formatter::kFormatUrlOmitDefaults;
  if (preserve_scheme) {
    format_types &= ~url_formatter::kFormatUrlOmitHTTP;
  } else {
    format_types |= url_formatter::kFormatUrlOmitHTTPS;
  }

  if (!preserve_subdomain) {
    format_types |= url_formatter::kFormatUrlOmitTrivialSubdomains;
  }

  return format_types;
}

// static
void AutocompleteMatch::LogSearchEngineUsed(
    const AutocompleteMatch& match,
    const TemplateURLService* template_url_service) {
  DCHECK(template_url_service);

  const TemplateURL* template_url = match.GetTemplateURL(template_url_service);
  if (!template_url) {
    return;
  }

  SearchEngineType search_engine_type =
      match.destination_url.is_valid()
          ? search_engine_utils::GetEngineType(match.destination_url)
          : SEARCH_ENGINE_OTHER;
  // Check for search engines types not present in prepopulated_engines.json.
  // TODO(https://issues.chromium.org/405167888): Remove this check once it is
  // no longer necessary to track these additional search engine types.
  if (search_engine_type == SEARCH_ENGINE_OTHER) {
    if (match.destination_url.is_valid() &&
        url::DomainIs(match.destination_url.host(), "siteadvisor.com")) {
      search_engine_type = SEARCH_ENGINE_MCAFEE;
    }
  }
  UMA_HISTOGRAM_ENUMERATION("Omnibox.SearchEngineType", search_engine_type,
                            SEARCH_ENGINE_MAX);

  if (template_url->CreatedByPolicy()) {
    UMA_HISTOGRAM_ENUMERATION("Omnibox.SearchEngineType.SetByEnterprisePolicy",
                              search_engine_type, SEARCH_ENGINE_MAX);

    switch (template_url->policy_origin()) {
      case TemplateURLData::PolicyOrigin::kDefaultSearchProvider:
        UMA_HISTOGRAM_ENUMERATION(
            "Omnibox.SearchEngineType.SetByEnterprisePolicy."
            "DefaultSearchProvider",
            search_engine_type, SEARCH_ENGINE_MAX);
        break;

      case TemplateURLData::PolicyOrigin::kSiteSearch:
        UMA_HISTOGRAM_ENUMERATION(
            "Omnibox.SearchEngineType.SetByEnterprisePolicy."
            "SiteSearchSettings",
            search_engine_type, SEARCH_ENGINE_MAX);
        break;

      case TemplateURLData::PolicyOrigin::kSearchAggregator:
        UMA_HISTOGRAM_ENUMERATION(
            "Omnibox.SearchEngineType.SetByEnterprisePolicy."
            "EnterpriseSearchAggregatorSettings",
            search_engine_type, SEARCH_ENGINE_MAX);
        break;

      default:
        NOTREACHED();
    }
  } else if (template_url->type() == TemplateURL::NORMAL) {
    if (template_url_service->GetDefaultSearchProvider() == template_url) {
      DefaultSearchManager::Source source =
          template_url_service->default_search_provider_source();
      if (source == DefaultSearchManager::FROM_USER) {
        UMA_HISTOGRAM_ENUMERATION(
            "Omnibox.SearchEngineType.SetByUser."
            "DefaultSearchProvider",
            search_engine_type, SEARCH_ENGINE_MAX);
      } else if (source == DefaultSearchManager::FROM_FALLBACK) {
        UMA_HISTOGRAM_ENUMERATION(
            "Omnibox.SearchEngineType.Fallback."
            "DefaultSearchProvider",
            search_engine_type, SEARCH_ENGINE_MAX);
      }
    }
  } else if (template_url->type() ==
             TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION) {
    if (template_url_service->GetDefaultSearchProvider() == template_url) {
      UMA_HISTOGRAM_ENUMERATION(
          "Omnibox.SearchEngineType.SetByExtension."
          "SettingsOverrideDefaultSearchProvider",
          search_engine_type, SEARCH_ENGINE_MAX);
    } else {
      // TODO(crbug.com/367330704): Find an extension that uses the Chrome
      //   Settings override to add an engine but that doesn't set is_default to
      //   true in order to manually test this code path.
      UMA_HISTOGRAM_ENUMERATION(
          "Omnibox.SearchEngineType.SetByExtension."
          "SettingsOverrideNonDefaultSearchProvider",
          search_engine_type, SEARCH_ENGINE_MAX);
    }
  } else if (template_url->type() == TemplateURL::OMNIBOX_API_EXTENSION) {
    // The omnibox API only allows for keyword/site search entries, not default
    // search engines so only one type of histogram needs to be recorded here.
    UMA_HISTOGRAM_ENUMERATION(
        "Omnibox.SearchEngineType.SetByExtension.OmniboxAPI",
        search_engine_type, SEARCH_ENGINE_MAX);
  }
}

void AutocompleteMatch::ComputeStrippedDestinationURL(
    const AutocompleteInput& input,
    const TemplateURLService* template_url_service) {
  // Other than document suggestions, computing `stripped_destination_url` will
  // have the same result during a match's lifecycle, so it's safe to skip
  // re-computing it if it's already computed. Document provider and history
  // quick provider document suggestions' `stripped_url`s are pre-computed by
  // the document provider, and overwriting them here would be wasteful and, in
  // the case of the document provider, prevent potential deduping.
  if (stripped_destination_url.is_empty()) {
    stripped_destination_url = GURLToStrippedGURL(
        destination_url, input, template_url_service, keyword,
        /*keep_search_intent_params=*/false);
  }
}

bool AutocompleteMatch::IsDocumentSuggestion() {
  const GURL docs_url = DocumentProvider::GetURLForDeduping(destination_url);
  // May as well set `stripped_destination_url` to avoid duplicate computation
  // later in `ComputeStrippedDestinationURL()`. Additionally tracking if the
  // suggestion is not a doc would add more clutter than benefit.
  if (docs_url.is_valid())
    stripped_destination_url = docs_url;
  return docs_url.is_valid();
}

bool AutocompleteMatch::IsActionCompatible() const {
  return type != AutocompleteMatchType::SEARCH_SUGGEST_ENTITY &&
         // Attaching to Tail Suggest types looks weird, and is actually
         // technically wrong because the Pedals annotator (and history clusters
         // annotator) both use match.contents. If we do want to turn on Actions
         // for tail suggest in the future, we should switch to using
         // match.fill_into_edit or maybe page title for URL matches, and come
         // up with a UI design for the button in the tail suggest layout.
         type != AutocompleteMatchType::SEARCH_SUGGEST_TAIL;
}

bool AutocompleteMatch::HasInstantKeyword(
    const TemplateURLService* template_url_service) const {
  if (associated_keyword.empty()) {
    return false;
  }
  const TemplateURL* turl =
      GetTemplateURLWithKeyword(template_url_service, associated_keyword, "");
  return turl && (turl->starter_pack_id() !=
                      template_url_starter_pack_data::StarterPackId::kNone ||
                  turl->featured_by_policy());
}

bool AutocompleteMatch::ShouldHideBasedOnStarterPack(
    const TemplateURLService* template_url_service) const {
  return StarterPackId(template_url_service) ==
         template_url_starter_pack_data::StarterPackId::kGemini;
}

void AutocompleteMatch::GetKeywordUiState(
    const TemplateURLService* template_url_service,
    bool is_history_embeddings_enabled,
    KeywordState* keyword_state,
    std::u16string* keyword_out,
    std::u16string* keyword_placeholder_out) const {
  *keyword_placeholder_out = GetKeywordPlaceholder(
      GetTemplateURL(template_url_service), is_history_embeddings_enabled);
  if (associated_keyword.empty()) {
    keyword_out->assign(
        IsExplicitlyInvokedKeyword(template_url_service) ? keyword : u"");
    *keyword_state =
        keyword_out->empty() ? KeywordState::kNone : KeywordState::kKeyword;
  } else {
    keyword_out->assign(associated_keyword);
    *keyword_state = KeywordState::kHint;
  }
}

bool AutocompleteMatch::IsExplicitlyInvokedKeyword(
    const TemplateURLService* template_url_service) const {
  if (keyword.empty() ||
      !ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_KEYWORD) ||
      template_url_service == nullptr) {
    return false;
  }

  const TemplateURL* t_url = GetTemplateURL(template_url_service);
  return t_url &&
         t_url->SupportsReplacement(template_url_service->search_terms_data());
}

// static
std::u16string AutocompleteMatch::GetKeywordPlaceholder(
    const TemplateURL* template_url,
    bool is_history_embeddings_enabled) {
#if BUILDFLAG(IS_IOS)
  // `kOmniboxScoped` isn't defined on iOS and all history embedding subfeatures
  // are disabled on iOS.
  return std::u16string();
#else
  if (!template_url) {
    return std::u16string();
  }
  if (!history_embeddings::GetFeatureParameters().omnibox_scoped) {
    return std::u16string();
  }
  if (template_url->CreatedByEnterpriseSearchAggregatorPolicy()) {
    return l10n_util::GetStringUTF16(IDS_OMNIBOX_GEMINI_SCOPE_PLACEHOLDER_TEXT);
  }
  int message_id;
  switch (template_url->starter_pack_id()) {
    case template_url_starter_pack_data::StarterPackId::kBookmarks:
      message_id = IDS_OMNIBOX_BOOKMARKS_SCOPE_PLACEHOLDER_TEXT;
      break;
    case template_url_starter_pack_data::StarterPackId::kHistory:
      message_id = is_history_embeddings_enabled
                       ? IDS_OMNIBOX_HISTORY_EMBEDDINGS_SCOPE_PLACEHOLDER_TEXT
                       : IDS_OMNIBOX_HISTORY_SCOPE_PLACEHOLDER_TEXT;
      break;
    case template_url_starter_pack_data::StarterPackId::kTabs:
      message_id = IDS_OMNIBOX_TABS_SCOPE_PLACEHOLDER_TEXT;
      break;
    case template_url_starter_pack_data::StarterPackId::kGemini:
      message_id = IDS_OMNIBOX_GEMINI_SCOPE_PLACEHOLDER_TEXT;
      break;
    case template_url_starter_pack_data::StarterPackId::kPage:
      message_id = IDS_OMNIBOX_PAGE_SCOPE_PLACEHOLDER_TEXT;
      break;
    case template_url_starter_pack_data::StarterPackId::kAiMode:
      message_id = IDS_OMNIBOX_AI_MODE_SCOPE_PLACEHOLDER_TEXT;
      break;
    default:
      return std::u16string();
  }
  return l10n_util::GetStringUTF16(message_id);
#endif
}

TemplateURL* AutocompleteMatch::GetTemplateURL(
    TemplateURLService* template_url_service) const {
  return GetTemplateURLWithKeyword(template_url_service, keyword, "");
}

const TemplateURL* AutocompleteMatch::GetTemplateURL(
    const TemplateURLService* template_url_service) const {
  return GetTemplateURLWithKeyword(template_url_service, keyword, "");
}

template_url_starter_pack_data::StarterPackId AutocompleteMatch::StarterPackId(
    const TemplateURLService* template_url_service) const {
  if (!from_keyword) {
    return template_url_starter_pack_data::StarterPackId::kNone;
  }
  const TemplateURL* turl = GetTemplateURL(template_url_service);
  return turl ? turl->starter_pack_id()
              : template_url_starter_pack_data::StarterPackId::kNone;
}

GURL AutocompleteMatch::ImageUrl() const {
  return image_url;
}

void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
                                             const std::string& value) {
  DCHECK(!property.empty());
  additional_info[property] = value;
}

void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
                                             const std::u16string& value) {
  RecordAdditionalInfo(property, base::UTF16ToUTF8(value));
}

void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
                                             int value) {
  RecordAdditionalInfo(property, base::NumberToString(value));
}

void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
                                             double value) {
  RecordAdditionalInfo(property, base::NumberToString(value));
}

void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
                                             base::Time value) {
  RecordAdditionalInfo(
      property, base::StringPrintf("%d hours ago",
                                   (base::Time::Now() - value).InHours()));
}

std::string AutocompleteMatch::GetAdditionalInfoForDebugging(
    const std::string& property) const {
  auto i(additional_info.find(property));
  return (i == additional_info.end()) ? std::string() : i->second;
}

metrics::OmniboxEventProto::ProviderType
AutocompleteMatch::GetOmniboxEventProviderType(int action_index) const {
  using metrics::OmniboxEventProto;

  // Mostly the `provider` provides the provider type below, but a few
  // action types have meaningful overrides here.
  if (action_index >= 0 && static_cast<size_t>(action_index) < actions.size()) {
    switch (actions[action_index]->ActionId()) {
      case OmniboxActionId::PEDAL:
        return OmniboxEventProto::PEDALS;
      case OmniboxActionId::TAB_SWITCH:
        return OmniboxEventProto::TAB_SWITCH;
      default:
        break;
    }
  }

  if (provider) {
    return provider->AsOmniboxEventProviderType();
  }

  return OmniboxEventProto::UNKNOWN_PROVIDER;
}

metrics::OmniboxEventProto::Suggestion::ResultType
AutocompleteMatch::GetOmniboxEventResultType(int action_index) const {
  using metrics::OmniboxEventProto;

  if (action_index >= 0 && static_cast<size_t>(action_index) < actions.size()) {
    switch (actions[action_index]->ActionId()) {
      case OmniboxActionId::PEDAL:
      case OmniboxActionId::CONTEXTUAL_SEARCH_ASK_ABOUT_PAGE:
      case OmniboxActionId::CONTEXTUAL_SEARCH_SELECT_REGION:
      case OmniboxActionId::CONTEXTUAL_SEARCH_OPEN_LENS:
        return OmniboxEventProto::Suggestion::PEDAL;
      case OmniboxActionId::TAB_SWITCH:
        return OmniboxEventProto::Suggestion::TAB_SWITCH;
      case OmniboxActionId::HISTORY_CLUSTERS:
      case OmniboxActionId::ACTION_IN_SUGGEST:
      case OmniboxActionId::EXTENSION_ACTION:
      case OmniboxActionId::CONTEXTUAL_SEARCH_FULFILLMENT:
      case OmniboxActionId::SITE_SEARCH:
        // Preserve existing behavior by continuing on to use the match `type`.
        break;
      case OmniboxActionId::STARTER_PACK_BOOKMARKS:
      case OmniboxActionId::STARTER_PACK_HISTORY:
      case OmniboxActionId::STARTER_PACK_TABS:
      case OmniboxActionId::STARTER_PACK_AI_MODE:
        return OmniboxEventProto::Suggestion::STARTER_PACK;
      case OmniboxActionId::CROSS_DEVICE_TAB:
        return OmniboxEventProto::Suggestion::CROSS_DEVICE_TAB;
      case OmniboxActionId::UNKNOWN:
      case OmniboxActionId::LAST:
        NOTREACHED();
    }
  }

  switch (type) {
    case AutocompleteMatchType::URL_WHAT_YOU_TYPED:
      return OmniboxEventProto::Suggestion::URL_WHAT_YOU_TYPED;
    case AutocompleteMatchType::HISTORY_URL:
      return OmniboxEventProto::Suggestion::HISTORY_URL;
    case AutocompleteMatchType::HISTORY_TITLE:
      return OmniboxEventProto::Suggestion::HISTORY_TITLE;
    case AutocompleteMatchType::HISTORY_BODY:
      return OmniboxEventProto::Suggestion::HISTORY_BODY;
    case AutocompleteMatchType::HISTORY_KEYWORD:
      return OmniboxEventProto::Suggestion::HISTORY_KEYWORD;
    case AutocompleteMatchType::NAVSUGGEST:
      return OmniboxEventProto::Suggestion::NAVSUGGEST;
    case AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED:
      return OmniboxEventProto::Suggestion::SEARCH_WHAT_YOU_TYPED;
    case AutocompleteMatchType::SEARCH_HISTORY:
    case AutocompleteMatchType::TILE_REPEATABLE_QUERY:
      return OmniboxEventProto::Suggestion::SEARCH_HISTORY;
    case AutocompleteMatchType::SEARCH_SUGGEST:
      return OmniboxEventProto::Suggestion::SEARCH_SUGGEST;
    case AutocompleteMatchType::SEARCH_SUGGEST_ENTITY:
      return OmniboxEventProto::Suggestion::SEARCH_SUGGEST_ENTITY;
    case AutocompleteMatchType::SEARCH_SUGGEST_TAIL:
      return OmniboxEventProto::Suggestion::SEARCH_SUGGEST_TAIL;
    case AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED:
      return OmniboxEventProto::Suggestion::SEARCH_SUGGEST_PERSONALIZED;
    case AutocompleteMatchType::SEARCH_SUGGEST_PROFILE:
      return OmniboxEventProto::Suggestion::SEARCH_SUGGEST_PROFILE;
    case AutocompleteMatchType::CALCULATOR:
      return OmniboxEventProto::Suggestion::CALCULATOR;
    case AutocompleteMatchType::SEARCH_OTHER_ENGINE:
      return OmniboxEventProto::Suggestion::SEARCH_OTHER_ENGINE;
    case AutocompleteMatchType::EXTENSION_APP_DEPRECATED:
      return OmniboxEventProto::Suggestion::EXTENSION_APP;
    case AutocompleteMatchType::BOOKMARK_TITLE:
      return OmniboxEventProto::Suggestion::BOOKMARK_TITLE;
    case AutocompleteMatchType::NAVSUGGEST_PERSONALIZED:
      return OmniboxEventProto::Suggestion::NAVSUGGEST_PERSONALIZED;
    case AutocompleteMatchType::CLIPBOARD_URL:
      return OmniboxEventProto::Suggestion::CLIPBOARD_URL;
    case AutocompleteMatchType::DOCUMENT_SUGGESTION:
      return OmniboxEventProto::Suggestion::DOCUMENT;
    case AutocompleteMatchType::CLIPBOARD_TEXT:
      return OmniboxEventProto::Suggestion::CLIPBOARD_TEXT;
    case AutocompleteMatchType::CLIPBOARD_IMAGE:
      return OmniboxEventProto::Suggestion::CLIPBOARD_IMAGE;
    case AutocompleteMatchType::TILE_SUGGESTION:
      return OmniboxEventProto::Suggestion::TILE_SUGGESTION;
    case AutocompleteMatchType::TILE_NAVSUGGEST:
    case AutocompleteMatchType::TILE_MOST_VISITED_SITE:
      return OmniboxEventProto::Suggestion::NAVSUGGEST;
    case AutocompleteMatchType::OPEN_TAB:
      return OmniboxEventProto::Suggestion::OPEN_TAB;
    case AutocompleteMatchType::HISTORY_CLUSTER:
      return OmniboxEventProto::Suggestion::HISTORY_CLUSTER;
    case AutocompleteMatchType::STARTER_PACK:
      return OmniboxEventProto::Suggestion::STARTER_PACK;
    case AutocompleteMatchType::VOICE_SUGGEST:
      return OmniboxEventProto::Suggestion::SEARCH_SUGGEST;
    case AutocompleteMatchType::PEDAL:
      return OmniboxEventProto::Suggestion::PEDAL;
    case AutocompleteMatchType::HISTORY_EMBEDDINGS:
      return OmniboxEventProto::Suggestion::HISTORY_EMBEDDINGS;
    case AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH:
      return OmniboxEventProto::Suggestion::FEATURED_ENTERPRISE_SEARCH;
    case AutocompleteMatchType::NULL_RESULT_MESSAGE:
      return OmniboxEventProto::Suggestion::NULL_RESULT_MESSAGE;
    case AutocompleteMatchType::HISTORY_EMBEDDINGS_ANSWER:
      return OmniboxEventProto::Suggestion::HISTORY_EMBEDDINGS_ANSWER;
    case AutocompleteMatchType::TAB_GROUP:
      return OmniboxEventProto::Suggestion::TAB_GROUP;
    case AutocompleteMatchType::CROSS_DEVICE_TAB:
      return OmniboxEventProto::Suggestion::CROSS_DEVICE_TAB;
    case AutocompleteMatchType::CONTACT_DEPRECATED:
    case AutocompleteMatchType::PHYSICAL_WEB_DEPRECATED:
    case AutocompleteMatchType::PHYSICAL_WEB_OVERFLOW_DEPRECATED:
    case AutocompleteMatchType::TAB_SEARCH_DEPRECATED:
    case AutocompleteMatchType::NUM_TYPES:
    default:
      break;
  }
  DUMP_WILL_BE_NOTREACHED() << "Unknown AutocompleteMatchType: " << type;
  return OmniboxEventProto::Suggestion::UNKNOWN_RESULT_TYPE;
}

bool AutocompleteMatch::IsVerbatimType() const {
  const bool is_keyword_verbatim_match =
      (type == AutocompleteMatchType::SEARCH_OTHER_ENGINE &&
       provider != nullptr &&
       provider->type() == AutocompleteProvider::TYPE_SEARCH);
  return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
         type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
         is_keyword_verbatim_match;
}

bool AutocompleteMatch::IsVerbatimUrlSuggestion() const {
  return type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
         std::ranges::any_of(duplicate_matches, [](const auto& match) {
           return match.type == AutocompleteMatchType::URL_WHAT_YOU_TYPED;
         });
}

bool AutocompleteMatch::IsSearchProviderSearchSuggestion() const {
  const bool from_search_provider =
      (provider && provider->type() == AutocompleteProvider::TYPE_SEARCH);
  return from_search_provider &&
         type != AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED;
}

bool AutocompleteMatch::IsOnDeviceSearchSuggestion() const {
  const bool from_on_device_provider =
      (provider &&
       provider->type() == AutocompleteProvider::TYPE_ON_DEVICE_HEAD);
  return from_on_device_provider && subtypes.contains(271);
}

int AutocompleteMatch::GetSortingOrder() const {
  if (IsFeaturedEnterpriseSearchType(type)) {
    return 0;
  }

  if (IsStarterPackType(type)) {
    return 1;
  }

  if constexpr (kIsAndroid && !kIsDesktopAndroid) {
    if (IsClipboardType(type)) {
      return 1;
    }
  }

#if !BUILDFLAG(IS_IOS)
  // Group history cluster suggestions with searches.
  if (type == AutocompleteMatchType::HISTORY_CLUSTER) {
    return 3;
  }
#endif  // !BUILDFLAG(IS_IOS)

  switch (enterprise_search_aggregator_type) {
    case EnterpriseSearchAggregatorType::NONE:
      break;
    case EnterpriseSearchAggregatorType::QUERY:
      return 5;
    case EnterpriseSearchAggregatorType::PEOPLE:
      return 6;
    case EnterpriseSearchAggregatorType::CONTENT:
      return 7;
  }

  if (IsSearchType(type)) {
    return 3;
  }

  // Group boosted shortcuts above searches.
  if (shortcut_boosted) {
    return 2;
  }

  if (type == AutocompleteMatchType::HISTORY_EMBEDDINGS_ANSWER) {
    return 8;
  }

  if (IsIphSuggestion()) {
    return 9;
  }

  if (IsToolbelt()) {
    return 10;
  }

  return 4;
}

bool AutocompleteMatch::HasCustomDescription() const {
  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_DESKTOP &&
      type == AutocompleteMatchType::CALCULATOR) {
    return true;
  }
  if (suggest_template.has_value() &&
      !suggest_template->secondary_text().text().empty()) {
    return true;
  }

  if (base::FeatureList::IsEnabled(omnibox::kInlineLocationSignaling) &&
      extra_headers.contains(kXGeoHeader)) {
    return true;
  }

  return type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY ||
         type == AutocompleteMatchType::SEARCH_SUGGEST_PROFILE ||
         type == AutocompleteMatchType::CLIPBOARD_TEXT ||
         type == AutocompleteMatchType::CLIPBOARD_IMAGE;
}

bool AutocompleteMatch::IsMlSignalLoggingEligible() const {
  const auto& ml_config = OmniboxFieldTrial::GetMLConfig();
  if (answer_type != omnibox::ANSWER_TYPE_UNSPECIFIED) {
    return false;
  }
  return type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
         type == AutocompleteMatchType::HISTORY_URL ||
         type == AutocompleteMatchType::HISTORY_TITLE ||
         type == AutocompleteMatchType::BOOKMARK_TITLE ||
         type == AutocompleteMatchType::NAVSUGGEST ||
         type == AutocompleteMatchType::NAVSUGGEST_PERSONALIZED ||
         type == AutocompleteMatchType::TILE_NAVSUGGEST ||
         (ml_config.shortcut_document_signals &&
          type == AutocompleteMatchType::DOCUMENT_SUGGESTION &&
          relevance != 0) ||
         AutocompleteMatch::IsSearchType(type) ||
         AutocompleteMatch::IsVerbatimType();
}

bool AutocompleteMatch::IsMlScoringEligible() const {
  if (!IsMlSignalLoggingEligible() || !scoring_signals.has_value()) {
    return false;
  }

  // Do not apply ML scoring to calculator or answer suggestions as the ML model
  // currently doesn't provide accurate scores for suggestions that have a low
  // click-through rate.
  if (type == AutocompleteMatchType::CALCULATOR ||
      answer_type != omnibox::ANSWER_TYPE_UNSPECIFIED) {
    return false;
  }

  // Do not apply ML scoring to stale suggestions sourced from the
  // DocumentProvider cache.
  if (type == AutocompleteMatchType::DOCUMENT_SUGGESTION && relevance == 0) {
    return false;
  }

  const auto& ml_config = OmniboxFieldTrial::GetMLConfig();

  // Search suggestions are conditionally eligible for ML scoring.
  if (AutocompleteMatch::IsSearchType(type)) {
    return ml_config.enable_ml_scoring_for_searches;
  }

  // Verbatim URL suggestions are conditionally eligible for ML scoring.
  // A "verbatim URL" suggestion is any suggestion that is UWYT itself or has
  // been deduped with a UWYT suggestion.
  if (AutocompleteMatch::IsVerbatimUrlSuggestion()) {
    return ml_config.enable_ml_scoring_for_verbatim_urls;
  }

  // Certain suggestion types are manually excluded from ML scoring (since
  // applying ML scoring to these suggestions currently results in suboptimal
  // behavior).
  if (type == AutocompleteMatchType::NAVSUGGEST ||
      type == AutocompleteMatchType::NAVSUGGEST_PERSONALIZED ||
      type == AutocompleteMatchType::TILE_NAVSUGGEST) {
    return false;
  }

  // If any of the duplicates under this match are ineligible for ML scoring,
  // then the top-level match (this) is also considered ineligible for ML
  // scoring.
  if (std::ranges::any_of(duplicate_matches, [](const auto& match) {
        return !match.IsMlScoringEligible();
      })) {
    return false;
  }

  return true;
}

bool AutocompleteMatch::IsTrendSuggestion() const {
  return subtypes.contains(/*omnibox::SUBTYPE_TRENDS=*/143);
}

bool AutocompleteMatch::IsIphSuggestion() const {
  return iph_type != IphType::kNone;
}

bool AutocompleteMatch::HasAction(OmniboxActionId action_id) const {
  return std::ranges::any_of(actions, [&](const auto& action) {
    return action && action->ActionId() == action_id;
  });
}

bool AutocompleteMatch::IsContextualSearchSuggestion() const {
  return subtypes.contains(omnibox::SuggestSubtype::SUBTYPE_CONTEXTUAL_SEARCH);
}

bool AutocompleteMatch::IsStaticContextualSearchSuggestion() const {
  return subtypes.contains(
      omnibox::SuggestSubtype::SUBTYPE_CONTEXTUAL_SEARCH_STATIC);
}

bool AutocompleteMatch::IsThreadsHistorySuggestion() const {
  return subtypes.contains(
      omnibox::SuggestSubtype::SUBTYPE_AI_MODE_MORE_THREADS_ENTRYPOINT);
}

bool AutocompleteMatch::IsToolbelt() const {
  return type == AutocompleteMatchType::NULL_RESULT_MESSAGE &&
         !actions.empty() && omnibox_feature_configs::Toolbelt::Get().enabled;
}

bool AutocompleteMatch::HasLensSearchAction() const {
  return (
      suggest_template.has_value() &&
      std::ranges::any_of(
          suggest_template->action_suggestions(), [](const auto& action) {
            return action.action_type() ==
                   omnibox::SuggestTemplateInfo::TemplateAction::ActionType::
                       SuggestTemplateInfo_TemplateAction_ActionType_CHROME_LENS;
          }));
}

bool AutocompleteMatch::IsSearchAimSuggestion() const {
  if (suggest_template.has_value()) {
    if (auto it = suggest_template->default_search_parameters().find("udm");
        it != suggest_template->default_search_parameters().end()) {
      return it->second == "50";
    }
  }
  return false;
}

OmniboxSuggestionKind AutocompleteMatch::GetOmniboxSuggestionKind() const {
  if (IsSearchAimSuggestion()) {
    return OmniboxSuggestionKind::kConversation;
  }
  if (IsSearchType(type)) {
    return OmniboxSuggestionKind::kSearch;
  }
  return OmniboxSuggestionKind::kNavigation;
}

void AutocompleteMatch::FilterOmniboxActions(
    const std::vector<OmniboxActionId>& allowed_action_ids) {
  // Short circuit if there's nothing to do.
  if (actions.empty()) {
    return;
  }

  // Find the type of object we can keep.
  auto allowed_action_id_iter =
      std::ranges::find_if(allowed_action_ids, [this](auto allowed_action_id) {
        return GetActionWhere([allowed_action_id](const auto& action) {
                 return action->ActionId() == allowed_action_id;
               }) != nullptr;
      });

  OmniboxActionId allowed_action_id =
      allowed_action_id_iter != allowed_action_ids.end()
          ? *allowed_action_id_iter
          : OmniboxActionId::LAST;

  std::erase_if(actions, [&](const auto& action) {
    return action->ActionId() != allowed_action_id;
  });
}

void AutocompleteMatch::FilterAndSortActionsInSuggest() {
  if (actions.empty()) {
    return;
  }

  std::stable_sort(
      actions.begin(), actions.end(), [](const auto& a1, const auto& a2) {
        return GetOrderForActionType(a1) < GetOrderForActionType(a2);
      });
}

bool AutocompleteMatch::IsTrivialAutocompletion() const {
  return type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
         type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
         type == AutocompleteMatchType::SEARCH_OTHER_ENGINE;
}

bool AutocompleteMatch::SupportsDeletion() const {
  return deletable || std::ranges::any_of(duplicate_matches, [](const auto& m) {
           return m.deletable;
         });
}

AutocompleteMatch
AutocompleteMatch::GetMatchWithContentsAndDescriptionPossiblySwapped() const {
  AutocompleteMatch copy(*this);
  if (copy.swap_contents_and_description) {
    std::swap(copy.contents, copy.description);
    std::swap(copy.contents_class, copy.description_class);
    copy.description_for_shortcuts.clear();
    copy.description_class_for_shortcuts.clear();
    // Clear bit to prevent accidentally performing the swap again.
    copy.swap_contents_and_description = false;
  }
  return copy;
}

void AutocompleteMatch::SetAllowedToBeDefault(const AutocompleteInput& input) {
  if (input.text().starts_with('@') && type != Type::SEARCH_WHAT_YOU_TYPED) {
    // @ inputs are very special. The only kind of match that can be default is
    // a search-what-you-typed sentinel suggestion, so as to not distract from
    // the starter pack suggestions. Note: There may be some edge cases to
    // consider if more providers are updated to use of this method; then we
    // may want to avoid applying this rule when in keyword mode.
    allowed_to_be_default_match = false;
  } else if (inline_autocompletion.empty()) {
    allowed_to_be_default_match = true;
  } else if (input.prevent_inline_autocomplete()) {
    allowed_to_be_default_match = false;
  } else if (input.text().empty() ||
             !base::IsUnicodeWhitespace(input.text().back())) {
    allowed_to_be_default_match = true;
  } else {
    // If we've reached here, the input ends in trailing whitespace. If the
    // trailing whitespace prefixes |inline_autocompletion|, then allow the
    // match to be default and remove the whitespace from
    // |inline_autocompletion|.
    size_t last_non_whitespace_pos =
        input.text().find_last_not_of(base::kWhitespaceUTF16);
    DCHECK_NE(last_non_whitespace_pos, std::string::npos);
    std::u16string_view whitespace_suffix =
        std::u16string_view(input.text()).substr(last_non_whitespace_pos + 1);
    if (base::StartsWith(inline_autocompletion, whitespace_suffix,
                         base::CompareCase::SENSITIVE)) {
      inline_autocompletion.erase(0, whitespace_suffix.size());
      allowed_to_be_default_match = true;
    } else {
      allowed_to_be_default_match = false;
    }
  }
}

size_t AutocompleteMatch::EstimateMemoryUsage() const {
  size_t res = 0;

  res += base::trace_event::EstimateMemoryUsage(fill_into_edit);
  res += base::trace_event::EstimateMemoryUsage(additional_text);
  res += base::trace_event::EstimateMemoryUsage(inline_autocompletion);
  res += base::trace_event::EstimateMemoryUsage(destination_url);
  res += base::trace_event::EstimateMemoryUsage(stripped_destination_url);
  res += base::trace_event::EstimateMemoryUsage(extra_headers);
  res += base::trace_event::EstimateMemoryUsage(image_dominant_color);
  res += base::trace_event::EstimateMemoryUsage(image_url);
  res += base::trace_event::EstimateMemoryUsage(icon_url);
  res += base::trace_event::EstimateMemoryUsage(entity_id);
  res += base::trace_event::EstimateMemoryUsage(website_uri);
  res += base::trace_event::EstimateMemoryUsage(tail_suggest_common_prefix);
  res += base::trace_event::EstimateMemoryUsage(contents);
  res += base::trace_event::EstimateMemoryUsage(contents_class);
  res += base::trace_event::EstimateMemoryUsage(description);
  res += base::trace_event::EstimateMemoryUsage(description_class);
  res += base::trace_event::EstimateMemoryUsage(description_for_shortcuts);
  res +=
      base::trace_event::EstimateMemoryUsage(description_class_for_shortcuts);
  res += base::trace_event::EstimateMemoryUsage(associated_keyword);
  res += base::trace_event::EstimateMemoryUsage(keyword);
  res += base::trace_event::EstimateMemoryUsage(search_terms_args);
  res += base::trace_event::EstimateMemoryUsage(post_content);
  res += base::trace_event::EstimateMemoryUsage(additional_info);
  res += base::trace_event::EstimateMemoryUsage(duplicate_matches);

  return res;
}

void AutocompleteMatch::UpgradeMatchWithPropertiesFrom(
    AutocompleteMatch& duplicate_match) {
  // TODO(manukh): There's some duplicate logic between `BetterDuplicate()` and
  //   `UpgradeMatchWithPropertiesFrom()`. This is unavoidable due to having to
  //   taking different fields from different duplicates, rather having 1 match
  //   that's absolutely overrides all other matches. Perhaps we can avoid this
  //   if we join the 2 functions.

  // For Entity Matches, absorb the duplicate match's |allowed_to_be_default|
  // and |inline_autocompletion| properties.
  if (type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY &&
      fill_into_edit == duplicate_match.fill_into_edit &&
      duplicate_match.allowed_to_be_default_match) {
    allowed_to_be_default_match = true;
    if (inline_autocompletion.empty()) {
      inline_autocompletion = duplicate_match.inline_autocompletion;
    }
  }

  // For Search Suggest and Search What-You-Typed matches, absorb any
  // Search History type.
  if ((type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
       type == AutocompleteMatchType::SEARCH_SUGGEST) &&
      fill_into_edit == duplicate_match.fill_into_edit &&
      IsSearchHistoryType(duplicate_match.type)) {
    type = duplicate_match.type;
    suggest_type = duplicate_match.suggest_type;
  }

  // And always absorb the higher relevance score of duplicates.
  if (duplicate_match.relevance > relevance) {
    RecordAdditionalInfo(kACMatchPropertyScoreBoostedFrom, relevance);
    relevance = duplicate_match.relevance;
    shortcut_boosted |= duplicate_match.shortcut_boosted;
  }

  from_previous = from_previous && duplicate_match.from_previous;

  // Absorb the `actions` and `takeover_action` so they won't be buried.
  // Don't absorb answer actions; they should always be created fresh.
  if (actions.empty() && !duplicate_match.actions.empty() &&
      IsActionCompatible()) {
    actions = std::move(duplicate_match.actions);
    takeover_action = std::move(duplicate_match.takeover_action);
  }

  // Prefer fresh suggestion text over potentially stale shortcut text for
  // bookmark paths and document metadata. Don't edit the omnibox text (i.e.
  // `fill_into_edit`, `inline_autocompletion`, and `additional_text`) as the
  // duplicate may not be `allowed_to_be_default_match`.
  if (GetDeduplicationProviderPreferenceScore(duplicate_match.provider) >
      GetDeduplicationProviderPreferenceScore(provider)) {
    contents = duplicate_match.contents;
    contents_class = duplicate_match.contents_class;
    description = duplicate_match.description;
    description_class = duplicate_match.description_class;
    description_for_shortcuts = duplicate_match.description_for_shortcuts;
    description_class_for_shortcuts =
        duplicate_match.description_class_for_shortcuts;
    swap_contents_and_description =
        duplicate_match.swap_contents_and_description;

    // Image data should stay in sync with the suggestion text.
    image_dominant_color = duplicate_match.image_dominant_color;
    image_url = duplicate_match.image_url;
    icon_url = duplicate_match.icon_url;

    // Prefer to keep the original `type` for more helpful metric logging.
    // However, searches and non-searches have different ranking & text display
    // (see `swap_contents_and_description` and `UpdateKeywordDescriptions()`).
    // Using text from a search/URL match but displaying the match like a
    // URL/search could look very broken.
    if (IsSearchType(type) != IsSearchType(duplicate_match.type))
      type = duplicate_match.type;
  }

  // Copy `rich_autocompletion_triggered` for counterfactual logging.
  if (rich_autocompletion_triggered == RichAutocompletionType::kNone) {
    rich_autocompletion_triggered =
        duplicate_match.rich_autocompletion_triggered;
  }

  // Merge ML scoring signals from duplicate match when appropriate.
  if (OmniboxFieldTrial::IsPopulatingUrlScoringSignalsEnabled() &&
      IsMlSignalLoggingEligible()) {
    MergeScoringSignals(duplicate_match);
  }
}

void AutocompleteMatch::MergeScoringSignals(const AutocompleteMatch& other) {
  // 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 (!other.scoring_signals.has_value()) {
    return;
  }

  // Records the ACMatch type of the duplicate match when two or more matches
  // with different ml scoring signals are merged.
  const char kACMatchPropertyScoringSignalsMerged[] = "Scoring signals merged";
  RecordAdditionalInfo(
      kACMatchPropertyScoringSignalsMerged,
      base::StrCat(
          {GetAdditionalInfoForDebugging(kACMatchPropertyScoringSignalsMerged),
           AutocompleteMatchType::ToString(other.type), ", ",
           other.GetAdditionalInfoForDebugging(
               kACMatchPropertyScoringSignalsMerged)}));

  if (!scoring_signals.has_value()) {
    scoring_signals = std::make_optional<ScoringSignals>();
  }

  // Take the maximum.
  if (other.scoring_signals->has_typed_count()) {
    scoring_signals->set_typed_count(std::max(
        scoring_signals->typed_count(), other.scoring_signals->typed_count()));
  }

  // Take the maximum.
  if (other.scoring_signals->has_visit_count()) {
    scoring_signals->set_visit_count(std::max(
        scoring_signals->visit_count(), other.scoring_signals->visit_count()));
  }

  // Take the minimum.
  if (scoring_signals->has_elapsed_time_last_visit_secs() &&
      other.scoring_signals->has_elapsed_time_last_visit_secs()) {
    scoring_signals->set_elapsed_time_last_visit_secs(
        std::min(scoring_signals->elapsed_time_last_visit_secs(),
                 other.scoring_signals->elapsed_time_last_visit_secs()));
  } else if (other.scoring_signals->has_elapsed_time_last_visit_secs()) {
    scoring_signals->set_elapsed_time_last_visit_secs(
        other.scoring_signals->elapsed_time_last_visit_secs());
  }

  // Take the maximum.
  if (other.scoring_signals->has_shortcut_visit_count()) {
    scoring_signals->set_shortcut_visit_count(
        std::max(scoring_signals->shortcut_visit_count(),
                 other.scoring_signals->shortcut_visit_count()));
  }

  // Take the minimum.
  if (scoring_signals->has_shortest_shortcut_len() &&
      other.scoring_signals->has_shortest_shortcut_len()) {
    scoring_signals->set_shortest_shortcut_len(
        std::min(scoring_signals->shortest_shortcut_len(),
                 other.scoring_signals->shortest_shortcut_len()));
  } else if (other.scoring_signals->has_shortest_shortcut_len()) {
    scoring_signals->set_shortest_shortcut_len(
        other.scoring_signals->shortest_shortcut_len());
  }

  // Take the minimum.
  if (scoring_signals->has_elapsed_time_last_shortcut_visit_sec() &&
      other.scoring_signals->has_elapsed_time_last_shortcut_visit_sec()) {
    scoring_signals->set_elapsed_time_last_shortcut_visit_sec(std::min(
        scoring_signals->elapsed_time_last_shortcut_visit_sec(),
        other.scoring_signals->elapsed_time_last_shortcut_visit_sec()));
  } else if (other.scoring_signals
                 ->has_elapsed_time_last_shortcut_visit_sec()) {
    scoring_signals->set_elapsed_time_last_shortcut_visit_sec(
        other.scoring_signals->elapsed_time_last_shortcut_visit_sec());
  }

  // Take the OR result.
  if (other.scoring_signals->has_is_host_only()) {
    scoring_signals->set_is_host_only(scoring_signals->is_host_only() ||
                                      other.scoring_signals->is_host_only());
  }

  // Take the maximum.
  if (other.scoring_signals->has_num_bookmarks_of_url()) {
    scoring_signals->set_num_bookmarks_of_url(
        std::max(scoring_signals->num_bookmarks_of_url(),
                 other.scoring_signals->num_bookmarks_of_url()));
  }

  // Take the minimum.
  if (scoring_signals->has_first_bookmark_title_match_position() &&
      other.scoring_signals->has_first_bookmark_title_match_position()) {
    scoring_signals->set_first_bookmark_title_match_position(
        std::min(scoring_signals->first_bookmark_title_match_position(),
                 other.scoring_signals->first_bookmark_title_match_position()));
  } else if (other.scoring_signals->has_first_bookmark_title_match_position()) {
    scoring_signals->set_first_bookmark_title_match_position(
        other.scoring_signals->first_bookmark_title_match_position());
  }

  // Take the maximum.
  if (other.scoring_signals->has_total_bookmark_title_match_length()) {
    scoring_signals->set_total_bookmark_title_match_length(
        std::max(scoring_signals->total_bookmark_title_match_length(),
                 other.scoring_signals->total_bookmark_title_match_length()));
  }

  // Take the maximum.
  if (other.scoring_signals->has_num_input_terms_matched_by_bookmark_title()) {
    scoring_signals->set_num_input_terms_matched_by_bookmark_title(std::max(
        scoring_signals->num_input_terms_matched_by_bookmark_title(),
        other.scoring_signals->num_input_terms_matched_by_bookmark_title()));
  }

  // Take the minimum.
  if (scoring_signals->has_first_url_match_position() &&
      other.scoring_signals->has_first_url_match_position()) {
    scoring_signals->set_first_url_match_position(
        std::min(scoring_signals->first_url_match_position(),
                 other.scoring_signals->first_url_match_position()));
  } else if (other.scoring_signals->has_first_url_match_position()) {
    scoring_signals->set_first_url_match_position(
        other.scoring_signals->first_url_match_position());
  }

  // Take the maximum.
  if (other.scoring_signals->has_total_url_match_length()) {
    scoring_signals->set_total_url_match_length(
        std::max(scoring_signals->total_url_match_length(),
                 other.scoring_signals->total_url_match_length()));
  }

  // Take the OR result.
  if (other.scoring_signals->has_host_match_at_word_boundary()) {
    scoring_signals->set_host_match_at_word_boundary(
        scoring_signals->host_match_at_word_boundary() ||
        other.scoring_signals->host_match_at_word_boundary());
  }

  // Take the maximum.
  if (other.scoring_signals->has_total_host_match_length()) {
    scoring_signals->set_total_host_match_length(
        std::max(scoring_signals->total_host_match_length(),
                 other.scoring_signals->total_host_match_length()));
  }

  // Take the maximum.
  if (other.scoring_signals->has_total_path_match_length()) {
    scoring_signals->set_total_path_match_length(
        std::max(scoring_signals->total_path_match_length(),
                 other.scoring_signals->total_path_match_length()));
  }

  // Take the maximum.
  if (other.scoring_signals->has_total_query_or_ref_match_length()) {
    scoring_signals->set_total_query_or_ref_match_length(
        std::max(scoring_signals->total_query_or_ref_match_length(),
                 other.scoring_signals->total_query_or_ref_match_length()));
  }

  // Take the maximum.
  if (other.scoring_signals->has_total_title_match_length()) {
    scoring_signals->set_total_title_match_length(
        std::max(scoring_signals->total_title_match_length(),
                 other.scoring_signals->total_title_match_length()));
  }

  // Take the OR result.
  if (other.scoring_signals->has_has_non_scheme_www_match()) {
    scoring_signals->set_has_non_scheme_www_match(
        scoring_signals->has_non_scheme_www_match() ||
        other.scoring_signals->has_non_scheme_www_match());
  }

  // Take the maximum.
  if (other.scoring_signals->has_num_input_terms_matched_by_title()) {
    scoring_signals->set_num_input_terms_matched_by_title(
        std::max(scoring_signals->num_input_terms_matched_by_title(),
                 other.scoring_signals->num_input_terms_matched_by_title()));
  }

  // Take the maximum.
  if (other.scoring_signals->has_num_input_terms_matched_by_url()) {
    scoring_signals->set_num_input_terms_matched_by_url(
        std::max(scoring_signals->num_input_terms_matched_by_url(),
                 other.scoring_signals->num_input_terms_matched_by_url()));
  }

  // Take the minimum.
  if (scoring_signals->has_length_of_url() &&
      other.scoring_signals->has_length_of_url()) {
    scoring_signals->set_length_of_url(
        std::min(scoring_signals->length_of_url(),
                 other.scoring_signals->length_of_url()));
  } else if (other.scoring_signals->has_length_of_url()) {
    scoring_signals->set_length_of_url(other.scoring_signals->length_of_url());
  }

  // Take the maximum.
  if (other.scoring_signals->has_site_engagement()) {
    scoring_signals->set_site_engagement(
        std::max(scoring_signals->site_engagement(),
                 other.scoring_signals->site_engagement()));
  }

  // Take the OR result.
  if (other.scoring_signals->has_allowed_to_be_default_match()) {
    scoring_signals->set_allowed_to_be_default_match(
        scoring_signals->allowed_to_be_default_match() ||
        other.scoring_signals->allowed_to_be_default_match());
  }

  // Take the maximum.
  if (other.scoring_signals->has_search_suggest_relevance()) {
    scoring_signals->set_search_suggest_relevance(
        std::max(scoring_signals->search_suggest_relevance(),
                 other.scoring_signals->search_suggest_relevance()));
  }

  // Take the OR result.
  if (other.scoring_signals->has_is_search_suggest_entity()) {
    scoring_signals->set_is_search_suggest_entity(
        scoring_signals->is_search_suggest_entity() ||
        other.scoring_signals->is_search_suggest_entity());
  }

  // Take the OR result.
  if (other.scoring_signals->has_is_verbatim()) {
    scoring_signals->set_is_verbatim(scoring_signals->is_verbatim() ||
                                     other.scoring_signals->is_verbatim());
  }

  // Take the OR result.
  if (other.scoring_signals->has_is_navsuggest()) {
    scoring_signals->set_is_navsuggest(scoring_signals->is_navsuggest() ||
                                       other.scoring_signals->is_navsuggest());
  }

  // Take the OR result.
  if (other.scoring_signals->has_is_search_suggest_tail()) {
    scoring_signals->set_is_search_suggest_tail(
        scoring_signals->is_search_suggest_tail() ||
        other.scoring_signals->is_search_suggest_tail());
  }

  // Take the OR result.
  if (other.scoring_signals->has_is_answer_suggest()) {
    scoring_signals->set_is_answer_suggest(
        scoring_signals->is_answer_suggest() ||
        other.scoring_signals->is_answer_suggest());
  }

  // Take the OR result.
  if (other.scoring_signals->has_is_calculator_suggest()) {
    scoring_signals->set_is_calculator_suggest(
        scoring_signals->is_calculator_suggest() ||
        other.scoring_signals->is_calculator_suggest());
  }
}

bool AutocompleteMatch::TryRichAutocompletion(
    const AutocompleteInput& input,
    const std::u16string& primary_text,
    const std::u16string& secondary_text,
    const std::u16string& shortcut_text) {
  const auto& params = RichAutocompletionParams::GetParams();

  if (!params.enabled)
    return false;

  if (input.prevent_inline_autocomplete())
    return false;

  // Lowercase the strings for case-insensitive comparisons.
  const std::u16string primary_text_lower{base::i18n::ToLower(primary_text)};
  const std::u16string input_text_lower{base::i18n::ToLower(input.text())};

  // Try matching the prefix of |primary_text|.
  if (base::StartsWith(primary_text_lower, input_text_lower,
                       base::CompareCase::SENSITIVE)) {
    // This case intentionally doesn't set |rich_autocompletion_triggered| since
    // presumably non-rich autocompletion should also be able to handle this
    // case.
    inline_autocompletion = primary_text.substr(input_text_lower.length());
    allowed_to_be_default_match = true;
    RecordAdditionalInfo("autocompletion", "primary & prefix");
    return true;
  }

  // Only shortcuts are allowed to autocomplete `secondary_text` and
  // `shortcut_text`.
  if (shortcut_text.empty()) {
    return false;
  }

  const std::u16string secondary_text_lower{
      base::i18n::ToLower(secondary_text)};

  // Try matching the prefix of `secondary_text`.
  if (input.text().size() >= params.autocomplete_titles_min_char &&
      base::StartsWith(secondary_text_lower, input_text_lower,
                       base::CompareCase::SENSITIVE)) {
    rich_autocompletion_triggered = RichAutocompletionType::kTitlePrefix;
    additional_text = primary_text;
    inline_autocompletion = secondary_text.substr(input_text_lower.length());
    allowed_to_be_default_match = true;
    RecordAdditionalInfo("autocompletion", "secondary & prefix");
    return true;
  }

  // Try matching the prefix of `shortcut_text`.
  if (input.text().size() >= params.autocomplete_shortcut_text_min_char &&
      base::StartsWith(base::i18n::ToLower(shortcut_text), input_text_lower,
                       base::CompareCase::SENSITIVE)) {
    rich_autocompletion_triggered = RichAutocompletionType::kShortcutTextPrefix;
    additional_text = primary_text;
    inline_autocompletion = shortcut_text.substr(input_text_lower.length());
    allowed_to_be_default_match = true;
    RecordAdditionalInfo("autocompletion", "shortcut text & prefix");
    return true;
  }

  // Don't try matching a non-prefix of `shortcut_text`. Shortcut matches are
  // intended for repeated inputs, i.e. inputs that are prefixes of previous
  // inputs.

  return false;
}

void AutocompleteMatch::WriteIntoTrace(perfetto::TracedValue context) const {
  perfetto::TracedDictionary dict = std::move(context).WriteDictionary();
  dict.Add("fill_into_edit", fill_into_edit);
  dict.Add("additional_text", additional_text);
  dict.Add("destination_url", destination_url);
  dict.Add("keyword", keyword);
}

OmniboxAction* AutocompleteMatch::GetActionAt(size_t index) const {
  return index >= actions.size() ? nullptr : actions[index].get();
}

bool AutocompleteMatch::HasTakeoverAction(OmniboxActionId id) const {
  return takeover_action && takeover_action->ActionId() == id;
}

AutocompleteMatch AutocompleteMatch::CreateActionMatch(
    size_t action_index) const {
  CHECK_LT(action_index, actions.size());
  CHECK_EQ(actions[action_index]->ActionId(), OmniboxActionId::PEDAL);

  AutocompleteMatch action_match(provider, relevance, false,
                                 AutocompleteMatchType::PEDAL);
  action_match.takeover_action = actions[action_index];
  action_match.transition = ui::PAGE_TRANSITION_GENERATED;
  action_match.suggest_type = omnibox::SuggestType::TYPE_NATIVE_CHROME;
  action_match.suggestion_group_id = suggestion_group_id;

  // Use the pedal text as primary match `contents`.
  action_match.contents = action_match.takeover_action->GetLabelStrings().hint;
  action_match.fill_into_edit = action_match.contents;
  if (action_match.contents.empty()) {
    action_match.contents_class.clear();
  } else {
    action_match.contents_class = {{0, ACMatchClassification::NONE}};
  }

  return action_match;
}

#if DCHECK_IS_ON()
void AutocompleteMatch::Validate() const {
  std::string provider_name = provider ? provider->GetName() : "None";
  ValidateClassifications(contents, contents_class, provider_name);
  ValidateClassifications(description, description_class, provider_name);
  ValidateClassifications(description_for_shortcuts,
                          description_class_for_shortcuts, provider_name);
}
#endif  // DCHECK_IS_ON()

// static
void AutocompleteMatch::ValidateClassifications(
    const std::u16string& text,
    const ACMatchClassifications& classifications,
    const std::string& provider_name) {
#if DCHECK_IS_ON()
  std::string debug_string = base::StrCat(
      {" text: ", base::UTF16ToUTF8(text), ", provider: ", provider_name,
       ", classifications (offset, style): "});
  for (const auto& classification : classifications) {
    base::StrAppend(&debug_string,
                    {" (", base::NumberToString(classification.offset), ", ",
                     base::NumberToString(classification.style), ")"});
  }

  if (text.empty()) {
    DCHECK(classifications.empty()) << debug_string;
    return;
  }

  // The classifications should always cover the whole string.
  DCHECK(!classifications.empty()) << " No classification;" << debug_string;
  DCHECK_EQ(0U, classifications[0].offset)
      << " Classification misses beginning;" << debug_string;
  if (classifications.size() == 1)
    return;

  // The classifications should always be sorted.
  size_t last_offset = classifications[0].offset;
  for (auto i(classifications.begin() + 1); i != classifications.end(); ++i) {
    DCHECK_GT(i->offset, last_offset)
        << " Unsorted classification;" << debug_string;
    DCHECK_LT(i->offset, text.length())
        << " Out of bounds classification;" << debug_string;
    last_offset = i->offset;
  }
#endif  // DCHECK_IS_ON()
}
