// 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/ui/autofill_external_delegate.h"

#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/feature_list.h"
#include "base/i18n/rtl.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/gmock_move_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/device_info.h"
#endif
#include "components/autofill/core/browser/autofill_trigger_source.h"
#include "components/autofill/core/browser/data_manager/addresses/address_data_manager.h"
#include "components/autofill/core/browser/data_manager/payments/payments_data_manager.h"
#include "components/autofill/core/browser/data_manager/personal_data_manager_observer.h"
#include "components/autofill/core/browser/data_manager/test_personal_data_manager.h"
#include "components/autofill/core/browser/data_model/addresses/autofill_profile.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/filling/autofill_ai/autofill_ai_access_manager_test_api.h"
#include "components/autofill/core/browser/filling/autofill_ai/field_filling_entity_util.h"
#include "components/autofill/core/browser/filling/field_filling_util.h"
#include "components/autofill/core/browser/filling/form_filler.h"
#include "components/autofill/core/browser/foundations/autofill_client.h"
#include "components/autofill/core/browser/foundations/autofill_manager_test_api.h"
#include "components/autofill/core/browser/foundations/browser_autofill_manager.h"
#include "components/autofill/core/browser/foundations/browser_autofill_manager_test_api.h"
#include "components/autofill/core/browser/foundations/test_autofill_client.h"
#include "components/autofill/core/browser/foundations/test_autofill_driver.h"
#include "components/autofill/core/browser/foundations/test_browser_autofill_manager.h"
#include "components/autofill/core/browser/foundations/with_test_autofill_client_driver_manager.h"
#include "components/autofill/core/browser/integrators/at_memory/at_memory_query_service.h"
#include "components/autofill/core/browser/integrators/at_memory/memory_data_type.h"
#include "components/autofill/core/browser/integrators/at_memory/memory_search_result.h"
#include "components/autofill/core/browser/integrators/at_memory/mock_at_memory_query_service.h"
#include "components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_metrics.h"
#include "components/autofill/core/browser/integrators/autofill_ai/mock_autofill_ai_manager.h"
#include "components/autofill/core/browser/integrators/compose/autofill_compose_delegate.h"
#include "components/autofill/core/browser/integrators/compose/mock_autofill_compose_delegate.h"
#include "components/autofill/core/browser/integrators/identity_credential/mock_identity_credential_delegate.h"
#include "components/autofill/core/browser/integrators/one_time_tokens/mock_otp_manager.h"
#include "components/autofill/core/browser/metrics/autofill_in_devtools_metrics.h"
#include "components/autofill/core/browser/metrics/autofill_metrics.h"
#include "components/autofill/core/browser/metrics/autofill_metrics_utils.h"
#include "components/autofill/core/browser/metrics/log_event.h"
#include "components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h"
#include "components/autofill/core/browser/metrics/suggestions_list_metrics.h"
#include "components/autofill/core/browser/network/autofill_ai/mock_autofill_ai_personal_context_access_manager.h"
#include "components/autofill/core/browser/network/autofill_ai/mock_wallet_pass_access_manager.h"
#include "components/autofill/core/browser/payments/constants.h"
#include "components/autofill/core/browser/payments/credit_card_access_manager.h"
#include "components/autofill/core/browser/payments/mock_iban_access_manager.h"
#include "components/autofill/core/browser/payments/payments_autofill_client.h"
#include "components/autofill/core/browser/payments/test/mock_ai_card_recommendation_manager.h"
#include "components/autofill/core/browser/payments/test/mock_bnpl_manager.h"
#include "components/autofill/core/browser/payments/test/mock_save_and_fill_manager.h"
#include "components/autofill/core/browser/payments/test_payments_autofill_client.h"
#include "components/autofill/core/browser/single_field_fillers/mock_single_field_fill_router.h"
#include "components/autofill/core/browser/studies/autofill_experiments.h"
#include "components/autofill/core/browser/suggestions/suggestion.h"
#include "components/autofill/core/browser/suggestions/suggestion_hiding_reason.h"
#include "components/autofill/core/browser/suggestions/suggestion_test_helpers.h"
#include "components/autofill/core/browser/suggestions/suggestion_type.h"
#include "components/autofill/core/browser/test_utils/autofill_form_test_utils.h"
#include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
#include "components/autofill/core/browser/test_utils/entity_data_test_utils.h"
#include "components/autofill/core/browser/test_utils/valuables_data_test_utils.h"
#include "components/autofill/core/browser/ui/tabbed_pane_enums.h"
#include "components/autofill/core/browser/webdata/autocomplete/autocomplete_entry.h"
#include "components/autofill/core/browser/webdata/autocomplete/autocomplete_table_label_sensitive.h"
#include "components/autofill/core/browser/webdata/autofill_ai/entity_table.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_service_test_helper.h"
#include "components/autofill/core/common/aliases.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/autofill/core/common/autofill_prefs.h"
#include "components/autofill/core/common/autofill_switches.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_data_test_api.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/password_form_fill_data.h"
#include "components/device_reauth/mock_device_authenticator.h"
#include "components/optimization_guide/core/optimization_guide_prefs.h"
#include "components/personal_context/core/mock_personal_context_eligibility_service.h"
#include "components/personal_context/core/personal_context_prefs.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/strings/grit/components_strings.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "url/origin.h"

namespace autofill {
namespace {

using ::base::test::RunOnceCallback;
using test::CreateAutofillSuggestion;
using test::CreateTestAddressFormData;
using test::GetPassportEntityInstance;
using test::GetPassportEntityInstanceWithRandomGuid;
using test::MaskEntityInstance;
using ::testing::_;
using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Field;
using ::testing::InSequence;
using ::testing::IsEmpty;
using ::testing::Matcher;
using ::testing::Mock;
using ::testing::MockFunction;
using ::testing::NiceMock;
using ::testing::Pointee;
using ::testing::Property;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SizeIs;
using ::testing::StartsWith;
using ::testing::Values;
using ::testing::VariantWith;
using ::testing::WithParamInterface;

// Action `SaveArgElementsTo<k>(pointer)` saves the value pointed to by the
// `k`th (0-based) argument of the mock function by moving it to `*pointer`.
ACTION_TEMPLATE(SaveArgElementsTo,
                HAS_1_TEMPLATE_PARAMS(int, k),
                AND_1_VALUE_PARAMS(pointer)) {
  auto span = testing::get<k>(args);
  pointer->assign(span.begin(), span.end());
}

using SuggestionPosition = AutofillSuggestionDelegate::SuggestionMetadata;

constexpr auto kDefaultSuggestionTriggerSource =
    AutofillSuggestionTriggerSource::kFormControlElementClicked;

constexpr AutofillTriggerSource DefaultTriggerSource() {
  if constexpr (BUILDFLAG(IS_ANDROID)) {
    return AutofillTriggerSource::kKeyboardAccessoryOrBottomSheet;
  }
  return AutofillTriggerSource::kPopup;
}

template <typename SuggestionsMatcher>
auto PopupOpenArgsAre(SuggestionsMatcher suggestions_matcher,
                      AutofillSuggestionTriggerSource trigger_source =
                          kDefaultSuggestionTriggerSource) {
  using PopupOpenArgs = AutofillClient::PopupOpenArgs;
  return AllOf(Field(&PopupOpenArgs::suggestions, suggestions_matcher),
               Field(&PopupOpenArgs::trigger_source, trigger_source));
}

// Checks whether the filling payload of a suggestion matches `payload`.
// `payload` can either be a pointer or a value (e.g., an `EntityInstance`).
template <typename Payload>
auto HasFillingPayload(Payload payload) {
  using PayloadValue = std::conditional_t<std::is_pointer_v<Payload>,
                                          std::remove_pointer_t<Payload>,
                                          std::remove_reference_t<Payload>>;
  using ConstPointer = std::add_pointer_t<std::add_const_t<PayloadValue>>;
  Matcher<PayloadValue> inner_matcher;
  if constexpr (std::is_pointer_v<Payload>) {
    inner_matcher = Eq(*payload);
  } else {
    inner_matcher = Eq(payload);
  }
  return VariantWith<ConstPointer>(Pointee(std::move(inner_matcher)));
}

class MockAutofillDriver : public TestAutofillDriver {
 public:
  using TestAutofillDriver::TestAutofillDriver;
  MockAutofillDriver(const MockAutofillDriver&) = delete;
  MockAutofillDriver& operator=(const MockAutofillDriver&) = delete;
  // Mock methods to enable testability.
  MOCK_METHOD(void,
              RendererShouldAcceptDataListSuggestion,
              (const FieldGlobalId&, const std::u16string&),
              (override));
  MOCK_METHOD(void, RendererShouldClearPreviewedForm, (), (override));
  MOCK_METHOD(void,
              RendererShouldTriggerSuggestions,
              (const FieldGlobalId&, AutofillSuggestionTriggerSource),
              (override));
  MOCK_METHOD(void,
              RendererShouldSetSuggestionAvailability,
              (const FieldGlobalId&, mojom::AutofillSuggestionAvailability),
              (override));
  MOCK_METHOD((base::flat_set<FieldGlobalId>),
              ApplyFormAction,
              (mojom::FormActionType action_type,
               mojom::ActionPersistence action_persistence,
               base::span<const FormFieldData> data,
               const FillId& fill_id,
               bool supports_refill,
               const url::Origin& triggered_origin,
               (const absl::flat_hash_map<FieldGlobalId, FieldType>&)),
              (override));
  MOCK_METHOD(void,
              ApplyFieldAction,
              (mojom::FieldActionType action_type,
               mojom::ActionPersistence action_persistence,
               const FieldGlobalId& field_id,
               const std::u16string& value),
              (override));
};

class MockPaymentsAutofillClient : public payments::TestPaymentsAutofillClient {
 public:
  explicit MockPaymentsAutofillClient(AutofillClient* client)
      : payments::TestPaymentsAutofillClient(client) {}
  ~MockPaymentsAutofillClient() override = default;

  MOCK_METHOD(void,
              ScanCreditCard,
              (CreditCardScanCallback callback),
              (override));
  MOCK_METHOD(void,
              OpenPromoCodeOfferDetailsURL,
              (const GURL& url),
              (override));
};

class MockAutofillClient : public TestAutofillClient {
 public:
  MockAutofillClient() {
    set_payments_autofill_client(
        std::make_unique<MockPaymentsAutofillClient>(this));
  }
  MockAutofillClient(const MockAutofillClient&) = delete;
  MockAutofillClient& operator=(const MockAutofillClient&) = delete;
  MOCK_METHOD(AutofillClient::SuggestionUiSessionId,
              ShowAutofillSuggestions,
              (const AutofillClient::PopupOpenArgs&,
               base::WeakPtr<AutofillSuggestionDelegate>),
              (override));
  MOCK_METHOD(void,
              UpdateAutofillSuggestions,
              (const std::vector<Suggestion>&,
               FillingProduct,
               AutofillSuggestionTriggerSource,
               AutofillSuggestionsIgnoreFocusLoss),
              (override));
  MOCK_METHOD(base::span<const Suggestion>,
              GetAutofillSuggestions,
              (),
              (const override));
  MOCK_METHOD(void,
              UpdateAutofillDataListValues,
              (base::span<const SelectOption> options),
              (override));
  MOCK_METHOD(void,
              HideSuggestions,
              (SuggestionHidingReason, std::optional<FillingProduct>),
              (override));
  MOCK_METHOD(void, OpenGeminiInSidebar, (const std::u16string&), (override));
  MOCK_METHOD(void, ShowAutofillSettings, (SuggestionType), (override));
  MOCK_METHOD(AutofillComposeDelegate*, GetComposeDelegate, (), (override));
  MOCK_METHOD(IdentityCredentialDelegate*,
              GetIdentityCredentialDelegate,
              (),
              (override));
  MOCK_METHOD(void,
              ShowAutofillAiFetchEntityFailureNotification,
              (),
              (override));

  // `IsAutofillTypeBlockedByPolicy` is needed in the mock because it is called
  // by `MayPerformAtMemoryAction` to evaluate enablement.
  MOCK_METHOD(bool,
              IsAutofillTypeBlockedByPolicy,
              (const GURL&, AutofillClient::AutofillPolicyDataCategory),
              (const, override));

  MOCK_METHOD(std::unique_ptr<device_reauth::DeviceAuthenticator>,
              GetDeviceAuthenticator,
              (std::string),
              (const, override));

#if BUILDFLAG(IS_ANDROID)
  MOCK_METHOD(void, ShowAutofillAiLoadingDialog, (), (override));
  MOCK_METHOD(void, DismissAutofillAiLoadingDialog, (), (override));
#endif

#if BUILDFLAG(IS_IOS)
  // Mock the client query ID check.
  bool IsLastQueriedField(FieldGlobalId field_id) override {
    return !last_queried_field_id_ || last_queried_field_id_ == field_id;
  }

  void set_last_queried_field(FieldGlobalId field_id) {
    last_queried_field_id_ = field_id;
  }

 private:
  FieldGlobalId last_queried_field_id_;
#endif
};

class TestCreditCardAccessManager : public CreditCardAccessManager {
 public:
  using CreditCardAccessManager::CreditCardAccessManager;
  void PrepareToFetchCreditCard() override {
    // Do nothing for testing.
  }
  MOCK_METHOD(void,
              FetchCreditCard,
              (const CreditCard*, OnCreditCardFetchedCallback),
              (override));
};

class MockBrowserAutofillManager : public TestBrowserAutofillManager {
 public:
  explicit MockBrowserAutofillManager(AutofillDriver* driver)
      : TestBrowserAutofillManager(driver) {
    test_api(*this).set_credit_card_access_manager(
        std::make_unique<TestCreditCardAccessManager>(this));
    test_api(*this).set_bnpl_manager(
        std::make_unique<NiceMock<MockBnplManager>>(this));
    test_api(*this).set_ai_card_recommendation_manager(
        std::make_unique<NiceMock<MockAiCardRecommendationManager>>(this));
  }
  MockBrowserAutofillManager(const MockBrowserAutofillManager&) = delete;
  MockBrowserAutofillManager& operator=(const MockBrowserAutofillManager&) =
      delete;

  MOCK_METHOD(void,
              UndoAutofill,
              (mojom::ActionPersistence action_persistence,
               const FormGlobalId& form_id,
               const FieldGlobalId& trigger_field_id),
              (override));
  MOCK_METHOD(void,
              FillOrPreviewForm,
              (mojom::ActionPersistence,
               const FormGlobalId&,
               const FieldGlobalId&,
               const FillingPayload&,
               AutofillTriggerSource,
               const base::flat_set<FieldGlobalId>&),
              (override));
  MOCK_METHOD(void,
              FillOrPreviewField,
              (mojom::ActionPersistence,
               mojom::FieldActionType,
               const FormGlobalId&,
               const FieldGlobalId&,
               const std::u16string&,
               FillingProduct,
               std::optional<FieldType>),
              (override));
  MOCK_METHOD(void,
              OnDidFillAddressFormFillingSuggestion,
              (const AutofillProfile&,
               const FormGlobalId&,
               const FieldGlobalId&,
               AutofillTriggerSource),
              (override));
  MOCK_METHOD(void,
              OnDidFillAddressOnTypingSuggestion,
              (const FieldGlobalId&,
               const std::u16string&,
               FieldType,
               const std::string&),
              (override));

  MOCK_METHOD(void, OnSuggestionsHidden, (SuggestionHidingReason), (override));

  MockAiCardRecommendationManager* GetMockAiCardRecommendationManager() {
    return static_cast<MockAiCardRecommendationManager*>(
        &GetAiCardRecommendationManager());
  }
};

class AutofillExternalDelegateTest : public testing::Test,
                                     public WithTestAutofillClientDriverManager<
                                         NiceMock<MockAutofillClient>,
                                         NiceMock<MockAutofillDriver>,
                                         NiceMock<MockBrowserAutofillManager>,
                                         MockPaymentsAutofillClient> {
 public:
  AutofillExternalDelegateTest() {
    scoped_feature_list_.InitWithFeatures({features::kAutofillAtMemory}, {});
  }

 protected:
  void SetUp() override {
    InitAutofillClient();
    mock_personal_context_service_ = std::make_unique<testing::NiceMock<
        personal_context::MockPersonalContextEligibilityService>>();
    ON_CALL(*mock_personal_context_service_, GetEligibilityState())
        .WillByDefault(testing::Return(
            personal_context::PersonalContextEligibilityState::kEligible));
    autofill_client().set_personal_context_eligibility_service(
        mock_personal_context_service_.get());
    autofill_client().GetPrefs()->registry()->RegisterIntegerPref(
        optimization_guide::prefs::kGeminiSettings,
        std::to_underlying(
            optimization_guide::prefs::GeminiSettingsPolicyState::kEnabled));
    autofill_client().GetPrefs()->SetBoolean(
        personal_context::prefs::kPersonalContextInAutofillSettingsToggleStatus,
        true);
    autofill_client().GetPrefs()->SetInteger(
        optimization_guide::prefs::kGeminiSettings,
        std::to_underlying(
            optimization_guide::prefs::GeminiSettingsPolicyState::kEnabled));
    autofill_client().set_entity_data_manager(
        std::make_unique<EntityDataManager>(
            autofill_client().GetPrefs(),
            autofill_client().GetIdentityManager(),
            autofill_client().GetSyncService(),
            webdata_helper_.autofill_webdata_service(),
            /*history_service=*/nullptr,
            /*pcontext_manager=*/nullptr,
            /*strike_database=*/nullptr,
            /*variation_country_code=*/GeoIpCountryCode("US")));
    CreateAutofillDriver();
  }

  void TearDown() override { DestroyAutofillClient(); }

  // Issue an OnQuery call.
  void IssueOnQuery(FormData form_data,
                    const gfx::Rect& caret_bounds = gfx::Rect(),
                    AutofillSuggestionTriggerSource trigger_source =
                        kDefaultSuggestionTriggerSource) {
    queried_form_ = std::move(form_data);
    autofill_manager().OnFormsSeen({queried_form()}, {},
                                   AutofillManagerTestApi::pass_key());
    external_delegate().OnQuery(queried_form(), queried_field(), caret_bounds,
                                trigger_source);
  }

  void IssueOnQuery(test::FormDescription form_description) {
    queried_form_ = test::GetFormData(form_description);
    autofill_manager().AddSeenForm(queried_form(),
                                   test::GetHeuristicTypes(form_description),
                                   test::GetServerTypes(form_description));
    external_delegate().OnQuery(queried_form(), queried_field(), gfx::Rect(),
                                kDefaultSuggestionTriggerSource);
  }

  void IssueOnQuery(const gfx::Rect& caret_bounds,
                    AutofillSuggestionTriggerSource trigger_source =
                        kDefaultSuggestionTriggerSource,
                    FieldType trigger_field_type = NAME_FIRST,
                    const std::string& autocomplete_attribute = "given-name") {
    FormGlobalId form_id = {autofill_driver().GetFrameToken(),
                            test::MakeFormRendererId()};
    FieldGlobalId field_id = test::MakeFieldGlobalId();
    IssueOnQuery(
        test::GetFormData({
            .fields = {{.role = trigger_field_type,
                        .host_frame = field_id.frame_token,
                        .renderer_id = field_id.renderer_id,
                        .autocomplete_attribute = autocomplete_attribute}},
            .host_frame = form_id.frame_token,
            .renderer_id = form_id.renderer_id,
        }),
        caret_bounds, trigger_source);
  }

  void IssueOnQuery(AutofillSuggestionTriggerSource trigger_source =
                        kDefaultSuggestionTriggerSource,
                    FieldType trigger_field_type = NAME_FIRST,
                    const std::string& autocomplete_attribute = "given-name") {
    IssueOnQuery(/*caret_bounds=*/gfx::Rect(), trigger_source,
                 trigger_field_type, autocomplete_attribute);
  }

  void IssueOnQuery(std::vector<SelectOption> datalist_options) {
    FormGlobalId form_id = {autofill_driver().GetFrameToken(),
                            test::MakeFormRendererId()};
    FieldGlobalId field_id = test::MakeFieldGlobalId();
    IssueOnQuery(
        test::GetFormData({
            .fields = {{.role = NAME_FIRST,
                        .host_frame = field_id.frame_token,
                        .renderer_id = field_id.renderer_id,
                        .autocomplete_attribute = "given-name",
                        .datalist_options = std::move(datalist_options)}},
            .host_frame = form_id.frame_token,
            .renderer_id = form_id.renderer_id,
        }),
        /*caret_bounds=*/gfx::Rect(), kDefaultSuggestionTriggerSource);
  }

  // Returns the triggering `AutofillField`. This is the only field in the form
  // created in `IssueOnQuery()`.
  AutofillField& get_triggering_autofill_field() {
    FormStructure& form =
        CHECK_DEREF(test_api(autofill_manager())
                        .FindCachedFormById(queried_form().global_id()));
    return CHECK_DEREF(
        form.GetFieldById(queried_form().fields()[0].global_id()));
  }

  // Adds `entity` and waits for the transaction to complete.
  void AddOrUpdateEntityInstance(const EntityInstance& entity) {
    autofill_client().GetEntityDataManager()->AddOrUpdateEntityInstance(entity);
    webdata_helper().WaitUntilIdle();
  }

  Matcher<const Suggestion&> HasMainText(const std::u16string& text) {
    return testing::Field(&Suggestion::main_text,
                          testing::Field(&Suggestion::Text::value, text));
  }

  Matcher<const Suggestion&> HasMinorText(const std::u16string& text) {
    return testing::Field(
        &Suggestion::minor_texts,
        testing::ElementsAre(testing::Field(&Suggestion::Text::value, text)));
  }

  Matcher<const Suggestion&> HasLabel(const std::u16string& label) {
    return testing::Field(
        &Suggestion::labels,
        testing::ElementsAre(testing::ElementsAre(
            testing::Field(&Suggestion::Text::value, label))));
  }

  // Set up the mock AtMemoryQueryService to return the provided results
  // for a specific query.
  void SetupMockAtMemoryQueryService(const std::u16string& query,
                                     MemorySearchResults results) {
    auto mock_service =
        std::make_unique<testing::NiceMock<MockAtMemoryQueryService>>();
    EXPECT_CALL(*mock_service, Query(Eq(query), _, _, _))
        .WillOnce(base::test::RunOnceCallback<3>(std::move(results)));
    // Inject the mock service into the client.
    autofill_client().set_at_memory_query_service(std::move(mock_service));
  }

  void StartAtMemorySession(
      AutofillSuggestionTriggerSource trigger_source =
          AutofillSuggestionTriggerSource::kAtMemoryTriggerString) {
    // Initialize the delegate's query form and field state.
    IssueOnQuery(trigger_source);
    // Assign a valid session ID to enable suggestion update callbacks.
    autofill_client().set_suggestion_ui_session_id(
        AutofillClient::SuggestionUiSessionId(1));
    // Simulate that the popup is displayed to set up the session and its
    // callbacks.
    external_delegate().OnSuggestionsShown({}, std::nullopt);
  }

  Matcher<const FormGlobalId&> HasQueriedFormId() {
    return Eq(queried_form().global_id());
  }

  Matcher<const FieldGlobalId&> HasQueriedFieldId() {
    return Eq(queried_field().global_id());
  }

  Matcher<const FieldGlobalId&> IsQueriedFieldId() {
    return Eq(queried_field().global_id());
  }

  AutofillExternalDelegate& external_delegate() {
    return *test_api(autofill_manager()).external_delegate();
  }
  PersonalDataManager& pdm() {
    return autofill_client().GetPersonalDataManager();
  }

  const FormData& queried_form() {
    CHECK(!queried_form_.fields().empty());
    return queried_form_;
  }

  const FormFieldData& queried_field() {
    return queried_form().fields().front();
  }

  void OnSuggestionsReturned(const FormFieldData& field,
                             const std::vector<Suggestion>& input_suggestions) {
    external_delegate().OnSuggestionsReturned(field, input_suggestions,
                                              /*prefilled_query=*/{});
  }

  FormData CreateTestFormWithBounds(
      const gfx::RectF& bounds = gfx::RectF(0, 0, 100, 20)) {
    test::FieldDescription field_desc = {
        .role = NAME_FIRST,
        .autocomplete_attribute = "given-name",
    };

    test::FormDescription form_desc;
    form_desc.fields.push_back(field_desc);

    FormData form = test::GetFormData(form_desc);
    std::vector<FormFieldData> fields = form.ExtractFields();
    if (!fields.empty()) {
      fields[0].set_bounds(bounds);
      form.set_fields(std::move(fields));
    }
    return form;
  }

  AutofillWebDataServiceTestHelper& webdata_helper() { return webdata_helper_; }

 private:
  base::test::TaskEnvironment task_environment_;
  test::AutofillUnitTestEnvironment autofill_test_environment_;

  AutofillWebDataServiceTestHelper webdata_helper_{
      std::make_unique<EntityTable>()};

  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<testing::NiceMock<
      personal_context::MockPersonalContextEligibilityService>>
      mock_personal_context_service_;

  // Form containing the triggering field that initialized the external delegate
  // `OnQuery`.
  FormData queried_form_;
};

TEST_F(AutofillExternalDelegateTest, GetMainFillingProduct) {
  IssueOnQuery();

  // Main filling product is not defined before the first call to
  // `OnSuggestionsReturned`.
  EXPECT_EQ(external_delegate().GetMainFillingProduct(), FillingProduct::kNone);

  // Show address suggestion in the popup.
  OnSuggestionsReturned(
      queried_field(), {CreateAutofillSuggestion(SuggestionType::kAddressEntry,
                                                 u"address suggestion"),
                        CreateAutofillSuggestion(SuggestionType::kManageAddress,
                                                 u"manage addresses")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(),
            FillingProduct::kAddress);

  // Show credit card suggestion in the popup.
  OnSuggestionsReturned(
      queried_field(),
      {CreateAutofillSuggestion(SuggestionType::kCreditCardEntry,
                                u"credit card suggestion"),
       CreateAutofillSuggestion(SuggestionType::kManageCreditCard,
                                u"manage payment methods")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(),
            FillingProduct::kCreditCard);

  // Show BNPL suggestion in the popup.
  OnSuggestionsReturned(queried_field(),
                        {CreateAutofillSuggestion(SuggestionType::kBnplEntry,
                                                  u"BNPL suggestion")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(),
            FillingProduct::kCreditCard);

  // Show merchant promo code suggestion in the popup.
  OnSuggestionsReturned(
      queried_field(),
      {CreateAutofillSuggestion(SuggestionType::kMerchantPromoCodeEntry,
                                u"promo code")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(),
            FillingProduct::kMerchantPromoCode);

  // Show IBAN suggestion in the popup.
  OnSuggestionsReturned(
      queried_field(),
      {CreateAutofillSuggestion(SuggestionType::kIbanEntry, u"fill IBAN")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(), FillingProduct::kIban);

  // Show password suggestion in the popup.
  OnSuggestionsReturned(
      queried_field(),
      {CreateAutofillSuggestion(SuggestionType::kPasswordEntry, u"password")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(),
            FillingProduct::kPassword);

  // Show compose suggestion in the popup.
  OnSuggestionsReturned(
      queried_field(),
      {CreateAutofillSuggestion(SuggestionType::kComposeResumeNudge,
                                u"generated text")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(),
            FillingProduct::kCompose);

  // Show only autocomplete suggestion in the popup.
  OnSuggestionsReturned(queried_field(), {CreateAutofillSuggestion(
                                             SuggestionType::kAutocompleteEntry,
                                             u"autocomplete")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(),
            FillingProduct::kAutocomplete);

  // Show only datalist suggestion in the popup.
  OnSuggestionsReturned(
      queried_field(),
      {CreateAutofillSuggestion(SuggestionType::kDatalistEntry, u"datalist")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(),
            FillingProduct::kDataList);

  // Show auxiliary helper suggestion in the popup.
  OnSuggestionsReturned(queried_field(), {CreateAutofillSuggestion(
                                             SuggestionType::kUndo, u"undo")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(), FillingProduct::kNone);

  // Show auxiliary helper suggestion in the popup.
  OnSuggestionsReturned(
      queried_field(),
      {CreateAutofillSuggestion(
          SuggestionType::kInsecureContextPaymentDisabledMessage,
          u"no autofill available")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(), FillingProduct::kNone);

  // Show save and fill suggestion in the popup.
  OnSuggestionsReturned(
      queried_field(),
      {CreateAutofillSuggestion(SuggestionType::kSaveAndFillCreditCardEntry,
                                u"save and fill suggestion")});
  EXPECT_EQ(external_delegate().GetMainFillingProduct(),
            FillingProduct::kCreditCard);
}

// Test that accepting the Autocomplete AtMemory button triggers AtMemory
// dialog.
TEST_F(AutofillExternalDelegateTest, SelectAutocompleteAtMemoryButton) {
  IssueOnQuery();
  EXPECT_CALL(autofill_driver(),
              RendererShouldTriggerSuggestions(
                  queried_field().global_id(),
                  AutofillSuggestionTriggerSource::kAtMemoryTriggerString));
  external_delegate().DidAcceptSuggestion(
      Suggestion(SuggestionType::kAutocompleteAtMemoryButton),
      SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest, AtMemoryDoesNotHideOnEmptySuggestions) {
  IssueOnQuery(AutofillSuggestionTriggerSource::kAtMemoryTriggerString);

  EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);

  // Return empty suggestions.
  OnSuggestionsReturned(queried_field(), {});
}

// Tests that accepting the AtMemory search affordance suggestion does not
// hide the popup.
TEST_F(AutofillExternalDelegateTest,
       AtMemorySearchAffordanceAcceptanceDoesNotHidePopup) {
  IssueOnQuery(AutofillSuggestionTriggerSource::kAtMemoryTriggerString);

  EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);

  external_delegate().DidAcceptSuggestion(
      Suggestion(u"some query", SuggestionType::kAtMemorySearchAffordance),
      SuggestionPosition{.multi_index = {0}});
}

// Tests that accessibility annotator trigger source uses the caret anchor
// type when valid caret bounds are available.
TEST_F(AutofillExternalDelegateTest, AtMemoryUsesCaretAnchorWithValidCaret) {
  gfx::RectF field_bounds(0, 0, 100, 20);
  gfx::Rect caret_bounds(10, 10, 1, 1);
  FormData form = CreateTestFormWithBounds(field_bounds);

  IssueOnQuery(form, caret_bounds,
               AutofillSuggestionTriggerSource::kAtMemoryTriggerString);

  const PopupAnchorType expected_anchor_type =
#if BUILDFLAG(IS_ANDROID)
      PopupAnchorType::kAtMemoryBottomSheet;
#else
      PopupAnchorType::kCaret;
#endif

  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(
                  AllOf(Field(&AutofillClient::PopupOpenArgs::element_bounds,
                              gfx::RectF(caret_bounds)),
                        Field(&AutofillClient::PopupOpenArgs::anchor_type,
                              expected_anchor_type)),
                  _));

  OnSuggestionsReturned(
      queried_field(),
      {CreateAutofillSuggestion(SuggestionType::kAddressEntry, u"suggestion")});
}

// Tests that AtMemory trigger source uses the bottom sheet anchor type.
TEST_F(AutofillExternalDelegateTest, AtMemoryUsesBottomSheetAnchor) {
  gfx::RectF field_bounds(0, 0, 100, 20);
  gfx::Rect empty_caret_bounds;
  FormData form = CreateTestFormWithBounds(field_bounds);

  IssueOnQuery(form, empty_caret_bounds,
               AutofillSuggestionTriggerSource::kAtMemoryTriggerString);

  const PopupAnchorType expected_anchor_type =
#if BUILDFLAG(IS_ANDROID)
      PopupAnchorType::kAtMemoryBottomSheet;
#else
      PopupAnchorType::kField;
#endif

  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(
                  AllOf(Field(&AutofillClient::PopupOpenArgs::element_bounds,
                              field_bounds),
                        Field(&AutofillClient::PopupOpenArgs::anchor_type,
                              expected_anchor_type)),
                  _));

  OnSuggestionsReturned(
      queried_field(),
      {CreateAutofillSuggestion(SuggestionType::kAddressEntry, u"suggestion")});
}

// Tests that accessibility annotator context menu trigger source uses the
// caret anchor type.
TEST_F(AutofillExternalDelegateTest, AtMemoryContextMenuUsesCaretAnchor) {
  gfx::RectF field_bounds(0, 0, 100, 20);
  gfx::Rect caret_bounds(10, 10, 1, 1);
  FormData form = CreateTestFormWithBounds(field_bounds);

  IssueOnQuery(form, caret_bounds,
               AutofillSuggestionTriggerSource::kAtMemoryContextMenu);

  const PopupAnchorType expected_anchor_type =
#if BUILDFLAG(IS_ANDROID)
      PopupAnchorType::kAtMemoryBottomSheet;
#else
      PopupAnchorType::kCaret;
#endif

  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(
                  AllOf(Field(&AutofillClient::PopupOpenArgs::element_bounds,
                              gfx::RectF(caret_bounds)),
                        Field(&AutofillClient::PopupOpenArgs::anchor_type,
                              expected_anchor_type)),
                  _));

  OnSuggestionsReturned(
      queried_field(),
      {CreateAutofillSuggestion(SuggestionType::kAddressEntry, u"suggestion")});
}

TEST_F(AutofillExternalDelegateTest, AtMemoryPopupDisplayed_TypedTrigger) {
  base::HistogramTester histogram_tester;
  StartAtMemorySession(AutofillSuggestionTriggerSource::kAtMemoryTriggerString);
  histogram_tester.ExpectUniqueSample(
      "Autofill.AtMemory.SearchBarDisplayed",
      AutofillMetrics::AtMemoryTriggerSource::kTypedTrigger, 1);
}

TEST_F(AutofillExternalDelegateTest, AtMemoryPopupDisplayed_ContextMenu) {
  base::HistogramTester histogram_tester;
  StartAtMemorySession(AutofillSuggestionTriggerSource::kAtMemoryContextMenu);
  histogram_tester.ExpectUniqueSample(
      "Autofill.AtMemory.SearchBarDisplayed",
      AutofillMetrics::AtMemoryTriggerSource::kContextMenu, 1);
}

TEST_F(AutofillExternalDelegateTest, AtMemoryMetricsRecorder_QuerySubmitted) {
  base::HistogramTester histogram_tester;
  StartAtMemorySession();

  MemorySearchResults search_results(
      MemorySearchStatus::kFinalResponseSuccess,
      {MemorySearchResult(MemoryDataType::kAddressFull, u"Address",
                          u"123 Main St")});
  SetupMockAtMemoryQueryService(u"some query", std::move(search_results));

  external_delegate().OnSearchSubmitted(u"some query");
  external_delegate().OnSuggestionsHidden(SuggestionHidingReason::kTabGone);

  histogram_tester.ExpectUniqueSample("Autofill.AtMemory.QuerySubmitted", true,
                                      1);
  histogram_tester.ExpectUniqueSample("Autofill.AtMemory.SuggestionAccepted",
                                      false, 1);
}

TEST_F(AutofillExternalDelegateTest, AtMemoryMetricsRecorder_NoQuerySubmitted) {
  base::HistogramTester histogram_tester;
  StartAtMemorySession();

  external_delegate().OnSuggestionsHidden(SuggestionHidingReason::kTabGone);

  histogram_tester.ExpectUniqueSample("Autofill.AtMemory.QuerySubmitted", false,
                                      1);
  histogram_tester.ExpectTotalCount("Autofill.AtMemory.SuggestionAccepted", 0);
}

TEST_F(AutofillExternalDelegateTest,
       AtMemoryMetricsRecorder_SuggestionAccepted_True) {
  base::HistogramTester histogram_tester;
  StartAtMemorySession();

  MemorySearchResults search_results(
      MemorySearchStatus::kFinalResponseSuccess,
      {MemorySearchResult(MemoryDataType::kAddressFull, u"Address",
                          u"123 Main St")});
  SetupMockAtMemoryQueryService(u"some query", std::move(search_results));

  external_delegate().OnSearchSubmitted(u"some query");

  Suggestion suggestion(u"some result", SuggestionType::kAtMemorySearchResult);
  suggestion.payload =
      Suggestion::AtMemoryPayload(u"pasted text", MemoryDataType::kAddressFull);

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
  external_delegate().OnSuggestionsHidden(SuggestionHidingReason::kTabGone);

  histogram_tester.ExpectUniqueSample("Autofill.AtMemory.SuggestionAccepted",
                                      true, 1);
}

// Tests that AtMemory search results from first-party sources include metadata
// as child suggestions with source attribution in the flyout menu.
TEST_F(AutofillExternalDelegateTest, AtMemoryFlyoutChildrenFirstPartySources) {
  StartAtMemorySession();

  std::vector<MemorySearchResult> entries;
  MemorySearchResult entry(MemoryDataType::kUnknown, u"Shoe size", u"42");
  entry.metadata_list.emplace_back(MemoryDataType::kUnknown, u"Store",
                                   u"example.com");
  entry.metadata_list.emplace_back(MemoryDataType::kNameFull, u"Name",
                                   u"Marian Paździoch");
  entry.sources.emplace_back(MemoryEntrySourceType::kGmail);
  entries.push_back(std::move(entry));

  MemorySearchResults search_results(MemorySearchStatus::kFinalResponseSuccess,
                                     std::move(entries));

  SetupMockAtMemoryQueryService(u"shoe size", std::move(search_results));

  auto matcher = ElementsAre(AllOf(
      HasMainText(u"42"),
      Field(
          &Suggestion::children,
          ElementsAre(
              AllOf(HasMainText(u"example.com"), HasLabel(u"Store")),
              AllOf(HasMainText(u"Marian Paździoch"), HasLabel(u"Name")),
              Field(&Suggestion::type, SuggestionType::kSeparator),
              AllOf(
                  HasMinorText(l10n_util::GetStringUTF16(
                      IDS_AUTOFILL_AT_MEMORY_SOURCE_ATTRIBUTION_PERSONAL_INTELLIGENCE)),
                  Field(&Suggestion::type,
                        SuggestionType::kAtMemorySourceAttribution)),
              Field(&Suggestion::type, SuggestionType::kSeparator),
              Field(&Suggestion::type,
                    SuggestionType::kManageEnhancedAutofill)))));

  InSequence sequence;
  // The first call notifies the UI that search has started and shows a fetching
  // indicator. The second call provides the actual results.
  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemoryFetching)),
                  _, _, _));
  EXPECT_CALL(autofill_client(), UpdateAutofillSuggestions(matcher, _, _, _));

  external_delegate().OnSearchSubmitted(u"shoe size");
}

// Tests that AtMemory search results from the Autofill source show a management
// option in the flyout menu.
TEST_F(AutofillExternalDelegateTest, AtMemoryFlyoutChildrenAutofillSource) {
  StartAtMemorySession();

  std::vector<MemorySearchResult> entries;
  MemorySearchResult entry(MemoryDataType::kAddressFull, u"Address",
                           u"1600 Amphitheatre Pkwy");
  entry.metadata_list.emplace_back(MemoryDataType::kAddressCity, u"City",
                                   u"Mountain View");
  entry.metadata_list.emplace_back(MemoryDataType::kAddressState, u"State",
                                   u"CA");
  entry.sources.emplace_back(MemoryEntrySourceType::kAutofill);
  entries.push_back(std::move(entry));

  MemorySearchResults search_results(MemorySearchStatus::kFinalResponseSuccess,
                                     std::move(entries));

  SetupMockAtMemoryQueryService(u"addr", std::move(search_results));

  auto matcher = ElementsAre(AllOf(
      HasMainText(u"1600 Amphitheatre Pkwy"),
      Field(&Suggestion::children,
            ElementsAre(AllOf(HasMainText(u"Mountain View"), HasLabel(u"City")),
                        AllOf(HasMainText(u"CA"), HasLabel(u"State")),
                        Field(&Suggestion::type, SuggestionType::kSeparator),
                        AllOf(HasMainText(l10n_util::GetStringUTF16(
                                  IDS_AUTOFILL_AT_MEMORY_MANAGE_CONTACT_INFO)),
                              Field(&Suggestion::type,
                                    SuggestionType::kManageAddress))))));

  InSequence sequence;
  // The first call notifies the UI that search has started and shows a fetching
  // indicator. The second call provides the actual results.
  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemoryFetching)),
                  _, _, _));
  EXPECT_CALL(autofill_client(), UpdateAutofillSuggestions(matcher, _, _, _));

  external_delegate().OnSearchSubmitted(u"addr");
}

// Tests that when a new search is triggered while a previous one is still
// running, the current suggestions are cleared before showing the new search
// results.
TEST_F(AutofillExternalDelegateTest,
       AtMemorySubsequentSearchClearsPreviousSuggestions) {
  StartAtMemorySession();

  std::vector<MemorySearchResult> entries1;
  MemorySearchResult entry(MemoryDataType::kAddressFull, u"Address",
                           u"1600 Amphitheatre Pkwy");
  entries1.push_back(std::move(entry));

  MemorySearchResults search_results1(MemorySearchStatus::kFinalResponseSuccess,
                                      std::move(entries1));

  auto mock_service =
      std::make_unique<testing::NiceMock<MockAtMemoryQueryService>>();
  MockAtMemoryQueryService* mock_service_ptr = mock_service.get();
  autofill_client().set_at_memory_query_service(std::move(mock_service));

  EXPECT_CALL(*mock_service_ptr, Query(std::u16string_view(u"addr"), _, _, _))
      .WillOnce(base::test::RunOnceCallback<3>(std::move(search_results1)));

  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemoryFetching)),
                  _, _, _));
  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemorySearchResult)),
                  _, _, _));

  external_delegate().OnSearchSubmitted(u"addr");

  std::vector<Suggestion> suggestions1 = {Suggestion(
      u"1600 Amphitheatre Pkwy", SuggestionType::kAtMemorySearchResult)};
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(testing::Return(suggestions1));

  // Second query. We use SaveArg to capture the callback and prevent it from
  // running immediately.
  base::RepeatingCallback<void(MemorySearchResults)> received_callback;
  EXPECT_CALL(*mock_service_ptr, Query(std::u16string_view(u"addr2"), _, _, _))
      .WillOnce(testing::SaveArg<3>(&received_callback));

  // We expect that UpdateAutofillSuggestions IS called when the second search
  // starts, which shows the fetching suggestion.
  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemoryFetching)),
                  _, _, _));

  external_delegate().OnSearchSubmitted(u"addr2");

  // Verify that expectations are met before we proceed.
  testing::Mock::VerifyAndClearExpectations(&autofill_client());

  // Now simulate results arriving for the second query.
  std::vector<MemorySearchResult> entries2;
  entries2.emplace_back(MemoryDataType::kAddressFull, u"Address",
                        u"1600 Amphitheatre Pkwy NW");
  MemorySearchResults search_results2(MemorySearchStatus::kFinalResponseSuccess,
                                      std::move(entries2));

  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemorySearchResult)),
                  _, _, _));
  received_callback.Run(std::move(search_results2));
}

// Tests that when a partial response is received, the controller continues
// to accept subsequent responses for the same query.
TEST_F(AutofillExternalDelegateTest, AtMemoryPartialResponseKeepsSearching) {
  StartAtMemorySession();

  auto mock_service =
      std::make_unique<testing::NiceMock<MockAtMemoryQueryService>>();
  MockAtMemoryQueryService* mock_service_ptr = mock_service.get();
  autofill_client().set_at_memory_query_service(std::move(mock_service));

  base::RepeatingCallback<void(MemorySearchResults)> received_callback;
  EXPECT_CALL(*mock_service_ptr, Query(std::u16string_view(u"addr"), _, _, _))
      .WillOnce(testing::SaveArg<3>(&received_callback));

  // Trigger the search, which shows the fetching suggestion.
  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemoryFetching)),
                  _, _, _));
  external_delegate().OnSearchSubmitted(u"addr");

  // Simulate first result arriving with kPartialResponseSuccess.
  std::vector<MemorySearchResult> entries1;
  entries1.emplace_back(MemoryDataType::kAddressFull, u"Address",
                        u"1600 Amphitheatre Pkwy");
  MemorySearchResults search_results1(
      MemorySearchStatus::kPartialResponseSuccess, std::move(entries1));

  // We expect that UpdateAutofillSuggestions IS called with these results.
  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemorySearchResult)),
                  _, _, _));

  received_callback.Run(std::move(search_results1));

  // Verify expectations so far.
  testing::Mock::VerifyAndClearExpectations(&autofill_client());

  // Simulate second results arriving for the same query (e.g. final results).
  std::vector<MemorySearchResult> entries2;
  entries2.emplace_back(MemoryDataType::kAddressFull, u"Address",
                        u"1600 Amphitheatre Pkwy NW");
  MemorySearchResults search_results2(MemorySearchStatus::kFinalResponseSuccess,
                                      std::move(entries2));

  // We expect that UpdateAutofillSuggestions IS called AGAIN with the new
  // results, because the previous response was only a partial success.
  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemorySearchResult)),
                  _, _, _));

  received_callback.Run(std::move(search_results2));
}

// Tests that when a non-partial response (e.g., final success) is received,
// the controller stops accepting subsequent responses for the same query.
TEST_F(AutofillExternalDelegateTest, AtMemoryFinalResponseStopsSearching) {
  StartAtMemorySession();

  auto mock_service =
      std::make_unique<testing::NiceMock<MockAtMemoryQueryService>>();
  MockAtMemoryQueryService* mock_service_ptr = mock_service.get();
  autofill_client().set_at_memory_query_service(std::move(mock_service));

  base::RepeatingCallback<void(MemorySearchResults)> received_callback;
  EXPECT_CALL(*mock_service_ptr, Query(std::u16string_view(u"addr"), _, _, _))
      .WillOnce(testing::SaveArg<3>(&received_callback));

  // Trigger the search, which shows the fetching suggestion.
  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemoryFetching)),
                  _, _, _));
  external_delegate().OnSearchSubmitted(u"addr");

  // Simulate first result arriving with kFinalResponseSuccess.
  std::vector<MemorySearchResult> entries1;
  entries1.emplace_back(MemoryDataType::kAddressFull, u"Address",
                        u"1600 Amphitheatre Pkwy");
  MemorySearchResults search_results1(MemorySearchStatus::kFinalResponseSuccess,
                                      std::move(entries1));

  // We expect that UpdateAutofillSuggestions IS called with these results.
  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemorySearchResult)),
                  _, _, _));

  received_callback.Run(std::move(search_results1));

  // Verify expectations so far.
  testing::Mock::VerifyAndClearExpectations(&autofill_client());

  // Simulate second results arriving for the same query.
  std::vector<MemorySearchResult> entries2;
  entries2.emplace_back(MemoryDataType::kAddressFull, u"Address",
                        u"1600 Amphitheatre Pkwy NW");
  MemorySearchResults search_results2(MemorySearchStatus::kFinalResponseSuccess,
                                      std::move(entries2));

  // We expect that UpdateAutofillSuggestions is NOT called because the
  // previous response was a final success and stopped the search.
  EXPECT_CALL(autofill_client(), UpdateAutofillSuggestions).Times(0);

  received_callback.Run(std::move(search_results2));
}

// Tests that if the user clears the filter, any late arriving responses from
// previous queries are ignored.
TEST_F(AutofillExternalDelegateTest,
       AtMemoryLateResponseIgnoredIfFilterCleared) {
  StartAtMemorySession();

  auto mock_service =
      std::make_unique<testing::NiceMock<MockAtMemoryQueryService>>();
  MockAtMemoryQueryService* mock_service_ptr = mock_service.get();
  autofill_client().set_at_memory_query_service(std::move(mock_service));

  base::RepeatingCallback<void(MemorySearchResults)> received_callback;
  EXPECT_CALL(*mock_service_ptr, Query(std::u16string_view(u"addr"), _, _, _))
      .WillOnce(testing::SaveArg<3>(&received_callback));

  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemoryFetching)),
                  _, _, _));
  external_delegate().OnSearchSubmitted(u"addr");

  // Now user clears the filter.
  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(testing::IsEmpty(), _, _, _));
  external_delegate().OnFilterChanged(u"");

  // Now simulate late results arriving for the first query.
  std::vector<MemorySearchResult> entries;
  entries.emplace_back(MemoryDataType::kAddressFull, u"Address",
                       u"1600 Amphitheatre Pkwy");
  MemorySearchResults search_results(MemorySearchStatus::kFinalResponseSuccess,
                                     std::move(entries));

  // We expect that UpdateAutofillSuggestions is NOT called with these results.
  EXPECT_CALL(autofill_client(), UpdateAutofillSuggestions).Times(0);

  received_callback.Run(std::move(search_results));
}

// Tests that results from a stale query (interrupted by a new query) are
// ignored and do not update the suggestions.
TEST_F(AutofillExternalDelegateTest, AtMemoryStaleResponseIgnored) {
  StartAtMemorySession();

  auto mock_service =
      std::make_unique<testing::NiceMock<MockAtMemoryQueryService>>();
  MockAtMemoryQueryService* mock_service_ptr = mock_service.get();
  autofill_client().set_at_memory_query_service(std::move(mock_service));

  base::RepeatingCallback<void(MemorySearchResults)> received_callback1;
  EXPECT_CALL(*mock_service_ptr, Query(std::u16string_view(u"addr1"), _, _, _))
      .WillOnce(testing::SaveArg<3>(&received_callback1));

  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemoryFetching)),
                  _, _, _));
  external_delegate().OnSearchSubmitted(u"addr1");

  // Trigger second search before first one completes.
  base::RepeatingCallback<void(MemorySearchResults)> received_callback2;
  EXPECT_CALL(*mock_service_ptr, Query(std::u16string_view(u"addr2"), _, _, _))
      .WillOnce(testing::SaveArg<3>(&received_callback2));

  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemoryFetching)),
                  _, _, _));

  external_delegate().OnSearchSubmitted(u"addr2");

  // Now simulate results arriving for the FIRST query.
  std::vector<MemorySearchResult> entries1;
  entries1.emplace_back(MemoryDataType::kAddressFull, u"Address",
                        u"1600 Amphitheatre Pkwy");
  MemorySearchResults search_results1(MemorySearchStatus::kFinalResponseSuccess,
                                      std::move(entries1));

  // We expect that UpdateAutofillSuggestions is NOT called with results from
  // query 1.
  EXPECT_CALL(autofill_client(), UpdateAutofillSuggestions).Times(0);

  received_callback1.Run(std::move(search_results1));

  // Verify expectations before we proceed.
  testing::Mock::VerifyAndClearExpectations(&autofill_client());

  // Now simulate results arriving for the SECOND query.
  std::vector<MemorySearchResult> entries2;
  entries2.emplace_back(MemoryDataType::kAddressFull, u"Address",
                        u"1600 Amphitheatre Pkwy NW");
  MemorySearchResults search_results2(MemorySearchStatus::kFinalResponseSuccess,
                                      std::move(entries2));

  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::type,
                                    SuggestionType::kAtMemorySearchResult)),
                  _, _, _));
  received_callback2.Run(std::move(search_results2));
}

class AutofillExternalDelegateAutoSuggestInactivityTest
    : public AutofillExternalDelegateTest,
      public testing::WithParamInterface<bool> {
 public:
  AutofillExternalDelegateAutoSuggestInactivityTest() {
    feature_list_.InitWithFeatureState(
        features::kAutofillNewSuggestionGeneration, GetParam());
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         AutofillExternalDelegateAutoSuggestInactivityTest,
                         testing::Bool());

// Test that selecting the AutoSuggest inactivity triggers the AtMemory dialog.
TEST_P(AutofillExternalDelegateAutoSuggestInactivityTest,
       SelectAutoSuggestInactivity) {
  IssueOnQuery();
  EXPECT_CALL(autofill_driver(),
              RendererShouldTriggerSuggestions(
                  queried_field().global_id(),
                  AutofillSuggestionTriggerSource::kAtMemoryTriggerString));
  external_delegate().DidAcceptSuggestion(
      Suggestion(SuggestionType::kAtMemoryInactivityNudge),
      SuggestionPosition{.multi_index = {0}});
}

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Tests that when a remote query returns kUnsupportedQuery, the delegate shows
// a specific suggestion offering to open Gemini.
TEST_F(AutofillExternalDelegateTest, AtMemoryRemoteQuery_UnsupportedQuery) {
  autofill_client().set_is_glic_enabled(true);
  StartAtMemorySession();

  SetupMockAtMemoryQueryService(u"shoe size",
                                {MemorySearchStatus::kUnsupportedQuery, {}});

  EXPECT_CALL(autofill_client(), UpdateAutofillSuggestions)
      .WillOnce(testing::Return())
      .WillOnce([this](const std::vector<Suggestion>& suggestions,
                       FillingProduct product,
                       AutofillSuggestionTriggerSource source,
                       AutofillSuggestionsIgnoreFocusLoss ignore) {
        EXPECT_THAT(
            suggestions,
            testing::ElementsAre(testing::AllOf(
                HasMainText(l10n_util::GetStringUTF16(
                    IDS_AUTOFILL_AT_MEMORY_UNSUPPORTED_QUERY_TITLE)),
                HasLabel(l10n_util::GetStringUTF16(
                    IDS_AUTOFILL_AT_MEMORY_UNSUPPORTED_QUERY_DESCRIPTION)),
                testing::Field(&Suggestion::type,
                               SuggestionType::kAtMemoryOpenGemini),
                testing::Field(&Suggestion::icon, Suggestion::Icon::kSpark))));
      });

  external_delegate().OnSearchSubmitted(u"shoe size");
}
#endif

// Tests that when a remote query returns no entries and status
// kFinalResponseSuccess, the delegate shows a "No data" suggestion.
TEST_F(AutofillExternalDelegateTest, AtMemoryRemoteQuery_NoData) {
  StartAtMemorySession();

  SetupMockAtMemoryQueryService(
      u"shoe size", {MemorySearchStatus::kFinalResponseSuccess, {}});

  EXPECT_CALL(autofill_client(), UpdateAutofillSuggestions)
      .WillOnce(testing::Return())
      .WillOnce([this](const std::vector<Suggestion>& suggestions,
                       FillingProduct product,
                       AutofillSuggestionTriggerSource source,
                       AutofillSuggestionsIgnoreFocusLoss ignore) {
        EXPECT_THAT(
            suggestions,
            testing::ElementsAre(testing::AllOf(
                HasMainText(
                    l10n_util::GetStringUTF16(IDS_AUTOFILL_AT_MEMORY_NO_DATA)),
                testing::Field(&Suggestion::type,
                               SuggestionType::kAtMemorySearchResult),
                testing::Field(&Suggestion::icon, Suggestion::Icon::kSadTab),
                testing::Field(
                    &Suggestion::acceptability,
                    Suggestion::Acceptability::kUnselectableAndUnacceptable))));
      });

  external_delegate().OnSearchSubmitted(u"shoe size");
}

TEST_F(AutofillExternalDelegateTest, AtMemoryRemoteQuery_NoConnection) {
  StartAtMemorySession();

  SetupMockAtMemoryQueryService(u"shoe size",
                                {MemorySearchStatus::kNoConnectionFailure, {}});

  EXPECT_CALL(autofill_client(), UpdateAutofillSuggestions)
      .WillOnce(Return())
      .WillOnce([this](const std::vector<Suggestion>& suggestions,
                       FillingProduct product,
                       AutofillSuggestionTriggerSource source,
                       AutofillSuggestionsIgnoreFocusLoss ignore) {
        EXPECT_THAT(
            suggestions,
            ElementsAre(AllOf(
                HasMainText(u"shoe size"),
                Field(&Suggestion::type, SuggestionType::kAtMemoryNoConnection),
                Field(&Suggestion::labels,
                      ElementsAre(ElementsAre(
                          Suggestion::Text(l10n_util::GetStringUTF16(
                              IDS_AUTOFILL_AT_MEMORY_NO_CONNECTION))))),
                Field(&Suggestion::icon, Suggestion::Icon::kSadTab),
                Field(
                    &Suggestion::acceptability,
                    Suggestion::Acceptability::kUnselectableAndUnacceptable))));
      });

  external_delegate().OnSearchSubmitted(u"shoe size");
}

class AutofillExternalDelegateAtMemoryGenericErrorTest
    : public AutofillExternalDelegateTest,
      public WithParamInterface<MemorySearchStatus> {};

INSTANTIATE_TEST_SUITE_P(All,
                         AutofillExternalDelegateAtMemoryGenericErrorTest,
                         Values(MemorySearchStatus::kInferenceFailure,
                                MemorySearchStatus::kInternalFailure));

// Tests that when a remote query returns no entries and an internal or
// inference failure status, the delegate shows a generic error suggestion.
TEST_P(AutofillExternalDelegateAtMemoryGenericErrorTest,
       AtMemoryRemoteQuery_GenericError) {
  StartAtMemorySession();

  SetupMockAtMemoryQueryService(u"shoe size", {GetParam(), {}});

  EXPECT_CALL(autofill_client(), UpdateAutofillSuggestions)
      .WillOnce(Return())
      .WillOnce([this](const std::vector<Suggestion>& suggestions,
                       FillingProduct product,
                       AutofillSuggestionTriggerSource source,
                       AutofillSuggestionsIgnoreFocusLoss ignore) {
        EXPECT_THAT(
            suggestions,
            ElementsAre(AllOf(
                HasMainText(l10n_util::GetStringUTF16(
                    IDS_AUTOFILL_AT_MEMORY_GENERIC_ERROR)),
                Field(&Suggestion::type, SuggestionType::kAtMemoryGenericError),
                Field(&Suggestion::icon, Suggestion::Icon::kSadTab),
                Field(
                    &Suggestion::acceptability,
                    Suggestion::Acceptability::kUnselectableAndUnacceptable))));
      });

  external_delegate().OnSearchSubmitted(u"shoe size");
}

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Tests that when the "Open Gemini" suggestion is accepted, it triggers
// opening Gemini in the sidebar.
TEST_F(AutofillExternalDelegateTest, AtMemoryAcceptOpenGeminiSuggestion) {
  IssueOnQuery();

  Suggestion suggestion(SuggestionType::kAtMemoryOpenGemini);
  suggestion.payload = Suggestion::OpenGeminiPayload(u"test prompt");

  EXPECT_CALL(autofill_client(),
              OpenGeminiInSidebar(std::u16string(u"test prompt")));
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}
#endif

// Test that our external delegate called the virtual methods at the right time.
TEST_F(AutofillExternalDelegateTest, TestExternalDelegateVirtualCalls) {
  IssueOnQuery();

  const auto kExpectedSuggestions =
      SuggestionVectorIdsAre(SuggestionType::kAddressEntry);
  EXPECT_CALL(
      autofill_client(),
      ShowAutofillSuggestions(PopupOpenArgsAre(kExpectedSuggestions), _));

  // This should call ShowAutofillSuggestions.
  const AutofillProfile profile = test::GetFullProfile();
  pdm().address_data_manager().AddProfile(profile);
  std::vector<Suggestion> autofill_item = {
      Suggestion(SuggestionType::kAddressEntry)};
  autofill_item[0].payload =
      Suggestion::AutofillProfilePayload(Suggestion::Guid(profile.guid()));
  OnSuggestionsReturned(queried_field(), autofill_item);

  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(profile), _, _));
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));

  // This should trigger a call to hide the popup since we've selected an
  // option.
  external_delegate().DidAcceptSuggestion(
      autofill_item[0], SuggestionPosition{.multi_index = {0}});
}

// Test that the field label is passed to the SingleFieldFillRouter when an
// autocomplete suggestion is removed.
TEST_F(AutofillExternalDelegateTest, RemoveAutocompleteSuggestion_LabelPassed) {
  base::test::ScopedFeatureList scoped_feature_list{
      features::kAutofillLabelSensitiveAutocomplete};
  const std::u16string kLabel = u"Field Label";
  const std::u16string kName = u"Some Field Name";
  const std::u16string kValue = u"Some Value";

  test::FormDescription form_description = {.fields = {{.role = NAME_FIRST,
                                                        .label = kLabel,
                                                        .name = kName,
                                                        .value = kValue}}};
  IssueOnQuery(form_description);

  const Suggestion suggestion = CreateAutofillSuggestion(
      SuggestionType::kAutocompleteEntry, kValue,
      AutocompleteSearchResultLabelSensitive(kValue, MatchingType::kUnknown,
                                             kName, kLabel, /*count=*/0));
  auto mock_single_field_fill_router =
      std::make_unique<MockSingleFieldFillRouter>(
          autofill_client().GetAutocompleteHistoryManager(), nullptr, nullptr);
  EXPECT_CALL(*mock_single_field_fill_router,
              OnRemoveCurrentSingleFieldSuggestion(
                  kName, kLabel, kValue, SuggestionType::kAutocompleteEntry));
  autofill_client().set_single_field_fill_router(
      std::move(mock_single_field_fill_router));

  external_delegate().RemoveSuggestion(suggestion);
}

// Test that data list elements for a node will appear in the Autofill popup.
TEST_F(AutofillExternalDelegateTest, ExternalDelegateDataList) {
  std::vector<SelectOption> data_list_items;
  data_list_items.emplace_back();

  EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(SizeIs(1)));
  IssueOnQuery(data_list_items);

  // This should call ShowAutofillSuggestions.
  const auto kExpectedSuggestions =
      SuggestionVectorIdsAre(SuggestionType::kDatalistEntry,
#if !BUILDFLAG(IS_ANDROID)
                             SuggestionType::kSeparator,
#endif
                             SuggestionType::kAddressEntry);
  EXPECT_CALL(
      autofill_client(),
      ShowAutofillSuggestions(PopupOpenArgsAre(kExpectedSuggestions), _));
  std::vector<Suggestion> autofill_item;
  autofill_item.emplace_back(/*main_text=*/u"", SuggestionType::kAddressEntry);
  OnSuggestionsReturned(queried_field(), autofill_item);

  // Try calling OnSuggestionsReturned with no Autofill values and ensure
  // the datalist items are still shown.
  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(PopupOpenArgsAre(SuggestionVectorIdsAre(
                                          SuggestionType::kDatalistEntry)),
                                      _));
  autofill_item.clear();
  OnSuggestionsReturned(queried_field(), autofill_item);
}

// Test that datalist values can get updated while a popup is showing.
TEST_F(AutofillExternalDelegateTest, UpdateDataListWhileShowingPopup) {
  EXPECT_CALL(autofill_client(), ShowAutofillSuggestions).Times(0);

  // Make sure just setting the data list values doesn't cause the popup to
  // appear.
  std::vector<SelectOption> data_list_items;
  data_list_items.emplace_back();

  EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(SizeIs(1)));
  IssueOnQuery(data_list_items);

  // Ensure the popup is displayed.
  const auto kExpectedSuggestions =
      SuggestionVectorIdsAre(SuggestionType::kDatalistEntry,
#if !BUILDFLAG(IS_ANDROID)
                             SuggestionType::kSeparator,
#endif
                             SuggestionType::kAddressEntry);
  EXPECT_CALL(
      autofill_client(),
      ShowAutofillSuggestions(PopupOpenArgsAre(kExpectedSuggestions), _));
  std::vector<Suggestion> autofill_item;
  autofill_item.emplace_back(SuggestionType::kAddressEntry);
  OnSuggestionsReturned(queried_field(), autofill_item);

  // This would normally get called from ShowAutofillSuggestions, but it is
  // mocked so we need to call OnSuggestionsShown ourselves.
  external_delegate().OnSuggestionsShown(autofill_item, std::nullopt);

  // Update the current data list and ensure the popup is updated.
  data_list_items.emplace_back();

  EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(SizeIs(2)));
  IssueOnQuery(data_list_items);
}

// Test that we _don't_ de-dupe autofill values against datalist values. We
// keep both with a separator.
TEST_F(AutofillExternalDelegateTest, DuplicateAutofillDatalistValues) {
  std::vector<SelectOption> datalist{{.value = u"Rick", .text = u"Deckard"},
                                     {.value = u"Beyonce", .text = u"Knowles"}};
  EXPECT_CALL(autofill_client(),
              UpdateAutofillDataListValues(
                  ElementsAre(AllOf(Field(&SelectOption::value, u"Rick"),
                                    Field(&SelectOption::text, u"Deckard")),
                              AllOf(Field(&SelectOption::value, u"Beyonce"),
                                    Field(&SelectOption::text, u"Knowles")))));
  IssueOnQuery(datalist);

  const auto kExpectedSuggestions = SuggestionVectorIdsAre(
      SuggestionType::kDatalistEntry, SuggestionType::kDatalistEntry,
#if !BUILDFLAG(IS_ANDROID)
      SuggestionType::kSeparator,
#endif
      SuggestionType::kAddressEntry);
  EXPECT_CALL(
      autofill_client(),
      ShowAutofillSuggestions(PopupOpenArgsAre(kExpectedSuggestions), _));

  // Have an Autofill item that is identical to one of the datalist entries.
  std::vector<Suggestion> autofill_item;
  autofill_item.emplace_back(SuggestionType::kAddressEntry);
  autofill_item[0].main_text =
      Suggestion::Text(u"Rick", Suggestion::Text::IsPrimary(true));
  autofill_item[0].labels = {{Suggestion::Text(u"Deckard")}};
  OnSuggestionsReturned(queried_field(), autofill_item);
}

// Test that we de-dupe autocomplete values against datalist values, keeping the
// latter in case of a match.
TEST_F(AutofillExternalDelegateTest, DuplicateAutocompleteDatalistValues) {
  std::vector<SelectOption> datalist{{.value = u"Rick", .text = u"Deckard"},
                                     {.value = u"Beyonce", .text = u"Knowles"}};
  EXPECT_CALL(autofill_client(),
              UpdateAutofillDataListValues(
                  ElementsAre(AllOf(Field(&SelectOption::value, u"Rick"),
                                    Field(&SelectOption::text, u"Deckard")),
                              AllOf(Field(&SelectOption::value, u"Beyonce"),
                                    Field(&SelectOption::text, u"Knowles")))));
  IssueOnQuery(datalist);

  const auto kExpectedSuggestions = SuggestionVectorIdsAre(
      // We are expecting only two data list entries.
      SuggestionType::kDatalistEntry, SuggestionType::kDatalistEntry,
#if !BUILDFLAG(IS_ANDROID)
      SuggestionType::kSeparator,
#endif
      SuggestionType::kAutocompleteEntry);
  EXPECT_CALL(
      autofill_client(),
      ShowAutofillSuggestions(PopupOpenArgsAre(kExpectedSuggestions), _));

  // Have an Autocomplete item that is identical to one of the datalist entries
  // and one that is distinct.
  std::vector<Suggestion> autocomplete_items;
  autocomplete_items.emplace_back(SuggestionType::kAutocompleteEntry);
  autocomplete_items[0].main_text =
      Suggestion::Text(u"Rick", Suggestion::Text::IsPrimary(true));
  autocomplete_items.emplace_back(SuggestionType::kAutocompleteEntry);
  autocomplete_items[1].main_text =
      Suggestion::Text(u"Cain", Suggestion::Text::IsPrimary(true));
  OnSuggestionsReturned(queried_field(), autocomplete_items);
}

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
    BUILDFLAG(IS_CHROMEOS)
// Test that `BnplManager::OnCreditCardSuggestionsShown` will not be called if
// the suggestion list doesn't contain a credit card entry.
TEST_F(AutofillExternalDelegateTest,
       BnplSuggestionsNotShownWithoutCreditCardEntry) {
  EXPECT_CALL(*autofill_manager().GetPaymentsBnplManager(),
              OnCreditCardSuggestionsShown)
      .Times(0);

  const std::vector<Suggestion> suggestions = {
      CreateAutofillSuggestion(SuggestionType::kAddressEntry),
      CreateAutofillSuggestion(SuggestionType::kSeparator),
      CreateAutofillSuggestion(SuggestionType::kManageCreditCard)};

  external_delegate().OnSuggestionsShown(suggestions, std::nullopt);
}

// Test that `BnplManager::OnCreditCardSuggestionsShown` will be called if the
// suggestion list contains a credit card entry.
TEST_F(AutofillExternalDelegateTest, BnplSuggestionsShownWithCreditCardEntry) {
  EXPECT_CALL(*autofill_manager().GetPaymentsBnplManager(),
              OnCreditCardSuggestionsShown);

  const std::vector<Suggestion> suggestions = {
      CreateAutofillSuggestion(SuggestionType::kCreditCardEntry),
      CreateAutofillSuggestion(SuggestionType::kSeparator),
      CreateAutofillSuggestion(SuggestionType::kManageCreditCard)};

  external_delegate().OnSuggestionsShown(suggestions, std::nullopt);
}

// Tests that when suggestions are hidden, the reason is correctly forwarded to
// the autofill manager.
TEST_F(AutofillExternalDelegateTest,
       OnSuggestionsHidden_NotifiesAutofillManager) {
  IssueOnQuery();

  SuggestionHidingReason reason = SuggestionHidingReason::kFocusChanged;

  EXPECT_CALL(autofill_manager(), OnSuggestionsHidden(reason));

  external_delegate().OnSuggestionsHidden(reason);
}

// Tests that the Autofill delegate fills a form with a VCN when a suggestion
// containing a BNPL entry is accepted, and the user completes the flow.
TEST_F(AutofillExternalDelegateTest, AcceptedBnplEntry_FormIsFilled) {
  IssueOnQuery();
  CreditCard card = test::GetVirtualCard();
  card.set_issuer_id(kBnplAffirmIssuerId);

  const std::optional<int64_t> expected_amount = 50'000'000;

  EXPECT_CALL(*autofill_manager().GetPaymentsBnplManager(),
              OnUserDecisionToUseBnpl(expected_amount, _))
      .WillOnce(RunOnceCallback<1>(card));
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(card),
                        AutofillTriggerSource::kPopup, _));

  Suggestion::PaymentsPayload payments_payload;
  payments_payload.extracted_amount_in_micros = expected_amount;
  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kBnplEntry,
                               /*main_text_value=*/u"BNPL suggestion",
                               payments_payload),
      {.multi_index = {0}});
}

// Tests that accepting a BNPL entry when PayNowPayLater tabs are enabled
// delegates to `BnplManager::OnIssuerAccepted` and does not hide the
// suggestions popup immediately.
TEST_F(AutofillExternalDelegateTest,
       AcceptedBnplEntry_PayNowPayLaterEnabled_CallsOnIssuerAccepted) {
  base::test::ScopedFeatureList scoped_feature_list{
      features::kAutofillEnablePayNowPayLaterTabs};

  IssueOnQuery();

  Suggestion suggestion(SuggestionType::kBnplEntry);
  BnplIssuer test_issuer = test::GetTestLinkedBnplIssuer();
  suggestion.payload = Suggestion::BnplIssuer(test_issuer);

  EXPECT_CALL(*autofill_manager().GetPaymentsBnplManager(),
              OnIssuerAccepted(test_issuer));

  EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

// Tests that accepting a BNPL entry when PayNowPayLater tabs are disabled
// delegates to `BnplManager::OnUserDecisionToUseBnpl` and hides the suggestions
// popup.
TEST_F(AutofillExternalDelegateTest,
       AcceptedBnplEntry_PayNowPayLaterEnabled_CallsOnUserDecisionToUseBnpl) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      features::kAutofillEnablePayNowPayLaterTabs);

  IssueOnQuery();

  const std::optional<int64_t> expected_amount = 50'000'000;
  EXPECT_CALL(*autofill_manager().GetPaymentsBnplManager(),
              OnUserDecisionToUseBnpl(expected_amount, _));
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));

  Suggestion::PaymentsPayload payments_payload;
  payments_payload.extracted_amount_in_micros = expected_amount;
  Suggestion suggestion = CreateAutofillSuggestion(
      SuggestionType::kBnplEntry, /*main_text_value=*/u"BNPL suggestion",
      payments_payload);

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

// Tests that `prefer_prev_arrow_side_on_suggestions_update` is true when
// tabbed popup is shown.
TEST_F(AutofillExternalDelegateTest, ShowTabbedPopup_PreferPrevArrowSide) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAutofillEnableAmountExtraction,
                            features::kAutofillEnableBuyNowPayLaterSyncing,
                            features::kAutofillEnableBuyNowPayLater,
                            features::kAutofillEnableAiBasedAmountExtraction,
                            features::kAutofillEnablePayNowPayLaterTabs},
      /*disabled_features=*/{});

  TestPaymentsDataManager& paydm =
      static_cast<TestPaymentsDataManager&>(pdm().payments_data_manager());
  paydm.AddCreditCard(test::GetCreditCard());
  paydm.AddBnplIssuer(test::GetTestLinkedBnplIssuer());

  ON_CALL(*static_cast<MockAutofillOptimizationGuideDecider*>(
              autofill_client().GetAutofillOptimizationGuideDecider()),
          IsUrlEligibleForBnplIssuer)
      .WillByDefault(Return(true));

  test::FormDescription form_description = {
      .fields = {
          {.role = CREDIT_CARD_NUMBER, .heuristic_type = CREDIT_CARD_NUMBER}}};
  IssueOnQuery(form_description);

  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(
                  Field("prefer_prev_arrow_side_on_suggestions_update",
                        &AutofillClient::PopupOpenArgs::
                            prefer_prev_arrow_side_on_suggestions_update,
                        true),
                  _));

  OnSuggestionsReturned(queried_field(),
                        {Suggestion(SuggestionType::kCreditCardEntry)});
}

// Tests that `show_tabbed_popup` is not present when the main filling
// product is not a credit card (e.g., an Address field).
TEST_F(AutofillExternalDelegateTest, ShowTabbedPopup_NotCreditCard) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAutofillEnableAmountExtraction,
                            features::kAutofillEnableBuyNowPayLaterSyncing,
                            features::kAutofillEnableBuyNowPayLater,
                            features::kAutofillEnableAiBasedAmountExtraction,
                            features::kAutofillEnablePayNowPayLaterTabs},
      /*disabled_features=*/{});

  TestPaymentsDataManager& paydm =
      static_cast<TestPaymentsDataManager&>(pdm().payments_data_manager());
  paydm.AddCreditCard(test::GetCreditCard());
  paydm.AddBnplIssuer(test::GetTestLinkedBnplIssuer());

  ON_CALL(*static_cast<MockAutofillOptimizationGuideDecider*>(
              autofill_client().GetAutofillOptimizationGuideDecider()),
          IsUrlEligibleForBnplIssuer)
      .WillByDefault(Return(true));

  test::FormDescription form_description = {
      .fields = {{.role = NAME_FIRST, .heuristic_type = NAME_FIRST}}};
  IssueOnQuery(form_description);

  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(
                  AllOf(PopupOpenArgsAre(SuggestionVectorIdsAre(
                            SuggestionType::kAddressEntry)),
                        Field("show_tabbed_popup",
                              &AutofillClient::PopupOpenArgs::show_tabbed_popup,
                              false)),
                  _));

  OnSuggestionsReturned(queried_field(),
                        {Suggestion(SuggestionType::kAddressEntry)});
}

// Tests that `show_tabbed_popup` is `kBnpl` when the main filling
// product is a credit card and the field is a credit card number field.
TEST_F(AutofillExternalDelegateTest, ShowTabbedPopup_EligibleFieldType) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAutofillEnableAmountExtraction,
                            features::kAutofillEnableBuyNowPayLaterSyncing,
                            features::kAutofillEnableBuyNowPayLater,
                            features::kAutofillEnableAiBasedAmountExtraction,
                            features::kAutofillEnablePayNowPayLaterTabs},
      /*disabled_features=*/{});

  TestPaymentsDataManager& paydm =
      static_cast<TestPaymentsDataManager&>(pdm().payments_data_manager());
  paydm.AddCreditCard(test::GetCreditCard());
  paydm.AddBnplIssuer(test::GetTestLinkedBnplIssuer());

  ON_CALL(*static_cast<MockAutofillOptimizationGuideDecider*>(
              autofill_client().GetAutofillOptimizationGuideDecider()),
          IsUrlEligibleForBnplIssuer)
      .WillByDefault(Return(true));

  test::FormDescription form_description = {
      .fields = {
          {.role = CREDIT_CARD_NUMBER, .heuristic_type = CREDIT_CARD_NUMBER}}};
  IssueOnQuery(form_description);

  EXPECT_CALL(
      autofill_client(),
      ShowAutofillSuggestions(
          AllOf(PopupOpenArgsAre(
                    SuggestionVectorIdsAre(SuggestionType::kCreditCardEntry)),
                Field("show_tabbed_popup",
                      &AutofillClient::PopupOpenArgs::show_tabbed_popup, true)),
          _));

  OnSuggestionsReturned(queried_field(),
                        {Suggestion(SuggestionType::kCreditCardEntry)});
}

// Tests that `show_tabbed_popup` is not present when the main filling
// product is a credit card but the heuristic type of the field doesn't
// trigger BNPL suggestions (e.g., CVC fields).
TEST_F(AutofillExternalDelegateTest, ShowTabbedPopup_IneligibleFieldType_Cvc) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAutofillEnableAmountExtraction,
                            features::kAutofillEnableBuyNowPayLaterSyncing,
                            features::kAutofillEnableBuyNowPayLater,
                            features::kAutofillEnableAiBasedAmountExtraction,
                            features::kAutofillEnablePayNowPayLaterTabs},
      /*disabled_features=*/{});

  TestPaymentsDataManager& paydm =
      static_cast<TestPaymentsDataManager&>(pdm().payments_data_manager());
  paydm.AddCreditCard(test::GetCreditCard());
  paydm.AddBnplIssuer(test::GetTestLinkedBnplIssuer());

  ON_CALL(*static_cast<MockAutofillOptimizationGuideDecider*>(
              autofill_client().GetAutofillOptimizationGuideDecider()),
          IsUrlEligibleForBnplIssuer)
      .WillByDefault(Return(true));

  test::FormDescription form_description = {
      .fields = {{.role = CREDIT_CARD_VERIFICATION_CODE,
                  .heuristic_type = CREDIT_CARD_VERIFICATION_CODE}}};
  IssueOnQuery(form_description);

  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(
                  AllOf(PopupOpenArgsAre(SuggestionVectorIdsAre(
                            SuggestionType::kCreditCardEntry)),
                        Field("show_tabbed_popup",
                              &AutofillClient::PopupOpenArgs::show_tabbed_popup,
                              false)),
                  _));

  OnSuggestionsReturned(queried_field(),
                        {Suggestion(SuggestionType::kCreditCardEntry)});
}

// Tests that has seen BNPL pref is updated when the main filling
// product is a credit card and the field is a credit card number field.
TEST_F(AutofillExternalDelegateTest, ShowTabbedPopup_PrefUpdated) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAutofillEnableAmountExtraction,
                            features::kAutofillEnableBuyNowPayLaterSyncing,
                            features::kAutofillEnableBuyNowPayLater,
                            features::kAutofillEnableAiBasedAmountExtraction,
                            features::kAutofillEnablePayNowPayLaterTabs},
      /*disabled_features=*/{});

  TestPaymentsDataManager& paydm =
      static_cast<TestPaymentsDataManager&>(pdm().payments_data_manager());
  paydm.AddCreditCard(test::GetCreditCard());
  paydm.AddBnplIssuer(test::GetTestLinkedBnplIssuer());

  ON_CALL(*static_cast<MockAutofillOptimizationGuideDecider*>(
              autofill_client().GetAutofillOptimizationGuideDecider()),
          IsUrlEligibleForBnplIssuer)
      .WillByDefault(Return(true));

  test::FormDescription form_description = {
      .fields = {
          {.role = CREDIT_CARD_NUMBER, .heuristic_type = CREDIT_CARD_NUMBER}}};
  IssueOnQuery(form_description);

  ASSERT_FALSE(paydm.IsAutofillHasSeenBnplPrefEnabled());

  OnSuggestionsReturned(queried_field(),
                        {Suggestion(SuggestionType::kCreditCardEntry)});

  EXPECT_TRUE(paydm.IsAutofillHasSeenBnplPrefEnabled());
}

// Tests that `show_tabbed_popup` is not present when the flag
// `kAutofillEnablePayNowPayLaterTabs` is disabled.
TEST_F(AutofillExternalDelegateTest, ShowTabbedPopup_FeatureDisabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAutofillEnableAmountExtraction,
                            features::kAutofillEnableBuyNowPayLaterSyncing,
                            features::kAutofillEnableBuyNowPayLater,
                            features::kAutofillEnableAiBasedAmountExtraction},
      /*disabled_features=*/{features::kAutofillEnablePayNowPayLaterTabs});

  TestPaymentsDataManager& paydm =
      static_cast<TestPaymentsDataManager&>(pdm().payments_data_manager());
  paydm.AddCreditCard(test::GetCreditCard());
  paydm.AddBnplIssuer(test::GetTestLinkedBnplIssuer());

  ON_CALL(*static_cast<MockAutofillOptimizationGuideDecider*>(
              autofill_client().GetAutofillOptimizationGuideDecider()),
          IsUrlEligibleForBnplIssuer)
      .WillByDefault(Return(true));

  test::FormDescription form_description = {
      .fields = {
          {.role = CREDIT_CARD_NUMBER, .heuristic_type = CREDIT_CARD_NUMBER}}};
  IssueOnQuery(form_description);

  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(
                  AllOf(PopupOpenArgsAre(SuggestionVectorIdsAre(
                            SuggestionType::kCreditCardEntry)),
                        Field("show_tabbed_popup",
                              &AutofillClient::PopupOpenArgs::show_tabbed_popup,
                              false)),
                  _));

  OnSuggestionsReturned(queried_field(),
                        {Suggestion(SuggestionType::kCreditCardEntry)});
}

// Ensures the BNPL Manager is notified of the user deciding to use BNPL when a
// pay later tab is opened.
TEST_F(AutofillExternalDelegateTest, OnTabSelected_PayLater) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAutofillEnableAmountExtraction,
                            features::kAutofillEnableBuyNowPayLaterSyncing,
                            features::kAutofillEnableBuyNowPayLater,
                            features::kAutofillEnableAiBasedAmountExtraction,
                            features::kAutofillEnablePayNowPayLaterTabs},
      /*disabled_features=*/{});

  base::OnceCallback<void(const CreditCard&)> captured_fill_callback;
  EXPECT_CALL(*autofill_manager().GetPaymentsBnplManager(),
              OnUserDecisionToUseBnpl(
                  /*final_checkout_amount=*/testing::Eq(std::nullopt),
                  /*on_bnpl_vcn_fetched_callback=*/testing::_))
      .Times(1)
      .WillOnce([&](std::optional<long>,
                    base::OnceCallback<void(const CreditCard&)> callback) {
        captured_fill_callback = std::move(callback);
      });

  external_delegate().OnTabSelected(TabbedPaneTabType::kPayLater);

  ASSERT_FALSE(captured_fill_callback.is_null());

  CreditCard test_card = test::GetCreditCard();

  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(
          /*action_persistence=*/mojom::ActionPersistence::kFill,
          /*form=*/testing::_,
          /*field_id=*/testing::_,
          /*filling_payload=*/
          testing::VariantWith<const CreditCard*>(testing::Eq(&test_card)),
          /*trigger_source=*/testing::_,
          /*blocked_fields=*/testing::_))
      .Times(1);

  std::move(captured_fill_callback).Run(test_card);
}

TEST_F(AutofillExternalDelegateTest, OnTabSelected_PayNow) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAutofillEnableAmountExtraction,
                            features::kAutofillEnableBuyNowPayLaterSyncing,
                            features::kAutofillEnableBuyNowPayLater,
                            features::kAutofillEnableAiBasedAmountExtraction,
                            features::kAutofillEnablePayNowPayLaterTabs},
      /*disabled_features=*/{});

  EXPECT_CALL(*autofill_manager().GetPaymentsBnplManager(),
              OnUserDecisionToUseSavedCards)
      .Times(1);

  external_delegate().OnTabSelected(TabbedPaneTabType::kPayNow);
}

#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
        // BUILDFLAG(IS_CHROMEOS)

// Test that the Autofill popup is able to display warnings explaining why
// Autofill is disabled for a website.
// Regression test for http://crbug.com/247880
TEST_F(AutofillExternalDelegateTest, AutofillWarnings) {
  IssueOnQuery();

  AutofillClient::PopupOpenArgs open_args;
  EXPECT_CALL(autofill_client(), ShowAutofillSuggestions)
      .WillOnce(DoAll(SaveArg<0>(&open_args),
                      Return(AutofillClient::SuggestionUiSessionId())));

  // This should call ShowAutofillSuggestions.
  std::vector<Suggestion> autofill_item;
  autofill_item.emplace_back(
      SuggestionType::kInsecureContextPaymentDisabledMessage);
  OnSuggestionsReturned(queried_field(), autofill_item);

  EXPECT_THAT(open_args.suggestions,
              SuggestionVectorIdsAre(
                  SuggestionType::kInsecureContextPaymentDisabledMessage));
  EXPECT_EQ(open_args.element_bounds, gfx::RectF());
  EXPECT_EQ(open_args.text_direction, base::i18n::UNKNOWN_DIRECTION);
  EXPECT_EQ(open_args.trigger_source, kDefaultSuggestionTriggerSource);
}

// Test that Autofill warnings are removed if there are also autocomplete
// entries in the vector.
TEST_F(AutofillExternalDelegateTest, AutofillWarningsNotShown_WithSuggestions) {
  IssueOnQuery();

  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(PopupOpenArgsAre(SuggestionVectorIdsAre(
                                          SuggestionType::kAutocompleteEntry)),
                                      _));
  std::vector<Suggestion> suggestions;
  suggestions.emplace_back(
      SuggestionType::kInsecureContextPaymentDisabledMessage);
  suggestions.emplace_back(SuggestionType::kAutocompleteEntry);
  suggestions[1].main_text =
      Suggestion::Text(u"Rick", Suggestion::Text::IsPrimary(true));
  OnSuggestionsReturned(queried_field(), suggestions);
}

// Test that the Autofill delegate doesn't try and fill a form with a
// negative unique id.
TEST_F(AutofillExternalDelegateTest, ExternalDelegateInvalidUniqueId) {
  IssueOnQuery();
  // Ensure it doesn't try to preview the negative id.
  EXPECT_CALL(autofill_manager(), FillOrPreviewForm).Times(0);
  EXPECT_CALL(autofill_driver(), RendererShouldClearPreviewedForm);
  const Suggestion suggestion{SuggestionType::kManageAddress};
  external_delegate().DidSelectSuggestion(suggestion);

  // Ensure it doesn't try to fill the form in with the negative id.
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  EXPECT_CALL(autofill_manager(), FillOrPreviewForm).Times(0);

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

// Tests that accepting a manage suggestion on Android keeps the bottom sheet
// open if triggered from AtMemory.
TEST_F(AutofillExternalDelegateTest,
       ManageSuggestion_AtMemory_KeepsBottomSheetOpenOnAndroid) {
  IssueOnQuery(AutofillSuggestionTriggerSource::kAtMemoryTriggerString);
  const Suggestion suggestion{SuggestionType::kManageAddress};

  if constexpr (BUILDFLAG(IS_ANDROID)) {
    EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);
  } else {
    EXPECT_CALL(autofill_client(),
                HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                                Eq(std::nullopt)));
  }

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

// Test that the Autofill delegate still allows previewing and filling
// specifically of the negative ID for SuggestionType::kIbanEntry.
TEST_F(AutofillExternalDelegateTest, ExternalDelegateFillsIbanEntry) {
  IssueOnQuery();

  EXPECT_CALL(
      autofill_client(),
      ShowAutofillSuggestions(
          PopupOpenArgsAre(SuggestionVectorIdsAre(SuggestionType::kIbanEntry)),
          _));
  std::vector<Suggestion> suggestions;
  Iban iban = test::GetLocalIban();
  suggestions.emplace_back(/*main_text=*/u"My doctor's IBAN",
                           SuggestionType::kIbanEntry);
  suggestions[0].labels = {
      {Suggestion::Text(iban.GetIdentifierStringForAutofillDisplay())}};
  suggestions[0].payload = Suggestion::Guid(iban.guid());
  OnSuggestionsReturned(queried_field(), suggestions);

  EXPECT_CALL(autofill_driver(), RendererShouldClearPreviewedForm());
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kPreview,
                         mojom::FieldActionType::kReplaceAll,
                         HasQueriedFormId(), HasQueriedFieldId(),
                         iban.GetIdentifierStringForAutofillDisplay(),
                         FillingProduct::kIban, std::optional(IBAN_VALUE)));
  external_delegate().DidSelectSuggestion(suggestions[0]);
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kFill,
                         mojom::FieldActionType::kReplaceAll,
                         HasQueriedFormId(), HasQueriedFieldId(), iban.value(),
                         FillingProduct::kIban, std::optional(IBAN_VALUE)));
  Suggestion suggestion(u"My doctor's IBAN", SuggestionType::kIbanEntry);
  suggestion.payload = Suggestion::Guid(iban.guid());
  suggestion.labels = {
      {Suggestion::Text(iban.GetIdentifierStringForAutofillDisplay())}};
  EXPECT_CALL(*payments_autofill_client().GetIbanManager(),
              OnSingleFieldSuggestionSelected(suggestion));
  ON_CALL(*payments_autofill_client().GetIbanAccessManager(), FetchValue)
      .WillByDefault([iban](const Suggestion::Payload& payload,
                            IbanAccessManager::OnIbanFetchedCallback callback) {
        std::move(callback).Run(iban.value());
        return IsAsync(false);
      });

  external_delegate().DidAcceptSuggestion(
      suggestions[0], SuggestionPosition{.multi_index = {0}});
}

// Test that the Autofill delegate still allows previewing and filling
// specifically of the negative ID for
// SuggestionType::kMerchantPromoCodeEntry.
TEST_F(AutofillExternalDelegateTest,
       ExternalDelegateFillsMerchantPromoCodeEntry) {
  IssueOnQuery();

  EXPECT_CALL(
      autofill_client(),
      ShowAutofillSuggestions(PopupOpenArgsAre(SuggestionVectorIdsAre(
                                  SuggestionType::kMerchantPromoCodeEntry)),
                              _));
  std::vector<Suggestion> suggestions;
  const std::u16string promo_code_value = u"PROMOCODE1234";
  suggestions.emplace_back(/*main_text=*/promo_code_value,
                           SuggestionType::kMerchantPromoCodeEntry);
  suggestions[0].main_text.value = promo_code_value;
  suggestions[0].labels = {{Suggestion::Text(u"12.34% off your purchase!")}};
  OnSuggestionsReturned(queried_field(), suggestions);

  EXPECT_CALL(autofill_driver(), RendererShouldClearPreviewedForm());
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kPreview,
                         mojom::FieldActionType::kReplaceAll,
                         HasQueriedFormId(), HasQueriedFieldId(),
                         promo_code_value, FillingProduct::kMerchantPromoCode,
                         std::optional(MERCHANT_PROMO_CODE)));
  external_delegate().DidSelectSuggestion(suggestions[0]);
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kFill,
                         mojom::FieldActionType::kReplaceAll,
                         HasQueriedFormId(), HasQueriedFieldId(),
                         promo_code_value, FillingProduct::kMerchantPromoCode,
                         std::optional(MERCHANT_PROMO_CODE)));

  external_delegate().DidAcceptSuggestion(
      suggestions[0], SuggestionPosition{.multi_index = {0}});
}

// Test that the Autofill delegate allows previewing `kLoyaltyCardEntry`
// suggestions.
TEST_F(AutofillExternalDelegateTest, ExternalDelegatePreviewsLoyaltyCardEntry) {
  IssueOnQuery();

  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(PopupOpenArgsAre(SuggestionVectorIdsAre(
                                          SuggestionType::kLoyaltyCardEntry)),
                                      _));
  std::vector<Suggestion> suggestions;
  const std::u16string loyalty_card_value = u"LOYALTYCARD1234";
  suggestions.emplace_back(/*main_text=*/loyalty_card_value,
                           SuggestionType::kLoyaltyCardEntry);
  suggestions[0].main_text.value = loyalty_card_value;
  OnSuggestionsReturned(queried_field(), suggestions);

  EXPECT_CALL(autofill_driver(), RendererShouldClearPreviewedForm());
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kPreview,
                         mojom::FieldActionType::kReplaceAll,
                         HasQueriedFormId(), HasQueriedFieldId(),
                         loyalty_card_value, FillingProduct::kLoyaltyCard,
                         std::optional(LOYALTY_MEMBERSHIP_ID)));
  external_delegate().DidSelectSuggestion(suggestions[0]);
}

// Test that the Autofill delegate allows filling `kLoyaltyCardEntry`
// suggestions.
TEST_F(AutofillExternalDelegateTest, ExternalDelegateFillsLoyaltyCardEntry) {
  IssueOnQuery();

  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(PopupOpenArgsAre(SuggestionVectorIdsAre(
                                          SuggestionType::kLoyaltyCardEntry)),
                                      _));
  LoyaltyCard loyalty_card = test::CreateLoyaltyCard();
  std::vector<Suggestion> suggestions;
  const std::u16string full_loyalty_card_value = u"LOYALTYCARD1234";
  suggestions.emplace_back(/*main_text=*/full_loyalty_card_value,
                           SuggestionType::kLoyaltyCardEntry);
  suggestions[0].main_text.value = full_loyalty_card_value;
  suggestions[0].payload = Suggestion::Guid(loyalty_card.id().value());
  OnSuggestionsReturned(queried_field(), suggestions);

  EXPECT_CALL(autofill_driver(), RendererShouldClearPreviewedForm());
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kPreview,
                         mojom::FieldActionType::kReplaceAll,
                         HasQueriedFormId(), HasQueriedFieldId(),
                         full_loyalty_card_value, FillingProduct::kLoyaltyCard,
                         std::optional(LOYALTY_MEMBERSHIP_ID)));
  external_delegate().DidSelectSuggestion(suggestions[0]);

  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(
          mojom::ActionPersistence::kFill, mojom::FieldActionType::kReplaceAll,
          HasQueriedFormId(), HasQueriedFieldId(), full_loyalty_card_value,
          FillingProduct::kLoyaltyCard, std::optional(LOYALTY_MEMBERSHIP_ID)));

  external_delegate().DidAcceptSuggestion(
      suggestions[0], SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest, AcceptManageLoyaltyCards) {
  Suggestion manage_suggestion =
      Suggestion(u"Manage cards", SuggestionType::kManageLoyaltyCard);
  EXPECT_CALL(autofill_client(),
              ShowAutofillSettings(SuggestionType::kManageLoyaltyCard));
  external_delegate().DidAcceptSuggestion(manage_suggestion,
                                          {.multi_index = {0}});
}

// Test that the Autofill delegate routes the merchant promo code suggestions
// footer redirect logic correctly.
TEST_F(AutofillExternalDelegateTest,
       ExternalDelegateMerchantPromoCodeSuggestionsFooter) {
  IssueOnQuery();
  const GURL gurl{"https://example.com/"};
  EXPECT_CALL(payments_autofill_client(), OpenPromoCodeOfferDetailsURL(gurl));

  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kSeePromoCodeDetails, u"baz foo",
                               gurl),
      SuggestionPosition{.multi_index = {0}});
}

// Test that the ClearPreview call is only sent if the form was being previewed
// (i.e. it isn't autofilling a password).
TEST_F(AutofillExternalDelegateTest, ExternalDelegateClearPreviewedForm) {
  const AutofillProfile profile = test::GetFullProfile();
  pdm().address_data_manager().AddProfile(profile);

  // Ensure selecting a new password entries or Autofill entries will
  // cause any previews to get cleared.
  IssueOnQuery();
  EXPECT_CALL(autofill_driver(), RendererShouldClearPreviewedForm());
  external_delegate().DidSelectSuggestion(
      CreateAutofillSuggestion(SuggestionType::kAddressEntry, u"baz foo"));
  EXPECT_CALL(autofill_driver(), RendererShouldClearPreviewedForm());
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kPreview, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(profile), _, _));
  external_delegate().DidSelectSuggestion(CreateAutofillSuggestion(
      SuggestionType::kAddressEntry, u"baz foo",
      Suggestion::AutofillProfilePayload(Suggestion::Guid(profile.guid()))));

  // Ensure selecting an autocomplete entry will cause any previews to
  // get cleared.
  EXPECT_CALL(autofill_driver(), RendererShouldClearPreviewedForm());
  EXPECT_CALL(autofill_manager(),
              FillOrPreviewField(
                  mojom::ActionPersistence::kPreview,
                  mojom::FieldActionType::kReplaceAll, HasQueriedFormId(),
                  HasQueriedFieldId(), std::u16string(u"baz foo"),
                  FillingProduct::kAutocomplete, std::optional<FieldType>()));
  external_delegate().DidSelectSuggestion(
      CreateAutofillSuggestion(SuggestionType::kAutocompleteEntry, u"baz foo"));

  CreditCard card = test::GetMaskedServerCard();
  pdm().payments_data_manager().AddCreditCard(card);
  // Ensure selecting a virtual card entry will cause any previews to
  // get cleared.
  EXPECT_CALL(autofill_driver(), RendererShouldClearPreviewedForm());
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kPreview, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(card), _, _));
  Suggestion suggestion(SuggestionType::kVirtualCreditCardEntry);
  suggestion.payload = Suggestion::Guid(card.guid());
  external_delegate().DidSelectSuggestion(suggestion);
}

// Test that the popup is hidden once we are done editing the autofill field.
TEST_F(AutofillExternalDelegateTest, ExternalDelegateHidePopupAfterEditing) {
  EXPECT_CALL(autofill_client(), ShowAutofillSuggestions);
  test::GenerateTestAutofillPopup(&external_delegate());

  EXPECT_CALL(
      autofill_client(),
      HideSuggestions(SuggestionHidingReason::kEndEditing, Eq(std::nullopt)));
  external_delegate().DidEndTextFieldEditing();
}

// Test that the driver is directed to accept the data list after being notified
// that the user accepted the data list suggestion.
TEST_F(AutofillExternalDelegateTest, ExternalDelegateAcceptDatalistSuggestion) {
  IssueOnQuery();
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  std::u16string dummy_string(u"baz qux");
  EXPECT_CALL(autofill_driver(),
              RendererShouldAcceptDataListSuggestion(
                  queried_field().global_id(), dummy_string));

  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kDatalistEntry, dummy_string),
      SuggestionPosition{.multi_index = {0}});
}

// Test that a11y autofill availability is set to `kAutofillAvailable` when
// the popup is open with regular autofill suggestions.
TEST_F(AutofillExternalDelegateTest, AutofillSuggestionAvailability_Autofill) {
  IssueOnQuery();

  std::vector<Suggestion> suggestions = {
      Suggestion(u"Suggestion main_text", SuggestionType::kAddressEntry)};
  OnSuggestionsReturned(queried_field(), suggestions);

  EXPECT_CALL(autofill_driver(),
              RendererShouldSetSuggestionAvailability(
                  queried_field().global_id(),
                  mojom::AutofillSuggestionAvailability::kAutofillAvailable));

  external_delegate().OnSuggestionsShown(suggestions, std::nullopt);
}

// Test that a11y autofill availability is set to `kAutofillAvailable` when
// the popup is open with the `kFillAutofillAi` suggestion.
TEST_F(AutofillExternalDelegateTest,
       AutofillSuggestionAvailabilityFillAutofillAi) {
  IssueOnQuery();

  std::vector<Suggestion> suggestions = {
      Suggestion(u"Autofill with AI", SuggestionType::kFillAutofillAi)};
  OnSuggestionsReturned(queried_field(), suggestions);

  EXPECT_CALL(autofill_driver(),
              RendererShouldSetSuggestionAvailability(
                  queried_field().global_id(),
                  mojom::AutofillSuggestionAvailability::kAutofillAvailable));

  external_delegate().OnSuggestionsShown(suggestions, std::nullopt);
}

// Test that a11y autofill availability is set to `kAutocompleteAvailable` when
// the popup is open with autocomplete suggestions.
TEST_F(AutofillExternalDelegateTest,
       AutofillSuggestionAvailability_Autocomplete) {
  IssueOnQuery();

  std::vector<Suggestion> suggestions = {
      Suggestion(u"Suggestion main_text", SuggestionType::kAutocompleteEntry)};
  OnSuggestionsReturned(queried_field(), suggestions);

  EXPECT_CALL(
      autofill_driver(),
      RendererShouldSetSuggestionAvailability(
          queried_field().global_id(),
          mojom::AutofillSuggestionAvailability::kAutocompleteAvailable));

  external_delegate().OnSuggestionsShown(suggestions, std::nullopt);
}

// Test that an accepted autofill suggestion will fill the form.
TEST_F(AutofillExternalDelegateTest, AcceptSuggestion) {
  const AutofillProfile profile = test::GetFullProfile();
  pdm().address_data_manager().AddProfile(profile);

  IssueOnQuery();
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(profile), _, _));

  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(
          SuggestionType::kAddressEntry, u"John Legend",
          Suggestion::AutofillProfilePayload(Suggestion::Guid(profile.guid()))),
      SuggestionPosition{.multi_index = {2}});
}

TEST_F(AutofillExternalDelegateTest,
       TestAddressSuggestionShown_MetricsEmitted) {
  base::HistogramTester histogram_tester;
  autofill_client().set_test_addresses({test::GetFullProfile()});
  IssueOnQuery();
  std::vector<Suggestion> suggestions = {CreateAutofillSuggestion(
      SuggestionType::kDevtoolsTestAddresses, u"Devtools")};
  OnSuggestionsReturned(queried_field(), suggestions);
  external_delegate().OnSuggestionsShown(suggestions, std::nullopt);
  histogram_tester.ExpectUniqueSample(
      "Autofill.TestAddressesEvent",
      autofill_metrics::AutofillInDevtoolsTestAddressesEvents::
          kTestAddressesSuggestionShown,
      1);
}

// Test that an accepted test address autofill suggestion will fill the form.
TEST_F(AutofillExternalDelegateTest, TestAddressSuggestion_FillAndPreview) {
  IssueOnQuery();
  const AutofillProfile profile = test::GetFullProfile();
  autofill_client().set_test_addresses({profile});
  const Suggestion suggestion = CreateAutofillSuggestion(
      SuggestionType::kDevtoolsTestAddressEntry, u"John Legend",
      Suggestion::AutofillProfilePayload(Suggestion::Guid(profile.guid())));
  base::HistogramTester histogram_tester;

  // Test preview.
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kPreview, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(profile), _, _));
  external_delegate().DidSelectSuggestion(suggestion);

  // Test fill.
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(profile), _, _));
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
  histogram_tester.ExpectUniqueSample(
      "Autofill.TestAddressesEvent",
      autofill_metrics::AutofillInDevtoolsTestAddressesEvents::
          kTestAddressesSuggestionSelected,
      1);
}

// Test that an accepted test verified email autofill suggestion will fill the
// form and that the delegate gets notified.
TEST_F(AutofillExternalDelegateTest, TestVerifiedEmailSuggestion_Preview) {
  IssueOnQuery();
  const Suggestion suggestion = CreateAutofillSuggestion(
      SuggestionType::kIdentityCredential, u"John Legend",
      Suggestion::IdentityCredentialPayload());

  // Test preview.
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kPreview, HasQueriedFormId(),
                        IsQueriedFieldId(), _, _, _));
  external_delegate().DidSelectSuggestion(suggestion);
}

// Test that an accepted test verified email autofill suggestion will fill the
// form and that the delegate gets notified.
TEST_F(AutofillExternalDelegateTest, TestVerifiedEmailSuggestion_Fill) {
  IssueOnQuery();
  const Suggestion suggestion = CreateAutofillSuggestion(
      SuggestionType::kIdentityCredential, u"John Legend",
      Suggestion::IdentityCredentialPayload());

  // Set up a mock identity credential delegate.
  MockIdentityCredentialDelegate mock;
  ON_CALL(autofill_client(), GetIdentityCredentialDelegate)
      .WillByDefault(Return(&mock));

  // Expect that the form filler gets notified.
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), _, _, _));
  // Expect that the delegate gets notified and pretend that the user has
  // accepted the prompt.
  EXPECT_CALL(mock, NotifySuggestionAccepted(_, /*show_modal=*/true, _))
      .WillOnce(RunOnceCallback<2>(/*accepted=*/true));

  // Test fill.
  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

// Test that an accepted verified email autofill suggestion will not fill the
// form if the user later rejects the prompt.
TEST_F(AutofillExternalDelegateTest,
       TestVerifiedEmailSuggestion_PromptRejectedNoFill) {
  IssueOnQuery();
  const Suggestion suggestion = CreateAutofillSuggestion(
      SuggestionType::kIdentityCredential, u"John Legend",
      Suggestion::IdentityCredentialPayload());

  // Set up a mock identity credential delegate.
  MockIdentityCredentialDelegate mock;
  ON_CALL(autofill_client(), GetIdentityCredentialDelegate)
      .WillByDefault(Return(&mock));

  // Expect that the delegate gets notified and pretend that the user has
  // rejected the prompt.
  EXPECT_CALL(mock, NotifySuggestionAccepted(_, /*show_modal=*/true, _))
      .WillOnce(RunOnceCallback<2>(/*accepted=*/false));

  // Test fill.
  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest,
       AcceptFirstPopupLevelSuggestion_LogSuggestionAcceptedMetric) {
  IssueOnQuery();
  const AutofillProfile profile = test::GetFullProfile();
  pdm().address_data_manager().AddProfile(profile);
  const int suggestion_accepted_row = 2;
  base::HistogramTester histogram_tester;

  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(
          SuggestionType::kAddressEntry, u"John Legend",
          Suggestion::AutofillProfilePayload(Suggestion::Guid(profile.guid()))),
      AutofillSuggestionDelegate::SuggestionMetadata{
          .multi_index = {static_cast<size_t>(suggestion_accepted_row)}});

  histogram_tester.ExpectUniqueSample("Autofill.SuggestionAcceptedIndex",
                                      suggestion_accepted_row, 1);
}

// Tests that when accepting a suggestion, the `AutofillSuggestionTriggerSource`
// is converted to the correct `AutofillTriggerSource`.
TEST_F(AutofillExternalDelegateTest, AcceptSuggestion_TriggerSource) {
  // Expect that `kFormControlElementClicked` translates to source `kPopup` or
  // `kKeyboardAccessory`, depending on the platform.
  const AutofillProfile profile = test::GetFullProfile();
  pdm().address_data_manager().AddProfile(profile);
  Suggestion suggestion = CreateAutofillSuggestion(
      SuggestionType::kAddressEntry, /*main_text_value=*/u"",
      Suggestion::AutofillProfilePayload(Suggestion::Guid(profile.guid())));

  IssueOnQuery(AutofillSuggestionTriggerSource::kFormControlElementClicked);
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(profile),
                        DefaultTriggerSource(), _));
  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {1}});
}

// Tests that on selecting and accepting a `kFillAutofillAi` suggestion with
// `Suggestion::AutofillAiPayload` payload previews and fills the form,
// respectively.
TEST_F(AutofillExternalDelegateTest, FillAutofillAiFillsFullForm) {
  base::test::ScopedFeatureList scoped_feature_list{
      features::kAutofillAiWithDataSchema};

  EntityInstance passport = GetPassportEntityInstance({.number = u"123"});
  AddOrUpdateEntityInstance(passport);
  IssueOnQuery({.fields = {{.role = NAME_FIRST},
                           {.role = NAME_LAST},
                           {.role = PASSPORT_NUMBER},
                           {.role = IBAN_VALUE},
                           {.role = UNKNOWN_TYPE}}});

  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(passport.guid());

  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kPreview, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(passport),
                        DefaultTriggerSource(), _));
  external_delegate().DidSelectSuggestion(fill_suggestion);

  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(passport),
                        DefaultTriggerSource(), _));
  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});
}

#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID) || \
    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_IOS)
// Tests that when accepting a `kFillAutofillAi` suggestion that requires
// re-authentication, the re-authentication flow is triggered and the form is
// filled upon success.
TEST_F(AutofillExternalDelegateTest, AutofillAiReauthFlow_ReauthAccepted) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {features::kAutofillAiWithDataSchema, features::kAutofillAiReauthRequired,
       features::kAutofillAiWalletPrivatePasses},
      {});
  autofill_client().GetPrefs()->SetBoolean(
      prefs::kAutofillAiReauthBeforeViewingSensitiveData, true);

  EntityInstance passport = GetPassportEntityInstanceWithRandomGuid();
  AddOrUpdateEntityInstance(passport);
  // Create form with a passport number, which triggers obfuscation and thus
  // re-auth.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});

  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(passport.guid());
  std::vector<Suggestion> all_suggestions = {
      fill_suggestion, Suggestion(SuggestionType::kFillAutofillAi)};
  OnSuggestionsReturned(queried_field(), all_suggestions);
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(all_suggestions));

  auto authenticator =
      std::make_unique<device_reauth::MockDeviceAuthenticator>();
  EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
      .WillOnce(Return(true));

  device_reauth::DeviceAuthenticator::AuthenticateCallback reauth_callback;
  EXPECT_CALL(*authenticator, AuthenticateWithMessage)
      .WillOnce(MoveArg<1>(&reauth_callback));

  test_api(autofill_manager().GetAutofillAiAccessManager())
      .SetDeviceAuthenticator(std::move(authenticator));

  {
    InSequence s;
    auto is_loading =
        Field(&Suggestion::is_loading, Suggestion::IsLoading(true));
    auto is_deactivated =
        AllOf(Field(&Suggestion::acceptability,
                    Suggestion::Acceptability::kSelectableButUnacceptable),
              Field(&Suggestion::is_loading, Suggestion::IsLoading(false)));

    EXPECT_CALL(
        autofill_client(),
        UpdateAutofillSuggestions(_, FillingProduct::kAutofillAi,
                                  kDefaultSuggestionTriggerSource,
                                  AutofillSuggestionsIgnoreFocusLoss(true)));

    EXPECT_CALL(
        autofill_manager(),
        FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                          IsQueriedFieldId(), HasFillingPayload(passport),
                          DefaultTriggerSource(), _));
  }

  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});
  ASSERT_FALSE(reauth_callback.is_null());
  std::move(reauth_callback).Run(true);
}

// Tests that the re-authentication message contains the origin host when
// accepting a `kFillAutofillAi` suggestion that requires re-authentication.
// Note however that the messaged passed to Android is empty since the
// platform does not allow it to begin with.
TEST_F(AutofillExternalDelegateTest, AutofillAiReauthFlow_ReauthMessage) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures({features::kAutofillAiWithDataSchema,
                                        features::kAutofillAiReauthRequired},
                                       {});
  autofill_client().GetPrefs()->SetBoolean(
      prefs::kAutofillAiReauthBeforeViewingSensitiveData, true);

  EntityInstance passport = GetPassportEntityInstanceWithRandomGuid();
  AddOrUpdateEntityInstance(passport);

  const GURL kUrl = GURL("https://acoolwebsite.test");
  autofill_client().set_last_committed_primary_main_frame_url(kUrl);
  // Create form with a passport number, which triggers obfuscation and thus
  // re-auth.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER,
                            .origin = url::Origin::Create(kUrl)}}});

  auto authenticator =
      std::make_unique<device_reauth::MockDeviceAuthenticator>();
  EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
      .WillOnce(Return(true));

  std::u16string expected_message;
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || \
    BUILDFLAG(IS_IOS)
  expected_message = l10n_util::GetStringFUTF16(IDS_AUTOFILL_AI_FILLING_REAUTH,
                                                base::UTF8ToUTF16(kUrl.host()));
#endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) ||
        // BUILDFLAG(IS_IOS)
  EXPECT_CALL(*authenticator, AuthenticateWithMessage(expected_message, _))
      .WillOnce(RunOnceCallback<1>(true));

  test_api(autofill_manager().GetAutofillAiAccessManager())
      .SetDeviceAuthenticator(std::move(authenticator));

  EXPECT_CALL(autofill_manager(), FillOrPreviewForm);

  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(passport.guid());
  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});
}

// Tests that when accepting a `kFillAutofillAi` suggestion that requires
// re-authentication, the form is NOT filled if re-authentication fails.
TEST_F(AutofillExternalDelegateTest, AutofillAiReauthFlow_ReauthRejected) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures({features::kAutofillAiWithDataSchema,
                                        features::kAutofillAiReauthRequired},
                                       {});
  autofill_client().GetPrefs()->SetBoolean(
      prefs::kAutofillAiReauthBeforeViewingSensitiveData, true);

  EntityInstance passport = GetPassportEntityInstanceWithRandomGuid();
  AddOrUpdateEntityInstance(passport);
  // Create form with a passport number, which triggers obfuscation and thus
  // re-auth.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});

  auto authenticator =
      std::make_unique<device_reauth::MockDeviceAuthenticator>();
  EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
      .WillOnce(Return(true));
  EXPECT_CALL(*authenticator, AuthenticateWithMessage)
      .WillOnce(RunOnceCallback<1>(false));
  test_api(autofill_manager().GetAutofillAiAccessManager())
      .SetDeviceAuthenticator(std::move(authenticator));
  EXPECT_CALL(autofill_manager(), FillOrPreviewForm).Times(0);

  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(passport.guid());
  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});
}

// Tests that the result of the re-authentication flow is recorded per field
// type.
TEST_F(AutofillExternalDelegateTest, AutofillAiReauthFlow_ResultMetrics) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures({features::kAutofillAiWithDataSchema,
                                        features::kAutofillAiReauthRequired},
                                       {});
  autofill_client().GetPrefs()->SetBoolean(
      prefs::kAutofillAiReauthBeforeViewingSensitiveData, true);

  EntityInstance passport = GetPassportEntityInstanceWithRandomGuid();
  AddOrUpdateEntityInstance(passport);
  // Create form with a passport number, which triggers obfuscation and thus
  // re-auth.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});

  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(passport.guid());

  {
    base::HistogramTester histogram_tester;
    auto authenticator =
        std::make_unique<device_reauth::MockDeviceAuthenticator>();
    EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
        .WillOnce(Return(true));
    EXPECT_CALL(*authenticator, AuthenticateWithMessage)
        .WillOnce(RunOnceCallback<1>(true));
    test_api(autofill_manager().GetAutofillAiAccessManager())
        .SetDeviceAuthenticator(std::move(authenticator));

    external_delegate().DidAcceptSuggestion(fill_suggestion,
                                            {.multi_index = {0}});

    histogram_tester.ExpectUniqueSample(
        "Autofill.Ai.ReauthToFill.ResultPerFieldType",
        GetBucketForAutofillAiReauthResultByFieldType(
            FieldType::PASSPORT_NUMBER, /*auth_succeeded=*/true),
        1);
  }

  {
    base::HistogramTester histogram_tester;
    auto authenticator =
        std::make_unique<device_reauth::MockDeviceAuthenticator>();
    EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
        .WillOnce(Return(true));
    EXPECT_CALL(*authenticator, AuthenticateWithMessage)
        .WillOnce(RunOnceCallback<1>(false));
    test_api(autofill_manager().GetAutofillAiAccessManager())
        .SetDeviceAuthenticator(std::move(authenticator));

    external_delegate().DidAcceptSuggestion(fill_suggestion,
                                            {.multi_index = {0}});

    histogram_tester.ExpectUniqueSample(
        "Autofill.Ai.ReauthToFill.ResultPerFieldType",
        GetBucketForAutofillAiReauthResultByFieldType(
            FieldType::PASSPORT_NUMBER, /*auth_succeeded=*/false),
        1);
  }
}

// Tests that when accepting a `kFillAutofillAi` suggestion that requires
// re-authentication, the form IS filled if no authenticator is available.
TEST_F(AutofillExternalDelegateTest, AutofillAiReauthFlow_NoAuthenticator) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures({features::kAutofillAiWithDataSchema,
                                        features::kAutofillAiReauthRequired},
                                       {});
  autofill_client().GetPrefs()->SetBoolean(
      prefs::kAutofillAiReauthBeforeViewingSensitiveData, true);

  EntityInstance passport = GetPassportEntityInstanceWithRandomGuid();
  AddOrUpdateEntityInstance(passport);
  // Create form with a passport number, which triggers obfuscation and thus
  // re-auth.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});

  test_api(autofill_manager().GetAutofillAiAccessManager())
      .SetDeviceAuthenticator(nullptr);
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(passport),
                        DefaultTriggerSource(), _));

  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(passport.guid());
  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});
}

// Tests that no authentication is required when filling `kFillAutofillAi` and
// the feature flag is off.
TEST_F(AutofillExternalDelegateTest, AutofillAiReauthFlow_FlagOff) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures({features::kAutofillAiWithDataSchema},
                                       {features::kAutofillAiReauthRequired});
  autofill_client().GetPrefs()->SetBoolean(
      prefs::kAutofillAiReauthBeforeViewingSensitiveData, true);

  EntityInstance passport = GetPassportEntityInstanceWithRandomGuid();
  AddOrUpdateEntityInstance(passport);
  // Create form with a passport number, which triggers obfuscation and thus
  // re-auth.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});

  EXPECT_CALL(autofill_client(),
              GetDeviceAuthenticator("Autofill.Ai.ReauthToFill"))
      .Times(0);
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(passport),
                        DefaultTriggerSource(), _));

  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(passport.guid());
  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});
}

// Tests that when accepting a `kFillAutofillAi` suggestion that requires
// re-authentication, the form IS filled without re-authentication if the pref
// is disabled.
TEST_F(AutofillExternalDelegateTest,
       AutofillAiReauthFlow_ReauthNotRequiredWhenPrefIsDisabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures({features::kAutofillAiWithDataSchema,
                                        features::kAutofillAiReauthRequired},
                                       {});
  autofill_client().GetPrefs()->SetBoolean(
      prefs::kAutofillAiReauthBeforeViewingSensitiveData, false);

  EntityInstance passport = GetPassportEntityInstanceWithRandomGuid();
  AddOrUpdateEntityInstance(passport);
  // Create form with a passport number, which triggers obfuscation and thus
  // re-auth.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});

  EXPECT_CALL(autofill_client(),
              GetDeviceAuthenticator("Autofill.Ai.ReauthToFill"))
      .Times(0);
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(passport),
                        DefaultTriggerSource(), _));

  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(passport.guid());
  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});
}
#endif

TEST_F(AutofillExternalDelegateTest, AcceptManageAutofillAi) {
  Suggestion manage_suggestion =
      Suggestion(u"Manage information", SuggestionType::kManageAutofillAi);
  EXPECT_CALL(autofill_client(),
              ShowAutofillSettings(SuggestionType::kManageAutofillAi));
  external_delegate().DidAcceptSuggestion(manage_suggestion,
                                          {.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest, AcceptedOtpSuggestion) {
  IssueOnQuery();

  std::u16string otp_value = u"123456";
  OtpFillData otp_fill_data;
  otp_fill_data[queried_field().global_id()] = otp_value;

  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(otp_fill_data),
                        DefaultTriggerSource(), _));
  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kOneTimePasswordEntry,
                               /*main_text_value=*/otp_value),
      {.multi_index = {0}});
}

class AutofillExternalDelegateWithWalletPrivatePassesTest
    : public AutofillExternalDelegateTest {
 public:
  AutofillExternalDelegateWithWalletPrivatePassesTest() {
    scoped_feature_list_.InitWithFeatures(
        {
            features::kAutofillAiWithDataSchema,
            features::kAutofillAiWalletPrivatePasses,
            features::kAutofillAiReauthRequired,
#if BUILDFLAG(IS_ANDROID)
            features::kAutofillAiShowServerWalletFillingYourInfoDialog,
#endif  // BUILDFLAG(IS_ANDROID)
        },
        {});
  }

  void SetUp() override {
    AutofillExternalDelegateTest::SetUp();
    autofill_client().set_wallet_pass_access_manager(
        std::make_unique<MockWalletPassAccessManager>());
  }

  MockWalletPassAccessManager& wallet_manager() {
    return static_cast<MockWalletPassAccessManager&>(
        *autofill_client().GetWalletPassAccessManager());
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<testing::NiceMock<
      personal_context::MockPersonalContextEligibilityService>>
      mock_personal_context_service_;
};

// Tests that when accepting a `kFillAutofillAi` suggestion that would fill a
// masked server entity, the entity is first fetched from the server.
TEST_F(AutofillExternalDelegateWithWalletPrivatePassesTest,
       AutofillAiFillMaskedServerEntity) {
  constexpr auto kPassportNumberType =
      AttributeType(AttributeTypeName::kPassportNumber);

  EntityInstance full_passport = GetPassportEntityInstance(
      {.record_type = EntityInstance::RecordType::kServerWallet});
  EntityInstance masked_passport = MaskEntityInstance(full_passport);
  ASSERT_NE(
      full_passport.attribute(kPassportNumberType)->GetCompleteRawInfo(),
      masked_passport.attribute(kPassportNumberType)->GetCompleteRawInfo());
  AddOrUpdateEntityInstance(masked_passport);

  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});
  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(
      masked_passport.guid(), /*requires_server_fetch=*/true);
  std::vector<Suggestion> suggestions = {fill_suggestion};
  OnSuggestionsReturned(queried_field(), suggestions);
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(suggestions));

  EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
      .Times(0);
  {
    InSequence s;
    EXPECT_CALL(wallet_manager(),
                GetUnmaskedWalletEntityInstance(masked_passport.guid(), _))
        .WillOnce(RunOnceCallback<1>(full_passport));
    EXPECT_CALL(
        autofill_manager(),
        FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                          IsQueriedFieldId(), HasFillingPayload(full_passport),
                          DefaultTriggerSource(), _));
    EXPECT_CALL(autofill_client(),
                HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                                std::optional(FillingProduct::kAutofillAi)));
  }

#if BUILDFLAG(IS_ANDROID)
  // The loading dialog should not be shown if the user doesn't need to
  // authenticate.
  EXPECT_CALL(autofill_client(), ShowAutofillAiLoadingDialog()).Times(0);
  EXPECT_CALL(autofill_client(), DismissAutofillAiLoadingDialog()).Times(0);
#endif  // BUILDFLAG(IS_ANDROID)

  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});
}

// Tests that when accepting a `kFillAutofillAi` suggestion that would fill a
// only non-obfuscated attributes of a server entity, the entity is not fetched
// from the server and the masked entity is used for filling.
TEST_F(AutofillExternalDelegateWithWalletPrivatePassesTest,
       AutofillAiFillMaskedServerEntityNonObfuscatedAttributesOnly) {
  EntityInstance full_passport = GetPassportEntityInstance(
      {.record_type = EntityInstance::RecordType::kServerWallet});
  EntityInstance masked_passport = MaskEntityInstance(full_passport);
  AddOrUpdateEntityInstance(masked_passport);

  IssueOnQuery({.fields = {{.role = PASSPORT_EXPIRATION_DATE},
                           {.role = PASSPORT_ISSUE_DATE},
                           {.role = PASSPORT_ISSUING_COUNTRY}}});
  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload =
      Suggestion::AutofillAiPayload(masked_passport.guid());
  std::vector<Suggestion> suggestions = {fill_suggestion};
  OnSuggestionsReturned(queried_field(), suggestions);
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(suggestions));

  EXPECT_CALL(wallet_manager(),
              GetUnmaskedWalletEntityInstance(masked_passport.guid(), _))
      .Times(0);
  EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
      .Times(0);
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(masked_passport),
                        DefaultTriggerSource(), _));
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              std::optional(FillingProduct::kAutofillAi)));

#if BUILDFLAG(IS_ANDROID)
  // The loading dialog should not be shown if the user doesn't need to
  // authenticate.
  EXPECT_CALL(autofill_client(), ShowAutofillAiLoadingDialog()).Times(0);
  EXPECT_CALL(autofill_client(), DismissAutofillAiLoadingDialog()).Times(0);
#endif  // BUILDFLAG(IS_ANDROID)

  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});
}

// Tests that when fetching a masked server entity fails, the suggestion is
// not filled.
TEST_F(AutofillExternalDelegateWithWalletPrivatePassesTest,
       AutofillAiFillMaskedServerEntityFetchingFails) {
  constexpr auto kPassportNumberType =
      AttributeType(AttributeTypeName::kPassportNumber);

  EntityInstance full_passport = GetPassportEntityInstance(
      {.record_type = EntityInstance::RecordType::kServerWallet});
  EntityInstance masked_passport = MaskEntityInstance(full_passport);
  ASSERT_NE(
      full_passport.attribute(kPassportNumberType)->GetCompleteRawInfo(),
      masked_passport.attribute(kPassportNumberType)->GetCompleteRawInfo());
  AddOrUpdateEntityInstance(masked_passport);

  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});
  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(
      masked_passport.guid(), /*requires_server_fetch=*/true);
  std::vector<Suggestion> suggestions = {fill_suggestion};
  OnSuggestionsReturned(queried_field(), suggestions);
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(suggestions));

  EXPECT_CALL(wallet_manager(),
              GetUnmaskedWalletEntityInstance(masked_passport.guid(), _))
      .WillOnce(RunOnceCallback<1>(std::nullopt));
  EXPECT_CALL(autofill_manager(), FillOrPreviewForm).Times(0);
  EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification);
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              std::optional(FillingProduct::kAutofillAi)));

#if BUILDFLAG(IS_ANDROID)
  // The loading dialog should not be shown if the user doesn't need to
  // authenticate.
  EXPECT_CALL(autofill_client(), ShowAutofillAiLoadingDialog()).Times(0);
  EXPECT_CALL(autofill_client(), DismissAutofillAiLoadingDialog()).Times(0);
#endif  // BUILDFLAG(IS_ANDROID)

  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});
}

#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID) || \
    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_IOS)
// Tests that when attempting to fill a masked server entity and re-auth fails,
// no failure notification is displayed.
TEST_F(AutofillExternalDelegateWithWalletPrivatePassesTest,
       AutofillAiFillMaskedServerEntityReauthFails) {
  autofill_client().GetPrefs()->SetBoolean(
      prefs::kAutofillAiReauthBeforeViewingSensitiveData, true);

  EntityInstance masked_passport = MaskEntityInstance(GetPassportEntityInstance(
      {.record_type = EntityInstance::RecordType::kServerWallet}));
  AddOrUpdateEntityInstance(masked_passport);

  // Show suggestions for `masked_passport`.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});
  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(
      masked_passport.guid(), /*requires_server_fetch=*/true);
  std::vector<Suggestion> suggestions = {fill_suggestion};
  OnSuggestionsReturned(queried_field(), suggestions);
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(suggestions));

  // Simulate a failed re-auth.
  auto authenticator =
      std::make_unique<device_reauth::MockDeviceAuthenticator>();
  EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
      .WillOnce(Return(true));
  EXPECT_CALL(*authenticator, AuthenticateWithMessage)
      .WillOnce(RunOnceCallback<1>(false));
  test_api(autofill_manager().GetAutofillAiAccessManager())
      .SetDeviceAuthenticator(std::move(authenticator));

  // Expect that the `wallet_manager()` is not called and that no unmask failure
  // notification is shown.
  EXPECT_CALL(wallet_manager(),
              GetUnmaskedWalletEntityInstance(masked_passport.guid(), _))
      .Times(0);
  EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
      .Times(0);
  EXPECT_CALL(autofill_manager(), FillOrPreviewForm).Times(0);
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              std::optional(FillingProduct::kAutofillAi)));

#if BUILDFLAG(IS_ANDROID)
  // The loading dialog should not be shown if the authentication fails.
  EXPECT_CALL(autofill_client(), ShowAutofillAiLoadingDialog()).Times(0);
  EXPECT_CALL(autofill_client(), DismissAutofillAiLoadingDialog()).Times(0);
#endif  // BUILDFLAG(IS_ANDROID)

  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});
}

// Tests that when attempting to fill a masked server entity and re-auth
// succeeds, no failure notification is displayed.
TEST_F(AutofillExternalDelegateWithWalletPrivatePassesTest,
       AutofillAiFillMaskedServerEntityReauthSucceeds) {
  autofill_client().GetPrefs()->SetBoolean(
      prefs::kAutofillAiReauthBeforeViewingSensitiveData, true);

  EntityInstance full_passport = GetPassportEntityInstance(
      {.record_type = EntityInstance::RecordType::kServerWallet});
  EntityInstance masked_passport = MaskEntityInstance(full_passport);
  AddOrUpdateEntityInstance(masked_passport);

  // Show suggestions for `masked_passport`.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});
  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(
      masked_passport.guid(), /*requires_server_fetch=*/true);
  std::vector<Suggestion> suggestions = {fill_suggestion};
  OnSuggestionsReturned(queried_field(), suggestions);
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(suggestions));

  // Simulate a failed re-auth.
  auto authenticator =
      std::make_unique<device_reauth::MockDeviceAuthenticator>();
  EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
      .WillOnce(Return(true));
  device_reauth::DeviceAuthenticator::AuthenticateCallback reauth_callback;
  EXPECT_CALL(*authenticator, AuthenticateWithMessage)
      .WillOnce(MoveArg<1>(&reauth_callback));
  test_api(autofill_manager().GetAutofillAiAccessManager())
      .SetDeviceAuthenticator(std::move(authenticator));

  EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
      .Times(0);
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(full_passport),
                        DefaultTriggerSource(), _));
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              std::optional(FillingProduct::kAutofillAi)));

  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});

  // The `AutofillAiAccessManager` will fetch the server wallet passport
  // entity from the `AutofillAiPersonalContextAccessManager` after successful
  // authentication.
  WalletPassAccessManager::GetUnmaskedEntityInstanceCallback
      get_unmasked_entity_callback;
  EXPECT_CALL(wallet_manager(),
              GetUnmaskedWalletEntityInstance(masked_passport.guid(), _))
      .WillOnce(MoveArg<1>(&get_unmasked_entity_callback));

#if BUILDFLAG(IS_ANDROID)
  // The loading dialog is shown only on Android after successful
  // authentication.
  EXPECT_CALL(autofill_client(), ShowAutofillAiLoadingDialog());
#endif  // BUILDFLAG(IS_ANDROID)

  // Simulate successful authentication.
  ASSERT_FALSE(reauth_callback.is_null());
  std::move(reauth_callback).Run(true);

#if BUILDFLAG(IS_ANDROID)
  // Dismiss the loading dialog after the entity is fetched.
  EXPECT_CALL(autofill_client(), DismissAutofillAiLoadingDialog());
#endif  // BUILDFLAG(IS_ANDROID)

  // Simulate the async response.
  ASSERT_FALSE(get_unmasked_entity_callback.is_null());
  std::move(get_unmasked_entity_callback).Run(full_passport);
}

// Tests that when attempting to fill a masked server entity and re-auth
// succeeds, no failure notification is displayed.
TEST_F(AutofillExternalDelegateWithWalletPrivatePassesTest,
       AutofillAiFillMaskedServerEntityReauthSucceeds_AccessManagerReset) {
  autofill_client().GetPrefs()->SetBoolean(
      prefs::kAutofillAiReauthBeforeViewingSensitiveData, true);

  EntityInstance full_passport = GetPassportEntityInstance(
      {.record_type = EntityInstance::RecordType::kServerWallet});
  EntityInstance masked_passport = MaskEntityInstance(full_passport);
  AddOrUpdateEntityInstance(masked_passport);

  // Show suggestions for `masked_passport`.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});
  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(
      masked_passport.guid(), /*requires_server_fetch=*/true);
  std::vector<Suggestion> suggestions = {fill_suggestion};
  OnSuggestionsReturned(queried_field(), suggestions);
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(suggestions));

  // Simulate a failed re-auth.
  auto authenticator =
      std::make_unique<device_reauth::MockDeviceAuthenticator>();
  EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
      .WillOnce(Return(true));
  device_reauth::DeviceAuthenticator::AuthenticateCallback reauth_callback;
  EXPECT_CALL(*authenticator, AuthenticateWithMessage)
      .WillOnce(MoveArg<1>(&reauth_callback));
  test_api(autofill_manager().GetAutofillAiAccessManager())
      .SetDeviceAuthenticator(std::move(authenticator));

  EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
      .Times(0);
  EXPECT_CALL(autofill_manager(), FillOrPreviewForm).Times(0);
  EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);

  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});

  // The `AutofillAiAccessManager` will fetch the server wallet passport
  // entity from the `AutofillAiPersonalContextAccessManager` after successful
  // authentication.
  WalletPassAccessManager::GetUnmaskedEntityInstanceCallback
      get_unmasked_entity_callback;
  EXPECT_CALL(wallet_manager(),
              GetUnmaskedWalletEntityInstance(masked_passport.guid(), _))
      .WillOnce(MoveArg<1>(&get_unmasked_entity_callback));

#if BUILDFLAG(IS_ANDROID)
  // The loading dialog is shown only on Android after successful
  // authentication.
  EXPECT_CALL(autofill_client(), ShowAutofillAiLoadingDialog());
#endif  // BUILDFLAG(IS_ANDROID)

  // Simulate successful authentication.
  ASSERT_FALSE(reauth_callback.is_null());
  std::move(reauth_callback).Run(true);

#if BUILDFLAG(IS_ANDROID)
  // The loading dialog must be dismissed even if the AutofillAiAccessManager
  // is reset.
  EXPECT_CALL(autofill_client(), DismissAutofillAiLoadingDialog());
#endif  // BUILDFLAG(IS_ANDROID)

  // Reset the access manager before the server request is complete.
  test_api(autofill_manager()).set_autofill_ai_access_manager(nullptr);

  // Simulate the async response.
  ASSERT_FALSE(get_unmasked_entity_callback.is_null());
  std::move(get_unmasked_entity_callback).Run(full_passport);
}

class AutofillExternalDelegateWithAmbientAutofillTest
    : public AutofillExternalDelegateTest {
 public:
  AutofillExternalDelegateWithAmbientAutofillTest() {
    scoped_feature_list_.InitWithFeatures(
        {
            features::kAutofillAiWithDataSchema,
            features::kAutofillAiReauthRequired,
            features::kAutofillAmbientAutofill,
            features::kAutofillAiWalletPrivatePasses,
#if BUILDFLAG(IS_ANDROID)
            features::kAutofillAiShowPersonalContextFillingYourInfoDialog,
#endif  // BUILDFLAG(IS_ANDROID)
        },
        {});
  }

  void SetUp() override {
    AutofillExternalDelegateTest::SetUp();
    personal_context_manager_ = std::make_unique<
        NiceMock<MockAutofillAiPersonalContextAccessManager>>();
    autofill_client().set_personal_context_access_manager(
        personal_context_manager_.get());
    autofill_client().GetPrefs()->SetBoolean(
        prefs::kAutofillAiReauthBeforeViewingSensitiveData, true);
  }

  void TearDown() override {
    autofill_client().set_personal_context_access_manager(nullptr);
    AutofillExternalDelegateTest::TearDown();
  }

  MockAutofillAiPersonalContextAccessManager& personal_context_manager() {
    return *personal_context_manager_;
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<testing::NiceMock<
      personal_context::MockPersonalContextEligibilityService>>
      mock_personal_context_service_;
  std::unique_ptr<MockAutofillAiPersonalContextAccessManager>
      personal_context_manager_;
};

// Tests that when accepting a `kFillAutofillAi` suggestion for a masked
// personal context entity, the entity is fetched and a loading state is shown
// if it is async.
TEST_F(AutofillExternalDelegateWithAmbientAutofillTest,
       AutofillAiFillMaskedPersonalContextEntity) {
  constexpr auto kPassportNumberType =
      AttributeType(AttributeTypeName::kPassportNumber);

  EntityInstance full_passport = GetPassportEntityInstance(
      {.record_type = EntityInstance::RecordType::kPersonalContext});
  EntityInstance masked_passport = MaskEntityInstance(full_passport);
  ASSERT_NE(
      full_passport.attribute(kPassportNumberType)->GetCompleteRawInfo(),
      masked_passport.attribute(kPassportNumberType)->GetCompleteRawInfo());
  autofill_client().GetEntityDataManager()->OnPrefetchContextComplete(
      personal_context_manager(), std::vector<EntityInstance>{masked_passport});

  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});
  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(
      masked_passport.guid(), /*requires_server_fetch=*/true);
  std::vector<Suggestion> suggestions = {fill_suggestion};
  OnSuggestionsReturned(queried_field(), suggestions);
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(suggestions));

  // Authenticator is not supported by the platform.
  auto authenticator =
      std::make_unique<device_reauth::MockDeviceAuthenticator>();
  EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
      .WillOnce(Return(false));
  test_api(autofill_manager().GetAutofillAiAccessManager())
      .SetDeviceAuthenticator(std::move(authenticator));

  EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
      .Times(0);

  auto is_loading = Field(&Suggestion::is_loading, Suggestion::IsLoading(true));
  auto is_unacceptable =
      Field(&Suggestion::acceptability,
            Suggestion::Acceptability::kSelectableButUnacceptable);
  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(AllOf(is_loading, is_unacceptable)),
                  FillingProduct::kAutofillAi, kDefaultSuggestionTriggerSource,
                  AutofillSuggestionsIgnoreFocusLoss(true)));

  AutofillAiPersonalContextAccessManager::GetUnmaskedSpiiEntityCallback
      callback;
  EXPECT_CALL(personal_context_manager(),
              GetUnmaskedSpiiEntity(masked_passport.guid(), _))
      .WillOnce(MoveArg<1>(&callback));

  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});

  // Now simulate the async response.
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(full_passport),
                        DefaultTriggerSource(), _));
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              std::optional(FillingProduct::kAutofillAi)));

#if BUILDFLAG(IS_ANDROID)
  // The loading dialog should not be shown if the user doesn't need to
  // authenticate.
  EXPECT_CALL(autofill_client(), ShowAutofillAiLoadingDialog()).Times(0);
  EXPECT_CALL(autofill_client(), DismissAutofillAiLoadingDialog()).Times(0);
#endif  // BUILDFLAG(IS_ANDROID)

  ASSERT_FALSE(callback.is_null());
  std::move(callback).Run(full_passport);
}

// Tests that when accepting a `kFillAutofillAi` suggestion for pcontext entity
// that requires re-authentication, the re-authentication flow is triggered and
// the form is filled upon success. The loading dialog is shown on Android while
// the data is fetched from the server.
TEST_F(AutofillExternalDelegateWithAmbientAutofillTest,
       AutofillAiReauthFlow_PersonalContextEntity_ReauthAccepted) {
  EntityInstance full_passport = GetPassportEntityInstanceWithRandomGuid(
      {.record_type = EntityInstance::RecordType::kPersonalContext});
  EntityInstance masked_passport = MaskEntityInstance(full_passport);
  autofill_client().GetEntityDataManager()->OnPrefetchContextComplete(
      personal_context_manager(), std::vector<EntityInstance>{masked_passport});

  // Create form with a passport number, which triggers obfuscation and thus
  // re-auth.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});
  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(
      masked_passport.guid(), /*requires_server_fetch=*/true);
  std::vector<Suggestion> all_suggestions = {fill_suggestion};
  OnSuggestionsReturned(queried_field(), all_suggestions);
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(all_suggestions));

  // Init the authenticator.
  auto authenticator =
      std::make_unique<device_reauth::MockDeviceAuthenticator>();
  EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
      .WillOnce(Return(true));
  device_reauth::DeviceAuthenticator::AuthenticateCallback reauth_callback;
  EXPECT_CALL(*authenticator, AuthenticateWithMessage)
      .WillOnce(MoveArg<1>(&reauth_callback));
  test_api(autofill_manager().GetAutofillAiAccessManager())
      .SetDeviceAuthenticator(std::move(authenticator));

  {
    InSequence s;

    EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
        .Times(0);

    auto is_loading =
        Field(&Suggestion::is_loading, Suggestion::IsLoading(true));
    auto is_unacceptable =
        Field(&Suggestion::acceptability,
              Suggestion::Acceptability::kSelectableButUnacceptable);
    EXPECT_CALL(
        autofill_client(),
        UpdateAutofillSuggestions(
            ElementsAre(AllOf(is_loading, is_unacceptable)),
            FillingProduct::kAutofillAi, kDefaultSuggestionTriggerSource,
            AutofillSuggestionsIgnoreFocusLoss(true)));

    EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
        .Times(0);
    EXPECT_CALL(
        autofill_manager(),
        FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                          IsQueriedFieldId(), HasFillingPayload(full_passport),
                          DefaultTriggerSource(), _));
    EXPECT_CALL(autofill_client(),
                HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                                std::optional(FillingProduct::kAutofillAi)));
  }

  // Simulate the user accepting the suggestion.
  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});

  // The `AutofillAiAccessManager` will fetch the personal context passport
  // entity from the `AutofillAiPersonalContextAccessManager` after successful
  // authentication.
  AutofillAiPersonalContextAccessManager::GetUnmaskedSpiiEntityCallback
      get_unmasked_entity_callback;
  EXPECT_CALL(personal_context_manager(),
              GetUnmaskedSpiiEntity(masked_passport.guid(), _))
      .WillOnce(MoveArg<1>(&get_unmasked_entity_callback));

#if BUILDFLAG(IS_ANDROID)
  // The loading dialog is shown only on Android.
  EXPECT_CALL(autofill_client(), ShowAutofillAiLoadingDialog());
#endif  // BUILDFLAG(IS_ANDROID)

  // Simulate successful authentication.
  ASSERT_FALSE(reauth_callback.is_null());
  std::move(reauth_callback).Run(true);

#if BUILDFLAG(IS_ANDROID)
  // Dismiss the loading dialog after the entity is fetched.
  EXPECT_CALL(autofill_client(), DismissAutofillAiLoadingDialog());
#endif  // BUILDFLAG(IS_ANDROID)

  // Simulate the async response.
  ASSERT_FALSE(get_unmasked_entity_callback.is_null());
  std::move(get_unmasked_entity_callback).Run(full_passport);
}

// Tests that when accepting a `kFillAutofillAi` suggestion for pcontext entity
// that requires re-authentication, the re-authentication flow is triggered and
// the form is filled upon success. The entity is returned directly if it's
// unmasked. The loading dialog is not shown on Android because there's no
// server request for the entity.
TEST_F(AutofillExternalDelegateWithAmbientAutofillTest,
       AutofillAiReauthFlow_UnmaskedPersonalContextEntity_ReauthAccepted) {
  EntityInstance full_passport = GetPassportEntityInstanceWithRandomGuid(
      {.record_type = EntityInstance::RecordType::kPersonalContext});
  autofill_client().GetEntityDataManager()->OnPrefetchContextComplete(
      personal_context_manager(), std::vector<EntityInstance>{full_passport});

  // Create form with a passport number, which triggers obfuscation and thus
  // re-auth.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});
  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(
      full_passport.guid(), /*requires_server_fetch=*/true);
  std::vector<Suggestion> all_suggestions = {fill_suggestion};
  OnSuggestionsReturned(queried_field(), all_suggestions);
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(all_suggestions));

  // Init the authenticator.
  auto authenticator =
      std::make_unique<device_reauth::MockDeviceAuthenticator>();
  EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
      .WillOnce(Return(true));
  device_reauth::DeviceAuthenticator::AuthenticateCallback reauth_callback;
  EXPECT_CALL(*authenticator, AuthenticateWithMessage)
      .WillOnce(MoveArg<1>(&reauth_callback));
  test_api(autofill_manager().GetAutofillAiAccessManager())
      .SetDeviceAuthenticator(std::move(authenticator));

  {
    InSequence s;

    EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
        .Times(0);

    auto is_loading =
        Field(&Suggestion::is_loading, Suggestion::IsLoading(true));
    auto is_unacceptable =
        Field(&Suggestion::acceptability,
              Suggestion::Acceptability::kSelectableButUnacceptable);
    EXPECT_CALL(
        autofill_client(),
        UpdateAutofillSuggestions(
            ElementsAre(AllOf(is_loading, is_unacceptable)),
            FillingProduct::kAutofillAi, kDefaultSuggestionTriggerSource,
            AutofillSuggestionsIgnoreFocusLoss(true)));

    EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
        .Times(0);
    EXPECT_CALL(
        autofill_manager(),
        FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                          IsQueriedFieldId(), HasFillingPayload(full_passport),
                          DefaultTriggerSource(), _));
    EXPECT_CALL(autofill_client(),
                HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                                std::optional(FillingProduct::kAutofillAi)));
  }

  // Simulate the user accepting the suggestion.
  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});

  // The `AutofillAiAccessManager` will return the unmaskes entity immediately,
  // no call to the `PersonalContextAccessManager will be made.
  EXPECT_CALL(personal_context_manager(), GetUnmaskedSpiiEntity).Times(0);

#if BUILDFLAG(IS_ANDROID)
  // The loading dialog won't be shown because no request to the
  // `PersonalContextAccessManager` is made.
  EXPECT_CALL(autofill_client(), ShowAutofillAiLoadingDialog()).Times(0);
  EXPECT_CALL(autofill_client(), DismissAutofillAiLoadingDialog()).Times(0);
#endif  // BUILDFLAG(IS_ANDROID)

  // Simulate the successful authentication.
  ASSERT_FALSE(reauth_callback.is_null());
  std::move(reauth_callback).Run(true);
}

// Tests that when accepting a `kFillAutofillAi` suggestion for pcontext entity
// that requires re-authentication, the re-authentication flow is triggered.
// The loading dialog is shown on Android during the server request to fetch
// the entity. The loading dialog is closed even if the
// `AutofillAiAccessManager` is deleted.
TEST_F(
    AutofillExternalDelegateWithAmbientAutofillTest,
    AutofillAiReauthFlow_PersonalContextEntity_AutofillAiAccessManagerIsReset) {
  EntityInstance full_passport = GetPassportEntityInstanceWithRandomGuid(
      {.record_type = EntityInstance::RecordType::kPersonalContext});
  EntityInstance masked_passport = MaskEntityInstance(full_passport);
  autofill_client().GetEntityDataManager()->OnPrefetchContextComplete(
      personal_context_manager(), std::vector<EntityInstance>{masked_passport});

  // Create form with a passport number, which triggers obfuscation and thus
  // re-auth.
  IssueOnQuery({.fields = {{.role = PASSPORT_NUMBER}}});
  Suggestion fill_suggestion(SuggestionType::kFillAutofillAi);
  fill_suggestion.payload = Suggestion::AutofillAiPayload(
      masked_passport.guid(), /*requires_server_fetch=*/true);
  std::vector<Suggestion> all_suggestions = {fill_suggestion};
  OnSuggestionsReturned(queried_field(), all_suggestions);
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(all_suggestions));

  // Init the authenticator.
  auto authenticator =
      std::make_unique<device_reauth::MockDeviceAuthenticator>();
  EXPECT_CALL(*authenticator, CanAuthenticateWithBiometricOrScreenLock)
      .WillOnce(Return(true));
  device_reauth::DeviceAuthenticator::AuthenticateCallback reauth_callback;
  EXPECT_CALL(*authenticator, AuthenticateWithMessage)
      .WillOnce(MoveArg<1>(&reauth_callback));
  test_api(autofill_manager().GetAutofillAiAccessManager())
      .SetDeviceAuthenticator(std::move(authenticator));

  {
    InSequence s;

    EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
        .Times(0);

    auto is_loading =
        Field(&Suggestion::is_loading, Suggestion::IsLoading(true));
    auto is_unacceptable =
        Field(&Suggestion::acceptability,
              Suggestion::Acceptability::kSelectableButUnacceptable);
    EXPECT_CALL(
        autofill_client(),
        UpdateAutofillSuggestions(
            ElementsAre(AllOf(is_loading, is_unacceptable)),
            FillingProduct::kAutofillAi, kDefaultSuggestionTriggerSource,
            AutofillSuggestionsIgnoreFocusLoss(true)));

    EXPECT_CALL(autofill_client(), ShowAutofillAiFetchEntityFailureNotification)
        .Times(0);
    EXPECT_CALL(autofill_manager(), FillOrPreviewForm).Times(0);
    EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);
  }

  // Simulate the user accepting the suggestion.
  external_delegate().DidAcceptSuggestion(fill_suggestion,
                                          {.multi_index = {0}});

  // The `AutofillAiAccessManager` will fetch the personal context passport
  // entity from the `AutofillAiPersonalContextAccessManager` after successful
  // authentication.
  AutofillAiPersonalContextAccessManager::GetUnmaskedSpiiEntityCallback
      get_unmasked_entity_callback;
  EXPECT_CALL(personal_context_manager(),
              GetUnmaskedSpiiEntity(masked_passport.guid(), _))
      .WillOnce(MoveArg<1>(&get_unmasked_entity_callback));

#if BUILDFLAG(IS_ANDROID)
  // The loading dialog is shown only on Android.
  EXPECT_CALL(autofill_client(), ShowAutofillAiLoadingDialog());
#endif  // BUILDFLAG(IS_ANDROID)

  // Simulate successful authentication.
  ASSERT_FALSE(reauth_callback.is_null());
  std::move(reauth_callback).Run(true);

#if BUILDFLAG(IS_ANDROID)
  // Dismiss the loading dialog after the entity is fetched.
  EXPECT_CALL(autofill_client(), DismissAutofillAiLoadingDialog());
#endif  // BUILDFLAG(IS_ANDROID)

  // Reset the access manager before the server request is complete.
  test_api(autofill_manager()).set_autofill_ai_access_manager(nullptr);

  // Simulate the async response.
  ASSERT_FALSE(get_unmasked_entity_callback.is_null());
  std::move(get_unmasked_entity_callback).Run(full_passport);
}

#endif

TEST_F(AutofillExternalDelegateTest,
       ComposeSuggestion_ComposeProactiveNudge_ForwardsCaretBoundsToClient) {
  const gfx::Rect caret_bounds = gfx::Rect(/*width=*/1, /*height=*/3);
  FormGlobalId form_id = test::MakeFormGlobalId();
  FieldGlobalId field_id = test::MakeFieldGlobalId();
  FormData form_data = test::GetFormData({
      .fields = {{.role = NAME_FIRST,
                  .host_frame = field_id.frame_token,
                  .renderer_id = field_id.renderer_id,
                  .autocomplete_attribute = "given-name"}},
      .host_frame = form_id.frame_token,
      .renderer_id = form_id.renderer_id,
  });
  // make sure the field bounds contain the caret.
  test_api(form_data).field(0).set_bounds(gfx::RectF(
      /*x=*/0, /*y=*/0, caret_bounds.width() * 2, caret_bounds.height() * 2));

  IssueOnQuery(std::move(form_data), caret_bounds);

  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(
                  AllOf(Field(&AutofillClient::PopupOpenArgs::element_bounds,
                              gfx::RectF(caret_bounds)),
                        Field(&AutofillClient::PopupOpenArgs::anchor_type,
                              PopupAnchorType::kCaret)),
                  _));
  NiceMock<MockAutofillComposeDelegate> compose_delegate;
  ON_CALL(autofill_client(), GetComposeDelegate)
      .WillByDefault(Return(&compose_delegate));
  ON_CALL(compose_delegate, ShouldAnchorNudgeOnCaret)
      .WillByDefault(Return(true));

  // This should call ShowAutofillSuggestions.
  OnSuggestionsReturned(queried_field(),
                        {Suggestion(SuggestionType::kComposeProactiveNudge)});
}

// Even though the `SuggestionType` is correct and the bounds are valid. The
// caret bounds are not used because the
// `ComposeDelegate::ShouldAnchorNudgeOnCaret()` is returning false.
TEST_F(
    AutofillExternalDelegateTest,
    ComposeSuggestion_ComposeProactiveNudge_ShouldAnchorNudgeOnCaretReturnsFalse_DoNotForwardsCaretBoundsToClient) {
  const gfx::Rect caret_bounds = gfx::Rect(/*width=*/1, /*height=*/3);
  FormGlobalId form_id = test::MakeFormGlobalId();
  FieldGlobalId field_id = test::MakeFieldGlobalId();
  FormData form_data = test::GetFormData({
      .fields = {{.role = NAME_FIRST,
                  .host_frame = field_id.frame_token,
                  .renderer_id = field_id.renderer_id,
                  .autocomplete_attribute = "given-name"}},
      .host_frame = form_id.frame_token,
      .renderer_id = form_id.renderer_id,
  });
  // make sure the field bounds contain the caret.
  const gfx::RectF field_bounds = gfx::RectF(
      /*x=*/0, /*y=*/0, caret_bounds.width() * 2, caret_bounds.height() * 2);
  test_api(form_data).field(0).set_bounds(field_bounds);

  IssueOnQuery(std::move(form_data), caret_bounds);

  EXPECT_CALL(
      autofill_client(),
      ShowAutofillSuggestions(
          Field(&AutofillClient::PopupOpenArgs::element_bounds, field_bounds),
          _));
  NiceMock<MockAutofillComposeDelegate> compose_delegate;
  ON_CALL(autofill_client(), GetComposeDelegate)
      .WillByDefault(Return(&compose_delegate));
  ON_CALL(compose_delegate, ShouldAnchorNudgeOnCaret)
      .WillByDefault(Return(false));

  // This should call ShowAutofillSuggestions.
  OnSuggestionsReturned(queried_field(),
                        {Suggestion(SuggestionType::kComposeProactiveNudge)});
}

TEST_F(
    AutofillExternalDelegateTest,
    ComposeSuggestion_ComposeProactiveNudge_CaretOutsideField_DoNotSendCaretBoundsToClient) {
  const gfx::Rect caret_bounds = gfx::Rect(/*width=*/1, /*height=*/3);
  FormGlobalId form_id = test::MakeFormGlobalId();
  FieldGlobalId field_id = test::MakeFieldGlobalId();
  FormData form_data = test::GetFormData({
      .fields = {{.role = NAME_FIRST,
                  .host_frame = field_id.frame_token,
                  .renderer_id = field_id.renderer_id,
                  .autocomplete_attribute = "given-name"}},
      .host_frame = form_id.frame_token,
      .renderer_id = form_id.renderer_id,
  });
  // make sure the field bounds do not contain the caret.
  const gfx::RectF field_bounds = gfx::RectF(
      /*x=*/caret_bounds.x() + caret_bounds.width() + 1,
      /*y=*/caret_bounds.y() + caret_bounds.height() + 1, caret_bounds.width(),
      caret_bounds.height());
  test_api(form_data).field(0).set_bounds(field_bounds);

  IssueOnQuery(std::move(form_data), caret_bounds);

  EXPECT_CALL(
      autofill_client(),
      ShowAutofillSuggestions(
          Field(&AutofillClient::PopupOpenArgs::element_bounds, field_bounds),
          _));
  NiceMock<MockAutofillComposeDelegate> compose_delegate;
  ON_CALL(autofill_client(), GetComposeDelegate)
      .WillByDefault(Return(&compose_delegate));
  ON_CALL(compose_delegate, ShouldAnchorNudgeOnCaret)
      .WillByDefault(Return(true));

  // This should call ShowAutofillSuggestions.
  OnSuggestionsReturned(queried_field(),
                        {Suggestion(SuggestionType::kComposeProactiveNudge)});
}

TEST_F(
    AutofillExternalDelegateTest,
    NonComposeSuggestion_NonComposeProactiveNudge_DoNotForwardsCaretBoundsToClient) {
  IssueOnQuery(gfx::Rect(/*width=*/123, /*height=*/123));

  const PopupAnchorType default_anchor_type =
#if BUILDFLAG(IS_ANDROID)
      PopupAnchorType::kKeyboardAccessory;
#else
      PopupAnchorType::kField;
#endif
  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(
                  AllOf(Field(&AutofillClient::PopupOpenArgs::element_bounds,
                              gfx::RectF(/*width=*/0, /*height=*/0)),
                        Field(&AutofillClient::PopupOpenArgs::anchor_type,
                              default_anchor_type)),
                  _));
  NiceMock<MockAutofillComposeDelegate> compose_delegate;
  ON_CALL(autofill_client(), GetComposeDelegate)
      .WillByDefault(Return(&compose_delegate));
  ON_CALL(compose_delegate, ShouldAnchorNudgeOnCaret)
      .WillByDefault(Return(true));

  // This should call ShowAutofillSuggestions.
  OnSuggestionsReturned(queried_field(),
                        {Suggestion(SuggestionType::kAutocompleteEntry)});
}

// Tests that accepting a Compose suggestion returns a callback that, when run,
// fills the trigger field.
TEST_F(AutofillExternalDelegateTest, ExternalDelegateOpensComposeAndFills) {
  NiceMock<MockAutofillComposeDelegate> compose_delegate;
  ON_CALL(autofill_client(), GetComposeDelegate)
      .WillByDefault(Return(&compose_delegate));

  IssueOnQuery();

  // Simulate receiving a Compose suggestion.
  EXPECT_CALL(autofill_client(),
              ShowAutofillSuggestions(PopupOpenArgsAre(SuggestionVectorIdsAre(
                                          SuggestionType::kComposeResumeNudge)),
                                      _));
  std::vector<Suggestion> suggestions = {
      Suggestion(SuggestionType::kComposeResumeNudge)};
  OnSuggestionsReturned(queried_field(), suggestions);

  // Simulate accepting a Compose suggestion.
  EXPECT_CALL(
      compose_delegate,
      OpenCompose(_, queried_field().global_id(),
                  AutofillComposeDelegate::UiEntryPoint::kAutofillPopup));
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  external_delegate().DidAcceptSuggestion(
      suggestions[0], SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest,
       Compose_AcceptDisable_CallsComposeDelegate) {
  NiceMock<MockAutofillComposeDelegate> compose_delegate;
  ON_CALL(autofill_client(), GetComposeDelegate)
      .WillByDefault(Return(&compose_delegate));

  IssueOnQuery();

  // Simulate accepting a Compose `SuggestionType::kComposeDisable`
  // suggestion.
  EXPECT_CALL(compose_delegate, DisableCompose);
  external_delegate().DidAcceptSuggestion(
      Suggestion(SuggestionType::kComposeDisable),
      SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest,
       Compose_AcceptGoToSettings_CallsComposeDelegate) {
  NiceMock<MockAutofillComposeDelegate> compose_delegate;
  ON_CALL(autofill_client(), GetComposeDelegate)
      .WillByDefault(Return(&compose_delegate));

  IssueOnQuery();

  // Simulate accepting a Compose `SuggestionType::kComposeGoToSettings`
  // suggestion.
  EXPECT_CALL(compose_delegate, GoToSettings);
  external_delegate().DidAcceptSuggestion(
      Suggestion(SuggestionType::kComposeGoToSettings),
      SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest,
       AcceptSaveAndFillCreditCardSuggestion_CallsSaveAndFillManager) {
  IssueOnQuery();

  EXPECT_CALL(*payments_autofill_client().GetSaveAndFillManager(),
              OnDidAcceptCreditCardSaveAndFillSuggestion(_));
  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kSaveAndFillCreditCardEntry),
      SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest, AcceptedSaveAndFillEntry_FillForm) {
  IssueOnQuery();
  CreditCard card = test::GetCreditCard();

  EXPECT_CALL(*payments_autofill_client().GetSaveAndFillManager(),
              OnDidAcceptCreditCardSaveAndFillSuggestion)
      .WillOnce([&](MockSaveAndFillManager::FillCardCallback callback) {
        std::move(callback).Run(card);
      });
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(card),
                        AutofillTriggerSource::kCreditCardSaveAndFill, _));

  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kSaveAndFillCreditCardEntry),
      SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest, SaveAndFillMetrics_SuggestionAccepted) {
  base::HistogramTester histogram;
  IssueOnQuery();

  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kSaveAndFillCreditCardEntry),
      SuggestionPosition{.multi_index = {0}});

  histogram.ExpectBucketCount(
      "Autofill.FormEvents.CreditCard.SaveAndFill",
      autofill_metrics::SaveAndFillFormEvent::kSuggestionAccepted, 1);
}

TEST_F(AutofillExternalDelegateTest,
       Compose_AcceptNeverShowOnThisWebsiteAgain_CallsComposeDelegate) {
  NiceMock<MockAutofillComposeDelegate> compose_delegate;
  ON_CALL(autofill_client(), GetComposeDelegate)
      .WillByDefault(Return(&compose_delegate));

  IssueOnQuery();

  // Simulate accepting a Compose
  // `SuggestionType::kComposeNeverShowOnThisSiteAgain` suggestion.
  EXPECT_CALL(compose_delegate, NeverShowComposeForOrigin);
  external_delegate().DidAcceptSuggestion(
      Suggestion(SuggestionType::kComposeNeverShowOnThisSiteAgain),
      SuggestionPosition{.multi_index = {0}});
}

#if !BUILDFLAG(IS_IOS)
// Test that the driver is directed to clear or undo the form after being
// notified that the user accepted the suggestion to clear or undo the form.
TEST_F(AutofillExternalDelegateTest, ExternalDelegateUndoForm) {
  IssueOnQuery();
  EXPECT_CALL(autofill_manager(), UndoAutofill);
  external_delegate().DidAcceptSuggestion(
      Suggestion(SuggestionType::kUndo),
      SuggestionPosition{.multi_index = {0}});
}

// Test that the driver is directed to undo the form after being notified that
// the user selected the suggestion to undo the form.
TEST_F(AutofillExternalDelegateTest, ExternalDelegateUndoPreviewForm) {
  IssueOnQuery();
  EXPECT_CALL(autofill_manager(), UndoAutofill);
  external_delegate().DidSelectSuggestion(Suggestion(SuggestionType::kUndo));
}
#endif

// Test that autofill client will scan a credit card after use accepted the
// suggestion to scan a credit card.
TEST_F(AutofillExternalDelegateTest, ScanCreditCardMenuItem) {
  IssueOnQuery();
  EXPECT_CALL(payments_autofill_client(), ScanCreditCard);
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));

  external_delegate().DidAcceptSuggestion(
      Suggestion(SuggestionType::kScanCreditCard),
      SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest, ScanCreditCardMetrics_SuggestionShown) {
  base::HistogramTester histogram;
  IssueOnQuery();
  std::vector<Suggestion> suggestions = {
      Suggestion(SuggestionType::kScanCreditCard)};
  OnSuggestionsReturned(queried_field(), suggestions);
  external_delegate().OnSuggestionsShown(suggestions, std::nullopt);

  histogram.ExpectUniqueSample("Autofill.ScanCreditCardPrompt",
                               AutofillMetrics::SCAN_CARD_ITEM_SHOWN, 1);
}

TEST_F(AutofillExternalDelegateTest, ScanCreditCardMetrics_SuggestionAccepted) {
  base::HistogramTester histogram;
  IssueOnQuery();
  std::vector<Suggestion> suggestions = {
      Suggestion(SuggestionType::kScanCreditCard)};
  OnSuggestionsReturned(queried_field(), suggestions);
  external_delegate().OnSuggestionsShown(suggestions, std::nullopt);

  external_delegate().DidAcceptSuggestion(
      Suggestion(SuggestionType::kScanCreditCard),
      SuggestionPosition{.multi_index = {0}});

  histogram.ExpectBucketCount("Autofill.ScanCreditCardPrompt",
                              AutofillMetrics::SCAN_CARD_ITEM_SHOWN, 1);
  histogram.ExpectBucketCount("Autofill.ScanCreditCardPrompt",
                              AutofillMetrics::SCAN_CARD_ITEM_SELECTED, 1);
  histogram.ExpectBucketCount("Autofill.ScanCreditCardPrompt",
                              AutofillMetrics::SCAN_CARD_OTHER_ITEM_SELECTED,
                              0);
}

TEST_F(AutofillExternalDelegateTest,
       ScanCreditCardMetrics_DifferentSuggestionAccepted) {
  base::HistogramTester histogram;
  IssueOnQuery();
  std::vector<Suggestion> suggestions = {
      Suggestion(SuggestionType::kScanCreditCard)};
  OnSuggestionsReturned(queried_field(), suggestions);
  external_delegate().OnSuggestionsShown(suggestions, std::nullopt);

  external_delegate().DidAcceptSuggestion(
      Suggestion(SuggestionType::kCreditCardEntry),
      SuggestionPosition{.multi_index = {0}});

  histogram.ExpectBucketCount("Autofill.ScanCreditCardPrompt",
                              AutofillMetrics::SCAN_CARD_ITEM_SHOWN, 1);
  histogram.ExpectBucketCount("Autofill.ScanCreditCardPrompt",
                              AutofillMetrics::SCAN_CARD_ITEM_SELECTED, 0);
  histogram.ExpectBucketCount("Autofill.ScanCreditCardPrompt",
                              AutofillMetrics::SCAN_CARD_OTHER_ITEM_SELECTED,
                              1);
}

TEST_F(AutofillExternalDelegateTest, ScanCreditCardMetrics_SuggestionNotShown) {
  base::HistogramTester histogram;
  IssueOnQuery();
  OnSuggestionsReturned(queried_field(), {});
  external_delegate().OnSuggestionsShown({}, std::nullopt);
  histogram.ExpectTotalCount("Autofill.ScanCreditCardPrompt", 0);
}

TEST_F(AutofillExternalDelegateTest, AutocompleteShown_MetricsEmitted) {
  base::HistogramTester histogram;
  IssueOnQuery();
  std::vector<Suggestion> suggestions = {CreateAutofillSuggestion(
      SuggestionType::kAutocompleteEntry, u"autocomplete")};
  OnSuggestionsReturned(queried_field(), suggestions);
  external_delegate().OnSuggestionsShown(suggestions, std::nullopt);
  histogram.ExpectBucketCount("Autocomplete.Events3",
                              AutofillMetrics::AUTOCOMPLETE_SUGGESTIONS_SHOWN,
                              1);
}

TEST_F(AutofillExternalDelegateTest, ScanCreditCard_FillForm) {
  IssueOnQuery();
  CreditCard card = test::GetCreditCard();
  EXPECT_CALL(payments_autofill_client(), ScanCreditCard)
      .WillOnce(
          [&](MockPaymentsAutofillClient::CreditCardScanCallback callback) {
            std::move(callback).Run(card);
          });
  EXPECT_CALL(autofill_manager(),
              FillOrPreviewForm(mojom::ActionPersistence::kFill, _, _,
                                HasFillingPayload(card), _, _));
  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kScanCreditCard),
      {.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest, IgnoreAutocompleteOffForAutofill) {
  const FormData form;
  FormFieldData field;
  field.set_is_focusable(true);
  field.set_should_autocomplete(false);

  external_delegate().OnQuery(form, field, /*caret_bounds=*/gfx::Rect(),
                              kDefaultSuggestionTriggerSource);

  std::vector<Suggestion> autofill_items;
  autofill_items.emplace_back(SuggestionType::kAutocompleteEntry);

  // Ensure the popup tries to show itself, despite autocomplete="off".
  EXPECT_CALL(autofill_client(), ShowAutofillSuggestions);
  EXPECT_CALL(autofill_client(), HideSuggestions(_, _)).Times(0);

  OnSuggestionsReturned(field, autofill_items);
}

TEST_F(AutofillExternalDelegateTest,
       ExternalDelegateFillFieldWithValue_Autocomplete) {
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  IssueOnQuery();

  base::HistogramTester histogram_tester;
  std::u16string dummy_autocomplete_string(u"autocomplete");
  Suggestion suggestion(SuggestionType::kAutocompleteEntry);
  suggestion.main_text.value = dummy_autocomplete_string;
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(
          mojom::ActionPersistence::kFill, mojom::FieldActionType::kReplaceAll,
          HasQueriedFormId(), HasQueriedFieldId(), dummy_autocomplete_string,
          FillingProduct::kAutocomplete, std::optional<FieldType>()));
  MockAutocompleteHistoryManager* autocomplete_history_manager =
      static_cast<MockAutocompleteHistoryManager*>(
          autofill_client().GetAutocompleteHistoryManager());
  EXPECT_CALL(*autocomplete_history_manager,
              OnSingleFieldSuggestionSelected(suggestion));

  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kAutocompleteEntry,
                               dummy_autocomplete_string),
      SuggestionPosition{.multi_index = {0}});

  histogram_tester.ExpectUniqueSample(
      "Autofill.SuggestionAcceptedIndex.Autocomplete", 0, 1);
}

TEST_F(AutofillExternalDelegateTest,
       ExternalDelegateFillFieldWithValue_AutofillAddressOnTyping) {
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  const AutofillProfile profile = test::GetFullProfile();
  pdm().address_data_manager().AddProfile(profile);
  IssueOnQuery();

  std::u16string dummy_autofill_on_typing_string(u"Jon doe");
  Suggestion suggestion = CreateAutofillSuggestion(
      SuggestionType::kAddressEntryOnTyping, dummy_autofill_on_typing_string,
      Suggestion::AutofillProfilePayload(Suggestion::Guid(profile.guid())));
  suggestion.field_by_field_filling_type_used = NAME_FULL;
  // Simulate that the user has typed the first 3 characters of their full name.
  get_triggering_autofill_field().set_value(
      dummy_autofill_on_typing_string.substr(0, 3));
  base::HistogramTester histogram_tester;

  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(
          mojom::ActionPersistence::kFill, mojom::FieldActionType::kReplaceAll,
          HasQueriedFormId(), HasQueriedFieldId(),
          profile.GetRawInfo(*suggestion.field_by_field_filling_type_used),
          FillingProduct::kAddress, std::optional(NAME_FULL)));
  EXPECT_CALL(autofill_manager(), OnDidFillAddressFormFillingSuggestion)
      .Times(0);
  EXPECT_CALL(
      autofill_manager(),
      OnDidFillAddressOnTypingSuggestion(
          IsQueriedFieldId(),
          profile.GetRawInfo(*suggestion.field_by_field_filling_type_used),
          NAME_FULL, profile.guid()));

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});

  histogram_tester.ExpectUniqueSample("Autofill.Suggestions.AcceptedType",
                                      SuggestionType::kAddressEntryOnTyping, 1);
  histogram_tester.ExpectUniqueSample(
      "Autofill.AddressSuggestionOnTyping.AddressFieldTypeUsed", NAME_FULL, 1);
  // Note that the triggeting field is classified.
  histogram_tester.ExpectUniqueSample(
      "Autofill.AddressSuggestionOnTypingAcceptance.FieldClassication", true,
      1);
  // Above it was simulated that the user typed 3 characters in the field.
  histogram_tester.ExpectBucketCount(
      "Autofill.AddressSuggestionOnTypingAcceptance.NumberOfCharactersTyped", 3,
      1);
}

TEST_F(AutofillExternalDelegateTest,
       ExternalDelegateFillFieldWithValue_MerchantPromoCode) {
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  IssueOnQuery();

  std::u16string dummy_promo_code_string(u"merchant promo");
  Suggestion suggestion(SuggestionType::kMerchantPromoCodeEntry);
  suggestion.main_text.value = dummy_promo_code_string;
  EXPECT_CALL(autofill_manager(),
              FillOrPreviewField(mojom::ActionPersistence::kFill,
                                 mojom::FieldActionType::kReplaceAll,
                                 HasQueriedFormId(), HasQueriedFieldId(),
                                 dummy_promo_code_string,
                                 FillingProduct::kMerchantPromoCode,
                                 std::optional(MERCHANT_PROMO_CODE)));
  EXPECT_CALL(*payments_autofill_client().GetMerchantPromoCodeManager(),
              OnSingleFieldSuggestionSelected(suggestion));

  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kMerchantPromoCodeEntry,
                               dummy_promo_code_string),
      SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest, ExternalDelegateFillFieldWithValue_Iban) {
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  IssueOnQuery();

  Iban iban = test::GetLocalIban();
  Suggestion suggestion(SuggestionType::kIbanEntry);
  suggestion.main_text.value = iban.GetIdentifierStringForAutofillDisplay();
  suggestion.payload = Suggestion::Guid(iban.guid());
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kFill,
                         mojom::FieldActionType::kReplaceAll,
                         HasQueriedFormId(), HasQueriedFieldId(), iban.value(),
                         FillingProduct::kIban, std::optional(IBAN_VALUE)));
  EXPECT_CALL(*payments_autofill_client().GetIbanManager(),
              OnSingleFieldSuggestionSelected(suggestion));

  ON_CALL(*payments_autofill_client().GetIbanAccessManager(), FetchValue)
      .WillByDefault([iban](const Suggestion::Payload& payload,
                            IbanAccessManager::OnIbanFetchedCallback callback) {
        std::move(callback).Run(iban.value());
        return IsAsync(false);
      });
  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kIbanEntry,
                               iban.GetIdentifierStringForAutofillDisplay(),
                               Suggestion::Guid(iban.guid())),
      SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest,
       ExternalDelegateFillFieldWithValue_FieldByFieldFilling) {
  const AutofillProfile profile = test::GetFullProfile();
  pdm().address_data_manager().AddProfile(profile);
  IssueOnQuery();
  Suggestion suggestion = CreateAutofillSuggestion(
      SuggestionType::kAddressFieldByFieldFilling, u"field by field",
      Suggestion::AutofillProfilePayload(Suggestion::Guid(profile.guid())));
  suggestion.field_by_field_filling_type_used = NAME_FIRST;
  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
                              Eq(std::nullopt)));
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(
          mojom::ActionPersistence::kFill, mojom::FieldActionType::kReplaceAll,
          HasQueriedFormId(), HasQueriedFieldId(),
          profile.GetRawInfo(*suggestion.field_by_field_filling_type_used),
          FillingProduct::kAddress, std::optional(NAME_FIRST)));
  EXPECT_CALL(autofill_manager(),
              OnDidFillAddressFormFillingSuggestion(
                  Property(&AutofillProfile::guid, profile.guid()),
                  queried_form().global_id(), IsQueriedFieldId(), _));

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

// Test that browser autofill manager will handle the unmasking request for the
// virtual card after users accept the suggestion to use a virtual card.
TEST_F(AutofillExternalDelegateTest, AcceptVirtualCardOptionItem) {
  IssueOnQuery();
  FormData form;
  CreditCard card = test::GetMaskedServerCard();
  pdm().payments_data_manager().AddCreditCard(card);
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kFill, HasQueriedFormId(),
                        IsQueriedFieldId(), _, _, _));
  Suggestion suggestion(SuggestionType::kVirtualCreditCardEntry);
  suggestion.payload = Suggestion::Guid(card.guid());
  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest, SelectVirtualCardOptionItem) {
  IssueOnQuery();
  CreditCard card = test::GetMaskedServerCard();
  pdm().payments_data_manager().AddCreditCard(card);
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewForm(mojom::ActionPersistence::kPreview, HasQueriedFormId(),
                        IsQueriedFieldId(), HasFillingPayload(card), _, _));
  Suggestion suggestion(SuggestionType::kVirtualCreditCardEntry);
  suggestion.payload = Suggestion::Guid(card.guid());
  external_delegate().DidSelectSuggestion(suggestion);
}

TEST_F(AutofillExternalDelegateTest, RemoveSuggestion_Autocomplete) {
  auto mock_single_field_fill_router =
      std::make_unique<MockSingleFieldFillRouter>(
          autofill_client().GetAutocompleteHistoryManager(), nullptr, nullptr);
  EXPECT_CALL(
      *mock_single_field_fill_router,
      OnRemoveCurrentSingleFieldSuggestion(
          std::u16string(u"name"), std::u16string(u""),
          std::u16string(u"value"), SuggestionType::kAutocompleteEntry));
  autofill_client().set_single_field_fill_router(
      std::move(mock_single_field_fill_router));

  AutocompleteEntry entry(AutocompleteKey("name", "value"), base::Time::Now(),
                          base::Time::Now());
  Suggestion autocomplete_suggestion(SuggestionType::kAutocompleteEntry);
  autocomplete_suggestion.payload = std::move(entry);
  EXPECT_TRUE(external_delegate().RemoveSuggestion(autocomplete_suggestion));
}

TEST_F(AutofillExternalDelegateTest, RemoveSuggestion_Address) {
  const AutofillProfile profile = test::GetFullProfile();
  pdm().address_data_manager().AddProfile(profile);
  ASSERT_TRUE(pdm().address_data_manager().GetProfileByGUID(profile.guid()));
  EXPECT_TRUE(external_delegate().RemoveSuggestion(CreateAutofillSuggestion(
      SuggestionType::kAddressEntry, u"address",
      Suggestion::AutofillProfilePayload(Suggestion::Guid(profile.guid())))));
  EXPECT_FALSE(pdm().address_data_manager().GetProfileByGUID(profile.guid()));
}

TEST_F(AutofillExternalDelegateTest, RemoveSuggestion_AddressFieldByField) {
  const AutofillProfile profile = test::GetFullProfile();
  pdm().address_data_manager().AddProfile(profile);
  ASSERT_TRUE(pdm().address_data_manager().GetProfileByGUID(profile.guid()));
  EXPECT_TRUE(external_delegate().RemoveSuggestion(CreateAutofillSuggestion(
      SuggestionType::kAddressFieldByFieldFilling, u"address",
      Suggestion::AutofillProfilePayload(Suggestion::Guid(profile.guid())))));
  EXPECT_FALSE(pdm().address_data_manager().GetProfileByGUID(profile.guid()));
}

TEST_F(AutofillExternalDelegateTest, RemoveSuggestion_LocalCard) {
  const CreditCard local_card = test::GetCreditCard();
  pdm().payments_data_manager().AddCreditCard(local_card);
  ASSERT_TRUE(
      pdm().payments_data_manager().GetCreditCardByGUID(local_card.guid()));
  EXPECT_TRUE(external_delegate().RemoveSuggestion(
      CreateAutofillSuggestion(SuggestionType::kCreditCardEntry, u"card",
                               Suggestion::Guid(local_card.guid()))));
  EXPECT_FALSE(
      pdm().payments_data_manager().GetCreditCardByGUID(local_card.guid()));
}

// Tests that server cards are not removed.
TEST_F(AutofillExternalDelegateTest, RemoveSuggestion_ServerCard) {
  const CreditCard server_card = test::GetMaskedServerCard();
  TestPaymentsDataManager& paydm =
      static_cast<TestPaymentsDataManager&>(pdm().payments_data_manager());
  paydm.SetAutofillWalletImportEnabled(true);
  paydm.AddServerCreditCard(server_card);
  ASSERT_TRUE(
      pdm().payments_data_manager().GetCreditCardByGUID(server_card.guid()));
  EXPECT_FALSE(external_delegate().RemoveSuggestion(
      CreateAutofillSuggestion(SuggestionType::kCreditCardEntry, u"card",
                               Suggestion::Guid(server_card.guid()))));
  EXPECT_TRUE(
      pdm().payments_data_manager().GetCreditCardByGUID(server_card.guid()));
}

// Tests that showing the personal context notice records an impression for
// AmbientAutofill.
TEST_F(AutofillExternalDelegateTest,
       OnSuggestionsShown_PersonalContextNotice_AmbientAutofill) {
  IssueOnQuery();
  autofill_client().set_suggestion_ui_session_id(
      AutofillClient::SuggestionUiSessionId(1));
  EXPECT_EQ(autofill_client()
                .GetPersonalContextFirstRunService()
                ->ambient_autofill_notice_impressions(),
            0);

  external_delegate().OnSuggestionsShown(
      std::vector<Suggestion>{
          Suggestion(SuggestionType::kPersonalContextNotice)},
      /*parent_suggestion_metadata=*/std::nullopt);

  EXPECT_EQ(autofill_client()
                .GetPersonalContextFirstRunService()
                ->ambient_autofill_notice_impressions(),
            1);

  // Showing suggestions again in the same session should not increment.
  external_delegate().OnSuggestionsShown(
      std::vector<Suggestion>{
          Suggestion(SuggestionType::kPersonalContextNotice)},
      /*parent_suggestion_metadata=*/std::nullopt);

  EXPECT_EQ(autofill_client()
                .GetPersonalContextFirstRunService()
                ->ambient_autofill_notice_impressions(),
            1);

  // Hiding and showing again should increment if we generate a new session ID.
  external_delegate().OnSuggestionsHidden(SuggestionHidingReason::kTabGone);
  autofill_client().set_suggestion_ui_session_id(
      AutofillClient::SuggestionUiSessionId(2));
  external_delegate().OnSuggestionsShown(
      std::vector<Suggestion>{
          Suggestion(SuggestionType::kPersonalContextNotice)},
      /*parent_suggestion_metadata=*/std::nullopt);

  EXPECT_EQ(autofill_client()
                .GetPersonalContextFirstRunService()
                ->ambient_autofill_notice_impressions(),
            2);
}

// Tests that showing the personal context notice records an impression for
// AtMemory.
TEST_F(AutofillExternalDelegateTest,
       OnSuggestionsShown_PersonalContextNotice_AtMemory) {
  IssueOnQuery(AutofillSuggestionTriggerSource::kAtMemoryTriggerString);
  autofill_client().set_suggestion_ui_session_id(
      AutofillClient::SuggestionUiSessionId(1));
  EXPECT_EQ(autofill_client()
                .GetPersonalContextFirstRunService()
                ->at_memory_notice_impressions(),
            0);

  external_delegate().OnSuggestionsShown(
      std::vector<Suggestion>{
          Suggestion(SuggestionType::kPersonalContextNotice)},
      /*parent_suggestion_metadata=*/std::nullopt);

  EXPECT_EQ(autofill_client()
                .GetPersonalContextFirstRunService()
                ->at_memory_notice_impressions(),
            1);

  // Showing suggestions again in the same session should not increment.
  external_delegate().OnSuggestionsShown(
      std::vector<Suggestion>{
          Suggestion(SuggestionType::kPersonalContextNotice)},
      /*parent_suggestion_metadata=*/std::nullopt);

  EXPECT_EQ(autofill_client()
                .GetPersonalContextFirstRunService()
                ->at_memory_notice_impressions(),
            1);

  // Hiding and showing again should increment if we generate a new session ID.
  external_delegate().OnSuggestionsHidden(SuggestionHidingReason::kTabGone);
  autofill_client().set_suggestion_ui_session_id(
      AutofillClient::SuggestionUiSessionId(2));
  external_delegate().OnSuggestionsShown(
      std::vector<Suggestion>{
          Suggestion(SuggestionType::kPersonalContextNotice)},
      /*parent_suggestion_metadata=*/std::nullopt);

  EXPECT_EQ(autofill_client()
                .GetPersonalContextFirstRunService()
                ->at_memory_notice_impressions(),
            2);
}

// Tests that the personal context notice is removed and the pref is updated for
// ambient autofill.
TEST_F(AutofillExternalDelegateTest,
       RemoveSuggestion_PersonalContextNotice_AmbientAutofill) {
  EXPECT_FALSE(autofill_client()
                   .GetPersonalContextFirstRunService()
                   ->is_ambient_autofill_notice_acknowledged());
  EXPECT_TRUE(external_delegate().RemoveSuggestion(
      Suggestion(SuggestionType::kPersonalContextNotice)));
  EXPECT_TRUE(autofill_client()
                  .GetPersonalContextFirstRunService()
                  ->is_ambient_autofill_notice_acknowledged());
  EXPECT_FALSE(autofill_client()
                   .GetPersonalContextFirstRunService()
                   ->is_at_memory_notice_acknowledged());
}

// Tests that the personal context notice is removed and the pref is updated for
// AtMemory.
TEST_F(AutofillExternalDelegateTest,
       RemoveSuggestion_PersonalContextNotice_AtMemory) {
  IssueOnQuery(AutofillSuggestionTriggerSource::kAtMemoryTriggerString);
  EXPECT_FALSE(autofill_client()
                   .GetPersonalContextFirstRunService()
                   ->is_at_memory_notice_acknowledged());
  EXPECT_TRUE(external_delegate().RemoveSuggestion(
      Suggestion(SuggestionType::kPersonalContextNotice)));
  EXPECT_TRUE(autofill_client()
                  .GetPersonalContextFirstRunService()
                  ->is_at_memory_notice_acknowledged());
  // Acknowledging the AtMemory notice also implicitly acknowledges the
  // Ambient Autofill notice.
  EXPECT_TRUE(autofill_client()
                  .GetPersonalContextFirstRunService()
                  ->is_ambient_autofill_notice_acknowledged());
}

TEST_F(AutofillExternalDelegateTest,
       RemoveSuggestion_AutofillAiPrivateInferenceNotice) {
  EXPECT_TRUE(external_delegate().RemoveSuggestion(
      Suggestion(SuggestionType::kAutofillAiPrivateInferenceNotice)));
  EXPECT_NE(autofill_client().GetPrefs()->GetTime(
                prefs::kAutofillAiPrivateInferenceNoticeAcknowledgedTimestamp),
            base::Time());
}

// Tests that accepting a personal context notice suggestion is a no-op.
TEST_F(AutofillExternalDelegateTest,
       DidAcceptSuggestion_PersonalContextNotice_NoOp) {
  IssueOnQuery();
  EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);
  external_delegate().DidAcceptSuggestion(
      Suggestion(SuggestionType::kPersonalContextNotice),
      SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest, RecordSuggestionTypeOnSuggestionAccepted) {
  base::HistogramTester histogram_tester;
  IssueOnQuery();

  const AutofillProfile profile = test::GetFullProfile();
  pdm().address_data_manager().AddProfile(profile);

  external_delegate().DidAcceptSuggestion(
      CreateAutofillSuggestion(SuggestionType::kAddressEntry),
      SuggestionPosition{.multi_index = {0}});

  histogram_tester.ExpectUniqueSample("Autofill.Suggestions.AcceptedType",
                                      SuggestionType::kAddressEntry, 1);
}

// Tests that setting `is_update` to true in
// `AttemptToDisplayAutofillSuggestions` leads to a call to
// `AutofillClient::UpdateAutofillSuggestions`.
TEST_F(AutofillExternalDelegateTest, UpdateSuggestions) {
  IssueOnQuery();

  std::vector<Suggestion> suggestions1 = {
      Suggestion(u"Some suggestion", SuggestionType::kAutocompleteEntry)};
  std::vector<Suggestion> suggestions2 = {
      Suggestion(u"Other suggestion", SuggestionType::kAutocompleteEntry)};

  {
    InSequence s;
    EXPECT_CALL(autofill_client(), ShowAutofillSuggestions);
    EXPECT_CALL(
        autofill_client(),
        UpdateAutofillSuggestions(suggestions2, FillingProduct::kAutocomplete,
                                  AutofillSuggestionTriggerSource::kUnspecified,
                                  AutofillSuggestionsIgnoreFocusLoss(false)));
  }

  OnSuggestionsReturned(queried_field(), suggestions1);
  external_delegate().AttemptToDisplayAutofillSuggestionsForTest(
      suggestions2, AutofillSuggestionTriggerSource::kUnspecified,
      std::nullopt);
}

// Tests that calling `AttemptToDisplayAutofillSuggestions` with empty
// `trigger_field` and different filling product doesn't hide the popup.
TEST_F(AutofillExternalDelegateTest, UpdateSuggestions_ProductChanged) {
  base::HistogramTester histogram_tester;
  IssueOnQuery();
  EXPECT_CALL(autofill_client(), ShowAutofillSuggestions).Times(1);
  EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);

  OnSuggestionsReturned(
      queried_field(),
      {Suggestion(u"Address suggestion", SuggestionType::kAddressEntry)});
  external_delegate().AttemptToDisplayAutofillSuggestionsForTest(
      {Suggestion(u"Autofill AI suggestion", SuggestionType::kFillAutofillAi)},
      AutofillSuggestionTriggerSource::kUnspecified, std::nullopt);

  int expected_sample = (std::to_underlying(FillingProduct::kAddress) << 8) |
                        std::to_underlying(FillingProduct::kAutofillAi);
  histogram_tester.ExpectUniqueSample(
      "Autofill.PopupUpdateIgnored.ProductMismatch", expected_sample, 1);
}

// TODO(crbug.com/41483208): Add test case where 'Show cards from your Google
// account' button is clicked. Encountered issues with test sync setup when
// attempting to make it.

#if BUILDFLAG(IS_IOS)
// Tests that outdated returned suggestions are discarded.
TEST_F(AutofillExternalDelegateTest, ShouldDiscardOutdatedSuggestions) {
  FieldGlobalId old_field_id = test::MakeFieldGlobalId();
  FormFieldData old_field;
  old_field.set_host_frame(old_field_id.frame_token);
  old_field.set_renderer_id(old_field_id.renderer_id);
  FieldGlobalId new_field_id = test::MakeFieldGlobalId();
  autofill_client().set_last_queried_field(new_field_id);
  IssueOnQuery();
  EXPECT_CALL(autofill_client(), ShowAutofillSuggestions).Times(0);
  OnSuggestionsReturned(old_field, std::vector<Suggestion>());
}
#endif

// Tests that AtMemory search results use the kReplaceSelectionForAtMemory
// action.
TEST_F(AutofillExternalDelegateTest, AtMemorySearchResult_UsesSpecialAction) {
  StartAtMemorySession();
  Suggestion suggestion(u"some result", SuggestionType::kAtMemorySearchResult);
  suggestion.payload =
      Suggestion::AtMemoryPayload(u"pasted text", MemoryDataType::kUnknown);

  // 1. There is currently no Preview.
  external_delegate().DidSelectSuggestion(suggestion);

  // 2. Test Fill.
  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kFill,
                         mojom::FieldActionType::kReplaceSelectionForAtMemory,
                         _, _, std::u16string(u"pasted text"),
                         FillingProduct::kAtMemory, _));
  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

// Tests that when an AtMemory search result requires async fetching,
// accepting the suggestion triggers a loading state UI update.
TEST_F(AutofillExternalDelegateTest,
       AtMemorySearchResult_Async_TriggersLoadingState) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      features::debug::kAtMemoryNoDeviceReauthCheck);

  autofill_client().set_last_committed_primary_main_frame_url(
      GURL("https://example.com"));
  autofill_client().set_at_memory_query_service(
      std::make_unique<NiceMock<MockAtMemoryQueryService>>());
  StartAtMemorySession();

  Suggestion suggestion(u"Passport", SuggestionType::kAtMemorySearchResult);
  Suggestion::AtMemoryPayload at_memory_payload(
      u"1234", MemoryDataType::kPassportNumber);
  at_memory_payload.identifier = std::string("personal-context-guid");
  at_memory_payload.is_personal_context_sourced = true;
  suggestion.payload = std::move(at_memory_payload);

  std::vector<Suggestion> suggestions = {suggestion};
  ON_CALL(autofill_client(), GetAutofillSuggestions)
      .WillByDefault(Return(suggestions));

  EXPECT_CALL(autofill_client(),
              UpdateAutofillSuggestions(
                  ElementsAre(Field(&Suggestion::is_loading,
                                    Suggestion::IsLoading(true))),
                  FillingProduct::kAtMemory,
                  AutofillSuggestionTriggerSource::kAtMemoryTriggerString,
                  AutofillSuggestionsIgnoreFocusLoss(true)));

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

// Tests that accepting an AtMemory suggestion for an IBAN attempts to fetch the
// value from the IbanAccessManager.
TEST_F(AutofillExternalDelegateTest, AtMemorySearchResult_RevealsIban) {
  IssueOnQuery(AutofillSuggestionTriggerSource::kAtMemoryTriggerString);

  Iban iban = test::GetLocalIban();
  Suggestion suggestion(u"some result", SuggestionType::kAtMemorySearchResult);

  Suggestion::AtMemoryPayload at_memory_payload(
      iban.GetIdentifierStringForAutofillDisplay(), MemoryDataType::kIban);
  at_memory_payload.identifier = Iban::Guid(iban.guid());
  at_memory_payload.memory_data_type = MemoryDataType::kIban;
  suggestion.payload = std::move(at_memory_payload);

  EXPECT_CALL(*payments_autofill_client().GetIbanAccessManager(), FetchValue)
      .WillOnce([iban](const Suggestion::Payload& payload,
                       IbanAccessManager::OnIbanFetchedCallback callback) {
        std::move(callback).Run(iban.value());
        return IsAsync(false);
      });

  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kFill,
                         mojom::FieldActionType::kReplaceSelectionForAtMemory,
                         _, _, iban.value(), FillingProduct::kAtMemory, _));

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

// Tests that accepting an AtMemory suggestion for a Credit Card attempts to
// fetch the value from the CreditCardAccessManager.
TEST_F(AutofillExternalDelegateTest, AtMemorySearchResult_RevealsCreditCard) {
  IssueOnQuery(AutofillSuggestionTriggerSource::kAtMemoryTriggerString);

  CreditCard card = test::GetCreditCard();
  pdm().payments_data_manager().AddCreditCard(card);

  Suggestion suggestion(u"some result", SuggestionType::kAtMemorySearchResult);

  Suggestion::AtMemoryPayload at_memory_payload(
      u"some text", MemoryDataType::kCreditCardNumber);
  at_memory_payload.identifier = card.guid();
  at_memory_payload.memory_data_type = MemoryDataType::kCreditCardNumber;
  suggestion.payload = std::move(at_memory_payload);

  TestCreditCardAccessManager* access_manager =
      static_cast<TestCreditCardAccessManager*>(
          autofill_manager().GetCreditCardAccessManager());

  EXPECT_CALL(*access_manager, FetchCreditCard)
      .WillOnce(
          [card](
              const CreditCard* passed_card,
              CreditCardAccessManager::OnCreditCardFetchedCallback callback) {
            std::move(callback).Run(card);
          });

  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kFill,
                         mojom::FieldActionType::kReplaceSelectionForAtMemory,
                         _, _, card.number(), FillingProduct::kAtMemory, _));

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

// Tests that accepting an AtMemory suggestion for an AutofillAI SPII attempts
// to fetch the value from the AutofillAiAccessManager.
TEST_F(AutofillExternalDelegateTest, AtMemorySearchResult_RevealsAutofillAi) {
  base::test::ScopedFeatureList scoped_feature_list{
      features::kAutofillAiWithDataSchema};

  EntityInstance passport = GetPassportEntityInstance();
  AddOrUpdateEntityInstance(passport);

  IssueOnQuery(AutofillSuggestionTriggerSource::kAtMemoryTriggerString,
               PASSPORT_NUMBER, "passport");

  Suggestion suggestion(u"some result", SuggestionType::kAtMemorySearchResult);

  Suggestion::AtMemoryPayload at_memory_payload(
      u"some text", MemoryDataType::kPassportNumber);
  at_memory_payload.identifier = passport.guid();
  at_memory_payload.memory_data_type = MemoryDataType::kPassportNumber;
  suggestion.payload = std::move(at_memory_payload);

  base::optional_ref<const AttributeInstance> passport_attribute =
      passport.attribute(AttributeType(AttributeTypeName::kPassportNumber));
  ASSERT_TRUE(passport_attribute.has_value());

  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kFill,
                         mojom::FieldActionType::kReplaceSelectionForAtMemory,
                         _, _, passport_attribute->GetCompleteRawInfo(),
                         FillingProduct::kAtMemory, _));

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

// Tests that accepting an AtMemory suggestion for a masked server AutofillAI
// private pass successfully initiates the server unmasking flow and fills the
// unmasked value.
TEST_F(AutofillExternalDelegateWithWalletPrivatePassesTest,
       AtMemorySearchResult_RevealsPrivatePassAutofillAi) {
  constexpr auto kPassportNumberType =
      AttributeType(AttributeTypeName::kPassportNumber);

  EntityInstance full_passport = GetPassportEntityInstance(
      {.record_type = EntityInstance::RecordType::kServerWallet});
  EntityInstance masked_passport = MaskEntityInstance(full_passport);
  ASSERT_NE(
      full_passport.attribute(kPassportNumberType)->GetCompleteRawInfo(),
      masked_passport.attribute(kPassportNumberType)->GetCompleteRawInfo());
  AddOrUpdateEntityInstance(masked_passport);

  IssueOnQuery(AutofillSuggestionTriggerSource::kAtMemoryTriggerString,
               PASSPORT_NUMBER, "passport");

  Suggestion suggestion(u"some result", SuggestionType::kAtMemorySearchResult);

  Suggestion::AtMemoryPayload at_memory_payload(
      u"some text", MemoryDataType::kPassportNumber);
  at_memory_payload.identifier = masked_passport.guid();
  at_memory_payload.memory_data_type = MemoryDataType::kPassportNumber;
  suggestion.payload = std::move(at_memory_payload);

  base::optional_ref<const AttributeInstance> passport_attribute =
      full_passport.attribute(kPassportNumberType);
  ASSERT_TRUE(passport_attribute.has_value());

  EXPECT_CALL(wallet_manager(),
              GetUnmaskedWalletEntityInstance(masked_passport.guid(), _))
      .WillOnce(RunOnceCallback<1>(full_passport));

  EXPECT_CALL(
      autofill_manager(),
      FillOrPreviewField(mojom::ActionPersistence::kFill,
                         mojom::FieldActionType::kReplaceSelectionForAtMemory,
                         _, _, passport_attribute->GetCompleteRawInfo(),
                         FillingProduct::kAtMemory, _));

  external_delegate().DidAcceptSuggestion(
      suggestion, SuggestionPosition{.multi_index = {0}});
}

TEST_F(AutofillExternalDelegateTest,
       ExternalDelegateHidesSuggestionsWhenNoSuggestionsReturned) {
  IssueOnQuery();

  EXPECT_CALL(autofill_client(),
              HideSuggestions(SuggestionHidingReason::kNoSuggestions,
                              Eq(std::nullopt)));

  // Return empty suggestions.
  OnSuggestionsReturned(queried_field(), {});
}

#if BUILDFLAG(IS_ANDROID)
TEST_F(AutofillExternalDelegateTest,
       ExternalDelegateDoesNotHideSuggestionsOnAndroidDesktop) {
  base::android::device_info::set_is_desktop_for_testing(true);
  absl::Cleanup reset =
      &base::android::device_info::reset_is_desktop_for_testing;
  IssueOnQuery();

  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kAutofillAndroidKeyboardAccessoryDynamicPositioning);

  EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);

  // Return empty suggestions.
  OnSuggestionsReturned(queried_field(), {});
}
#endif

// Tests that the "Maximize rewards" suggestion functions properly when the
// user clicks it.
TEST_F(AutofillExternalDelegateTest,
       DidAcceptMaximizeCreditCardBenefitsSuggestion) {
  base::test::ScopedFeatureList feature_list{
      features::kAutofillEnableAiCardRecommendation};
  IssueOnQuery();

  // Ensure that MaximizeCreditCardBenefits is triggered
  EXPECT_CALL(autofill_manager().GetAiCardRecommendationManager(),
              MaximizeCreditCardBenefits);

  // Ensure that the suggestions popup is not hidden.
  EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);
  // Ensure that this suggestion does not fill the credit card info.
  EXPECT_CALL(autofill_manager(), FillOrPreviewForm).Times(0);

  external_delegate().DidAcceptSuggestion(
      Suggestion(SuggestionType::kMaximizeCreditCardBenefitsEntry),
      SuggestionPosition{.multi_index = {0}});
}

// Tests that AttemptToDisplayAutofillSuggestions() hides any open suggestions
// with kStaleData when the target field is not focusable or the driver cannot
// show UI.
TEST_F(AutofillExternalDelegateTest,
       ExternalDelegateHidesSuggestionsWhenFieldIsUnfocusable) {
  IssueOnQuery();

  EXPECT_CALL(
      autofill_client(),
      HideSuggestions(SuggestionHidingReason::kStaleData, Eq(std::nullopt)));

  FormFieldData unfocusable_field = queried_field();
  unfocusable_field.set_is_focusable(false);
  OnSuggestionsReturned(unfocusable_field,
                        {Suggestion(SuggestionType::kAddressEntry)});
}

}  // namespace

}  // namespace autofill
