// Copyright 2025 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/content/browser/integrators/actor/autofill_annotations_provider_impl.h"

#include "base/containers/map_util.h"
#include "base/functional/function_ref.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/data_manager/personal_data_manager.h"
#include "components/autofill/core/browser/form_types.h"
#include "components/autofill/core/browser/foundations/autofill_client.h"
#include "components/autofill/core/browser/foundations/autofill_manager.h"
#include "components/optimization_guide/content/browser/page_content_proto_util.h"
#include "components/optimization_guide/proto/features/common_quality_data.pb.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"

namespace optimization_guide {

using autofill::AutofillClient;
using autofill::AutofillField;
using autofill::AutofillManager;
using autofill::ContentAutofillDriver;
using autofill::DenseSet;
using autofill::FieldGlobalId;
using autofill::FieldRendererId;
using autofill::FieldType;
using autofill::FieldTypeGroup;
using autofill::FieldTypeSet;
using autofill::FormGlobalId;
using autofill::FormStructure;
using autofill::FormType;
using autofill::LocalFrameToken;
using autofill::PersonalDataManager;
using autofill::ValuablesDataManager;

namespace {

// Key for `base::SupportsUserData` aspects of `SectionMapping`.
const void* const kSectionMappingKey = &kSectionMappingKey;

std::string FormGlobalIdToString(FormGlobalId form_id) {
  return base::StrCat({form_id.frame_token.ToString(), "_",
                       base::NumberToString(form_id.renderer_id.value())});
}

class SectionMapping : public base::SupportsUserData::Data {
 public:
  SectionMapping();
  SectionMapping(const SectionMapping&) = delete;
  SectionMapping& operator=(const SectionMapping&) = delete;
  ~SectionMapping() override;

  static SectionMapping* GetInstance(
      ConvertAIPageContentToProtoSession& session);

  uint32_t GetOrCreateSectionIdentifier(const FormGlobalId& form_id,
                                        const std::string& section);

 private:
  base::flat_map<std::string, uint32_t> autofill_section_numbers_;
};

SectionMapping::SectionMapping() = default;
SectionMapping::~SectionMapping() = default;

// static
SectionMapping* SectionMapping::GetInstance(
    ConvertAIPageContentToProtoSession& session) {
  SectionMapping* mapping =
      static_cast<SectionMapping*>(session.GetUserData(kSectionMappingKey));
  if (!mapping) {
    auto new_mapping = std::make_unique<SectionMapping>();
    mapping = new_mapping.get();
    session.SetUserData(kSectionMappingKey, std::move(new_mapping));
  }
  return mapping;
}

uint32_t SectionMapping::GetOrCreateSectionIdentifier(
    const FormGlobalId& form_id,
    const std::string& section) {
  // Because different forms can have the same section titles, we use
  // (form_id, section) as the key for `section_numbers`.
  const std::string section_id = FormGlobalIdToString(form_id) + section;

  // Find the current section or create a new one.
  auto iter = autofill_section_numbers_.find(section_id);
  if (iter == autofill_section_numbers_.end()) {
    iter = autofill_section_numbers_
               .emplace(section_id, autofill_section_numbers_.size())
               .first;
  }
  return iter->second;
}

const FormStructure* GetAutofillForm(
    content::RenderFrameHost& render_frame_host,
    const FieldGlobalId& field_global_id) {
  content::WebContents& web_contents =
      *content::WebContents::FromRenderFrameHost(&render_frame_host);

  // Use the `ContentAutofillDriver` of the main frame because forms are
  // flattened and propagated into the primary main frame `AutofillManager`.
  ContentAutofillDriver* autofill_driver =
      ContentAutofillDriver::GetForRenderFrameHost(
          web_contents.GetPrimaryMainFrame());
  if (!autofill_driver) {
    return nullptr;
  }

  AutofillManager& autofill_manager = autofill_driver->GetAutofillManager();
  return autofill_manager.FindCachedFormById(field_global_id);
}

// Returns the redaction reason for a given Autofill `field_type`. Note that
// some field types return 'no redaction needed' from this method only because a
// decision has not yet been made on them.
AutofillFieldRedactionReason GetRedactionReason(FieldType field_type) {
  switch (field_type) {
    // We should not redact cases where we have not identified the field type.
    case autofill::NO_SERVER_DATA:
    case autofill::UNKNOWN_TYPE:
      return AutofillFieldRedactionReason::kNoRedactionNeeded;

    // Names are not redacted.
    case autofill::NAME_FIRST:
    case autofill::NAME_MIDDLE:
    case autofill::NAME_LAST:
    case autofill::NAME_MIDDLE_INITIAL:
    case autofill::NAME_FULL:
    case autofill::NAME_SUFFIX:
    case autofill::NAME_LAST_FIRST:
    case autofill::NAME_LAST_CONJUNCTION:
    case autofill::NAME_LAST_SECOND:
    case autofill::NAME_HONORIFIC_PREFIX:
    case autofill::ALTERNATIVE_FULL_NAME:
    case autofill::ALTERNATIVE_GIVEN_NAME:
    case autofill::ALTERNATIVE_FAMILY_NAME:
      return AutofillFieldRedactionReason::kNoRedactionNeeded;

    // Email address is not redacted.
    case autofill::EMAIL_ADDRESS:
      return AutofillFieldRedactionReason::kNoRedactionNeeded;

    // Cardholder name is not redacted.
    case autofill::CREDIT_CARD_NAME_FULL:
    case autofill::CREDIT_CARD_NAME_FIRST:
    case autofill::CREDIT_CARD_NAME_LAST:
      return AutofillFieldRedactionReason::kNoRedactionNeeded;

    // Other credit card data is redacted.
    case autofill::CREDIT_CARD_NUMBER:
    case autofill::CREDIT_CARD_EXP_MONTH:
    case autofill::CREDIT_CARD_EXP_2_DIGIT_YEAR:
    case autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR:
    case autofill::CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR:
    case autofill::CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR:
    case autofill::CREDIT_CARD_TYPE:
    case autofill::CREDIT_CARD_VERIFICATION_CODE:
    case autofill::CREDIT_CARD_STANDALONE_VERIFICATION_CODE:
      return AutofillFieldRedactionReason::kShouldRedactForPayments;

    // Password fields have already been redacted on the renderer side.
    case autofill::PASSWORD:
    case autofill::ACCOUNT_CREATION_PASSWORD:
    case autofill::NOT_ACCOUNT_CREATION_PASSWORD:
    case autofill::USERNAME:
    case autofill::USERNAME_AND_EMAIL_ADDRESS:
    case autofill::NEW_PASSWORD:
    case autofill::PROBABLY_NEW_PASSWORD:
    case autofill::NOT_NEW_PASSWORD:
    case autofill::CONFIRMATION_PASSWORD:
    case autofill::SINGLE_USERNAME:
    case autofill::SINGLE_USERNAME_FORGOT_PASSWORD:
    case autofill::SINGLE_USERNAME_WITH_INTERMEDIATE_VALUES:
      return AutofillFieldRedactionReason::kNoRedactionNeeded;

    // The following fields have not yet been decided upon. By default they are
    // considered non-sensitive to avoid over-redacting. Any item here may
    // change from `AutofillFieldRedactionReason::kNoRedactionNeeded` to `true`
    // in the future.
    case autofill::ADDRESS_HOME_LINE1:
    case autofill::ADDRESS_HOME_LINE2:
    case autofill::ADDRESS_HOME_APT_NUM:
    case autofill::ADDRESS_HOME_CITY:
    case autofill::ADDRESS_HOME_STATE:
    case autofill::ADDRESS_HOME_ZIP:
    case autofill::ADDRESS_HOME_COUNTRY:
    case autofill::ADDRESS_HOME_STREET_ADDRESS:
    case autofill::ADDRESS_HOME_SORTING_CODE:
    case autofill::ADDRESS_HOME_DEPENDENT_LOCALITY:
    case autofill::ADDRESS_HOME_LINE3:
    case autofill::ADDRESS_HOME_STREET_NAME:
    case autofill::ADDRESS_HOME_HOUSE_NUMBER:
    case autofill::ADDRESS_HOME_SUBPREMISE:
    case autofill::ADDRESS_HOME_OTHER_SUBUNIT:
    case autofill::ADDRESS_HOME_ADDRESS:
    case autofill::ADDRESS_HOME_ADDRESS_WITH_NAME:
    case autofill::ADDRESS_HOME_FLOOR:
    case autofill::ADDRESS_HOME_OVERFLOW:
    case autofill::ADDRESS_HOME_LANDMARK:
    case autofill::ADDRESS_HOME_OVERFLOW_AND_LANDMARK:
    case autofill::ADDRESS_HOME_ADMIN_LEVEL2:
    case autofill::ADDRESS_HOME_STREET_LOCATION:
    case autofill::ADDRESS_HOME_BETWEEN_STREETS:
    case autofill::ADDRESS_HOME_BETWEEN_STREETS_OR_LANDMARK:
    case autofill::ADDRESS_HOME_STREET_LOCATION_AND_LOCALITY:
    case autofill::ADDRESS_HOME_STREET_LOCATION_AND_LANDMARK:
    case autofill::ADDRESS_HOME_DEPENDENT_LOCALITY_AND_LANDMARK:
    case autofill::ADDRESS_HOME_BETWEEN_STREETS_1:
    case autofill::ADDRESS_HOME_BETWEEN_STREETS_2:
    case autofill::ADDRESS_HOME_HOUSE_NUMBER_AND_APT:
    case autofill::ADDRESS_HOME_APT:
    case autofill::ADDRESS_HOME_APT_TYPE:
    case autofill::ADDRESS_HOME_ZIP_AND_CITY:
    case autofill::ADDRESS_HOME_ZIP_PREFIX:
    case autofill::ADDRESS_HOME_ZIP_SUFFIX:
    case autofill::PHONE_HOME_NUMBER:
    case autofill::PHONE_HOME_CITY_CODE:
    case autofill::PHONE_HOME_COUNTRY_CODE:
    case autofill::PHONE_HOME_CITY_AND_NUMBER:
    case autofill::PHONE_HOME_WHOLE_NUMBER:
    case autofill::PHONE_HOME_EXTENSION:
    case autofill::PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX:
    case autofill::PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX:
    case autofill::PHONE_HOME_NUMBER_PREFIX:
    case autofill::PHONE_HOME_NUMBER_SUFFIX:
    case autofill::COMPANY_NAME:
    case autofill::MERCHANT_EMAIL_SIGNUP:
    case autofill::MERCHANT_PROMO_CODE:
    case autofill::AMBIGUOUS_TYPE:
    case autofill::SEARCH_TERM:
    case autofill::PRICE:
    case autofill::NOT_PASSWORD:
    case autofill::NOT_USERNAME:
    case autofill::IBAN_VALUE:
    case autofill::NUMERIC_QUANTITY:
    case autofill::DELIVERY_INSTRUCTIONS:
    case autofill::LOYALTY_MEMBERSHIP_ID:
    case autofill::PASSPORT_NUMBER:
    case autofill::PASSPORT_ISSUING_COUNTRY:
    case autofill::PASSPORT_EXPIRATION_DATE:
    case autofill::PASSPORT_ISSUE_DATE:
    case autofill::LOYALTY_MEMBERSHIP_PROGRAM:
    case autofill::LOYALTY_MEMBERSHIP_PROVIDER:
    case autofill::VEHICLE_LICENSE_PLATE:
    case autofill::VEHICLE_VIN:
    case autofill::VEHICLE_MAKE:
    case autofill::VEHICLE_MODEL:
    case autofill::DRIVERS_LICENSE_REGION:
    case autofill::DRIVERS_LICENSE_NUMBER:
    case autofill::DRIVERS_LICENSE_EXPIRATION_DATE:
    case autofill::DRIVERS_LICENSE_ISSUE_DATE:
    case autofill::VEHICLE_YEAR:
    case autofill::VEHICLE_PLATE_STATE:
    case autofill::EMAIL_OR_LOYALTY_MEMBERSHIP_ID:
    case autofill::NATIONAL_ID_CARD_NUMBER:
    case autofill::NATIONAL_ID_CARD_EXPIRATION_DATE:
    case autofill::NATIONAL_ID_CARD_ISSUE_DATE:
    case autofill::NATIONAL_ID_CARD_ISSUING_COUNTRY:
    case autofill::KNOWN_TRAVELER_NUMBER:
    case autofill::KNOWN_TRAVELER_NUMBER_EXPIRATION_DATE:
    case autofill::REDRESS_NUMBER:
    case autofill::FLIGHT_RESERVATION_FLIGHT_NUMBER:
    case autofill::FLIGHT_RESERVATION_CONFIRMATION_CODE:
    case autofill::FLIGHT_RESERVATION_TICKET_NUMBER:
    case autofill::FLIGHT_RESERVATION_DEPARTURE_AIRPORT:
    case autofill::FLIGHT_RESERVATION_ARRIVAL_AIRPORT:
    case autofill::FLIGHT_RESERVATION_DEPARTURE_DATE:
    case autofill::ORDER_ID:
    case autofill::ORDER_DATE:
    case autofill::ORDER_MERCHANT_NAME:
    case autofill::SHIPMENT_TRACKING_NUMBER:
      return AutofillFieldRedactionReason::kNoRedactionNeeded;

    // OTPs are sensitive short-lived secrets and should be redacted.
    case autofill::ONE_TIME_CODE:
      return AutofillFieldRedactionReason::kShouldRedactForOtp;

    // These cases are not produced by field classification, but have to be
    // handled so that the switch is complete.
    case autofill::EMPTY_TYPE:
    case autofill::MAX_VALID_FIELD_TYPE:
      return AutofillFieldRedactionReason::kNoRedactionNeeded;
  }
}

// Returns the AutofillFieldRedactionReason for a set of field types.
// Payment wins over OTP when both feature gates are enabled for the same field
// so mixed predictions resolve deterministically.
AutofillFieldRedactionReason GetRedactionReason(
    const FieldTypeSet& field_types) {
  bool should_redact_for_payments = false;
  bool should_redact_for_otp = false;

  for (const FieldType field_type : field_types) {
    switch (GetRedactionReason(field_type)) {
      case AutofillFieldRedactionReason::kNoRedactionNeeded:
        break;
      case AutofillFieldRedactionReason::kShouldRedactForPayments:
        should_redact_for_payments = true;
        break;
      case AutofillFieldRedactionReason::kShouldRedactForOtp:
        should_redact_for_otp = true;
        break;
    }
  }

  if (should_redact_for_payments &&
      IsAutofillRedactionReasonEnabled(
          AutofillFieldRedactionReason::kShouldRedactForPayments)) {
    return AutofillFieldRedactionReason::kShouldRedactForPayments;
  }
  if (should_redact_for_otp &&
      IsAutofillRedactionReasonEnabled(
          AutofillFieldRedactionReason::kShouldRedactForOtp)) {
    return AutofillFieldRedactionReason::kShouldRedactForOtp;
  }

  return AutofillFieldRedactionReason::kNoRedactionNeeded;
}

std::string CoarseFieldTypeToString(proto::CoarseAutofillFieldType type) {
  switch (type) {
    case proto::COARSE_AUTOFILL_FIELD_TYPE_UNSUPPORTED:
      return "COARSE_AUTOFILL_FIELD_TYPE_UNSUPPORTED";
    case proto::COARSE_AUTOFILL_FIELD_TYPE_ADDRESS:
      return "COARSE_AUTOFILL_FIELD_TYPE_ADDRESS";
    case proto::COARSE_AUTOFILL_FIELD_TYPE_CREDIT_CARD:
      return "COARSE_AUTOFILL_FIELD_TYPE_CREDIT_CARD";
    case proto::COARSE_AUTOFILL_FIELD_TYPE_OTP:
      return "COARSE_AUTOFILL_FIELD_TYPE_OTP";
    default:
      // Covers future extensions to the proto - if you see this logged please
      // add a case above!
      return "Unknown CoarseAutofillFieldType value";
  }
}

}  // namespace

AutofillAnnotationsProviderImpl::~AutofillAnnotationsProviderImpl() = default;

std::optional<AutofillFieldMetadata>
AutofillAnnotationsProviderImpl::GetAutofillFieldData(
    content::RenderFrameHost& render_frame_host,
    int32_t dom_node_id,
    ConvertAIPageContentToProtoSession& session) {
  // Determine `AutofillField` from Autofill.
  FieldGlobalId field_global_id = {
      LocalFrameToken(render_frame_host.GetFrameToken().value()),
      FieldRendererId(dom_node_id)};
  const FormStructure* form =
      GetAutofillForm(render_frame_host, field_global_id);
  if (!form) {
    // This vlog is very spammy, as we are called for every form control that
    // APC encounters.
    VLOG(3) << "GetAutofillFieldData - No form found for DOM node "
            << dom_node_id << " in render frame host for "
            << render_frame_host.GetLastCommittedOrigin();
    return std::nullopt;
  }
  const AutofillField* field = form->GetFieldById(field_global_id);
  if (!field) {
    // This vlog is very spammy, as we are called for every form control that
    // APC encounters.
    VLOG(3) << "GetAutofillFieldData - No field found for DOM node "
            << dom_node_id << " in render frame host for "
            << render_frame_host.GetLastCommittedOrigin();
    return std::nullopt;
  }

  AutofillFieldMetadata metadata;

  metadata.section_id =
      SectionMapping::GetInstance(session)->GetOrCreateSectionIdentifier(
          form->global_id(), field->section().ToString());

  const DenseSet<FormType>& form_types = field->Type().GetFormTypes();
  const FieldTypeSet& field_types = field->Type().GetTypes();

  metadata.coarse_field_type = [&] {
    if (form_types.contains(FormType::kAddressForm)) {
      return proto::COARSE_AUTOFILL_FIELD_TYPE_ADDRESS;
    } else if (form_types.contains(FormType::kCreditCardForm) ||
               form_types.contains(FormType::kStandaloneCvcForm)) {
      return proto::COARSE_AUTOFILL_FIELD_TYPE_CREDIT_CARD;
    } else if (form_types.contains(FormType::kOneTimePasswordForm)) {
      return proto::COARSE_AUTOFILL_FIELD_TYPE_OTP;
    }
    return proto::COARSE_AUTOFILL_FIELD_TYPE_UNSUPPORTED;
  }();

  metadata.redaction_reason = GetRedactionReason(field_types);

  VLOG(2) << "GetAutofillFieldData - DOM node " << dom_node_id << " ("
          << field->name() << ") in " << field->origin()
          << " has form_types: " << form_types
          << ", field_types: " << field_types << ", coarse_field_type: "
          << CoarseFieldTypeToString(metadata.coarse_field_type)
          << ", redaction_reason: " << metadata.redaction_reason;

  return metadata;
}

AutofillAvailability AutofillAnnotationsProviderImpl::GetAutofillAvailability(
    content::RenderFrameHost& render_frame_host) {
  content::WebContents& web_contents =
      *content::WebContents::FromRenderFrameHost(&render_frame_host);
  ContentAutofillDriver* autofill_driver =
      ContentAutofillDriver::GetForRenderFrameHost(
          web_contents.GetPrimaryMainFrame());
  if (!autofill_driver) {
    VLOG(2) << "GetAutofillAvailability - no autofill driver for "
            << render_frame_host.GetLastCommittedOrigin();
    return {};
  }

  AutofillClient& client = autofill_driver->GetAutofillClient();
  if (!client.HasPersonalDataManager()) {
    VLOG(2) << "GetAutofillAvailability - no personal data manager for "
            << render_frame_host.GetLastCommittedOrigin();
    return {};
  }
  const PersonalDataManager& pdm = client.GetPersonalDataManager();

  const bool address_autofill_enabled = client.IsAutofillProfileEnabled();
  const bool has_address_profiles =
      !pdm.address_data_manager().GetProfiles().empty();
  const bool payment_autofill_enabled =
      client.GetPaymentsAutofillClient()->IsAutofillPaymentMethodsEnabled();
  const bool has_credit_cards =
      !pdm.payments_data_manager().GetCreditCards().empty();

  VLOG(2) << "GetAutofillAvailability - url: "
          << render_frame_host.GetLastCommittedOrigin()
          << ", address_autofill_enabled: " << address_autofill_enabled
          << ", has_address_profiles: " << has_address_profiles
          << ", payment_autofill_enabled: " << payment_autofill_enabled
          << ", has_credit_cards: " << has_credit_cards;

  return AutofillAvailability{
      .has_fillable_address = address_autofill_enabled && has_address_profiles,
      .has_fillable_credit_card = payment_autofill_enabled && has_credit_cards,
  };
}

}  // namespace optimization_guide
