// Copyright 2013 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/autofill/core/browser/autofill_field.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <array>
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>

#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/not_fatal_until.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/types/optional_ref.h"
#include "base/types/pass_key.h"
#include "components/autofill/core/browser/autofill_format_string.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/field_type_utils.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/heuristic_source.h"
#include "components/autofill/core/browser/proto/api_v1.pb.h"
#include "components/autofill/core/browser/proto/password_requirements.pb.h"
#include "components/autofill/core/browser/proto/server.pb.h"
#include "components/autofill/core/browser/suggestions/suggestion_util.h"
#include "components/autofill/core/common/autofill_constants.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/dense_set.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/html_field_types.h"
#include "components/autofill/core/common/logging/log_buffer.h"
#include "components/autofill/core/common/signatures.h"
#include "components/autofill/core/common/unique_ids.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"

namespace autofill {

template <>
struct DenseSetTraits<FieldPrediction::Source>
    : EnumDenseSetTraits<FieldPrediction::Source,
                         FieldPrediction::Source_MIN,
                         FieldPrediction::Source_MAX> {};

namespace {

// Defines a precedence in type predictions for `winning_type` over
// `losing_type`.
// Autofill has a default precedence during field type prioritization. This
// struct allows defining edges cases where the default precedence should not
// apply. The primary and secondary types could be a `FieldType`, a
// `FieldTypeSet` or a `FieldTypeGroup`. Additionally, the secondary type could
// be an `HtmlFieldType` (The first type cannot since these types are always
// preferred by default).
struct PredictionPrecedenceException {
  // Returns whether, in `field`, `possibly_winning_type` should be preferred
  // over `possibly_losing_type` according to this exception.
  bool ExceptionApplies(
      const AutofillField& field,
      FieldType possibly_winning_type,
      std::variant<FieldType, HtmlFieldType> possibly_losing_type) const {
    return (!field_condition || field_condition(field)) &&
           IsWinningType(possibly_winning_type) &&
           IsLosingType(possibly_losing_type);
  }

 private:
  bool IsWinningType(FieldType field_type) const {
    DCHECK(winning_type || winning_types || winning_type_group);
    return (winning_type && field_type == winning_type) ||
           (winning_types && winning_types->contains(field_type)) ||
           (winning_type_group &&
            winning_type_group == GroupTypeOfFieldType(field_type));
  }

  bool IsLosingType(std::variant<FieldType, HtmlFieldType> field_type) const {
    DCHECK(losing_type || losing_html_type || losing_types ||
           losing_type_group);
    return std::visit(
        absl::Overload{
            [&](FieldType type) {
              return (losing_type && type == losing_type) ||
                     (losing_types && losing_types->contains(type)) ||
                     (losing_type_group &&
                      losing_type_group == GroupTypeOfFieldType(type));
            },
            [&](HtmlFieldType type) {
              return (losing_html_type && losing_html_type == type) ||
                     (losing_type_group &&
                      losing_type_group ==
                          GroupTypeOfFieldType(
                              HtmlFieldTypeToBestCorrespondingFieldType(type)));
            }},
        field_type);
  }

 public:
  std::optional<FieldType> losing_type;
  std::optional<HtmlFieldType> losing_html_type;
  std::optional<FieldTypeSet> losing_types;
  std::optional<FieldTypeGroup> losing_type_group;
  std::optional<FieldType> winning_type;
  std::optional<FieldTypeSet> winning_types;
  std::optional<FieldTypeGroup> winning_type_group;
  bool (*field_condition)(const AutofillField&) = nullptr;
};

static constexpr auto kPreferredServerTypesOverHtmlTypes =
    std::to_array<PredictionPrecedenceException>({
        // If autocomplete=tel/tel-* and server confirms it really is a phone
        // field, we always use the server prediction as html types are not
        // very reliable.
        {.losing_type_group = FieldTypeGroup::kPhone,
         .winning_type_group = FieldTypeGroup::kPhone},

        // When the server predicts that an address is a street name or a house
        // number, we prioritize this over "address-line[1|2]" autocomplete
        // since those signals are usually stronger for this combination.
        {.losing_html_type = HtmlFieldType::kAddressLine1,
         .winning_types =
             FieldTypeSet{ADDRESS_HOME_STREET_NAME, ADDRESS_HOME_HOUSE_NUMBER}},
        {.losing_html_type = HtmlFieldType::kAddressLine2,
         .winning_types =
             FieldTypeSet{ADDRESS_HOME_STREET_NAME, ADDRESS_HOME_HOUSE_NUMBER}},

        // If the explicit type is cc-exp and the server agrees on a 2 vs 4
        // digit specialization of cc-exp, use that specialization.
        // TODO(crbug.com/40266396) Delete this exception when
        // features::kAutofillEnableExpirationDateImprovements has launched as
        // this should be covered by
        // FormStructureRationalizer::RationalizeAutocompleteAttributes.
        {.losing_html_type = HtmlFieldType::kCreditCardExp,
         .winning_types = FieldTypeSet{CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR,
                                       CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR}},
    });

static constexpr auto kPreferredHeuristicTypesOverHtmlTypes =
    std::to_array<PredictionPrecedenceException>({
        // This list is used for new field types that do not have a clear
        // corresponding HTML type. In these cases, the local heuristics
        // predictions will be used to determine the field overall type.
        {.losing_html_type = HtmlFieldType::kAddressLevel1,
         .winning_types = FieldTypeSet{ADDRESS_HOME_ADMIN_LEVEL2,
                                       ADDRESS_HOME_DEPENDENT_LOCALITY}},
        {.losing_html_type = HtmlFieldType::kAddressLevel2,
         .winning_types = FieldTypeSet{ADDRESS_HOME_ADMIN_LEVEL2,
                                       ADDRESS_HOME_BETWEEN_STREETS,
                                       ADDRESS_HOME_BETWEEN_STREETS_OR_LANDMARK,
                                       ADDRESS_HOME_DEPENDENT_LOCALITY}},
        {.losing_html_type = HtmlFieldType::kAddressLevel3,
         .winning_type = ADDRESS_HOME_DEPENDENT_LOCALITY},
        {.losing_html_type = HtmlFieldType::kAddressLine1,
         .winning_types =
             FieldTypeSet{ADDRESS_HOME_DEPENDENT_LOCALITY,
                          ADDRESS_HOME_STREET_NAME, ADDRESS_HOME_HOUSE_NUMBER}},
        {.losing_html_type = HtmlFieldType::kAddressLine2,
         .winning_types =
             FieldTypeSet{
                 ADDRESS_HOME_APT_NUM, ADDRESS_HOME_BETWEEN_STREETS_OR_LANDMARK,
                 ADDRESS_HOME_DEPENDENT_LOCALITY,
                 ADDRESS_HOME_OVERFLOW_AND_LANDMARK, ADDRESS_HOME_OVERFLOW,
                 ADDRESS_HOME_STREET_NAME, ADDRESS_HOME_HOUSE_NUMBER}},
        {.losing_html_type = HtmlFieldType::kAddressLine3,
         .winning_types =
             FieldTypeSet{ADDRESS_HOME_APT_NUM, ADDRESS_HOME_DEPENDENT_LOCALITY,
                          ADDRESS_HOME_OVERFLOW}},
        {.losing_html_type = HtmlFieldType::kStreetAddress,
         .winning_types =
             FieldTypeSet{ADDRESS_HOME_STREET_NAME, ADDRESS_HOME_HOUSE_NUMBER}},
        {.losing_html_type = HtmlFieldType::kOrganization,
         .winning_type = ADDRESS_HOME_BETWEEN_STREETS_OR_LANDMARK},
        {.losing_html_type = HtmlFieldType::kFamilyName,
         .winning_type = ALTERNATIVE_FAMILY_NAME},
        {.losing_html_type = HtmlFieldType::kGivenName,
         .winning_type = ALTERNATIVE_GIVEN_NAME},
        {.losing_html_type = HtmlFieldType::kName,
         .winning_type = ALTERNATIVE_FULL_NAME},

        // If the explicit type is cc-exp and heuristics agree on a 2 vs 4 digit
        // specialization of cc-exp, use that specialization.
        // TODO(crbug.com/40266396) Delete this exception when
        // features::kAutofillEnableExpirationDateImprovements has launched as
        // this should be covered by
        // FormStructureRationalizer::RationalizeAutocompleteAttributes().
        {.losing_html_type = HtmlFieldType::kCreditCardExp,
         .winning_types = FieldTypeSet{CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR,
                                       CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR}},

        // It is very likely that developers use `autocomplete="country-code"`
        // when they want to represent a phone country code, because of its
        // higher visibility over `tel-country-code`. For that reason we prefer
        // the heuristic type in that case, as it comes with a strong guarantee
        // of a matching grammar. For additional caution, the exception is
        // restricted to select elements for which the heuristics are even more
        // accurate due to added restrictions.
        {.losing_html_type = HtmlFieldType::kCountryCode,
         .winning_type = PHONE_HOME_COUNTRY_CODE,
         .field_condition =
             [](const AutofillField& field) {
               return field.IsSelectElement();
             }},

        // Since the HTML standard does not support loyalty card types, the more
        // accurate heuristic type (that also includes email) is preferred.
        {.losing_html_type = HtmlFieldType::kEmail,
         .winning_type = EMAIL_OR_LOYALTY_MEMBERSHIP_ID},
    });

static constexpr auto kPreferredHeuristicTypesOverServerTypes = std::to_array<
    PredictionPrecedenceException>({
    // The list is used for new field types that the server may have learned
    // incorrectly. In these cases, the local heuristics predictions will be
    // used to determine the field type.
    {.losing_type = ADDRESS_HOME_CITY,
     .winning_types = FieldTypeSet{ADDRESS_HOME_ADMIN_LEVEL2,
                                   ADDRESS_HOME_DEPENDENT_LOCALITY}},
    {.losing_type = ADDRESS_HOME_HOUSE_NUMBER,
     .winning_types =
         FieldTypeSet{ADDRESS_HOME_HOUSE_NUMBER_AND_APT, ADDRESS_HOME_APT_NUM}},
    {.losing_type = ADDRESS_HOME_APT_NUM,
     .winning_type = ADDRESS_HOME_HOUSE_NUMBER_AND_APT},
    {.losing_type = ADDRESS_HOME_LINE1,
     .winning_types = FieldTypeSet{ADDRESS_HOME_BETWEEN_STREETS,
                                   ADDRESS_HOME_DEPENDENT_LOCALITY}},
    {.losing_type = ADDRESS_HOME_LINE2,
     .winning_types =
         FieldTypeSet{ADDRESS_HOME_APT_NUM, ADDRESS_HOME_BETWEEN_STREETS,
                      ADDRESS_HOME_DEPENDENT_LOCALITY, ADDRESS_HOME_LANDMARK,
                      ADDRESS_HOME_BETWEEN_STREETS_OR_LANDMARK,
                      ADDRESS_HOME_OVERFLOW_AND_LANDMARK,
                      ADDRESS_HOME_OVERFLOW}},
    {.losing_type = ADDRESS_HOME_LINE3,
     .winning_types =
         FieldTypeSet{ADDRESS_HOME_APT_NUM, ADDRESS_HOME_DEPENDENT_LOCALITY,
                      ADDRESS_HOME_OVERFLOW}},
    {.losing_type = ADDRESS_HOME_STREET_ADDRESS,
     .winning_type = ADDRESS_HOME_BETWEEN_STREETS},
    {.losing_type = ADDRESS_HOME_STATE,
     .winning_type = ADDRESS_HOME_DEPENDENT_LOCALITY},
    // TODO(crbug.com/359768803): Remove overrides for alternative names once
    // the feature is rolled out.
    {.losing_type = NAME_FULL, .winning_type = ALTERNATIVE_FULL_NAME},
    {.losing_type = NAME_FIRST, .winning_type = ALTERNATIVE_GIVEN_NAME},
    {.losing_type = NAME_LAST, .winning_type = ALTERNATIVE_FAMILY_NAME},
    {.losing_type = NAME_LAST_SECOND, .winning_type = ALTERNATIVE_FAMILY_NAME},

    // Sometimes the server and heuristics disagree on whether a name field
    // should be associated with an address or a credit card. There was a
    // decision to prefer the heuristics in these cases, but it looks like it
    // might be better to fix this server-side.
    // See http://crbug.com/429236 for background.
    {.losing_type = CREDIT_CARD_NAME_FULL, .winning_type = NAME_FULL},
    {.losing_type = NAME_FULL, .winning_type = CREDIT_CARD_NAME_FULL},
    {.losing_type = NAME_FIRST, .winning_type = CREDIT_CARD_NAME_FIRST},
    {.losing_type = NAME_LAST, .winning_type = CREDIT_CARD_NAME_LAST},

    // Retain a preference for the CVC heuristic over the server's password
    // predictions (http://crbug.com/469007)
    {.losing_type_group = FieldTypeGroup::kPasswordField,
     .winning_type = CREDIT_CARD_VERIFICATION_CODE},

    // For the following types, the heuristic predictions get precedence over
    // the server predictions.
    {.losing_types = FieldTypeSet::all(),
     .winning_types =
         FieldTypeSet{NAME_LAST_FIRST, NAME_LAST_SECOND,
                      ADDRESS_HOME_STREET_NAME, ADDRESS_HOME_HOUSE_NUMBER,
                      IBAN_VALUE, MERCHANT_PROMO_CODE}},

    // For loyalty card fields the heuristic predictions get precedence over
    // `UNKNOWN_TYPE` server prediction.
    {.losing_type = UNKNOWN_TYPE, .winning_type = LOYALTY_MEMBERSHIP_ID},

    // Since the loyalty card types are rather new and the server needs time to
    // pick it up, the more accurate heuristic type (that also includes email)
    // is preferred.
    {.losing_type = EMAIL_ADDRESS,
     .winning_type = EMAIL_OR_LOYALTY_MEMBERSHIP_ID},
});

bool PreferTypeAccordingToExceptions(
    base::span<const PredictionPrecedenceException> precedence_exceptions,
    const AutofillField& field,
    FieldType field_type_1,
    std::variant<FieldType, HtmlFieldType> field_type_2) {
  return std::ranges::any_of(
      precedence_exceptions,
      [&](const PredictionPrecedenceException& exception) {
        return exception.ExceptionApplies(field, field_type_1, field_type_2);
      });
}

// Returns true, if the prediction is non-experimental and should be used by
// autofill or password manager.
// Note: A `NO_SERVER_DATA` prediction with `SOURCE_UNSPECIFIED` may also be a
// default prediction. We don't need to store it, because its meaning is that
// there is no default prediction.
bool IsDefaultPrediction(const FieldPrediction& prediction) {
  constexpr DenseSet<FieldPrediction::Source> default_sources = {
      FieldPrediction::SOURCE_AUTOFILL_DEFAULT,
      FieldPrediction::SOURCE_PASSWORDS_DEFAULT,
      FieldPrediction::SOURCE_OVERRIDE,
      FieldPrediction::SOURCE_MANUAL_OVERRIDE};
  return default_sources.contains(prediction.source());
}

bool IsAutofillAiPrediction(const FieldPrediction& prediction) {
  switch (prediction.source()) {
    case FieldPrediction::SOURCE_UNSPECIFIED:
    case FieldPrediction::SOURCE_AUTOFILL_DEFAULT:
    case FieldPrediction::SOURCE_PASSWORDS_DEFAULT:
    case FieldPrediction::SOURCE_OVERRIDE:
    case FieldPrediction::SOURCE_FIELD_RANKS:
    case FieldPrediction::SOURCE_MANUAL_OVERRIDE:
    case FieldPrediction::SOURCE_AUTOFILL_COMBINED_TYPES:
      return false;
    case FieldPrediction::SOURCE_AUTOFILL_AI:
    case FieldPrediction::SOURCE_AUTOFILL_AI_CROWDSOURCING:
      return true;
  }
  // This is not using `NOTREACHED()` because the `FieldPrediction` may
  // originate from outside of Chrome and may not have been validated.
  return false;
}

// Returns true if for two consecutive events, the second event may be ignored.
// In that case, if `event1` is at the back of AutofillField::field_log_events_,
// `event2` is not supposed to be added.
bool AreCollapsibleLogEvents(const AutofillField::FieldLogEventType& event1,
                             const AutofillField::FieldLogEventType& event2) {
  return std::visit(
      [](const auto& e1, const auto& e2) {
        if constexpr (std::is_same_v<decltype(e1), decltype(e2)>) {
          return AreCollapsible(e1, e2);
        }
        return false;
      },
      event1, event2);
}

}  // namespace

// LINT.IfChange(PredictionSourceTranslation)

std::string_view AutofillPredictionSourceToStringView(
    AutofillPredictionSource source) {
  switch (source) {
    case AutofillPredictionSource::kHeuristics:
      return "Heuristics";
    case AutofillPredictionSource::kServerCrowdsourcing:
      return "ServerCrowdsourcing";
    case AutofillPredictionSource::kServerOverride:
      return "ServerOverride";
    case AutofillPredictionSource::kAutocomplete:
      return "AutocompleteAttribute";
    case AutofillPredictionSource::kRationalization:
      return "Rationalization";
  }
  NOTREACHED();
}

// LINT.ThenChange(/tools/metrics/histograms/metadata/autofill/histograms.xml:AutofillPredictionSources)

Section Section::FromAutocomplete(Section::Autocomplete autocomplete) {
  Section section;
  if (autocomplete.section.empty() &&
      autocomplete.mode == HtmlFieldMode::kNone) {
    return section;
  }
  section.value_ = std::move(autocomplete);
  return section;
}

Section Section::FromFieldIdentifier(
    const FormFieldData& field,
    base::flat_map<LocalFrameToken, size_t>& frame_token_ids) {
  Section section;
  // Set the section's value based on the field identifiers: the field's name,
  // mapped frame id, renderer id. We do not use LocalFrameTokens but instead
  // map them to consecutive integers using `frame_token_ids`, which uniquely
  // identify a frame within a given FormStructure. Since we do not intend to
  // compare sections from different FormStructures, this is sufficient.
  //
  // We intentionally do not include the LocalFrameToken in the section
  // because frame tokens should not be sent to a renderer.
  //
  // TODO(crbug.com/40200532): Remove special handling of FrameTokens.
  size_t generated_frame_id =
      frame_token_ids.emplace(field.host_frame(), frame_token_ids.size())
          .first->second;
  section.value_ = FieldIdentifier(base::UTF16ToUTF8(field.name()),
                                   generated_frame_id, field.renderer_id());
  return section;
}

Section::Section() = default;

Section::Section(const Section& section) = default;
Section& Section::operator=(const Section& section) = default;

Section::Section(Section&& section) = default;
Section& Section::operator=(Section&& section) = default;

Section::~Section() = default;

Section::operator bool() const {
  return !is_default();
}

bool Section::is_from_autocomplete() const {
  return std::holds_alternative<Autocomplete>(value_);
}

bool Section::is_from_fieldidentifier() const {
  return std::holds_alternative<FieldIdentifier>(value_);
}

bool Section::is_default() const {
  return std::holds_alternative<Default>(value_);
}

std::string Section::ToString() const {
  static constexpr char kDefaultSection[] = "-default";

  std::string section_name;
  if (const Autocomplete* autocomplete = std::get_if<Autocomplete>(&value_)) {
    // To prevent potential section name collisions, append `kDefaultSection`
    // suffix to fields without a `HtmlFieldMode`. Without this, 'autocomplete'
    // attribute values "section--shipping street-address" and "shipping
    // street-address" would have the same prefix.
    section_name = autocomplete->section +
                   (autocomplete->mode != HtmlFieldMode::kNone
                        ? "-" + HtmlFieldModeToString(autocomplete->mode)
                        : kDefaultSection);
  } else if (const FieldIdentifier* f = std::get_if<FieldIdentifier>(&value_)) {
    FieldIdentifier field_identifier = *f;
    section_name = base::StrCat(
        {field_identifier.field_name, "_",
         base::NumberToString(field_identifier.local_frame_id), "_",
         base::NumberToString(field_identifier.field_renderer_id.value())});
  }

  return section_name.empty() ? kDefaultSection : section_name;
}

LogBuffer& operator<<(LogBuffer& buffer, const Section& section) {
  return buffer << section.ToString();
}

std::ostream& operator<<(std::ostream& os, const Section& section) {
  return os << section.ToString();
}

AutofillField::AutofillField() {
  local_type_predictions_.fill(NO_SERVER_DATA);
}

AutofillField::AutofillField(FieldSignature field_signature) : AutofillField() {
  field_signature_ = field_signature;
}

AutofillField::AutofillField(const FormFieldData& field) {
  UpdateFieldData(field);
  initial_value_ = value();
  local_type_predictions_.fill(NO_SERVER_DATA);
}

AutofillField::AutofillField(AutofillField&&) = default;
AutofillField::AutofillField(const AutofillField&) = default;

AutofillField& AutofillField::operator=(AutofillField&&) = default;
AutofillField& AutofillField::operator=(const AutofillField&) = default;

AutofillField::~AutofillField() = default;

// static
std::unique_ptr<AutofillField> AutofillField::Clone(
    const AutofillField& other,
    AutofillFieldCopyKey pass_key) {
  return base::WrapUnique(new AutofillField(other));
}

std::unique_ptr<AutofillField> AutofillField::CreateForPasswordManagerUpload(
    FieldSignature field_signature) {
  std::unique_ptr<AutofillField> field;
  field.reset(new AutofillField(field_signature));
  return field;
}

FieldType AutofillField::heuristic_type() const {
  return heuristic_type(GetActiveHeuristicSource());
}

FieldType AutofillField::heuristic_type(HeuristicSource s) const {
  // Special handling for ML model predictions.
  if (s == HeuristicSource::kAutofillMachineLearning) {
    FieldType regex_type =
        local_type_predictions_[static_cast<size_t>(HeuristicSource::kRegexes)];
    if (regex_type == FieldType::NO_SERVER_DATA) {
      regex_type = FieldType::UNKNOWN_TYPE;
    }
    FieldType model_type = local_type_predictions_[static_cast<size_t>(
        HeuristicSource::kAutofillMachineLearning)];
    // We fall back to regex heuristics in the following cases:
    // - The regex heuristics detected a type that the model does not support
    //   (e.g. IBAN).
    // - The model returned NO_SERVER_DATA, indicating that execution failed
    //   or that a confidence threshold was not reached.
    bool model_supports_regex_type =
        ml_supported_types_ && ml_supported_types_->contains(regex_type);
    if (!model_supports_regex_type || model_type == FieldType::NO_SERVER_DATA) {
      return regex_type;
    }
    return model_type;
  }

  FieldType type = local_type_predictions_[static_cast<size_t>(s)];
  // Guaranteed by construction of `local_type_predictions_`.
  DCHECK(ToSafeFieldType(type).has_value());
  // `NO_SERVER_DATA` would mean that there is no heuristic type. Client code
  // presumes there is a prediction, therefore we coalesce to `UNKNOWN_TYPE`.
  // Shadow predictions however are not used and we care whether the type is
  // `UNKNOWN_TYPE` or whether we never ran the heuristics.
  return type != NO_SERVER_DATA || s != GetActiveHeuristicSource()
             ? type
             : UNKNOWN_TYPE;
}

FieldType AutofillField::server_type() const {
  return server_predictions_.empty()
             ? NO_SERVER_DATA
             : ToSafeFieldType(server_predictions_[0].type())
                   .value_or(NO_SERVER_DATA);
}

void AutofillField::set_heuristic_type(HeuristicSource s, FieldType type) {
  CHECK(ToSafeFieldType(type).has_value(), base::NotFatalUntil::M142);
  local_type_predictions_[static_cast<size_t>(s)] = type;
  if (s == GetActiveHeuristicSource()) {
    overall_type_ = std::nullopt;
  }
}

void AutofillField::set_server_predictions(
    std::vector<FieldPrediction> predictions) {
  overall_type_ = std::nullopt;
  server_predictions_.clear();

  for (auto& prediction : predictions) {
    MaybeAddServerPrediction(std::move(prediction));
  }

  if (server_predictions_.empty()) {
    // Equivalent to a `NO_SERVER_DATA` prediction from `SOURCE_UNSPECIFIED`.
    server_predictions_.emplace_back();
  }
}

void AutofillField::MaybeAddServerPrediction(FieldPrediction prediction) {
  overall_type_ = std::nullopt;
  if (server_predictions_.size() == 1 &&
      server_predictions_[0].type() == NO_SERVER_DATA &&
      server_predictions_[0].source() == FieldPrediction::SOURCE_UNSPECIFIED) {
    // If the only existing "server prediction" is an empty one, remove it.
    server_predictions_.clear();
  }

  const FieldType field_type =
      ToSafeFieldType(prediction.type()).value_or(NO_SERVER_DATA);
  prediction.set_type(field_type);

  if (!prediction.has_source()) {
    // TODO(crbug.com/40243028): captured tests store old autofill api
    // response recordings without `source` field. We need to maintain the old
    // behavior until these recordings will be migrated.
    server_predictions_.push_back(std::move(prediction));
    return;
  }

  if (prediction.source() == FieldPrediction::SOURCE_UNSPECIFIED) {
    // A prediction with `SOURCE_UNSPECIFIED` is one of two things:
    //   1. No prediction for default, a.k.a. `NO_SERVER_DATA`. The absence
    //      of a prediction may not be creditable to a particular prediction
    //      source.
    //   2. An experiment that is missing from the `PredictionSource` enum.
    //      Protobuf corrects unknown values to 0 when parsing.
    // Neither case is actionable.
    return;
  }

  if (IsDefaultPrediction(prediction)) {
    server_predictions_.push_back(std::move(prediction));
  } else if (IsAutofillAiPrediction(prediction)) {
    if (base::FeatureList::IsEnabled(features::kAutofillAiWithDataSchema)) {
      server_predictions_.push_back(std::move(prediction));
    }
  }
}

void AutofillField::SetHtmlType(HtmlFieldType type, HtmlFieldMode mode) {
  html_type_ = type;
  html_mode_ = mode;
  overall_type_ = std::nullopt;
}

void AutofillField::SetTypeTo(const AutofillType& type,
                              std::optional<AutofillPredictionSource> source) {
  DCHECK(!type.GetTypes().empty());
  overall_type_ = {type, source};
}

AutofillType AutofillField::ComputedType() const {
  return GetComputedPredictionResult().type;
}

AutofillType AutofillField::Type() const {
  return GetOverallPredictionResult().type;
}

std::optional<AutofillPredictionSource> AutofillField::PredictionSource()
    const {
  return GetOverallPredictionResult().source;
}

AutofillType AutofillField::MakeAutofillType(FieldType primary_field_type,
                                             bool is_country_code) const {
  // Indicates whether `ft` may be part of the union type.
  auto is_union_type_candidate = [](FieldType ft) {
    return GroupTypeOfFieldType(ft) == FieldTypeGroup::kAutofillAi &&
           base::FeatureList::IsEnabled(features::kAutofillAiWithDataSchema);
  };

  // Returns the union of
  // - `primary_field_type` and
  // - the types of the `predictions` that satisfy is_union_type_candidate().
  auto get_filtered_types = [&](base::span<const FieldPrediction> predictions) {
    FieldTypeSet field_types = {primary_field_type};
    for (const auto& prediction : predictions) {
      const std::optional<FieldType> ft = ToSafeFieldType(prediction.type());
      if (ft && is_union_type_candidate(*ft)) {
        field_types.insert(*ft);
      }
    }
    return field_types;
  };

  // Looks for the longest prefix of `server_predictions_` whose filtered
  // FieldTypes satisfy the AutofillType constraints.
  FieldTypeSet field_types;
  size_t prefix_length = server_predictions_.size();
  do {
    field_types = get_filtered_types(
        base::span(server_predictions_).first(prefix_length));
  } while (!AutofillType::TestConstraints(field_types) && prefix_length-- > 0);
  DCHECK(field_types.contains(primary_field_type));
  return AutofillType(field_types, is_country_code);
}

AutofillField::PredictionResult AutofillField::GetOverallPredictionResult()
    const {
  // Server overrides are granted precedence unconditionally.
  if (!server_predictions_.empty() && server_predictions_[0].override() &&
      server_type() != NO_SERVER_DATA) {
    return {MakeAutofillType(server_type()),
            AutofillPredictionSource::kServerOverride};
  }
  if (!overall_type_) {
    overall_type_ = GetComputedPredictionResult();
  }
  return *overall_type_;
}

AutofillField::PredictionResult AutofillField::GetComputedPredictionResult()
    const {
  // Some of these (in particular, heuristic_type()) are slow to compute, so
  // cache them in local variables.
  const HtmlFieldType html_type_local = html_type();
  const FieldType server_type_local = server_type();
  const FieldType heuristic_type_local = heuristic_type();
  const FieldType password_ml_classification_type_local =
      heuristic_type(HeuristicSource::kPasswordManagerMachineLearning);

  // #### Handle HTML types.

  // In general, HTML types have precedence over server and heuristic types,
  // except for the cases listed in `kPreferredServerTypesOverHtmlTypes` and
  // `kPreferredHeuristicsTypesOverHtmlTypes`.
  if (html_type_local != HtmlFieldType::kUnspecified &&
      html_type_local != HtmlFieldType::kUnrecognized) {
    if (PreferTypeAccordingToExceptions(kPreferredServerTypesOverHtmlTypes,
                                        *this, server_type_local,
                                        html_type_local)) {
      return {MakeAutofillType(server_type_local),
              AutofillPredictionSource::kServerCrowdsourcing};
    }

    if (PreferTypeAccordingToExceptions(kPreferredHeuristicTypesOverHtmlTypes,
                                        *this, heuristic_type_local,
                                        html_type_local)) {
      return {MakeAutofillType(heuristic_type_local),
              AutofillPredictionSource::kHeuristics};
    }
    // The following is a hack. If we have relevant server predictions to add
    // to the AutofillType, we want to add them. We must not do that if
    // `html_type_local == kCountryCode` because in that case,
    // `html_type_local` and
    // `HtmlFieldTypeToBestCorrespondingFieldType(html_type_local)` behave
    // differently (crbug.com/436013479). In all other cases, they are
    // identical, except for AutofillType::ToString().
    // TODO(crbug.com/436013479): Remove AutofillType::is_country_code().
    AutofillType type = MakeAutofillType(
        HtmlFieldTypeToBestCorrespondingFieldType(html_type_local),
        /*is_country_code=*/html_type_local == HtmlFieldType::kCountryCode);
    return {type, AutofillPredictionSource::kAutocomplete};
  }

  // #### Handle on-device ML classifications.

  // If the field was classified as an OTP field by PasswordManager and
  // `server_type_local` and `html_type_local` did not contradict it,
  // return PasswordManager prediction.
  if (password_ml_classification_type_local == ONE_TIME_CODE &&
      (server_type_local == NO_SERVER_DATA ||
       GroupTypeOfFieldType(server_type_local) ==
           FieldTypeGroup::kPasswordField)) {
    return {AutofillType(password_ml_classification_type_local),
            AutofillPredictionSource::kHeuristics};
  }

  // #### Handle Server types.

  // In general, server types have precedence over heuristic types, except for
  // the cases listed in `kPreferredHeuristicTypesOverServerTypes`.
  if (server_type_local != NO_SERVER_DATA &&
      !PreferTypeAccordingToExceptions(kPreferredHeuristicTypesOverServerTypes,
                                       *this, heuristic_type_local,
                                       server_type_local)) {
    return {MakeAutofillType(server_type_local),
            AutofillPredictionSource::kServerCrowdsourcing};
  }

  // #### Handle Heuristic types.

  return {MakeAutofillType(heuristic_type_local),
          heuristic_type_local != UNKNOWN_TYPE
              ? std::optional(AutofillPredictionSource::kHeuristics)
              : std::nullopt};
}

const std::u16string& AutofillField::value_for_import() const {
  bool should_consider_value_for_import =
      IsSelectElement() || initial_value() != value();
  if (base::FeatureList::IsEnabled(
          features::kAutofillEnableImportOfUnchangedValuesForCountryAndState)) {
    should_consider_value_for_import |=
        Type().GetAddressType() == ADDRESS_HOME_COUNTRY ||
        Type().GetAddressType() == ADDRESS_HOME_STATE;
  }
  if (!should_consider_value_for_import) {
    return base::EmptyString16();
  }

  if (const std::optional<std::u16string>& text = selected_option_text()) {
    return *text;
  }
  return value();
}

FieldSignature AutofillField::GetFieldSignature() const {
  return field_signature_ ? *field_signature_
                          : CalculateFieldSignatureByNameAndType(
                                name(), form_control_type());
}

std::string AutofillField::FieldSignatureAsStr() const {
  return base::NumberToString(GetFieldSignature().value());
}

bool AutofillField::IsFieldFillable() const {
  return std::ranges::any_of(Type().GetTypes(), IsFillableFieldType);
}

bool AutofillField::HasExpirationDateType() const {
  static constexpr FieldTypeSet kExpirationDateTypes = {
      CREDIT_CARD_EXP_MONTH, CREDIT_CARD_EXP_2_DIGIT_YEAR,
      CREDIT_CARD_EXP_4_DIGIT_YEAR, CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR,
      CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR};
  return Type().GetTypes().contains_any(kExpirationDateTypes);
}

bool AutofillField::ShouldSuppressSuggestionsAndFillingByDefault(
    AutocompleteUnrecognizedBehavior ac_unrecognized_behavior) const {
  // This is an exception - a field was autofilled and then JS on site changed
  // autocomplete attribute's value to unrecognized. This is done in order to
  // preserve the ability to swap an autofilled value for a different one.
  // See crbug.com/469057923 for details.
  if (last_modifier() == FieldModifier::kAutofill) {
    return false;
  }

  // Suggestions/filling will not be suppressed if one of the following is true:
  // 1. The autocomplete attribute is valid (can be seen in the HTML spec).
  // 2. The field's type comes from a server override.
  // 3. The field type has a credit-card-related classification.
  if (html_type_ != HtmlFieldType::kUnrecognized ||
      PredictionSource() == AutofillPredictionSource::kServerOverride ||
      Type().GetCreditCardType() != UNKNOWN_TYPE) {
    return false;
  }

  switch (ac_unrecognized_behavior) {
    case AutocompleteUnrecognizedBehavior::kSuggestionsSuppressed:
      return true;
    case AutocompleteUnrecognizedBehavior::kSuggestionsAllowed:
      return !base::FeatureList::IsEnabled(
          features::kAutofillEnableSkippingUnrecognizedAttribute);
  }
}

void AutofillField::SetPasswordRequirements(PasswordRequirementsSpec spec) {
  password_requirements_ = std::move(spec);
}

base::optional_ref<const AutofillFormatString> AutofillField::format_string()
    const {
  if (form_control_type() == FormControlType::kInputDate) {
    static const base::NoDestructor<AutofillFormatString> kFormat(
        AutofillFormatString(u"YYYY-MM-DD", FormatString_Type_DATE));
    return *kFormat;
  }
  if (form_control_type() == FormControlType::kInputMonth) {
    static const base::NoDestructor<AutofillFormatString> kFormat(
        AutofillFormatString(u"YYYY-MM", FormatString_Type_DATE));
    return *kFormat;
  }
  if (format_string_source_ == AutofillFormatStringSource::kUnset) {
    return std::nullopt;
  }
  return format_string_;
}

void AutofillField::UpdateFieldData(const FormFieldData& field_data) {
  FormFieldData::operator=(field_data);

  field_signature_ =
      CalculateFieldSignatureByNameAndType(name(), form_control_type());
}

void AutofillField::AppendLogEventIfNotRepeated(
    const FieldLogEventType& log_event) {
  if (!field_log_events_) {
    return;
  }
  if (field_log_events_->empty() ||
      field_log_events_->back().index() != log_event.index() ||
      !AreCollapsibleLogEvents(field_log_events_->back(), log_event)) {
    if (field_log_events_->size() < kMaxLogEventsPerField) {
      field_log_events_->push_back(log_event);
    } else {
      // For fields that exceed the number of allowed events, we do not keep
      // track of any events to avoid memory regressions.
      field_log_events_ = std::nullopt;
    }
  }
}

bool AutofillField::WasAutofilledWithFallback() const {
  return autofilled_type_ &&
         (!overall_type_ ||
          !overall_type_->type.GetTypes().contains(*autofilled_type_));
}

DenseSet<FieldModifier> AutofillField::all_modifiers() const {
  return DenseSet<FieldModifier>(field_modifiers_);
}

std::optional<FieldModifier> AutofillField::last_modifier() const {
  return field_modifiers_.empty() ? std::nullopt
                                  : std::optional(field_modifiers_.back());
}

void AutofillField::AddFieldModifier(FieldModifier modifier) {
  std::erase(field_modifiers_, modifier);
  field_modifiers_.push_back(modifier);
}

void AutofillField::RemoveFieldModifier(FieldModifier modifier,
                                        base::PassKey<FormFiller> pass_key) {
  std::erase(field_modifiers_, modifier);
}

}  // namespace autofill
