// 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.

#import "components/autofill/ios/browser/autofill_driver_ios.h"

#include <algorithm>
#import <concepts>
#import <functional>
#import <optional>
#import <type_traits>
#import <utility>
#import <variant>

#import "base/check_deref.h"
#import "base/containers/to_vector.h"
#import "base/feature_list.h"
#import "base/functional/bind.h"
#import "base/memory/ptr_util.h"
#import "base/memory/raw_ptr.h"
#import "base/memory/weak_ptr.h"
#import "base/metrics/histogram.h"
#import "base/metrics/histogram_functions.h"
#import "base/notimplemented.h"
#import "base/observer_list.h"
#import "components/autofill/core/browser/autofill_field.h"
#import "components/autofill/core/browser/filling/form_filler.h"
#import "components/autofill/core/browser/form_structure.h"
#import "components/autofill/core/browser/foundations/autofill_driver_router.h"
#import "components/autofill/core/common/autofill_debug_features.h"
#import "components/autofill/core/common/autofill_features.h"
#import "components/autofill/core/common/field_data_manager.h"
#import "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
#import "components/autofill/core/common/unique_ids.h"
#import "components/autofill/ios/browser/autofill_client_ios.h"
#import "components/autofill/ios/browser/autofill_driver_ios_bridge.h"
#import "components/autofill/ios/browser/autofill_driver_ios_factory.h"
#import "components/autofill/ios/browser/autofill_java_script_feature.h"
#import "components/autofill/ios/browser/autofill_util.h"
#import "components/autofill/ios/browser/form_fetch_batcher.h"
#import "components/autofill/ios/common/features.h"
#import "components/autofill/ios/common/field_data_manager_factory_ios.h"
#import "components/autofill/ios/form_util/child_frame_registrar.h"
#import "components/ukm/ios/ukm_url_recorder.h"
#import "ios/web/public/browser_state.h"
#import "ios/web/public/js_messaging/content_world.h"
#import "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/web_state.h"
#import "mojo/public/cpp/bindings/pending_remote.h"
#import "services/network/public/cpp/shared_url_loader_factory.h"
#import "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#import "url/origin.h"

namespace autofill {

namespace {

// WithNewVersion() bumps the FormData::version of each form. This should be
// called for every browser form before it enters AutofillManager so that
// AutofillManager can distinguish newer and older forms.
//
// TODO(crbug.com/40144964): Use this in all renderer -> browser communications.
// TODO(crbug.com/40144964): Remove once FormData objects aren't stored
// globally anymore.

// No-op: add types to the `requires` clause below as necessary.
template <typename T>
  requires(std::is_scalar_v<std::remove_cvref_t<T>>)
T&& WithNewVersion(T&& x) {
  return std::forward<T>(x);
}

auto& WithNewVersion(const FormData& browser_form) {
  static FormVersion version_counter;
  ++*version_counter;
  // This const_cast is a hack to avoid additional copies. It's OK because the
  // FormData is owned by AutofillDriverRouter, FormData::version is written
  // only here and read only in AutofillManager.
  const_cast<FormData&>(browser_form).set_version(version_counter);
  return browser_form;
}

auto& WithNewVersion(const std::optional<FormData>& browser_form) {
  if (browser_form) {
    WithNewVersion(*browser_form);
  }
  return browser_form;
}

template <typename... Args>
base::OnceCallback<void(Args...)> WithNewVersion(
    base::OnceCallback<void(Args...)> cb) {
  return base::BindOnce(
      [](base::OnceCallback<void(Args...)> cb, Args... args) {
        std::move(cb).Run(WithNewVersion(std::forward<Args>(args))...);
      },
      std::move(cb));
}

// AutofillDriverIOS::router_ only ever routes between instances of
// AutofillDriverIOS, so this cast is safe.
AutofillDriverIOS* cast(AutofillDriver* driver) {
  return static_cast<AutofillDriverIOS*>(driver);
}

base::TimeDelta GetDocumentFormScanPeriod() {
  return base::Milliseconds(kAutofillDocumentFormScanPeriodMs.Get());
}

base::TimeDelta GetFilteredDocumentFormScanPeriod() {
  return base::Milliseconds(kAutofillFilteredDocumentFormScanPeriodMs.Get());
}

bool UseXhrFix() {
  return base::FeatureList::IsEnabled(kAutofillFixXhrForXframe);
}

}  // namespace

// static
AutofillDriverIOS* AutofillDriverIOS::FromWebStateAndWebFrame(
    web::WebState* web_state,
    web::WebFrame* web_frame) {
  if (AutofillClientIOS* client = AutofillClientIOS::FromWebState(web_state)) {
    return client->GetAutofillDriverFactory().DriverForFrame(web_frame);
  }
  return nullptr;
}

// static
AutofillDriverIOS* AutofillDriverIOS::FromWebStateAndLocalFrameToken(
    web::WebState* web_state,
    LocalFrameToken token) {
  web::WebFramesManager* frames_manager =
      AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(web_state);
  web::WebFrame* frame = frames_manager->GetFrameWithId(token.ToString());
  return frame ? FromWebStateAndWebFrame(web_state, frame) : nullptr;
}

AutofillDriverIOS::AutofillDriverIOS(
    web::WebState* web_state,
    web::WebFrame* web_frame,
    AutofillClient* client,
    AutofillDriverRouter* router,
    id<AutofillDriverIOSBridge> bridge,
    base::PassKey<AutofillDriverIOSFactory> pass_key)
    : web_state_(web_state),
      web_frame_id_(web_frame ? web_frame->GetFrameId() : ""),
      bridge_(bridge),
      client_(*client),
      manager_(std::make_unique<BrowserAutofillManager>(this)),
      router_(router),
      document_scan_batcher_(bridge,
                             web_frame ? web_frame->AsWeakPtr() : nullptr,
                             GetDocumentFormScanPeriod()),
      document_filtered_scan_batcher_(
          bridge,
          web_frame ? web_frame->AsWeakPtr() : nullptr,
          GetFilteredDocumentFormScanPeriod()) {
  manager_observation_.Observe(manager_.get());

  std::optional<base::UnguessableToken> token_temp =
      DeserializeJavaScriptFrameId(web_frame_id_);
  if (token_temp) {
    local_frame_token_ = LocalFrameToken(*token_temp);
  }
}

AutofillDriverIOS::~AutofillDriverIOS() {
  Unregister();
  RecordTriggeredFormExtractionMetrics();
}

LocalFrameToken AutofillDriverIOS::GetFrameToken() const {
  return local_frame_token_;
}

std::optional<LocalFrameToken> AutofillDriverIOS::Resolve(FrameToken query) {
  // TODO(crbug.com/503264715): Resolve() should returns std::nullopt if `query`
  // does not refer to a child frame of `web_frame()`.
  if (std::holds_alternative<LocalFrameToken>(query)) {
    return std::get<LocalFrameToken>(query);
  }
  CHECK(std::holds_alternative<RemoteFrameToken>(query));
  auto remote_token = std::get<RemoteFrameToken>(query);
  auto* registrar = ChildFrameRegistrar::FromWebState(web_state_);
  return registrar ? registrar->LookupChildFrame(remote_token) : std::nullopt;
}

AutofillDriverIOS* AutofillDriverIOS::GetParent() {
  return parent_.get();
}

bool AutofillDriverIOS::IsActive() const {
  return true;  // iOS has no MPArch.
}

bool AutofillDriverIOS::IsEmbedded() const {
  return false;  // iOS has no MPArch.
}

AutofillClient& AutofillDriverIOS::GetAutofillClient() {
  return *client_;
}

BrowserAutofillManager& AutofillDriverIOS::GetAutofillManager() {
  return *manager_;
}

ukm::SourceId AutofillDriverIOS::GetPageUkmSourceId() const {
  return ukm::GetSourceIdForWebStateDocument(web_state_);
}

bool AutofillDriverIOS::IsPolicyControlledFeatureAutofillEnabled() const {
  // Give the "autofill" permission to the main frame of the webstate by
  // default.
  if (!web_frame() || web_frame()->IsMainFrame()) {
    return true;
  }

  // Also propagate that permission to the direct children of the main
  // frame on the same origin as the main frame.
  if (parent_ && parent_->web_frame() && parent_->web_frame()->IsMainFrame() &&
      web_frame()) {
    return parent_->web_frame()->GetSecurityOrigin() ==
           web_frame()->GetSecurityOrigin();
  }

  // Return false as share-autofill is not allowed.
  return false;
}

bool AutofillDriverIOS::IsPolicyControlledFeatureManualTextEnabled() const {
  return true;
}

bool AutofillDriverIOS::CanShowAutofillUi() const {
  return true;
}

base::flat_set<FieldGlobalId> AutofillDriverIOS::ApplyFormAction(
    mojom::FormActionType action_type,
    mojom::ActionPersistence action_persistence,
    base::span<const FormFieldData> fields,
    const FillId& fill_id,
    bool supports_refill,
    const url::Origin& triggered_origin,
    const absl::flat_hash_map<FieldGlobalId, FieldType>& field_type_map) {
  switch (action_type) {
    case mojom::FormActionType::kUndo:
    case mojom::FormActionType::kFill: {
      auto callback = [](AutofillDriver& driver,
                         mojom::FormActionType action_type,
                         mojom::ActionPersistence action_persistence,
                         const std::vector<FormFieldData::FillData>& fields,
                         const FillId& fill_id, bool supports_refill) {
        web::WebFrame* frame = cast(&driver)->web_frame();
        if (frame) {
          [cast(&driver)->bridge_ fillData:fields
                                   inFrame:frame
                            withActionType:action_type];
        }
      };

      const url::Origin main_origin =
          client_->GetLastCommittedPrimaryMainFrameOrigin();
      return router_->ApplyFormAction(
          callback, action_type, action_persistence, fields, fill_id,
          supports_refill, main_origin, triggered_origin, field_type_map);
    }
  }
}

void AutofillDriverIOS::ApplyFieldAction(
    mojom::FieldActionType action_type,
    mojom::ActionPersistence action_persistence,
    const FieldGlobalId& field_id,
    const std::u16string& value) {
  auto callback = [](AutofillDriver& driver, mojom::FieldActionType action_type,
                     mojom::ActionPersistence action_persistence,
                     FieldRendererId field, const std::u16string& value) {
    // For now, only support filling.
    switch (action_persistence) {
      case mojom::ActionPersistence::kFill: {
        [cast(&driver)->bridge_
            fillSpecificFormField:field
                        withValue:value
                       actionType:action_type
                          inFrame:cast(&driver)->web_frame()];
        break;
      }
      case mojom::ActionPersistence::kPreview:
        return;
    }
  };
  router_->ApplyFieldAction(callback, action_type, action_persistence, field_id,
                            value);
}

void AutofillDriverIOS::ExtractFormWithField(
    FieldGlobalId field_id,
    base::OnceCallback<void(AutofillDriver*, const std::optional<FormData>&)>
        final_handler) {
  if (!web_frame()) {
    std::move(final_handler).Run(nullptr, std::nullopt);
    return;
  }

  // TODO(crbug.com/455870070): Introduce a `fetchForm` method in the agent
  // that would extract a single form only given the renderer id and replace
  // the call to `fetchFormsFiltered()` with it.
  router_->ExtractFormWithField(
      [](AutofillDriver& request_target, FieldRendererId field_renderer_id,
         AutofillDriverRouter::RendererFormHandler renderer_form_handler) {
        auto completion_handler = base::BindOnce(
            [](FieldRendererId field_renderer_id,
               AutofillDriverRouter::RendererFormHandler renderer_form_handler,
               std::optional<std::vector<FormData>> forms) {
              if (!forms) {
                std::move(renderer_form_handler).Run(std::nullopt);
                return;
              }
              auto it = std::ranges::find_if(*forms, [&](const FormData& form) {
                return std::ranges::contains(form.fields(), field_renderer_id,
                                             &FormFieldData::renderer_id);
              });
              std::move(renderer_form_handler)
                  .Run(it == forms->end() ? std::nullopt
                                          : std::optional(std::move(*it)));
            },
            field_renderer_id, std::move(renderer_form_handler));

        auto& source = static_cast<AutofillDriverIOS&>(request_target);
        [source.bridge_ fetchFormsFiltered:std::nullopt
                                   inFrame:source.web_frame()
                         completionHandler:std::move(completion_handler)];
      },
      field_id, WithNewVersion(std::move(final_handler)));
}

void AutofillDriverIOS::ExposeDomNodeIdsInAllFrames() {}

void AutofillDriverIOS::SendTypePredictionsToRenderer(
    const FormStructure& form) {
  CHECK(base::FeatureList::IsEnabled(
      features::debug::kAutofillShowTypePredictions));
  auto callback = [](AutofillDriver& driver,
                     const std::vector<FormDataPredictions>& preds) {
    web::WebFrame* frame = cast(&driver)->web_frame();
    if (!frame) {
      return;
    }
    [cast(&driver)->bridge_ fillFormDataPredictions:preds inFrame:frame];
  };

  router_->SendTypePredictionsToRenderer(callback,
                                         form.GetFieldTypePredictions());
}

void AutofillDriverIOS::RendererShouldAcceptDataListSuggestion(
    const FieldGlobalId& field_id,
    const std::u16string& value) {}

void AutofillDriverIOS::TriggerFormExtractionInDriverFrame(
    AutofillDriverRouterAndFormForestPassKey pass_key) {
  if (!is_processed()) {
    return;
  }

  if (base::FeatureList::IsEnabled(
          features::kAutofillAcrossIframesIosTriggerFormExtraction)) {
    ScanForms();
  }
}

void AutofillDriverIOS::ScanForms(bool immediately) {
  if (!web_frame()) {
    return;
  }

  const auto callback = [](id<AutofillDriverIOSBridge> bridge,
                           base::WeakPtr<web::WebFrame> frame,
                           std::optional<std::vector<FormData>> forms) {
    if (!frame || !forms || forms->empty()) {
      return;
    }
    [bridge notifyFormsSeen:*std::move(forms) inFrame:frame.get()];
  };

  if (base::FeatureList::IsEnabled(kAutofillThrottleDocumentFormScanIos)) {
    immediately ? document_scan_batcher_.PushRequestAndRun(base::BindOnce(
                      callback, bridge_, web_frame()->AsWeakPtr()))
                : document_scan_batcher_.PushRequest(base::BindOnce(
                      callback, bridge_, web_frame()->AsWeakPtr()));
  } else {
    [bridge_ fetchFormsFiltered:std::nullopt
                        inFrame:web_frame()
              completionHandler:base::BindOnce(callback, bridge_,
                                               web_frame()->AsWeakPtr())];
  }
}

void AutofillDriverIOS::FetchFormsFilteredByName(
    const std::u16string& form_name,
    FormFetchCompletion completion) {
  if (!web_frame()) {
    return;
  }

  if (base::FeatureList::IsEnabled(
          kAutofillThrottleFilteredDocumentFormScanIos)) {
    document_filtered_scan_batcher_.PushRequest(std::move(completion),
                                                form_name);
  } else {
    [bridge_ fetchFormsFiltered:form_name
                        inFrame:web_frame()
              completionHandler:std::move(completion)];
  }
}

void AutofillDriverIOS::TriggerFormExtractionInAllFrames(
    base::OnceCallback<void(bool)> form_extraction_finished_callback) {
  NOTIMPLEMENTED();
}

void AutofillDriverIOS::ClearFormCacheInAllFrames() {
  NOTIMPLEMENTED();
}

void AutofillDriverIOS::ObserveFieldVisibility(
    const FieldGlobalId& field_id,
    mojo::PendingRemote<mojom::AutofillVisibilityObserver> observer) {
  // iOS doesn't support this yet.
  NOTIMPLEMENTED();
}

void AutofillDriverIOS::GetFourDigitCombinationsFromDom(
    base::OnceCallback<void(const std::vector<std::string>&)>
        potential_matches) {
  // TODO(crbug.com/40260122): Implement GetFourDigitCombinationsFromDom() in
  // iOS.
  NOTIMPLEMENTED();
}

void AutofillDriverIOS::ExtractLabeledTextNodeValue(
    const std::u16string& value_regex,
    const std::u16string& label_regex,
    uint32_t number_of_ancestor_levels_to_search,
    base::OnceCallback<void(const std::string& amount)> response_callback) {
  NOTIMPLEMENTED();
}

void AutofillDriverIOS::RendererShouldClearPreviewedForm() {}

void AutofillDriverIOS::RendererShouldTriggerSuggestions(
    const FieldGlobalId& field_id,
    AutofillSuggestionTriggerSource trigger_source) {
  // Triggering suggestions from the browser process is currently only used for
  // manual fallbacks on Desktop. It is not implemented on iOS.
  NOTIMPLEMENTED();
}

void AutofillDriverIOS::RendererShouldSetSuggestionAvailability(
    const FieldGlobalId& field_id,
    mojom::AutofillSuggestionAvailability suggestion_availability) {}

std::optional<net::IsolationInfo> AutofillDriverIOS::GetIsolationInfo() {
  // On iOS we have a single, shared URLLoaderFactory provided by BrowserState.
  // As it is shared, it is not trusted and we cannot assign trusted_params
  // to the network request. On iOS, the IsolationInfo should always be nullopt.
  return std::nullopt;
}

web::WebFrame* AutofillDriverIOS::web_frame() const {
  web::WebFramesManager* frames_manager =
      AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(web_state_);
  return frames_manager->GetFrameWithId(web_frame_id_);
}

void AutofillDriverIOS::AskForValuesToFill(const FormData& form,
                                           const FieldGlobalId& field_id) {
  auto callback =
      [](AutofillDriver& driver, const FormData& form,
         const FieldGlobalId& field_id, const gfx::Rect& bounding_box,
         AutofillSuggestionTriggerSource trigger_source,
         std::optional<PasswordSuggestionRequest> password_request) {
        driver.GetAutofillManager().OnAskForValuesToFill(
            form, field_id, bounding_box, trigger_source,
            std::move(password_request),
            /*pass_key=*/{});
      };
  // The caret position is currently not extracted on iOS.
  gfx::Rect caret_bounds;
  // TODO(crbug.com/40269303): Distinguish between different trigger sources.
  router_->AskForValuesToFill(callback, *this, form, field_id, caret_bounds,
                              AutofillSuggestionTriggerSource::kiOS,
                              std::nullopt);
}

void AutofillDriverIOS::DidAutofillForm(const FormData& form) {
  if (UseXhrFix()) {
    // Update the last_interacted_form_ locally in the renderer frame before
    // routing so XHR detection can be done when a renderer form is deleted.
    UpdateLastInteractedForm(/*form_data=*/form);
  }
  auto callback = [](AutofillDriver& driver, const FormData& form) {
    if (!UseXhrFix()) {
      cast(&driver)->UpdateLastInteractedForm(/*form_data=*/form);
    }
    driver.GetAutofillManager().OnDidAutofillForm(form, /*pass_key=*/{});
  };
  router_->DidAutofillForm(callback, *this, form);
}

void AutofillDriverIOS::FormsSeen(
    std::vector<FormData> updated_forms,
    const std::vector<FormGlobalId>& removed_forms) {
  auto callback = [](AutofillDriver& driver,
                     std::vector<FormData> updated_forms,
                     std::vector<FormGlobalId> removed_forms) {
    driver.GetAutofillManager().OnFormsSeen(std::move(updated_forms),
                                            std::move(removed_forms),
                                            /*pass_key=*/{});
  };

  // Any RemoteFrameTokens encountered for the first time should be posted to
  // the registrar, which allows this driver to be established as the parent
  // of the child frame.
  for (const FormData& form : updated_forms) {
    for (const FrameTokenWithPredecessor& child_frame : form.child_frames()) {
      // This std::get is safe because on iOS, FormData::child_frames is
      // only ever populated with RemoteFrameTokens. std::get will fail a
      // CHECK if this assumption is ever wrong.
      auto token = std::get<RemoteFrameToken>(child_frame.token);
      auto* registrar = ChildFrameRegistrar::GetOrCreateForWebState(web_state_);
      if (registrar && known_child_frames_.insert(token).second) {
        registrar->DeclareNewRemoteToken(
            token, base::BindOnce(&AutofillDriverIOS::SetSelfAsParent,
                                  weak_ptr_factory_.GetWeakPtr(), form));
      }
    }
  }
  router_->FormsSeen(callback, *this, std::move(updated_forms), removed_forms);
}

void AutofillDriverIOS::FormSubmitted(
    const FormData& form,
    mojom::SubmissionSource submission_source) {
  auto callback = [webstate_ptr = web_state_](
                      AutofillDriver& driver, const FormData& form,
                      mojom::SubmissionSource submission_source) {
    CHECK(webstate_ptr);
    base::UmaHistogramEnumeration(kAutofillSubmissionDetectionSourceHistogram,
                                  submission_source);
    driver.GetAutofillManager().OnFormSubmitted(form, submission_source,
                                                /*pass_key=*/{});
    if (UseXhrFix()) {
      // Clear the last interacted form on the child frames to not trigger
      // XHR again in case there were some interactions with forms in these
      // frames prior to submission. This is to avoid spamming submits.
      for (const auto& remote_token : form.child_frames()) {
        if (std::optional<LocalFrameToken> local_token =
                driver.Resolve(remote_token.token)) {
          if (AutofillDriverIOS* child_driver =
                  FromWebStateAndLocalFrameToken(webstate_ptr, *local_token)) {
            child_driver->ClearLastInteractedForm();
          }
        }
      }
    }
    cast(&driver)->ClearLastInteractedForm();
  };
  router_->FormSubmitted(callback, *this, form, submission_source);
  if (UseXhrFix()) {
    ClearLastInteractedForm();
  }
}

void AutofillDriverIOS::CaretMovedInFormField(const FormData& form,
                                              const FieldGlobalId& field_id,
                                              const gfx::Rect& caret_bounds) {
  GetAutofillManager().OnCaretMovedInFormField(form, field_id, caret_bounds,
                                               /*pass_key=*/{});
}

void AutofillDriverIOS::TextFieldValueChanged(const FormData& form,
                                              const FieldGlobalId& field_id,
                                              base::TimeTicks timestamp) {
  if (UseXhrFix()) {
    // Update the last_interacted_form_ locally in the renderer frame before
    // routing so XHR detection can be done when a renderer form is deleted.
    UpdateLastInteractedForm(
        /*form_data=*/form,
        /*formless_field=*/form.renderer_id() ? FieldRendererId()
                                              : field_id.renderer_id);
  }
  auto callback = [&](AutofillDriver& driver, const FormData& form,
                      const FieldGlobalId& field_global_id,
                      base::TimeTicks timestamp) {
    if (!UseXhrFix()) {
      cast(&driver)->UpdateLastInteractedForm(
          /*form_data=*/form,
          /*formless_field=*/form.renderer_id() ? FieldRendererId()
                                                : field_global_id.renderer_id);
    }
    driver.GetAutofillManager().OnTextFieldValueChanged(
        form, field_id, timestamp, /*pass_key=*/{});
  };

  router_->TextFieldValueChanged(callback, *this, form, field_id, timestamp);
}

void AutofillDriverIOS::SetParent(base::WeakPtr<AutofillDriverIOS> parent) {
  if (unregistered_) {
    // Do not set parent if the driver was unregistered to avoid any risk
    // of connecting it back into a form tree, where it has to be kept alone as
    // a standalone node.
    return;
  }

  parent_ = std::move(parent);
}

void AutofillDriverIOS::SetSelfAsParent(const FormData& form,
                                        LocalFrameToken token) {
  if (unregistered_) {
    return;
  }

  AutofillDriverIOS* child_driver =
      FromWebStateAndLocalFrameToken(web_state_, token);
  if (child_driver) {
    child_driver->SetParent(weak_ptr_factory_.GetWeakPtr());
  }
  // Redeclare the forms as seen to take into account the new parent to
  // establish the relation between the child frames and their host form in the
  // forms tree.
  auto callback = [](AutofillDriver& driver,
                     std::vector<FormData> updated_forms,
                     std::vector<FormGlobalId> removed_forms) {
    driver.GetAutofillManager().OnFormsSeen(std::move(updated_forms),
                                            std::move(removed_forms),
                                            /*pass_key=*/{});
  };
  router_->FormsSeen(callback, *this, {form}, {});
}

void AutofillDriverIOS::UpdateLastInteractedForm(
    const FormData& form_data,
    const FieldRendererId& formless_field) {
  last_interacted_form_.emplace(form_data, formless_field);
}

void AutofillDriverIOS::ClearLastInteractedForm() {
  last_interacted_form_.reset();
}

void AutofillDriverIOS::OnAutofillManagerStateChanged(
    AutofillManager& manager,
    LifecycleState old_state,
    LifecycleState new_state) {
  switch (new_state) {
    case LifecycleState::kInactive:
    case LifecycleState::kActive:
    case LifecycleState::kPendingReset:
      break;
    case LifecycleState::kPendingDeletion:
      manager_observation_.Reset();
      break;
  }
}

void AutofillDriverIOS::OnAfterFormsSeen(
    AutofillManager& manager,
    base::span<const FormGlobalId> updated_forms,
    base::span<const FormGlobalId> removed_forms) {
  DCHECK_EQ(&manager, manager_.get());
  if (updated_forms.empty()) {
    return;
  }
  std::vector<raw_ref<const FormStructure>> form_structures;
  form_structures.reserve(updated_forms.size());
  for (const FormGlobalId& form : updated_forms) {
    if (const FormStructure* form_structure =
            manager.FindCachedFormById(form)) {
      form_structures.emplace_back(*form_structure);
    }
  }
  if (web::WebFrame* frame = web_frame()) {
    [bridge_ handleParsedForms:form_structures inFrame:frame];
  }
}

void AutofillDriverIOS::FormsRemoved(
    const std::set<FormRendererId>& removed_forms,
    const std::set<FieldRendererId>& removed_unowned_fields) {
  bool submission_detected = DetectFormSubmissionAfterFormRemoval(
      removed_forms, removed_unowned_fields);
  RecordFormRemoval(
      submission_detected, /*removed_forms_count=*/removed_forms.size(),
      /*removed_unowned_fields_count=*/removed_unowned_fields.size());

  if (submission_detected) {
    UpdateLastInteractedFormFromFieldDataManager();

    FormSubmitted(last_interacted_form_->form_data,
                  mojom::SubmissionSource::XHR_SUCCEEDED);
  }

  // Report the removed forms so Autofill can be synced with the DOM state.

  std::vector<FormGlobalId> forms_to_report =
      base::ToVector(removed_forms, [&](const auto& renderer_id) {
        return FormGlobalId{.frame_token = local_frame_token_,
                            .renderer_id = renderer_id};
      });

  if (!removed_unowned_fields.empty()) {
    // Determine whether the synthetic form should be reported as removed based
    // on the removed fields, where having all fields removed is considered as
    // a deletion.
    FormGlobalId synthetic_global_id = {.frame_token = local_frame_token_,
                                        .renderer_id = FormRendererId(0)};
    if (const FormStructure* form =
            GetAutofillManager().FindCachedFormById(synthetic_global_id)) {
      base::flat_set<FieldRendererId> form_fields = base::ToVector(
          form->fields(), [](const std::unique_ptr<AutofillField>& field) {
            return field->renderer_id();
          });
      // If the synthetic form fields are a subset of the removed fields, it
      // means that all the synthetic form fields were removed.
      const bool is_deleted =
          std::ranges::includes(removed_unowned_fields, form_fields);
      if (is_deleted) {
        forms_to_report.emplace_back(synthetic_global_id);
      }
    }

    // TODO(crbug.com/351685487): Trigger forms extraction if there are some
    // fields removed but not all of them.
  }

  if (!forms_to_report.empty()) {
    FormsSeen(/*updated_forms=*/{}, /*removed_forms=*/forms_to_report);
  }
}

bool AutofillDriverIOS::DetectFormSubmissionAfterFormRemoval(
    const std::set<FormRendererId>& removed_forms,
    const std::set<FieldRendererId>& removed_unowned_fields) const {
  // Detect a form submission only if the last interacted form or formless field
  // was removed.
  if (!last_interacted_form_) {
    return false;
  }

  const auto& last_interacted_form_id =
      last_interacted_form_->form_data.renderer_id();
  // Check if the last interacted form was removed.
  if (last_interacted_form_id &&
      removed_forms.find(last_interacted_form_id) != removed_forms.end()) {
    return true;
  }

  const auto& last_formless_field_id = last_interacted_form_->formless_field;

  // Check if the last interacted formless field was removed.
  return removed_unowned_fields.find(last_formless_field_id) !=
         removed_unowned_fields.end();
}

void AutofillDriverIOS::Unregister() {
  router_->UnregisterDriver(*this, /*driver_is_dying=*/true);
  unregistered_ = true;
  parent_ = nullptr;
  weak_ptr_factory_.InvalidateWeakPtrs();
}

void AutofillDriverIOS::OnDidTriggerFormFetch() {
  ++form_extraction_trigger_count_;
}

void AutofillDriverIOS::UpdateLastInteractedFormFromFieldDataManager() {
  CHECK(last_interacted_form_);

  auto* frame = web_frame();
  if (!frame) {
    return;
  }

  FieldDataManager* field_data_manager =
      FieldDataManagerFactoryIOS::FromWebFrame(frame);

  // Update the snapshot of the last interacted form with the data in
  // FieldDataManager.
  std::vector<FormFieldData> fields =
      last_interacted_form_->form_data.ExtractFields();
  for (auto& field : fields) {
    const auto& field_id = field.renderer_id();
    if (!field_data_manager->HasFieldData(field_id)) {
      continue;
    }
    field.set_value(field_data_manager->GetUserInput(field_id));
    field.set_properties_mask(
        field_data_manager->GetFieldPropertiesMask(field_id));
  }
  last_interacted_form_->form_data.set_fields(std::move(fields));
}

void AutofillDriverIOS::RecordFormRemoval(bool submission_detected,
                                          int removed_forms_count,
                                          int removed_unowned_fields_count) {
  base::UmaHistogramBoolean(/*name=*/kFormSubmissionAfterFormRemovalHistogram,
                            /*sample=*/submission_detected);
  base::UmaHistogramCounts100(
      /*name=*/kFormRemovalRemovedUnownedFieldsHistogram,
      /*sample=*/removed_unowned_fields_count);

}

void AutofillDriverIOS::RecordTriggeredFormExtractionMetrics() {
  if (form_extraction_trigger_count_ < 1) {
    // Do not record anything if no extraction was performed to not pollute
    // the data with 0 extraction cases that don't really mean anything.
    // We usually expect at least one extraction to be triggered in the frame
    // when its content is loaded. We are not interested in tracking the cases
    // where extraction didn't happen, not for these metrics at least.
    return;
  }

  base::UmaHistogramCounts100(
      "Autofill.iOS.TriggeredFormExtractionFromDriver.SmallRange",
      form_extraction_trigger_count_);
  base::UmaHistogramCounts1000(
      "Autofill.iOS.TriggeredFormExtractionFromDriver.MediumRange",
      form_extraction_trigger_count_);
  base::UmaHistogramCounts10000(
      "Autofill.iOS.TriggeredFormExtractionFromDriver.LargeRange",
      form_extraction_trigger_count_);
}

void AutofillDriverIOS::GetNonceForEmailVerification(
    FieldGlobalId email_field_id,
    base::OnceCallback<void(const std::optional<std::string>&)> callback) {
  // TODO(crbug.com/380367784): Implement email verification on iOS.
  NOTIMPLEMENTED();
  std::move(callback).Run(std::nullopt);
}

void AutofillDriverIOS::SendEmailVerificationToken(FieldGlobalId email_field_id,
                                                   const std::string& email,
                                                   const std::string& token) {
  // TODO(crbug.com/380367784): Implement email verification on iOS.
  NOTIMPLEMENTED();
}

void AutofillDriverIOS::UpdateEmailVerificationState(
    const FieldGlobalId& email_field_id,
    mojom::EmailVerificationState state) {
  // TODO(crbug.com/380367784): Implement email verification on iOS.
  NOTIMPLEMENTED();
}

bool AutofillDriverIOS::IsSafeToFill(const FormFieldData& field,
                                     FieldType filled_type,
                                     const url::Origin& main_origin,
                                     const url::Origin& trigger_origin) const {
  return router_->IsSafeToFill(field, filled_type, main_origin, trigger_origin);
}

void AutofillDriverIOS::ScrollFieldIntoView(FieldGlobalId field_id) {
  auto callback = [](AutofillDriver& driver, FieldRendererId field) {
    web::WebFrame* frame = cast(&driver)->web_frame();
    if (frame) {
      [cast(&driver)->bridge_ scrollFieldIntoView:field inFrame:frame];
    }
  };
  router_->ScrollFieldIntoView(callback, field_id);
}

}  // namespace autofill
