// Copyright 2023 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/metrics/quality_metrics.h"

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <string>

#include "base/check.h"
#include "base/feature_list.h"
#include "base/i18n/char_iterator.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "components/autofill/core/browser/autofill_browser_util.h"
#include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/data_quality/autofill_data_util.h"
#include "components/autofill/core/browser/field_type_utils.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/filling/filling_product.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/browser/form_types.h"
#include "components/autofill/core/browser/metrics/autofill_metrics.h"
#include "components/autofill/core/browser/metrics/field_filling_stats_and_score_metrics.h"
#include "components/autofill/core/browser/metrics/form_interactions_ukm_logger.h"
#include "components/autofill/core/browser/metrics/prediction_quality_metrics.h"
#include "components/autofill/core/browser/metrics/quality_metrics_filling.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/html_field_types.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/icu/source/common/unicode/urename.h"
#include "third_party/icu/source/common/unicode/uscript.h"
#include "third_party/icu/source/common/unicode/utypes.h"

namespace autofill::autofill_metrics {

namespace {

void LogPerfectFillingMetric(const FormStructure& form) {
  // Denotes whether for a given FillingProduct, the form has a field which was
  // last filled with this product (and maybe user/JS edited afterwards).
  const bool has_address = std::ranges::any_of(form, [](const auto& field) {
    return field->filling_product() == FillingProduct::kAddress;
  });
  const bool has_credit_card = std::ranges::any_of(form, [](const auto& field) {
    return field->filling_product() == FillingProduct::kCreditCard;
  });

  if (!has_address && !has_credit_card) {
    return;
  }

  // A perfectly filled form is submitted as it was filled from Autofill
  // without subsequent changes. This means that in a perfect filling
  // scenario, a field is either autofilled, empty, has value at page load or
  // has value set by JS.
  const bool perfect_filling = IsFormStructurePerfectlyFilled(form);

  // The perfect filling metric is only recorded if Autofill was used on at
  // least one field. This conditions this metric on Assistance, Readiness and
  // Acceptance. Perfect filling is recorded for addresses and credit cards
  // separately.
  if (has_address) {
    AutofillMetrics::LogAutofillPerfectFilling(/*is_address=*/true,
                                               perfect_filling);
  }
  if (has_credit_card) {
    AutofillMetrics::LogAutofillPerfectFilling(/*is_address=*/false,
                                               perfect_filling);
  }
}

// Logs metrics related to how long it took the user from load/interaction time
// till form submission.
void LogDurationMetrics(
    const FormStructure& form,
    base::TimeTicks load_time,
    base::TimeTicks interaction_time,
    base::TimeTicks now,
    AutocompleteUnrecognizedBehavior ac_unrecognized_behavior) {
  size_t num_detected_field_types =
      std::ranges::count_if(form, [](const auto& field) {
        return FieldHasMeaningfulPossibleFieldTypes(*field);
      });
  bool form_has_autofilled_fields = std::ranges::any_of(
      form.fields(), [](const std::unique_ptr<AutofillField>& field) {
        return field->last_modifier() == FieldModifier::kAutofill;
      });
  bool has_observed_one_time_code_field = std::ranges::any_of(
      form.fields(), [](const std::unique_ptr<AutofillField>& field) {
        return field->html_type() == HtmlFieldType::kOneTimeCode;
      });
  if (num_detected_field_types >= kMinRequiredFieldsForHeuristics ||
      num_detected_field_types >= kMinRequiredFieldsForQuery) {
    // `now` should always be available.
    CHECK(!now.is_null());
    // The |load_time| might be unset, in the case that the form was
    // dynamically added to the DOM.
    // Submission should chronologically follow form load, however
    // this might not be true in case of a timezone change. Therefore make
    // sure to log the elapsed time between submission time and load time only
    // if it is positive. Same is applied below.
    if (!load_time.is_null() && now >= load_time) {
      base::TimeDelta elapsed = now - load_time;
      if (form_has_autofilled_fields) {
        AutofillMetrics::LogFormFillDurationFromLoadWithAutofill(elapsed);
      } else {
        AutofillMetrics::LogFormFillDurationFromLoadWithoutAutofill(elapsed);
      }
    }
    // The |interaction_time| might be unset, in the case that the user
    // submitted a blank form.
    if (!interaction_time.is_null() && now >= interaction_time) {
      base::TimeDelta elapsed = now - interaction_time;
      AutofillMetrics::LogFormFillDurationFromInteraction(
          form.GetFormTypes(ac_unrecognized_behavior),
          form_has_autofilled_fields, elapsed);
    }
  }
  if (has_observed_one_time_code_field) {
    if (!load_time.is_null() && now >= load_time) {
      base::TimeDelta elapsed = now - load_time;
      AutofillMetrics::LogFormFillDurationFromLoadForOneTimeCode(elapsed);
    }
    if (!interaction_time.is_null() && now >= interaction_time) {
      base::TimeDelta elapsed = now - interaction_time;
      AutofillMetrics::LogFormFillDurationFromInteractionForOneTimeCode(
          elapsed);
    }
  }
}

// Returns the character set of the submitted value for the alternative name
// field.
AutofillAlternativeNameFieldValueCharacterSet
GetAlternativeNameFieldValueCharacterSet(
    const std::u16string& submitted_value) {
  UErrorCode error = U_ZERO_ERROR;
  for (base::i18n::UTF16CharIterator iter(submitted_value); !iter.end();
       iter.Advance()) {
    if (uscript_getScript(iter.get(), &error) == USCRIPT_KATAKANA) {
      return AutofillAlternativeNameFieldValueCharacterSet::kKatakana;
    } else if (uscript_getScript(iter.get(), &error) == USCRIPT_HIRAGANA) {
      return AutofillAlternativeNameFieldValueCharacterSet::kHiragana;
    }
  }
  return AutofillAlternativeNameFieldValueCharacterSet::kOther;
}

// Records the character set of the submitted value for each alternative name
// field in the form.
void LogSubmittedAlternativeNameCharacterSetValues(const FormStructure& form) {
  for (const std::unique_ptr<AutofillField>& field : form) {
    if (IsAlternativeNameType(field->Type().GetAddressType()) &&
        !field->value().empty()) {
      base::UmaHistogramEnumeration(
          "Autofill.SubmittedAlternativeNameFieldValueCharacterSet",
          GetAlternativeNameFieldValueCharacterSet(field->value()));
    }
  }
}

void LogPredictionMetrics(
    const FormStructure& form,
    FormInteractionsUkmLogger& form_interactions_ukm_logger,
    ukm::SourceId source_id,
    bool observed_submission,
    base::TimeTicks now) {
  const QualityMetricType metric_type =
      observed_submission ? TYPE_SUBMISSION : TYPE_NO_SUBMISSION;
  for (const std::unique_ptr<AutofillField>& field : form) {
    LogFieldTypeAtSubmissionMetrics(*field);
    LogHeuristicPredictionQualityMetrics(form_interactions_ukm_logger,
                                         source_id, form, *field, metric_type,
                                         now);
    LogServerPredictionQualityMetrics(form_interactions_ukm_logger, source_id,
                                      form, *field, metric_type, now);
    LogOverallPredictionQualityMetrics(form_interactions_ukm_logger, source_id,
                                       form, *field, metric_type, now);
    LogEmailFieldPredictionMetrics(*field);
    LogFieldPredictionOverlapMetrics(*field);
  }
}

void LogFillingMetrics(
    const FormStructure& form,
    FormInteractionsUkmLogger& form_interactions_ukm_logger,
    ukm::SourceId source_id,
    bool observed_submission,
    base::TimeTicks now,
    AutocompleteUnrecognizedBehavior ac_unrecognized_behavior) {
  const QualityMetricType metric_type =
      observed_submission ? TYPE_SUBMISSION : TYPE_NO_SUBMISSION;
  for (const std::unique_ptr<AutofillField>& field : form) {
    form_interactions_ukm_logger.LogFieldFillStatus(source_id, form, *field,
                                                    metric_type, now);
  }
  if (!observed_submission) {
    return;
  }
  LogPerfectFillingMetric(form);
  LogFieldFillingStatsAndScore(form, ac_unrecognized_behavior);
  LogFillingQualityMetrics(form, ac_unrecognized_behavior);

  FieldTypeSet autofilled_field_types;
  for (const std::unique_ptr<AutofillField>& field : form) {
    if (field->all_modifiers().contains(FieldModifier::kAutofill)) {
      AutofillMetrics::LogEditedAutofilledFieldAtSubmission(
          form_interactions_ukm_logger, source_id, form, *field);
    }
    if (FieldHasMeaningfulPossibleFieldTypes(*field) &&
        field->last_modifier() == FieldModifier::kAutofill) {
      autofilled_field_types.insert_all(field->Type().GetTypes());
    }
  }
  if (form.GetFormTypes(ac_unrecognized_behavior)
          .contains(FormType::kCreditCardForm)) {
    AutofillMetrics::LogCreditCardSeamlessnessAtSubmissionTime(
        autofilled_field_types);
  }
}

}  // namespace

void LogQualityMetrics(
    const FormStructure& form_structure,
    base::TimeTicks load_time,
    base::TimeTicks interaction_time,
    base::TimeTicks submission_time,
    FormInteractionsUkmLogger& form_interactions_ukm_logger,
    ukm::SourceId source_id,
    bool observed_submission,
    AutocompleteUnrecognizedBehavior ac_unrecognized_behavior) {
  base::TimeTicks now = base::TimeTicks::Now();
  LogPredictionMetrics(form_structure, form_interactions_ukm_logger, source_id,
                       observed_submission, now);
  LogFillingMetrics(form_structure, form_interactions_ukm_logger, source_id,
                    observed_submission, now, ac_unrecognized_behavior);
  if (observed_submission) {
    // TODO(crbug.com/359768803): Remove this metric once the feature is
    // launched.
    LogSubmittedAlternativeNameCharacterSetValues(form_structure);
    LogDurationMetrics(form_structure, load_time, interaction_time,
                       submission_time, ac_unrecognized_behavior);
  }
}

// Log the quality of the heuristics and server predictions for this form
// structure, if autocomplete attributes are present on the fields (since the
// autocomplete attribute takes precedence over other type predictions).
void LogQualityMetricsBasedOnAutocomplete(
    const FormStructure& form_structure,
    FormInteractionsUkmLogger& form_interactions_ukm_logger,
    ukm::SourceId source_id) {
  const QualityMetricType metric_type = TYPE_AUTOCOMPLETE_BASED;
  base::TimeTicks now = base::TimeTicks::Now();
  for (const auto& field : form_structure) {
    if (field->html_type() != HtmlFieldType::kUnspecified &&
        field->html_type() != HtmlFieldType::kUnrecognized) {
      LogHeuristicPredictionQualityMetrics(form_interactions_ukm_logger,
                                           source_id, form_structure, *field,
                                           metric_type, now);
      LogServerPredictionQualityMetrics(form_interactions_ukm_logger, source_id,
                                        form_structure, *field, metric_type,
                                        now);
    }
  }
}

}  // namespace autofill::autofill_metrics
