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

#include "chrome/browser/ui/views/autofill/popup/popup_view_views.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <ranges>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/containers/extend.h"
#include "base/containers/to_vector.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/not_fatal_until.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/favicon/large_icon_service_factory.h"
#include "chrome/browser/image_fetcher/image_fetcher_service_factory.h"
#include "chrome/browser/media/webrtc/desktop_capture_access_handler.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_key.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
#include "chrome/browser/ui/autofill/autofill_suggestion_controller_utils.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/passwords/ui_utils.h"
#include "chrome/browser/ui/views/autofill/popup/popup_at_memory_ai_disclosure_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_base_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_bnpl_footnote_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_centered_text_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_interactive_row_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_loading_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_notice_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_row_factory_utils.h"
#include "chrome/browser/ui/views/autofill/popup/popup_row_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_search_bar_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_separator_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_title_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h"
#include "chrome/browser/ui/views/autofill/popup/popup_warning_view.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/common/webui_url_constants.h"
#include "components/autofill/content/browser/content_autofill_client.h"
#include "components/autofill/core/browser/data_model/payments/credit_card.h"
#include "components/autofill/core/browser/filling/filling_product.h"
#include "components/autofill/core/browser/metrics/autofill_metrics.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_type.h"
#include "components/autofill/core/browser/ui/autofill_resource_utils.h"
#include "components/autofill/core/browser/ui/tabbed_pane_enums.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/favicon/core/large_icon_service.h"
#include "components/image_fetcher/core/image_fetcher_service.h"
#include "components/input/native_web_keyboard_event.h"
#include "components/optimization_guide/core/feature_registry/feature_registration.h"
#include "components/optimization_guide/core/model_execution/model_execution_prefs.h"
#include "components/password_manager/core/browser/password_sync_util.h"
#include "components/prefs/pref_service.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/service/sync_service.h"
#include "components/user_education/common/feature_promo/feature_promo_controller.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_id.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/blink/web_input_event.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_border_arrow_utils.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/tabbed_pane/tabbed_pane.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"

using views::BubbleBorder;

namespace autofill {

namespace {

// The minimum width should exceed the maximum size of a cursor, which is 128
// (see crbug.com/40064089).
constexpr int kAutofillPopupMinWidth = 156;
static_assert(kAutofillPopupMinWidth > 128);

constexpr int kMaxPopupWithSearchBarHeight = 400;

// Preferred position relative to the control sides of the sub-popup.
constexpr std::array<views::BubbleArrowSide, 2> kDefaultSubPopupSides = {
    views::BubbleArrowSide::kLeft, views::BubbleArrowSide::kRight};
constexpr std::array<views::BubbleArrowSide, 2> kDefaultSubPopupSidesRTL = {
    views::BubbleArrowSide::kRight, views::BubbleArrowSide::kLeft};

int GetContentsVerticalPadding() {
  return ChromeLayoutProvider::Get()->GetDistanceMetric(
      DISTANCE_CONTENT_LIST_VERTICAL_SINGLE);
}

bool CanShowRootPopup(AutofillPopupController& controller) {
#if BUILDFLAG(IS_MAC)
  // It's possible for the container_view to not be in a window. In that case,
  // cancel the popup since we can't fully set it up.
  if (!platform_util::GetTopLevel(controller.container_view())) {
    return false;
  }
#else
  // If the top level widget can't be found, cancel the popup since we can't
  // fully set it up. On Mac Cocoa browser, |observing_widget| is null
  // because the parent is not a views::Widget.
  if (!views::Widget::GetTopLevelWidgetForNativeView(
          controller.container_view())) {
    return false;
  }
#endif

  return true;
}

// Returns true when the suggestion should open a sub popup menu automatically
// when hovering the content area. This is used for manual fallback
// suggestions.
bool ContentCellShouldOpenSubPopupSuggestion(const Suggestion& suggestion) {
  return !suggestion.IsAcceptable() && suggestion.IsSelectable() &&
         !suggestion.children.empty();
}

// If `is_root_popup` is `true`, the result list corresponds to sides defined in
// `PopupBaseView::kDefaultPreferredPopupSides`, when `prefer_prev_arrow_side`
// is also `true` the side of the `prev_arrow` is added at the beginning of
// the list. Having the previous arrow side preferred allows to avoid popup
// jumpings in most cases when the updated suggestions change the popup size
// as well and a new side is considered optimal.
// For non root popups, the values are taken from `kDefaultSubPopupSides[RTL]`.
std::vector<views::BubbleArrowSide> GetPreferredPopupSides(
    bool is_root_popup,
    bool prefer_prev_arrow_side,
    BubbleBorder::Arrow prev_arrow) {
  if (is_root_popup && prefer_prev_arrow_side &&
      prev_arrow != BubbleBorder::Arrow::NONE) {
    static constexpr size_t n_default_preferred_sides =
        PopupBaseView::kDefaultPreferredPopupSides.size();
    std::vector<views::BubbleArrowSide> preferred_popup_sides(
        n_default_preferred_sides + 1);
    // The first element is filled with the previous arrow side to minimize
    // jumping due to changed popup size and potentially new optimal position.
    preferred_popup_sides[0] = views::GetBubbleArrowSide(prev_arrow);

    // Fill the rest of elements with the default ones.
    base::span(preferred_popup_sides)
        .last(n_default_preferred_sides)
        .copy_from(PopupBaseView::kDefaultPreferredPopupSides);

    return preferred_popup_sides;
  } else if (is_root_popup) {
    return base::ToVector(PopupBaseView::kDefaultPreferredPopupSides);
  }
  return base::ToVector(base::i18n::IsRTL() ? kDefaultSubPopupSidesRTL
                                            : kDefaultSubPopupSides);
}

void DefaultA11yAnnouncer(const std::u16string& message, bool polite) {
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser();
  if (!browser) {
    return;
  }
  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
  if (!browser_view) {
    return;
  }

  if (polite) {
    browser_view->GetViewAccessibility().AnnouncePolitely(message);
  } else {
    browser_view->GetViewAccessibility().AnnounceText(message);
  }
}

// Builder responsible for creating the body and footer containers of
// `PopupViewViews`. It encapsulates tracking the height of rows to allow
// applying a height limit to the popup that can depend on the size of its
// entries.
class PopupRowContainerBuilder {
 public:
  // Returns builder used for creating `PopupViewViews::body_container_`.
  static PopupRowContainerBuilder CreateBodyBuilder(int popup_min_width) {
    return PopupRowContainerBuilder(
        /*container=*/views::Builder<views::BoxLayoutView>()
            .SetOrientation(views::BoxLayout::Orientation::kVertical)
            .SetInsideBorderInsets(
                gfx::Insets::VH(GetContentsVerticalPadding(), 0))
            .Build(),
        popup_min_width, /*initial_visible_entries=*/0,
        /*initial_visible_height=*/0);
  }

  // Returns builder used for creating `PopupViewViews::footer_container_`.
  static PopupRowContainerBuilder CreateFooterBuilder(
      int popup_min_width,
      int initial_visible_entries,
      int initial_visible_height) {
    return PopupRowContainerBuilder(
        /*container=*/views::Builder<views::BoxLayoutView>()
            .SetOrientation(views::BoxLayout::Orientation::kVertical)
            .SetBackground(
                views::CreateSolidBackground(ui::kColorDropdownBackground))
            .Build(),
        popup_min_width, initial_visible_entries, initial_visible_height);
  }

  // Returns the built container and a vector of raw pointers to the added rows.
  std::pair<std::unique_ptr<views::BoxLayoutView>,
            std::vector<PopupViewViews::RowPointer>>
  Build() && {
    return {std::move(container_), std::move(rows_)};
  }

  int visible_entries() const { return visible_entries_; }
  int visible_height() const { return visible_height_; }

  // Adds a new row to the body container and updates height accordingly.
  template <typename T>
    requires std::derived_from<T, views::View>
  T* AddRow(std::unique_ptr<T> row_view) {
    T* new_view = container_->AddChildView(std::move(row_view));

    constexpr bool kIsRelevantEntry = !std::same_as<T, PopupSeparatorView>;
    if (visible_entries_ < PopupViewViews::kAutofillPopupMaxVisibleEntries) {
      double height_factor = 1.0;
      if constexpr (kIsRelevantEntry) {
        // Last entry might be partially visible. Since separators are not
        // counted as entries, they are always fully visible, but do not count
        // towards `visible_entries_`.
        height_factor = std::min(
            PopupViewViews::kAutofillPopupMaxVisibleEntries - visible_entries_,
            1.0);
        ++visible_entries_;
      }
      visible_height_ += height_factor * MaxHeightOfRow(new_view);
    } else if constexpr (kIsRelevantEntry) {
      has_fully_hidden_entry = true;
    }

    rows_.push_back(new_view);
    return new_view;
  }

  // Returns the desired maximum height of the built container for clipping the
  // height on a surrounding scroll area, or `std::nullopt` if no height limit
  // is necessary.
  std::optional<int> GetScrollClippingHeight() const {
    if (!has_fully_hidden_entry ||
        !base::FeatureList::IsEnabled(
            features::kAutofillEnableEntryLimitInPopup)) {
      // Do not apply a height limit when the scrollbar would be very small
      // (less than one full entry). This also serves as a safeguard to avoid
      // returning a minimally too small height limit.
      return std::nullopt;
    }
    return visible_height_;
  }

 private:
  explicit PopupRowContainerBuilder(
      std::unique_ptr<views::BoxLayoutView> container,
      int popup_min_width,
      int initial_visible_entries,
      int initial_visible_height)
      : container_(std::move(container)),
        visible_entries_(initial_visible_entries),
        visible_height_(initial_visible_height),
        popup_min_width_(popup_min_width) {
    CHECK(container_);
    visible_height_ += container_->GetInsideBorderInsets().height();
  }

  // Returns the maximal possible size of a row by using the minimal width of
  // the popup. In multi-line rows, this will lead to the maximum number of
  // lines and thus the maximum height. For single-line rows this will be equal
  // to the actual height. Additionally consider margins since they are added
  // separately by the layout manager in the usual code path for the size
  // calculation.
  int MaxHeightOfRow(views::View* row_view) const {
    int height = row_view->GetHeightForWidth(popup_min_width_);
    if (const gfx::Insets* margins =
            row_view->GetProperty(views::kMarginsKey)) {
      height += margins->height();
    }
    return height;
  }

  // Container owned by this builder that is currently being constructed.
  std::unique_ptr<views::BoxLayoutView> container_;
  // Collection of all added rows.
  std::vector<PopupViewViews::RowPointer> rows_;
  // Number of relevant (i.e. non-separator) entries that were encountered so
  // far and that should be (at least partially) visible according to
  // `PopupViewViews::kAutofillPopupMaxVisibleEntries`.
  int visible_entries_ = 0;
  // The calculated desired height of the body container that should be visible
  // according to `PopupViewViews::kAutofillPopupMaxVisibleEntries`.
  int visible_height_ = 0;
  // Indicator if there is at least one relevant entry that is entirely out of
  // view after restricting the height to `visible_height_`.
  bool has_fully_hidden_entry = false;
  // Minimum width the popup can have.
  const int popup_min_width_;
};

}  // namespace

// Creates a new popup view instance. The Widget parent is taken either from
// the top level widget for the root popup or from the parent for sub-popups.
// Setting Widget's parent enables its internal child-lifetime management,
// see `Widget::InitParams::parent` doc comment for details.
PopupViewViews::PopupViewViews(
    base::WeakPtr<AutofillPopupController> controller,
    base::WeakPtr<ExpandablePopupParentView> parent,
    views::Widget* parent_widget,
    std::optional<const AutofillPopupView::SubPopupConfig> sub_popup_config)
    : PopupBaseView(controller,
                    parent_widget,
                    views::Widget::InitParams::Activatable::kDefault,
                    /*show_arrow_pointer=*/false),
      controller_(controller),
      parent_(parent),
      sub_popup_config_(std::move(sub_popup_config)) {
  InitViews();

  GetViewAccessibility().SetRole(ax::mojom::Role::kListBox);
  GetViewAccessibility().SetName(
      l10n_util::GetStringUTF16(IDS_AUTOFILL_POPUP_ACCESSIBLE_NODE_DATA));
}

PopupViewViews::PopupViewViews(
    base::WeakPtr<AutofillPopupController> controller,
    std::optional<const AutofillPopupView::SearchBarConfig> search_bar_config,
    std::optional<const AutofillPopupView::TabbedPaneConfig> tabbed_pane_config,
    std::optional<const AutofillPopupView::SubPopupConfig> sub_popup_config)
    : PopupBaseView(controller,
                    views::Widget::GetTopLevelWidgetForNativeView(
                        controller->container_view()),
                    search_bar_config
                        ? views::Widget::InitParams::Activatable::kYes
                        : views::Widget::InitParams::Activatable::kDefault),
      controller_(controller),
      search_bar_config_(std::move(search_bar_config)),
      tabbed_pane_config_(std::move(tabbed_pane_config)),
      sub_popup_config_(std::move(sub_popup_config)) {
  InitViews();

  GetViewAccessibility().SetRole(ax::mojom::Role::kListBox);
}

PopupViewViews::~PopupViewViews() = default;

void PopupViewViews::OnMouseEntered(const ui::MouseEvent& event) {
  if (parent_ && parent_->get()) {
    parent_->get()->OnMouseEnteredInChildren();
  }
}

void PopupViewViews::OnMouseExited(const ui::MouseEvent& event) {
  OnMouseExitedInChildren();
}

void PopupViewViews::OnPaint(gfx::Canvas* canvas) {
  views::View::OnPaint(canvas);
  if (controller_) {
    controller_->OnPopupPainted();
  }
}

bool PopupViewViews::Show(
    AutoselectFirstSuggestion autoselect_first_suggestion) {
  base::AutoReset show_in_progress_reset(&show_in_progress_, search_bar_);

  UpdateAccessibleStates();
  if (!DoShow()) {
    return false;
  }

  has_keyboard_focus_ = !parent_;

  if (search_bar_) {
    // Make sure the search bar is focused before possible first suggestion
    // selection, because `SetSelectedCell()` eventually leads to calling
    // `SetPopupFocusOverride()` and focusing the input while having a view
    // overriding focus makes `ViewAXPlatformNodeDelegate` unhappy.
    search_bar_->Focus();
  }

  MaybeAutoSelectSuggestion(autoselect_first_suggestion);

  // `SetSelectedCell` can hide the popup and destroy the controller.
  if (!controller_) {
    return false;
  }
  // Compose has separate on show announcements.
  // TODO(crbug.com/340359989): Replace with AutofillComposeDelegate::OnShow
  if (controller_->GetMainFillingProduct() == FillingProduct::kCompose) {
    switch (controller_->GetSuggestionAt(0).type) {
      case SuggestionType::kComposeResumeNudge:
      case SuggestionType::kComposeSavedStateNotification: {
        const std::u16string saved_state_message = l10n_util::GetStringUTF16(
            IDS_COMPOSE_SUGGESTION_AX_MESSAGE_ON_SHOW_RESUME);
        a11y_announcer_.Run(saved_state_message, /*polite=*/true);
        break;
      }
      case SuggestionType::kComposeProactiveNudge: {
        const std::u16string proactive_message = l10n_util::GetStringUTF16(
            IDS_COMPOSE_SUGGESTION_AX_MESSAGE_ON_SHOW_PROACTIVE);
        a11y_announcer_.Run(proactive_message, /*polite=*/true);
        break;
      }
      case SuggestionType::kComposeDisable:
      case SuggestionType::kComposeGoToSettings:
      case SuggestionType::kComposeNeverShowOnThisSiteAgain:
        break;
      default:
        // All Compose SuggestionTypes should already be handled.
        NOTREACHED();
    }
  }

  MaybeAnnounceCurrentTabAndFootnote();
  MaybeAnnouncePasswordRecoveryPopup();
  MaybeAnnounceLoadingState();
  if (!MaybeA11yFocusInformationalSuggestion()) {
    return false;
  }

  return !CanActivate() || (GetWidget() && GetWidget()->IsActive());
}

void PopupViewViews::MaybeAutoSelectSuggestion(
    AutoselectFirstSuggestion force_by_trigger_source) {
  if (!controller_ || controller_->GetLineCount() == 0) {
    return;
  }

  const SuggestionType first_suggestion_type =
      controller_->GetSuggestionAt(0).type;

  if (ShouldAutoselectFirstSuggestion(force_by_trigger_source,
                                      first_suggestion_type)) {
    // Selecting first selectable row.
    // TODO(crbug.com/327931044): Remove the if condition and make the else as
    // the default as part of cleanup.
    if (controller_->GetSuggestionAt(0).IsSelectable()) {
      SetSelectedCell(CellIndex{0u, PopupRowView::CellType::kContent},
                      PopupCellSelectionSource::kNonUserInput);
    } else {
      SetSelectedCell(std::nullopt, PopupCellSelectionSource::kNonUserInput);
      SelectNextRow(PopupCellSelectionSource::kNonUserInput);
    }
  }
}

void PopupViewViews::Hide() {
  open_sub_popup_timer_.Stop();
  StopSubPopupClosing();

  // The controller is no longer valid after it hides us.
  controller_ = nullptr;
  UpdateAccessibleStates();
  DoHide();
}

std::optional<PopupViewViews::CellIndex> PopupViewViews::GetSelectedCell()
    const {
  // If the suggestions were updated, the cell index may no longer be
  // up-to-date, but it cannot simply be reset, because we would lose the
  // current selection. Therefore some validity checks need to be performed
  // here.
  if (!row_with_selected_cell_ ||
      !HasSelectablePopupInteractiveRowViewAt(*row_with_selected_cell_)) {
    return std::nullopt;
  }

  if (std::optional<PopupRowView::CellType> cell_type =
          GetPopupInteractiveRowViewAt(*row_with_selected_cell_)
              .GetSelectedCell()) {
    return CellIndex{*row_with_selected_cell_, *cell_type};
  }
  return std::nullopt;
}

void PopupViewViews::SetSelectedCell(std::optional<CellIndex> cell_index,
                                     PopupCellSelectionSource source) {
  SetSelectedCell(cell_index, source, AutoselectFirstSuggestion(false));
}

bool PopupViewViews::HandleKeyPressEvent(
    const input::NativeWebKeyboardEvent& event) {
  // If a subpopup has not received focus yet but a horizontal key press event
  // happens, this means the user wants to navigate from a selected cell in
  // the parent to the currently open subpopup. In this case, we select
  // the first subpopup cell.
  if (!has_keyboard_focus_) {
    bool capture_keyboard_focus =
        (event.windows_key_code == ui::VKEY_RIGHT && !base::i18n::IsRTL()) ||
        (event.windows_key_code == ui::VKEY_LEFT && base::i18n::IsRTL());
    if (capture_keyboard_focus) {
      // Select first row.
      SetSelectedCell(std::nullopt, PopupCellSelectionSource::kNonUserInput);
      SelectNextRow(PopupCellSelectionSource::kNonUserInput);
      return true;
    }
    return false;
  }

  // If the row can handle the event itself (e.g. switching between cells in the
  // same row), we let it.
  if (std::optional<CellIndex> selected_cell = GetSelectedCell()) {
    if (GetPopupInteractiveRowViewAt(selected_cell->first)
            .HandleKeyPressEvent(event)) {
      return true;
    }
  }

  if (controller_->GetMainFillingProduct() == FillingProduct::kCompose) {
    return HandleKeyPressEventForCompose(event);
  }

  if (controller_->GetMainFillingProduct() == FillingProduct::kAtMemory) {
    return HandleKeyPressEventForAtMemory(event);
  }

  const bool kHasShiftModifier =
      (event.GetModifiers() & blink::WebInputEvent::kShiftKey);
  const bool kHasNonShiftModifier =
      (event.GetModifiers() & blink::WebInputEvent::kKeyModifiers &
       ~blink::WebInputEvent::kShiftKey);

  switch (event.windows_key_code) {
    case ui::VKEY_UP:
      SelectPreviousRow();
      return true;
    case ui::VKEY_DOWN:
      SelectNextRow(PopupCellSelectionSource::kKeyboard);
      return true;
    case ui::VKEY_LEFT:
    case ui::VKEY_RIGHT:
      return HandlePopupHorizontalNavigation(event);
    case ui::VKEY_PRIOR:  // Page up.
      // Set no line and then select the next line in case the first line is not
      // selectable.
      SetSelectedCell(std::nullopt, PopupCellSelectionSource::kKeyboard);
      SelectNextRow(PopupCellSelectionSource::kKeyboard);
      return true;
    case ui::VKEY_NEXT:  // Page down.
      SetSelectedCell(std::nullopt, PopupCellSelectionSource::kKeyboard);
      SelectPreviousRow();
      return true;
    case ui::VKEY_DELETE:
      return kHasShiftModifier && RemoveSelectedCell();
    case ui::VKEY_ESCAPE:
      if (SelectParentPopupContentCell()) {
        return true;
      }
      // If this is the root popup view and there was no sub-popup open (find
      // the check for it above) just close itself.
      if (!parent_) {
        controller_->Hide(SuggestionHidingReason::kUserAborted);
        return true;
      }
      return false;
    case ui::VKEY_TAB:
      // We want TAB or Shift+TAB press to cause the selected line to be
      // accepted, but still return false so the tab key press propagates and
      // change the cursor location.
      // We do not want to handle Mod+TAB for other modifiers because this may
      // have other purposes (e.g., change the tab).
      if (!kHasNonShiftModifier) {
        // If there is a BNPL footnote settings link, we want to focus it to
        // make it accessible for keyboard and screenreader users, since the
        // BNPL footnote suggestion itself is not selectable.
        PopupBnplFootnoteView* bnpl_footnote = GetBnplFootnoteView();
        if (bnpl_footnote) {
          if (bnpl_footnote->IsSettingsLinkFocused()) {
            return false;
          }
          bnpl_footnote->FocusSettingsLink();
          SetSelectedCell(std::nullopt, PopupCellSelectionSource::kKeyboard);
          return true;
        }

        AcceptSelectedContentOrCreditCardCell(
            AutofillMetrics::SuggestionAcceptedMethod::kKeyboard);
      }
      return false;
    case ui::VKEY_RETURN:
      if (!GetSelectedCell()) {
        PopupBnplFootnoteView* bnpl_footnote = GetBnplFootnoteView();
        if (bnpl_footnote && bnpl_footnote->IsSettingsLinkFocused()) {
          bnpl_footnote->ActivateSettingsLink();
          return true;
        }
      }
      return false;
    default:
      return false;
  }
}

bool PopupViewViews::HandleKeyPressEventForCompose(
    const input::NativeWebKeyboardEvent& event) {
  CHECK_EQ(controller_->GetMainFillingProduct(), FillingProduct::kCompose);
  const bool kHasShiftModifier =
      (event.GetModifiers() & blink::WebInputEvent::kShiftKey);
  switch (event.windows_key_code) {
    case ui::VKEY_ESCAPE:
      controller_->Hide(SuggestionHidingReason::kUserAborted);
      return true;
    case ui::VKEY_UP:
      if (GetSelectedCell()) {
        SelectPreviousRow();
        return true;
      }
      return false;
    case ui::VKEY_DOWN:
      if (GetSelectedCell()) {
        SelectNextRow(PopupCellSelectionSource::kKeyboard);
        return true;
      }
      return false;
    case ui::VKEY_LEFT:
    case ui::VKEY_RIGHT:
      return HandlePopupHorizontalNavigation(event);
    case ui::VKEY_TAB: {
      const bool is_root_popup = !parent_;
      // TAB should only be handled by the root popup. The subpopup only deals
      // with selection (ENTER) and arrow navigation.
      if (!is_root_popup) {
        return false;
      }
      std::optional<CellIndex> selected_cell = GetSelectedCell();
      // The `!row_with_open_sub_popup_` check is to make sure that we only
      // select the content cell if there is no subpopup open. This is because
      // if one presses TAB from the subpopup, we also want to close the root
      // popup (and navigate to the next HTML element).
      const bool tab_pressed_popup_unselected =
          !selected_cell && !kHasShiftModifier && !row_with_open_sub_popup_;
      if (tab_pressed_popup_unselected) {
        // If there is no selected cell in the compose popup, TAB should select
        // the single compose nudge entry.
        SetSelectedCell(CellIndex(0, PopupRowView::CellType::kContent),
                        PopupCellSelectionSource::kKeyboard);
        return true;
      }

      const bool tab_pressed_popup_selected =
          selected_cell && !kHasShiftModifier;
      if (tab_pressed_popup_selected) {
        // TAB should close the popup and focus the next HTML element if the
        // Compose entry is selected.
        controller_->Hide(SuggestionHidingReason::kUserAborted);
        return false;
      }

      const bool shift_tab_pressed_popup_unselected_no_subpopup =
          !selected_cell && kHasShiftModifier && !row_with_open_sub_popup_;
      if (shift_tab_pressed_popup_unselected_no_subpopup) {
        // If the Compose suggestion is not selected, Shift+TAB should not be
        // handled.
        return false;
      }

      const bool shift_tab_pressed_has_subpopup =
          kHasShiftModifier && row_with_open_sub_popup_;
      if (shift_tab_pressed_has_subpopup) {
        // In this case, focus on the root/parent popup content area. This
        // closes the sub-popup.
        SetSelectedCell(CellIndex(0, PopupRowView::CellType::kContent),
                        PopupCellSelectionSource::kKeyboard);
        return true;
      }

      const bool shift_tab_pressed_root_popup_selected =
          selected_cell && kHasShiftModifier && is_root_popup;
      if (shift_tab_pressed_root_popup_selected) {
        // Shift+TAB should remove the selection when the root popup is
        // selected, but keep the popup open.
        SetSelectedCell(std::nullopt, PopupCellSelectionSource::kKeyboard);
        return true;
      }
      return false;
    }
    default:
      return false;
  }
}

bool PopupViewViews::HandleKeyPressEventForAtMemory(
    const input::NativeWebKeyboardEvent& event) {
  CHECK_EQ(controller_->GetMainFillingProduct(), FillingProduct::kAtMemory);

  switch (event.windows_key_code) {
    case ui::VKEY_ESCAPE:
      controller_->Hide(SuggestionHidingReason::kUserAborted);
      return true;
    case ui::VKEY_RETURN: {
      if (GetSelectedCell()) {
        return AcceptSelectedContentOrCreditCardCell(
            AutofillMetrics::SuggestionAcceptedMethod::kKeyboard);
      }
      if (search_bar_) {
        controller_->SetFilter(
            AutofillPopupController::StringFilter(search_bar_->GetText()),
            AutofillPopupController::FilterSource::kSearchSubmitted);
        return true;
      }
      return false;
    }
    case ui::VKEY_UP:
      if (GetSelectedCell()) {
        SelectPreviousRow();
        return true;
      }
      return false;
    case ui::VKEY_DOWN:
      if (GetSelectedCell()) {
        SelectNextRow(PopupCellSelectionSource::kKeyboard);
        return true;
      }
      if (HasSelectablePopupInteractiveRowViewAt(0)) {
        SetSelectedCell(CellIndex(0, PopupRowView::CellType::kContent),
                        PopupCellSelectionSource::kKeyboard);
        return true;
      }
      return false;
    case ui::VKEY_LEFT:
    case ui::VKEY_RIGHT:
      return HandlePopupHorizontalNavigation(event);
    default:
      return false;
  }
}

bool PopupViewViews::HandlePopupHorizontalNavigation(
    const input::NativeWebKeyboardEvent& event) {
  if (event.windows_key_code == ui::VKEY_LEFT) {
    // `base::i18n::IsRTL` is used here instead of the controller's method
    // because the controller's `IsRTL` depends on the language of the focused
    // field and not the overall UI language. However, the layout of the popup
    // is determined by the overall UI language.
    if (base::i18n::IsRTL()) {
      return SelectNextHorizontalCell();
    } else {
      if (SelectParentPopupContentCell()) {
        return true;
      }
      return SelectPreviousHorizontalCell();
    }
  }
  if (event.windows_key_code == ui::VKEY_RIGHT) {
    if (base::i18n::IsRTL()) {
      if (SelectParentPopupContentCell()) {
        return true;
      }
      return SelectPreviousHorizontalCell();
    } else {
      return SelectNextHorizontalCell();
    }
  }
  return false;
}

void PopupViewViews::SelectPreviousRow() {
  DCHECK(!rows_.empty());
  std::optional<CellIndex> old_index = GetSelectedCell();
  // Temporarily use an int to avoid underflows.
  int new_row = old_index ? static_cast<int>(old_index->first) - 1 : -1;
  for (size_t i = 0;
       i < rows_.size() && !HasSelectablePopupInteractiveRowViewAt(new_row);
       i++) {
    --new_row;
    if (new_row < 0) {
      new_row = static_cast<int>(rows_.size()) - 1;
    }
  }

  // `kControl` is used to show a sub-popup with child suggestions. It can
  // only be selected on a new row if the corresponding suggestion has
  // children.
  PopupRowView* row_view = MaybeGetPopupRowViewAt(new_row);
  const PopupRowView::CellType new_cell_type =
      (old_index && old_index->second == PopupRowView::CellType::kControl &&
       row_view && row_view->GetExpandChildSuggestionsView())
          ? PopupRowView::CellType::kControl
          : PopupRowView::CellType::kContent;
  SetSelectedCell(CellIndex{new_row, new_cell_type},
                  PopupCellSelectionSource::kKeyboard);
}

void PopupViewViews::SelectNextRow(PopupCellSelectionSource source) {
  DCHECK(!rows_.empty());
  std::optional<CellIndex> old_index = GetSelectedCell();

  size_t new_row = old_index ? old_index->first + 1u : 0u;
  for (size_t i = 0;
       i < rows_.size() && !HasSelectablePopupInteractiveRowViewAt(new_row);
       i++) {
    ++new_row;
    if (new_row >= rows_.size()) {
      new_row = 0u;
    }
  }

  // `kControl` is used to show a sub-popup with child suggestions. It can
  // only be selected on a new row if the corresponding suggestion has
  // children.
  PopupRowView* row_view = MaybeGetPopupRowViewAt(new_row);
  const PopupRowView::CellType new_cell_type =
      (old_index && old_index->second == PopupRowView::CellType::kControl &&
       row_view && row_view->GetExpandChildSuggestionsView())
          ? PopupRowView::CellType::kControl
          : PopupRowView::CellType::kContent;
  SetSelectedCell(CellIndex{new_row, new_cell_type}, source);
}

bool PopupViewViews::SelectNextHorizontalCell() {
  std::optional<CellIndex> selected_cell = GetSelectedCell();
  if (selected_cell &&
      HasSelectablePopupInteractiveRowViewAt(selected_cell->first)) {
    PopupRowView* row = MaybeGetPopupRowViewAt(selected_cell->first);
    if (selected_cell->second == PopupRowView::CellType::kContent && row &&
        row->GetExpandChildSuggestionsView()) {
      SetSelectedCell(
          CellIndex{selected_cell->first, PopupRowView::CellType::kControl},
          PopupCellSelectionSource::kKeyboard, AutoselectFirstSuggestion(true));
      return true;
    }
  }

  if (tabbed_pane_) {
    const size_t current_tab = tabbed_pane_->GetSelectedTabIndex();
    if (current_tab + 1 < tabbed_pane_->GetTabCount()) {
      tabbed_pane_->SelectTabAt(current_tab + 1);
      return true;
    }
  }

  return false;
}

bool PopupViewViews::SelectPreviousHorizontalCell() {
  std::optional<CellIndex> selected_cell = GetSelectedCell();
  if (selected_cell &&
      selected_cell->second == PopupRowView::CellType::kControl &&
      HasSelectablePopupInteractiveRowViewAt(selected_cell->first)) {
    SetSelectedCell(
        CellIndex{selected_cell->first, PopupRowView::CellType::kContent},
        PopupCellSelectionSource::kKeyboard);
    return true;
  }

  if (tabbed_pane_) {
    const size_t current_tab = tabbed_pane_->GetSelectedTabIndex();
    if (current_tab > 0) {
      tabbed_pane_->SelectTabAt(current_tab - 1);
      return true;
    }
  }

  return false;
}

bool PopupViewViews::AcceptSelectedContentOrCreditCardCell(
    AutofillMetrics::SuggestionAcceptedMethod accept_method) {
  std::optional<CellIndex> index = GetSelectedCell();
  if (!controller_ || !index) {
    return false;
  }

  if (index->second != PopupRowView::CellType::kContent) {
    return false;
  }

  if (!IsStandaloneSuggestionType(
          controller_->GetSuggestionAt(index->first).type)) {
    return false;
  }

  PopupRowView* row = MaybeGetPopupRowViewAt(index->first);
  return row && row->Accept(accept_method);
}

bool PopupViewViews::RemoveSelectedCell() {
  std::optional<CellIndex> index = GetSelectedCell();

  // Only content cells can be removed.
  if (!index || index->second != PopupRowView::CellType::kContent ||
      !controller_) {
    return false;
  }

  if (!controller_->RemoveSuggestion(index->first,
                                     AutofillMetrics::SingleEntryRemovalMethod::
                                         kKeyboardShiftDeletePressed)) {
    return false;
  }

  return true;
}

void PopupViewViews::OnSuggestionsChanged(bool prefer_prev_arrow_side) {
  // New suggestions invalidate this scheduling (if it's running), cancel it.
  open_sub_popup_timer_.Stop();
  SetRowWithOpenSubPopup(std::nullopt);

  CreateSuggestionViews();

  if (search_bar_) {
    search_bar_->SetLoading(controller_ && controller_->IsSearching());
  }

  // Updating bounds and redrawing popup can cause the popup to hide.
  if (!DoUpdateBoundsAndRedrawPopup(prefer_prev_arrow_side)) {
    return;
  }

  MaybeAutoSelectSuggestion();
  MaybeAnnouncePasswordRecoveryPopup();
  MaybeAnnounceLoadingState();
  if (!MaybeA11yFocusInformationalSuggestion()) {
    return;
  }
  ShowIPHFeaturePromos();
}

bool PopupViewViews::OverlapsWithPictureInPictureWindow() const {
  return BoundsOverlapWithPictureInPictureWindow(GetBoundsInScreen());
}

std::optional<int32_t> PopupViewViews::GetAxUniqueId() {
  return std::optional<int32_t>(
      PopupBaseView::GetViewAccessibility().GetUniqueId());
}

void PopupViewViews::AxAnnounce(const std::u16string& text) {
  a11y_announcer_.Run(text, /*polite=*/false);
}

base::WeakPtr<AutofillPopupView> PopupViewViews::CreateSubPopupView(
    base::WeakPtr<AutofillPopupController> controller) {
  if (GetWidget() && controller) {
    return (new PopupViewViews(controller, weak_ptr_factory_.GetWeakPtr(),
                               GetWidget(), sub_popup_config_))
        ->GetWeakPtr();
  }
  return nullptr;
}

std::optional<size_t> PopupViewViews::GetIndexOfSubPopupAnchorSuggestion()
    const {
  return row_with_open_sub_popup_;
}

bool PopupViewViews::HasFocus() const {
  if (!GetWidget()) {
    return false;
  }

  // The `CanActivate() && show_in_progress_` expression is needed to cover
  // the case when this method is called during the `GetWidget()->Show()`
  // execution and the popup is not yet active. It optimistically responds
  // `true` and requires an additional `GetWidget()->IsActive()` check after
  // the `GetWidget()->Show()` call to ensure the popup is shown successfully.
  return (CanActivate() && show_in_progress_) || GetWidget()->IsActive();
}

void PopupViewViews::OnWidgetVisibilityChanged(views::Widget* widget,
                                               bool visible) {
  if (!visible) {
    return;
  }

  // Show the in-product-help promo anchored to this bubble.
  // The in-product-help promo is a bubble anchored to this row item to show
  // educational messages. The promo bubble should only be shown once in one
  // session and has a limit for how many times it can be shown at most in a
  // period of time.
  ShowIPHFeaturePromos();
}

void PopupViewViews::SearchBarOnInputChanged(std::u16string_view query) {
  if (controller_) {
    controller_->SetFilter(
        query.empty() ? std::nullopt
                      : std::optional(AutofillPopupController::StringFilter(
                            std::u16string(query))),
        AutofillPopupController::FilterSource::kInputChanged);
  }
}

void PopupViewViews::SearchBarOnFocusLost() {
  if (controller_) {
    controller_->Hide(SuggestionHidingReason::kSearchBarFocusLost);
  }
}

bool PopupViewViews::SearchBarHandleKeyPressed(const ui::KeyEvent& event) {
  if (!controller_) {
    return false;
  }

// TODO(crbug.com/339187209): On MaOS, conversion of a character key event to
// `NativeWebKeyboardEvent` hits `NOTREACHED` in `ui::DomKeyFromNSEvent`. This
// doesn't affect our use case as we handle only non-character events
// (Arrows/Esc/etc) for navigation. But it needs to be investigated and ideally
// removed.
#if BUILDFLAG(IS_MAC)
  if (event.is_char()) {
    return false;
  }
#endif  // BUILDFLAG(IS_MAC)

  // Handling events in the controller (the delegate's handler is prioritized by
  // the search bar) enables keyboard navigation when the search bar input
  // field is focused.
  return controller_->HandleKeyPressEvent(input::NativeWebKeyboardEvent(event));
}

void PopupViewViews::TabSelectedAt(int index) {
  CHECK_LT(base::checked_cast<size_t>(index), tabbed_pane_config_->tabs.size());
  controller_->OnTabSelected(index, tabbed_pane_config_->tabs[index].type);
  MaybeAnnounceCurrentTabAndFootnote();
}

void PopupViewViews::SetSelectedCell(
    std::optional<CellIndex> cell_index,
    PopupCellSelectionSource source,
    AutoselectFirstSuggestion autoselect_first_suggestion,
    bool suppress_popup) {
  if (!controller_) {
    return;
  }

  std::optional<CellIndex> old_index = GetSelectedCell();
  if (old_index == cell_index) {
    return;
  }

  if (old_index) {
    if (!TrackAndRun(this, [this, old_index]() {
          GetPopupInteractiveRowViewAt(old_index->first)
              .SetSelectedCell(std::nullopt);
        })) {
      return;
    }
  }

  // New selected cell invalidates this scheduling (if it's running), cancel it.
  open_sub_popup_timer_.Stop();

  if (cell_index) {
    // The sub-popup hiding is canceled because the newly selected/entered cell
    // rules visibility from now.
    OnMouseEnteredInChildren();
  }

  if (cell_index && HasSelectablePopupInteractiveRowViewAt(cell_index->first)) {
    if (auto* footnote = GetBnplFootnoteView()) {
      // Since cell selection is based on virtual focus and not real focus,
      // we need to manually unfocus the settings link when updating virtual
      // focus.
      if (!TrackAndRun(footnote,
                       [footnote]() { footnote->UnfocusSettingsLink(); })) {
        return;
      }
    }
    has_keyboard_focus_ = true;

    row_with_selected_cell_ = cell_index->first;
    PopupInteractiveRowView& new_selected_row =
        GetPopupInteractiveRowViewAt(cell_index->first);
    if (!TrackAndRun(&new_selected_row, [&new_selected_row, cell_index]() {
          new_selected_row.SetSelectedCell(cell_index->second);
        })) {
      return;
    }
    new_selected_row.ScrollViewToVisible();

    if (!controller_) {
      // The previous SetSelectedCell() call may have hidden the popup.
      return;
    }
    const Suggestion& suggestion =
        controller_->GetSuggestionAt(cell_index->first);

    bool can_open_sub_popup =
        !suppress_popup && !suggestion.is_loading &&
        (cell_index->second == PopupRowView::CellType::kControl ||
         ContentCellShouldOpenSubPopupSuggestion(suggestion));

    CHECK(!can_open_sub_popup ||
          !controller_->GetSuggestionAt(cell_index->first).children.empty());

    std::optional<size_t> row_with_open_sub_popup =
        can_open_sub_popup ? std::optional(cell_index->first) : std::nullopt;
    base::TimeDelta delay = source == PopupCellSelectionSource::kMouse
                                ? kMouseOpenSubPopupDelay
                                : kNonMouseOpenSubPopupDelay;
    open_sub_popup_timer_.Start(
        FROM_HERE, delay,
        base::BindOnce(&PopupViewViews::SetRowWithOpenSubPopup,
                       weak_ptr_factory_.GetWeakPtr(), row_with_open_sub_popup,
                       autoselect_first_suggestion));
  } else {
    row_with_selected_cell_ = std::nullopt;
    if (!cell_index) {
      ScheduleSubPopupClosing();
    }
  }
}

void PopupViewViews::ShowIPHFeaturePromos() {
  BrowserWindowInterface* browser = GetBrowser();
  if (!browser) {
    return;
  }

  if (!GetWidget() || !GetWidget()->IsVisible() || !controller_) {
    return;
  }

  for (const auto& iph_metadata : base::MakeFlatSet<Suggestion::IPHMetadata>(
           controller_->GetSuggestions(), /*comp=*/{},
           &Suggestion::iph_metadata)) {
    if (iph_metadata.feature) {
      user_education::FeaturePromoParams params(*iph_metadata.feature);
      // Setting the params to a `std::vector` (even if it is empty), indicates
      // to the framework that a substitution should be made. Therefore only
      // set it if `iph_params` is non-empty.
      if (!iph_metadata.iph_params.empty()) {
        params.body_params = iph_metadata.iph_params;
        params.screen_reader_params = iph_metadata.iph_params;
      }
      BrowserUserEducationInterface::From(browser)->MaybeShowFeaturePromo(
          std::move(params));
    }
  }
}

void PopupViewViews::MaybeAnnouncePasswordRecoveryPopup() {
  if (!controller_ || controller_->GetSuggestions().empty()) {
    return;
  }

  if (controller_->GetSuggestionAt(0).type ==
      SuggestionType::kBackupPasswordEntry) {
    a11y_announcer_.Run(
        l10n_util::GetStringUTF16(
            IDS_PASSWORD_MANAGER_UI_PASSWORD_RECOVERY_SHOWN_A11Y_ANNOUNCEMENT),
        /*polite=*/true);
  }
}

void PopupViewViews::MaybeAnnounceLoadingState() {
  if (!controller_ || controller_->GetSuggestions().empty()) {
    return;
  }

  if (controller_->GetSuggestionAt(0).type ==
      SuggestionType::kLoadingThrobber) {
    a11y_announcer_.Run(l10n_util::GetStringUTF16(
                            IDS_AUTOFILL_BNPL_PROGRESS_DIALOG_LOADING_MESSAGE),
                        /*polite=*/true);
  }
}

void PopupViewViews::MaybeAnnounceCurrentTabAndFootnote() {
  std::u16string announcement;

  if (tabbed_pane_) {
    size_t index = tabbed_pane_->GetSelectedTabIndex();
    size_t total = tabbed_pane_->GetTabCount();
    std::u16string tab_title(tabbed_pane_->GetTabAt(index)->GetTitleText());

    announcement = l10n_util::GetStringFUTF16(
        IDS_AUTOFILL_PAY_NOW_PAY_LATER_TAB_ACCESSIBILITY_ANNOUNCEMENT,
        tab_title, base::NumberToString16(index + 1),
        base::NumberToString16(total));
  }

  std::u16string footnote_text;
  if (PopupBnplFootnoteView* bnpl_footnote = GetBnplFootnoteView()) {
    footnote_text = bnpl_footnote->GetFullText();
  }

  if (!footnote_text.empty()) {
    if (!announcement.empty()) {
      announcement = l10n_util::GetStringFUTF16(
          IDS_AUTOFILL_A11Y_ANNOUNCEMENT_CONCATENATE_TWO_STRINGS, announcement,
          footnote_text);
    } else {
      announcement = footnote_text;
    }
  }

  if (!announcement.empty()) {
    a11y_announcer_.Run(announcement, /*polite=*/true);
  }
}

void PopupViewViews::UpdateAccessibleStates() const {
  if (controller_) {
    GetViewAccessibility().SetIsExpanded();
    GetViewAccessibility().SetIsInvisible(false);
  } else {
    GetViewAccessibility().SetIsCollapsed();
    GetViewAccessibility().SetIsInvisible(true);
  }
}

bool PopupViewViews::HasSelectablePopupInteractiveRowViewAt(
    size_t index) const {
  return index < rows_.size() &&
         (std::holds_alternative<PopupRowView*>(rows_[index]) ||
          std::holds_alternative<PopupInteractiveRowView*>(rows_[index])) &&
         GetPopupInteractiveRowViewAt(index).IsSelectable();
}

PopupBnplFootnoteView* PopupViewViews::GetBnplFootnoteView() const {
  for (const RowPointer& row : rows_) {
    if (const auto* footnote = std::get_if<PopupBnplFootnoteView*>(&row)) {
      return *footnote;
    }
  }
  return nullptr;
}

void PopupViewViews::InitViews() {
  a11y_announcer_ = base::BindRepeating(&DefaultA11yAnnouncer);

  SetNotifyEnterExitOnChild(true);
  SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical));

  if (search_bar_config_) {
    const bool is_at_memory =
        controller_ &&
        controller_->GetMainFillingProduct() == FillingProduct::kAtMemory;
    search_bar_ = AddChildView(std::make_unique<PopupSearchBarView>(
        search_bar_config_->placeholder, search_bar_config_->initial_value,
        *this,
        /*show_indicator=*/is_at_memory,
        /*show_search_icon_sparkle=*/is_at_memory,
        /*debounce_delay=*/
        is_at_memory ? base::TimeDelta()
                     : PopupSearchBarView::kInputChangeCallbackDelay));
    search_bar_->SetProperty(views::kMarginsKey,
                             gfx::Insets::VH(GetContentsVerticalPadding(), 0));
    search_bar_->SetLoading(controller_ && controller_->IsSearching());
    AddChildView(std::make_unique<PopupSeparatorView>(/*vertical_padding=*/0));
  }

  if (tabbed_pane_config_) {
    CreateTabbedPaneView();
  }

  suggestions_container_ =
      AddChildView(views::Builder<views::BoxLayoutView>()
                       .SetOrientation(views::BoxLayout::Orientation::kVertical)
                       .Build());

  BrowserWindowInterface* browser = GetBrowser();
  if (Profile* profile = browser ? browser->GetProfile() : nullptr) {
    auto* favicon_service =
        LargeIconServiceFactory::GetForBrowserContext(profile);
    auto* image_fetcher =
        ImageFetcherServiceFactory::GetForKey(profile->GetProfileKey())
            ->GetImageFetcher(
                image_fetcher::ImageFetcherConfig::kInMemoryWithDiskCache);
    password_favicon_loader_ = std::make_unique<PasswordFaviconLoaderImpl>(
        favicon_service, image_fetcher);
  }

  CreateSuggestionViews();
  UpdateAccessibleStates();
}

void PopupViewViews::CreateSuggestionViews() {
  // Null all pointers prior to deleting the children views to avoid temporarily
  // dangling pointers that might be picked up by dangle detection builds. Also,
  // `footer_container_` is instantiated conditionally, which can make its value
  // obsolete after `OnSuggestionsChanged()`.
  scroll_view_ = nullptr;
  body_container_ = nullptr;
  footer_container_ = nullptr;
  rows_.clear();
  suggestions_container_->RemoveAllChildViews();

  const int kInterItemsPadding = GetContentsVerticalPadding();
  const std::vector<Suggestion> suggestions = controller_->GetSuggestions();
  const std::vector<
      std::optional<AutofillPopupController::SuggestionFilterMatch>>&
      filter_matches = controller_->GetSuggestionFilterMatches();

  SetBackground(views::CreateSolidBackground(ui::kColorDropdownBackground));

  if (search_bar_ &&
      controller_->ShouldShowNoSuggestionsMessage(search_bar_config_)) {
    suggestions_container_->AddChildView(
        std::make_unique<PopupCenteredTextView>(
            search_bar_config_->no_results_message));
  }

  int current_visible_body_entries = 0;
  int current_body_visible_height = 0;
  size_t current_line_number = 0u;
  // Add the body rows, if there are any.
  if (!suggestions.empty() && !IsFooterItem(suggestions, 0u)) {
    // Create a container to wrap the "regular" (non-footer) rows.
    auto body_builder =
        PopupRowContainerBuilder::CreateBodyBuilder(GetPopupMinWidth());

    for (; current_line_number < suggestions.size() &&
           !IsFooterItem(suggestions, current_line_number);
         ++current_line_number) {
      switch (suggestions[current_line_number].type) {
        case SuggestionType::kSeparator:
          body_builder.AddRow(
              std::make_unique<PopupSeparatorView>(kInterItemsPadding));
          break;
        case SuggestionType::kTitle:
          body_builder.AddRow(std::make_unique<PopupTitleView>(
              suggestions[current_line_number].main_text.value));
          break;
        case SuggestionType::kInsecureContextPaymentDisabledMessage:
          body_builder.AddRow(std::make_unique<PopupWarningView>(
              suggestions[current_line_number]));
          break;
        case SuggestionType::kLoadingThrobber: {
          body_builder.AddRow(std::make_unique<PopupLoadingView>(
              suggestions[current_line_number]
                  .expected_number_of_suggestions.value_or(1)));
          break;
        }
        case SuggestionType::kAtMemoryFetching: {
          body_builder.AddRow(std::make_unique<PopupCenteredTextView>(
              suggestions[current_line_number].main_text.value));
          break;
        }
        case SuggestionType::kPersonalContextNotice: {
          body_builder.AddRow(CreatePersonalContextNoticeView(
              *this, a11y_announcer_, controller(), current_line_number));
          break;
        }
        case SuggestionType::kAutofillAiPrivateInferenceNotice: {
          body_builder.AddRow(CreateAutofillAiPrivateInferenceNoticeView(
              *this, a11y_announcer_, controller(), current_line_number));
          break;
        }
        // The default section contains all selectable rows and includes
        // autocomplete, address, credit cards and passwords.
        default:
          std::optional<AutofillPopupController::SuggestionFilterMatch>
              filter_match =
                  filter_matches.empty() ? std::nullopt
                                         : filter_matches[current_line_number];
          PopupRowView* row_view = body_builder.AddRow(CreatePopupRowView(
              controller(), /*a11y_selection_delegate=*/*this,
              /*selection_delegate=*/*this, current_line_number,
              std::move(filter_match), password_favicon_loader_.get()));

          // Set appropriate element ids for IPH targets, it is important to
          // set them earlier to make sure the elements are discoverable later
          // during popup's visibility change and the promo bubble showing.
          if (ui::ElementIdentifier element_id =
                  GetAutofillPopupCellElementIdentifier(
                      suggestions[current_line_number].iph_metadata.feature)) {
            row_view->SetProperty(views::kElementIdentifierKey, element_id);
          }
      }
    }

    current_visible_body_entries = body_builder.visible_entries();
    current_body_visible_height = body_builder.visible_height();
    std::optional<int> scroll_clipping_height =
        body_builder.GetScrollClippingHeight();
    auto [body_container, rows] = std::move(body_builder).Build();
    rows_ = std::move(rows);
    std::unique_ptr<views::ScrollView> scroll_view =
        views::Builder<views::ScrollView>()
            .SetBackgroundColor(ui::kColorDropdownBackground)
            .SetHorizontalScrollBarMode(
                views::ScrollView::ScrollBarMode::kDisabled)
            .SetDrawOverflowIndicator(false)
            .ClipHeightTo(
                0, scroll_clipping_height.value_or(
                       body_container->GetHeightForWidth(GetPopupMaxWidth())))
            .Build();
    body_container_ = scroll_view->SetContents(std::move(body_container));
    scroll_view_ = suggestions_container_->AddChildView(std::move(scroll_view));
    // If `kUiCompositorScrollWithLayers` is enabled, then a ScrollView performs
    // scrolling by using layers. These layers are not affected by the clip path
    // of the widget and their corners remain unrounded, thus going beyond
    // the popup's rounded corners. To avoid these, set a corner radius for
    // the ScrollView's ViewPort if layer scrolling is enabled.
    if (scroll_view_ && base::FeatureList::IsEnabled(
                            ::features::kUiCompositorScrollWithLayers)) {
      scroll_view_->SetViewportRoundedCornerRadius(
          gfx::RoundedCornersF(GetCornerRadius()));
    }
    suggestions_container_->SetFlexForView(scroll_view_.get(), 1);
  }

  if (current_line_number >= suggestions.size()) {
    return;
  }

  if (!IsFooterScrollable()) {
    // Add a separator between the main list of suggestions and the footer with
    // no vertical padding as these elements have their own top/bottom paddings.
    if (suggestions[current_line_number].type == SuggestionType::kSeparator) {
      rows_.push_back(suggestions_container_->AddChildView(
          std::make_unique<PopupSeparatorView>(/*vertical_padding=*/0)));
      ++current_line_number;
    }
  }

  // Continue counting visible entries as if they were part of the body
  // container in case the footer is scrollable.
  auto footer_builder = PopupRowContainerBuilder::CreateFooterBuilder(
      GetPopupMinWidth(),
      /*initial_visible_entries=*/current_visible_body_entries,
      /*initial_visible_height=*/current_body_visible_height);

  for (; current_line_number < suggestions.size(); ++current_line_number) {
    DCHECK(IsFooterItem(suggestions, current_line_number));
    // The footer can contain either footer views or separator lines.
    if (suggestions[current_line_number].type == SuggestionType::kSeparator) {
      footer_builder.AddRow(
          std::make_unique<PopupSeparatorView>(kInterItemsPadding));
    } else if (suggestions[current_line_number].type ==
               SuggestionType::kBnplFootnote) {
      footer_builder.AddRow(std::make_unique<PopupBnplFootnoteView>(
          controller(), /*a11y_selection_delegate=*/*this,
          base::BindRepeating(&DefaultA11yAnnouncer)));
    } else if (suggestions[current_line_number].type ==
               SuggestionType::kAtMemoryAiDisclosure) {
      footer_builder.AddRow(std::make_unique<PopupAtMemoryAiDisclosureView>(
          controller(), /*a11y_selection_delegate=*/*this));
    } else {
      footer_builder.AddRow(CreatePopupRowView(
          controller(), /*a11y_selection_delegate=*/*this,
          /*selection_delegate=*/*this, current_line_number));
    }
  }
  std::optional<int> scroll_clipping_height =
      footer_builder.GetScrollClippingHeight();
  auto [footer_container, rows] = std::move(footer_builder).Build();
  base::Extend(rows_, std::move(rows));

  if (IsFooterScrollable()) {
    footer_container_ =
        body_container_->AddChildView(std::move(footer_container));

    if (scroll_view_) {
      // Adjust the scrollable area height. Make sure this adjustment always
      // happens after changes that can affect `body_container_`'s size.
      scroll_view_->ClipHeightTo(
          0, scroll_clipping_height.value_or(
                 body_container_->GetHeightForWidth(GetPopupMaxWidth())));
    }
  } else {
    footer_container_ =
        suggestions_container_->AddChildView(std::move(footer_container));

    // If the last item in the footer is a BNPL footnote, then we don't need
    // any extra padding at the bottom. The BNPL footnote has its own bottom
    // padding to ensure it is properly shaded.
    int footer_bottom_padding =
        suggestions.back().type == SuggestionType::kBnplFootnote
            ? 0
            : kInterItemsPadding;
    footer_container_->SetInsideBorderInsets(
        gfx::Insets::TLBR(kInterItemsPadding, 0, footer_bottom_padding, 0));

    suggestions_container_->SetFlexForView(footer_container_, 0);
  }
}

gfx::Size PopupViewViews::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  gfx::Size size;
  // Use the original popup width if a tabbed pane is present to maintain the
  // same popup width during tab switches.
  if (tabbed_pane_initial_width_.has_value()) {
    size = views::View::CalculatePreferredSize(
        views::SizeBounds(tabbed_pane_initial_width_.value(), {}));
    size.set_width(tabbed_pane_initial_width_.value());
  } else {
    size = views::View::CalculatePreferredSize(available_size);
    const int max_width = GetPopupMaxWidth();
    if (size.width() > max_width) {
      // TODO(crbug.com/40232718): When we set the vertical axis to stretch,
      // BoxLayout will occupy the entire vertical axis size. Two calculations
      // are needed to correct this.
      //
      // Following crrev.com/c/5828724, the dialog box will fit the text more
      // closely. But this will break the pixel test, so make it a fixed size.
      size =
          views::View::CalculatePreferredSize(views::SizeBounds(max_width, {}));
      size.set_width(max_width);
    }
  }

  // This popup height limiting for popups with a search bar addresses a minor
  // UX concern when a potentially long list makes the search bar appear far
  // away from the field and thus less obvious what the search bar belongs to.
  if (search_bar_) {
    size.set_height(std::min(kMaxPopupWithSearchBarHeight, size.height()));
  }

  return size;
}

void PopupViewViews::CreateTabbedPaneView() {
  auto tabbed_pane = std::make_unique<views::TabbedPane>();

  auto create_empty_pane = []() {
    auto pane = std::make_unique<views::View>();
    pane->SetBorder(
        views::CreateEmptyBorder(ChromeLayoutProvider::Get()->GetInsetsMetric(
            views::INSETS_DIALOG_SUBSECTION)));
    return pane;
  };

  const int kCustomTabHeight = 52;
  for (const auto& tab_config : tabbed_pane_config_->tabs) {
    tabbed_pane->AddTab(tab_config.title, create_empty_pane());

    views::TabbedPaneTab* tab_view =
        tabbed_pane->GetTabAt(tabbed_pane->GetTabCount() - 1);
    tab_view->SetHeight(kCustomTabHeight);
  }

  // TODO(crbug.com/477689220): Investigate better approaches (e.g., layout
  // management) for sizing the tabbed pane so it doesn't overlap the
  // suggestions container.
  // Calculate and set the preferred size for the tabbed pane based on its
  // contents.
  tabbed_pane->SetPreferredSize(tabbed_pane->GetPreferredSize());
  tabbed_pane->SetListener(this);

  // Filter suggestions for the default tab.
  controller_->SetFilter(kDefaultSuggestionTabIndex,
                         AutofillPopupController::FilterSource::kTabSelected);

  tabbed_pane_ = AddChildView(std::move(tabbed_pane));
}

bool PopupViewViews::DoUpdateBoundsAndRedrawPopup() {
  return DoUpdateBoundsAndRedrawPopup(/*prefer_prev_arrow_side=*/false);
}

bool PopupViewViews::DoUpdateBoundsAndRedrawPopup(bool prefer_prev_arrow_side) {
  gfx::Size preferred_size = CalculatePreferredSize({});

  // Store the original width if a tabbed pane is present to maintain the same
  // popup width during tab switches.
  if (tabbed_pane_ && !tabbed_pane_initial_width_) {
    tabbed_pane_initial_width_ = preferred_size.width();
  }

  gfx::Rect popup_bounds;

  const gfx::Rect content_area_bounds = GetContentAreaBounds();
  // TODO(crbug.com/40799454) Once popups can render outside the main window on
  // Linux, use the screen bounds.
  const gfx::Rect top_window_bounds = GetTopWindowBounds();
  const gfx::Rect& max_bounds_for_popup =
      PopupMayExceedContentAreaBounds(controller_->GetWebContents())
          ? top_window_bounds
          : content_area_bounds;

  // Intersect with the current monitor's work area to avoid showing popups
  // outside the screen.
  gfx::Rect visible_content_area_bounds = IntersectWithDisplayBounds(
      controller_->GetWebContents(), max_bounds_for_popup);

  gfx::Rect element_bounds =
      gfx::ToEnclosingRect(controller_->element_bounds());

  // An element that is contained by the `content_area_bounds` (even if empty,
  // which means either the height or the width is 0) is never outside the
  // content area. An empty element case can happen with caret bounds, which
  // sometimes has 0 width.
  if (!visible_content_area_bounds.Contains(element_bounds)) {
    // If the element exceeds the content area, ensure that the popup is still
    // visually attached to the input element.
    element_bounds.Intersect(content_area_bounds);
    if (element_bounds.IsEmpty()) {
      controller_->Hide(SuggestionHidingReason::kElementOutsideOfContentArea);
      return false;
    }
  }

  // Consider the element is |kElementBorderPadding| pixels larger at the top
  // and at the bottom in order to reposition the dropdown, so that it doesn't
  // look too close to the element.
  element_bounds.Inset(
      gfx::Insets::VH(/*vertical=*/-kElementBorderPadding, /*horizontal=*/0));

  if ((!body_container_ || body_container_->children().empty()) &&
      (!footer_container_ || footer_container_->children().empty()) &&
      !search_bar_) {
    controller_->Hide(SuggestionHidingReason::kNoSuggestions);
    return false;
  }

  if (!CanShowDropdownInBounds(visible_content_area_bounds)) {
    controller_->Hide(SuggestionHidingReason::kInsufficientSpace);
    return false;
  }

  CalculatePopupYAndHeight(preferred_size.height(), visible_content_area_bounds,
                           element_bounds, &popup_bounds);

  // Adjust the width to compensate for a scroll bar, if necessary, and for
  // other rules.
  int scroll_width = 0;
  if (scroll_view_ && preferred_size.height() > popup_bounds.height()) {
    preferred_size.set_height(popup_bounds.height());

    // Because the preferred size is greater than the bounds available, the
    // contents will have to scroll. The scroll bar will steal width from the
    // content and smoosh everything together. Instead, add to the width to
    // compensate.
    scroll_width = scroll_view_->GetScrollBarLayoutWidth();
  }

  // Width clamping happens here because:
  // 1. The exact width isn't known earlier since a scrollbar might still be
  //    added, which impacts the width.
  // 2. If the width is under `min_width`, the content fits without any
  //    wrapping. Expanding it early in `CalculatePreferredSize()`
  //    is not necessary since it doesn't impact the preferred height
  //    calculation.
  // 3. Clamping early in `CalculatePreferredSize()` would cause non-overlay
  //    scrollbars (like on Windows) to be added on top of `min_width`, making
  //    the popup wider than necessary.
  preferred_size.set_width(std::clamp(preferred_size.width() + scroll_width,
                                      GetPopupMinWidth(), GetPopupMaxWidth()));

  views::BubbleBorder* border = static_cast<views::BubbleBorder*>(
      GetWidget()->GetRootView()->GetBorder());
  std::vector<views::BubbleArrowSide> preferred_popup_sides =
      GetPreferredPopupSides(
          /*is_root_popup=*/!parent_, prefer_prev_arrow_side,
          /*prev_arrow=*/border ? border->arrow() : BubbleBorder::Arrow::NONE);
  popup_bounds = GetOptimalPositionAndPlaceArrowOnPopup(
      element_bounds, visible_content_area_bounds, preferred_size,
      preferred_popup_sides);

  if (OverlapsWithAnotherPrompt(popup_bounds)) {
    controller_->Hide(SuggestionHidingReason::kOverlappingWithAnotherPrompt);
    return false;
  }

  // The pip surface is given the most preference while rendering. So, the
  // autofill popup should not be shown when the picture in picture window
  // hides the autofill form behind it.
  // For more details on how this can happen, see crbug.com/40060742.
  if (BoundsOverlapWithPictureInPictureWindow(popup_bounds)) {
    controller_->Hide(
        SuggestionHidingReason::kOverlappingWithPictureInPictureWindow);
    return false;
  }

  SetSize(preferred_size);

  popup_bounds.Inset(-GetWidget()->GetRootView()->GetInsets());
  GetWidget()->SetBounds(popup_bounds);
  UpdateClipPath();

  SchedulePaint();
  return true;
}

void PopupViewViews::OnMouseEnteredInChildren() {
  if (parent_ && parent_->get()) {
    parent_->get()->OnMouseEnteredInChildren();
  }

  // Cancel scheduled sub-popup closing.
  StopSubPopupClosing();
}

void PopupViewViews::OnMouseExitedInChildren() {
  const std::optional<size_t> row_to_check = row_with_selected_cell_.has_value()
                                                 ? row_with_selected_cell_
                                                 : row_with_open_sub_popup_;
  if (row_to_check && controller_ &&
      *row_to_check < static_cast<size_t>(controller_->GetLineCount())) {
    if (PopupRowView* row = MaybeGetPopupRowViewAt(*row_to_check)) {
      // If we are hovering the control cell, do not schedule close.
      views::View* control_view = row->GetExpandChildSuggestionsView();
      if (control_view && control_view->IsMouseHovered()) {
        return;
      }

      // Similarly, do not close the sub-popup if we are hovering the content
      // cell and the content cell acts in the same way as the chevron.
      const Suggestion& suggestion =
          controller_->GetSuggestionAt(*row_to_check);
      if (ContentCellShouldOpenSubPopupSuggestion(suggestion) &&
          row->GetContentView().IsMouseHovered()) {
        return;
      }
    }
  }

  if (parent_ && parent_->get()) {
    parent_->get()->OnMouseExitedInChildren();
  }

  ScheduleSubPopupClosing();
}

void PopupViewViews::ScheduleSubPopupClosing() {
  if (!row_with_open_sub_popup_ ||
      no_selection_sub_popup_close_timer_.IsRunning()) {
    return;
  }

  const base::TimeDelta hide_delay =
      sub_popup_config_ ? sub_popup_config_->no_selection_hide_delay
                        : kNoSelectionHideSubPopupDelay;

  no_selection_sub_popup_close_timer_.Start(
      FROM_HERE, hide_delay,
      base::BindRepeating(&PopupViewViews::SetRowWithOpenSubPopup,
                          weak_ptr_factory_.GetWeakPtr(), std::nullopt,
                          AutoselectFirstSuggestion(false)));
}

void PopupViewViews::StopSubPopupClosing() {
  no_selection_sub_popup_close_timer_.Stop();
}

bool PopupViewViews::IsFooterScrollable() const {
  // Footer items of a root popup are expected to be more prioritized and
  // therefore "sticky", i.e. not being scrollable with the whole popup content.
  // `body_container_` is the container of regular suggestions, it must exist
  // to place the footer there and thus make it scrollable too.
  return parent_ && body_container_;
}

int PopupViewViews::GetPopupMinWidth() const {
  if (controller_ &&
      controller_->GetMainFillingProduct() == FillingProduct::kAtMemory) {
    return kAtMemoryPopupWidth;
  }
  return kAutofillPopupMinWidth;
}

int PopupViewViews::GetPopupMaxWidth() const {
  if (!controller_) {
    return kAutofillPopupMaxWidth;
  }
  if (controller_->GetMainFillingProduct() == FillingProduct::kAtMemory) {
    return kAtMemoryPopupWidth;
  }
  if (parent_ &&
      controller_->GetMainFillingProduct() == FillingProduct::kAutofillAi) {
    return kAutofillAiSubPopupMaxWidth;
  }
  return kAutofillPopupMaxWidth;
}

bool PopupViewViews::CanShowDropdownInBounds(
    const gfx::Rect& visible_content_area_bounds) const {
  gfx::Rect element_bounds =
      gfx::ToEnclosingRect(controller_->element_bounds());

  // At least one suggestion and the sticky footer should be shown in the
  // visible_content_area_bounds of the content area so that the user notices
  // the presence of the popup.
  int min_height = 0;
  if (body_container_ && !body_container_->children().empty()) {
    min_height += body_container_->children()[0]->GetPreferredSize().height();
  }
  if (search_bar_) {
    min_height += search_bar_->GetPreferredSize().height();
  }
  if (footer_container_ && !footer_container_->children().empty() &&
      !IsFooterScrollable()) {
    // The footer is not scrollable, its full height should be considered.
    min_height += footer_container_->GetPreferredSize().height();
  }

  return CanShowDropdownHere(min_height, visible_content_area_bounds,
                             element_bounds);
}

PopupRowView* PopupViewViews::MaybeGetPopupRowViewAt(size_t index) {
  if (index >= rows_.size()) {
    return nullptr;
  }
  if (auto* row = std::get_if<PopupRowView*>(&rows_[index])) {
    return *row;
  }
  return nullptr;
}

void PopupViewViews::SetRowWithOpenSubPopup(
    std::optional<size_t> row_index,
    AutoselectFirstSuggestion autoselect_first_suggestion) {
  if (!controller_) {
    return;
  }

  if (row_with_open_sub_popup_ == row_index) {
    return;
  }

  // Close previously open sub-popup if any.
  if (row_with_open_sub_popup_) {
    if (PopupRowView* row = MaybeGetPopupRowViewAt(*row_with_open_sub_popup_)) {
      controller_->HideSubPopup();
      row->SetChildSuggestionsDisplayed(false);
      row_with_open_sub_popup_ = std::nullopt;
    }
  }

  // Open a sub-popup on the new cell if provided.
  if (row_index) {
    // Any open sub-popup change invalidates a pending close timer for an
    // earlier sub-popup.
    StopSubPopupClosing();
    PopupRowView* row = MaybeGetPopupRowViewAt(*row_index);
    if (row && row->IsSelectable()) {
      const Suggestion& suggestion = controller_->GetSuggestionAt(*row_index);

      CHECK(!suggestion.children.empty());

      if (controller_->OpenSubPopup(row->GetControlCellBounds(),
                                    suggestion.children,
                                    autoselect_first_suggestion)) {
        row->SetChildSuggestionsDisplayed(true);
        row_with_open_sub_popup_ = row_index;
        if (autoselect_first_suggestion) {
          row->SetSelectedCell(std::nullopt);
        }
      }
    }
  }
}

bool PopupViewViews::SelectParentPopupContentCell() {
  if (!row_with_open_sub_popup_) {
    return false;
  }
  size_t row_index = *row_with_open_sub_popup_;
  // Closing the sub-popup by setting `std::nullopt` is required as
  // `suppress_popup=true` is not enough: the sub-popup closing will be
  // prevented by the "same value" check.
  SetRowWithOpenSubPopup(std::nullopt);
  SetSelectedCell(CellIndex{row_index, PopupRowView::CellType::kContent},
                  PopupCellSelectionSource::kKeyboard,
                  AutoselectFirstSuggestion(false),
                  /*suppress_popup=*/true);
  return true;
}

bool PopupViewViews::MaybeA11yFocusInformationalSuggestion() {
  if (rows_.size() != 1) {
    return true;
  }

  if (auto* warning_view = std::get_if<PopupWarningView*>(&rows_[0]);
      warning_view && *warning_view) {
    PopupWarningView* view_ptr = *warning_view;
    return TrackAndRun(
        view_ptr, [this, view_ptr]() { NotifyAXSelection(*view_ptr); },
        [view_ptr]() {
          view_ptr->NotifyAccessibilityEventDeprecated(ax::mojom::Event::kFocus,
                                                       true);
        });
  }

  return true;
}

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

BEGIN_METADATA(PopupViewViews)
END_METADATA

DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(
    PopupViewViews,
    kAutofillBnplAffirmOrZipSuggestionElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(
    PopupViewViews,
    kAutofillBnplAffirmZipOrKlarnaSuggestionElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(PopupViewViews,
                                      kAutofillCreditCardBenefitElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(
    PopupViewViews,
    kAutofillCreditCardSuggestionEntryElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(PopupViewViews,
                                      kAutofillAiValuablesElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(PopupViewViews,
                                      kAutofillAiOptInIphElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(PopupViewViews,
                                      kAutofillWalletDirectOffersIphElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(
    PopupViewViews,
    kAutofillStandaloneCvcSuggestionElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(PopupViewViews,
                                      kAutofillSuggestionElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(PopupViewViews,
                                      kAutofillHomeWorkSuggestionElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(PopupViewViews,
                                      kAutofillEnableLoyaltyCardsElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(
    PopupViewViews,
    kAutofillAccountNameEmailSuggestionElementId);

// static
base::WeakPtr<AutofillPopupView> AutofillPopupView::Create(
    base::WeakPtr<AutofillPopupController> controller,
    std::optional<const AutofillPopupView::SearchBarConfig> search_bar_config,
    std::optional<const AutofillPopupView::TabbedPaneConfig> tabbed_pane_config,
    std::optional<const AutofillPopupView::SubPopupConfig> sub_popup_config) {
  if (!controller || !CanShowRootPopup(*controller)) {
    return nullptr;
  }

  return (new PopupViewViews(controller, std::move(search_bar_config),
                             std::move(tabbed_pane_config),
                             std::move(sub_popup_config)))
      ->GetWeakPtr();
}

}  // namespace autofill
