// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_FILLING_FORM_FILLER_H_
#define COMPONENTS_AUTOFILL_CORE_BROWSER_FILLING_FORM_FILLER_H_

#include <stddef.h>

#include <map>
#include <memory>
#include <optional>
#include <string>
#include <variant>

#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/types/optional_ref.h"
#include "components/autofill/core/browser/autofill_trigger_source.h"
#include "components/autofill/core/browser/data_model/autofill_ai/entity_instance.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/filling/field_filling_skip_reason.h"
#include "components/autofill/core/browser/filling/field_filling_util.h"
#include "components/autofill/core/browser/filling/form_autofill_history.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/browser/integrators/one_time_tokens/otp_suggestion.h"
#include "components/autofill/core/browser/suggestions/suggestion_util.h"
#include "components/autofill/core/common/autofill_constants.h"
#include "components/autofill/core/common/dense_set.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
#include "components/autofill/core/common/unique_ids.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"

namespace autofill {

class AutofillClient;
class AutofillProfile;
class BrowserAutofillManager;
class CreditCard;
class LogManager;
enum class FillingProduct;

// Denotes the reason for triggering a refill attempt.
// These values are persisted to UMA logs. Entries should not be renumbered and
// numeric values should never be reused. Keep this enum up to date with the one
// in tools/metrics/histograms/metadata/autofill/enums.xml.
enum class RefillTriggerReason {
  kFormChanged = 0,
  kSelectOptionsChanged = 1,
  kExpirationDateFormatted = 2,
  kProgrammaticRefill = 3,
  kMaxValue = kProgrammaticRefill
};

using VerifiedProfile = std::map<FieldType, std::u16string>;

using FillingPayload = std::variant<const AutofillProfile*,
                                    const CreditCard*,
                                    const EntityInstance*,
                                    const VerifiedProfile*,
                                    const OtpFillData*>;

// Helper class responsible for [re]filling forms and fields.
//
// It is privately owned by the BrowserAutofillManager, which is the only
// component that talks to it.
//
// It receives cached data and is responsible for either filling directly or
// triggering a refill (which eventually results in a filling operation), and
// then sending the filled form to the renderer (via AutofillDriver).
//
// Additionally, it provides an API to determine which fields will be
// filled/skipped based on the given context.
//
// The class is directly responsible for modifying the cached fields in the form
// cache (BrowserAutofillManager::form_structures_) since it receives references
// to cached fields and modifies some attributes during filling.
//
// The class is also indirectly responsible for modifying blink forms, since
// after filling FormData objects it sends them to the renderer, which is
// directly responsible for filling the web forms.
//
// It holds any state that is only relevant for [re]filling.
class FormFiller {
 public:
  explicit FormFiller(BrowserAutofillManager& manager);

  FormFiller(const FormFiller&) = delete;
  FormFiller& operator=(const FormFiller&) = delete;

  virtual ~FormFiller();

  class RefillOptions {
   public:
    static RefillOptions NotRefill();
    static RefillOptions Refill(FieldTypeSet originally_filled,
                                RefillTriggerReason reason);

    bool is_refill() const;
    bool may_refill(const FieldTypeSet& field_type) const;
    std::optional<RefillTriggerReason> reason() const { return reason_; }

   private:
    RefillOptions();

    // Both are `std::nullopt` if and only if `is_refill()` is false.
    std::optional<FieldTypeSet> originally_filled_;
    std::optional<RefillTriggerReason> reason_;
  };

  // Given `field`, and the `trigger_field`, return the set of all reasons for
  // that field to be skipped for filling. If the field should not be skipped,
  // an empty set is returned (and not {FieldFillingSkipReason::kNotSkipped}).
  // `type_count` tracks the number of times a type of field has been filled.
  // `refill_options` includes refill-related data such as what groups were
  // filled in the initial filling.
  // `blocked_fields` are fields which must not be filled because another
  // filling operation or product of higher priority claims them.
  // `filling_product` is the type of filling calling this function.
  static DenseSet<FieldFillingSkipReason> GetFillingSkipReasonsForField(
      const AutofillField& field,
      const AutofillField& trigger_field,
      const RefillOptions& refill_options,
      base::flat_map<FieldType, size_t>& type_count,
      const base::flat_set<FieldGlobalId>& blocked_fields,
      AutofillTriggerSource trigger_source,
      AutocompleteUnrecognizedBehavior ac_unrecognized_behavior);

  // Resets states that FormFiller holds and maintains.
  void Reset();

  base::TimeDelta limit_before_automatic_refill() const {
    return limit_before_automatic_refill_;
  }

  // Given a `form`, returns a map from each field's id to the skip reason for
  // that field. See additional comments in `GetFillingSkipReasonsForField()`.
  // `blocked_fields` are fields which must not be filled because another
  // filling operation or product of higher priority claims them.
  static base::flat_map<FieldGlobalId, DenseSet<FieldFillingSkipReason>>
  GetFieldFillingSkipReasons(const FormStructure& form,
                             const AutofillField& trigger_field,
                             const RefillOptions& refill_options,
                             FillingProduct filling_product,
                             AutofillTriggerSource trigger_source,
                             const AutofillClient& client,
                             base::flat_set<FieldGlobalId> blocked_fields);

  // Reverts the last autofill operation on `form` that affected the field with
  // `trigger_field_id`. `action_persistence` denotes whether this is an actual
  // fill or preview operation on the renderer side.
  void UndoAutofill(mojom::ActionPersistence action_persistence,
                    FormStructure& form,
                    const FieldGlobalId& trigger_field_id,
                    FillingProduct filling_product);

  // Records filling information if possible and routes back to the renderer.
  void FillOrPreviewField(mojom::ActionPersistence action_persistence,
                          mojom::FieldActionType action_type,
                          const FieldGlobalId& field_id,
                          AutofillField* field,
                          const std::u16string& value,
                          FillingProduct filling_product,
                          std::optional<FieldType> field_type_used);

  // Fills or previews the data from `filling_payload` into `form`.
  // `blocked_fields` are fields which must not be filled because another
  // filling operation or product of higher priority claims them.
  // `forced_fill_values` contains values for fields to be filled directly,
  // regardless of `filling_payload`.
  // TODO(crbug.com/40227071): Clean up the API.
  void FillOrPreviewForm(
      mojom::ActionPersistence action_persistence,
      const FillingPayload& filling_payload,
      FormStructure& form,
      AutofillField& trigger_field,
      AutofillTriggerSource trigger_source,
      const base::flat_set<FieldGlobalId>& blocked_fields,
      FillId fill_id,
      const std::map<FieldGlobalId, FillingValueAndType>& forced_fill_values,
      RefillOptions refill_options);

  // Prevents any automatic refill of the operation `fill_id`. A renderer may
  // call this when a JavaScript observes the `autofill` event and may therefore
  // programmatically trigger a refill.
  void SuppressAutomaticRefills(const FillId& fill_id);

  // May or may not trigger a refill of `fill_id`. Programmatic refills (unlike
  // automatic refills) are initiated by JavaScript.
  void MaybeScheduleProgrammaticRefill(const FillId& fill_id);

  // May or may not trigger a refill operation on `form`. Automatic refills
  // (unlike programmatic refills) are caused by dynamic changes in the DOM.
  //
  // `field` and `old_value` are only needed when `refill_trigger_reason` is
  // `RefillTriggerReason::kExpirationDateFormatted`, and in that case `field`
  // is the one that was reformatted and `old_value` is the value `field` had
  // before the reformatting.
  void MaybeScheduleAutomaticRefill(
      const FormStructure& form,
      RefillTriggerReason refill_trigger_reason,
      AutofillTriggerSource trigger_source,
      base::optional_ref<const AutofillField> field = std::nullopt,
      base::optional_ref<const std::u16string> old_value = std::nullopt);

  base::WeakPtr<FormFiller> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 protected:
  struct RefillContext;

 private:
  friend class FormFillerTestApi;
  friend class TestFormFiller;

  struct AugmentedFillingPayload;

  // Stores a refill `context` for `form_id`. If `context` is null, erases the
  // entry corresponding to `form_id`.
  void SetRefillContext(FormGlobalId form_id,
                        std::unique_ptr<RefillContext> context);

  RefillContext* GetRefillContext(FormGlobalId form_id);
  RefillContext* GetRefillContext(const FillId& fill_id);

  // Schedules a call of TriggerRefill. Virtual for testing.
  virtual void ScheduleRefill(const FormGlobalId& form_id,
                              RefillContext& refill_context,
                              AutofillTriggerSource trigger_source,
                              RefillTriggerReason refill_trigger_reason);

  // Attempts to refill the form with corresponding `form_id`.
  void TriggerRefill(const FormGlobalId& form_id,
                     AutofillTriggerSource trigger_source,
                     RefillTriggerReason refill_trigger_reason);

  // Initializes the `RefillContext` for `form` if applicable, and no-op
  // otherwise. Returns true if a new refill context was successfully
  // initialized, indicating a possibility for a refill to happen eventually.
  bool MaybeInitializeRefillContext(
      mojom::ActionPersistence action_persistence,
      const FormStructure& form,
      const AutofillField& autofill_trigger_field,
      const AugmentedFillingPayload& augmented_filling_payload,
      const base::flat_set<FieldGlobalId>& blocked_fields,
      FillId fill_id,
      const std::vector<FormFieldData>& result_fields,
      const absl::flat_hash_map<FieldGlobalId, FieldType>& filled_field_types,
      RefillOptions refill_options);

  struct ValueAndTypeAndOverride : public FillingValueAndType {
    bool value_is_an_override = false;
  };

  // Returns the value to fill along with the field type and if the value is an
  // override. Returns `std::nullopt` if no value to fill could be found.
  std::optional<ValueAndTypeAndOverride> GetFieldFillingData(
      const AutofillField& field,
      const AugmentedFillingPayload& filling_payload,
      const std::map<FieldGlobalId, FillingValueAndType>& forced_fill_values,
      mojom::ActionPersistence action_persistence,
      bool allow_suggestion_swapping,
      std::string* failure_to_fill);

  // Fills `field` and modifies the states needed by the renderer for filling.
  void FillField(const ValueAndTypeAndOverride& filling_content,
                 FormFieldData& field,
                 mojom::ActionPersistence action_persistence,
                 AutofillTriggerSource trigger_source,
                 bool allow_suggestion_swapping);

  // Updates the cached `AutofillField`s in `form` with the information
  // resulting from a filling operation.
  void UpdateCacheOnFill(
      FormStructure& form,
      base::span<const FormFieldData> browser_filled_fields,
      const base::flat_set<FieldGlobalId>& safe_filled_field_ids,
      const absl::flat_hash_map<FieldGlobalId, FieldType>& filled_field_types,
      const AugmentedFillingPayload& augmented_filling_payload) const;

  // Appends `TriggerFillFieldLogEvent` and `FillFieldLogEvents` to the relevant
  // fields in the `form` if there was a filling operation.
  void AppendFillLogEvents(
      FormStructure& form,
      AutofillField& trigger_field,
      const base::flat_set<FieldGlobalId>& safe_field_ids,
      const base::flat_map<FieldGlobalId, DenseSet<FieldFillingSkipReason>>&
          skip_reasons,
      const FillingPayload& filling_payload,
      bool is_refill);

  // Similar to the static GetFieldFillingSkipReasons() but adds additional skip
  // reasons based on `filling_content`.
  base::flat_map<FieldGlobalId, DenseSet<FieldFillingSkipReason>>
  GetFieldFillingSkipReasons(
      const FormStructure& form,
      const AutofillField& trigger_field,
      const RefillOptions& refill_options,
      FillingProduct filling_product,
      AutofillTriggerSource trigger_source,
      const AutofillClient& client,
      base::flat_set<FieldGlobalId> blocked_fields,
      const base::flat_map<FieldGlobalId,
                           base::expected<ValueAndTypeAndOverride,
                                          std::string>>& filling_content);

  // Logs information about the ongoing fill operation on `form` to
  // chrome://autofill-internals.
  void LogFillingInternal(
      mojom::ActionPersistence action_persistence,
      const FormStructure& form,
      RefillOptions refill_options,
      FillingProduct filling_product,
      const base::flat_map<FieldGlobalId,
                           base::expected<FormFiller::ValueAndTypeAndOverride,
                                          std::string>>& filling_content,
      const base::flat_map<FieldGlobalId, DenseSet<FieldFillingSkipReason>>&
          skip_reasons);

  LogManager* log_manager();

  // Container holding the history of Autofill filling operations. Used to undo
  // some of the filling operations.
  FormAutofillHistory form_autofill_history_;

  // A map from FormGlobalId to RefillContext instances used to make refill
  // attempts for dynamic forms.
  std::map<FormGlobalId, std::unique_ptr<RefillContext>> refill_context_;

  // The maximum amount of time between a change in the form and the original
  // fill that triggers a refill. This value is only changed in browser tests,
  // where time cannot be mocked, to avoid flakiness.
  base::TimeDelta limit_before_automatic_refill_ = kLimitBeforeAutomaticRefill;
  base::TimeDelta limit_before_programmatic_refill_ =
      kLimitBeforeProgrammaticRefill;

  const raw_ref<BrowserAutofillManager> manager_;

  base::WeakPtrFactory<FormFiller> weak_ptr_factory_{this};
};

}  // namespace autofill

#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_FILLING_FORM_FILLER_H_
