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

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

#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>

#include "base/check.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
#include "chrome/browser/ui/autofill/autofill_suggestion_controller_utils.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/views/autofill/payments/bnpl_issuer_linked_pill.h"
#include "chrome/browser/ui/views/autofill/popup/lazy_loading_image_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_base_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_cell_utils.h"
#include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_row_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_row_with_button_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/user_education/user_education_service.h"
#include "components/autofill/core/browser/filling/filling_product.h"
#include "components/autofill/core/browser/metrics/autofill_metrics.h"
#include "components/autofill/core/browser/payments/bnpl_util.h"
#include "components/autofill/core/browser/suggestions/suggestion.h"
#include "components/autofill/core/browser/suggestions/suggestion_type.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/password_manager/core/common/password_manager_constants.h"
#include "components/strings/grit/components_strings.h"
#include "components/user_education/common/new_badge/new_badge_controller.h"
#include "components/user_education/views/new_badge_label.h"
#include "content/public/browser/web_contents.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_id.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/throbber.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/style/typography.h"
#include "ui/views/vector_icons.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"

namespace autofill {

namespace {

constexpr int kCustomIconSize = 16;

// The size of a close or delete icon.
constexpr int kCloseIconSize = 16;

// The custom horizontal spacing between labels in the same row for AtMemory
// results suggestions.
constexpr int kAtMemoryLabelHorizontalSpacing = 4;

// Popup items that use a leading icon instead of a trailing one.
constexpr auto kPopupItemTypesUsingLeadingIcons = DenseSet<SuggestionType>(
    {SuggestionType::kAllLoyaltyCardsEntry,
     SuggestionType::kAllSavedPasswordsEntry,
     SuggestionType::kAutofillAiSourceAttribution,
     SuggestionType::kManageAddress, SuggestionType::kManageCreditCard,
     SuggestionType::kManageAutofillAi,
     SuggestionType::kManageAutofillAiIdentityDocs,
     SuggestionType::kManageAutofillAiShopping,
     SuggestionType::kManageAutofillAiTravel, SuggestionType::kManageIban,
     SuggestionType::kManageLoyaltyCard,
     SuggestionType::kManageEnhancedAutofill, SuggestionType::kRemoveAutofillAi,
     SuggestionType::kUndo, SuggestionType::kViewPasswordDetails,
     SuggestionType::kPendingStateSignin,
     SuggestionType::kWebauthnSignInWithAnotherDevice});

// Max width for the username and masked password.
constexpr int kAutofillPopupUsernameMaxWidth = 272;
constexpr int kAutofillPopupPasswordMaxWidth = 108;

// Max width for the Autofill suggestion text.
constexpr int kAutofillSuggestionMaxWidth = 192;

// Max lines for the AtMemory suggestion text.
constexpr int kAtMemorySuggestionMaxLines = 2;
// Max width for the AtMemory suggestion text.
constexpr int kAtMemorySuggestionWidth = 236;

// Multiline suggestions look crammed without extra vertical margin.
constexpr int kAutofillMultilineSuggestionAdditionalVerticalMargin = 8;

constexpr auto kMainTextStyle = views::style::TextStyle::STYLE_BODY_3_MEDIUM;
constexpr auto kMainTextStyleLight = views::style::TextStyle::STYLE_BODY_3;
constexpr auto kMainTextStyleHighlighted =
    views::style::TextStyle::STYLE_BODY_3_BOLD;
constexpr auto kMinorTextStyle = views::style::TextStyle::STYLE_BODY_4;
constexpr auto kDisabledTextStyle = views::style::TextStyle::STYLE_DISABLED;
constexpr float kDisabledBnplOpacity = 0.38f;

// Returns a wrapper around `closure` that posts it to the default message
// queue instead of executing it directly. This is to avoid that the callback's
// caller can suicide by (unwittingly) deleting itself or its parent.
base::RepeatingClosure CreateExecuteSoonWrapper(base::RepeatingClosure task) {
  return base::BindRepeating(
      [](base::RepeatingClosure delayed_task) {
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, std::move(delayed_task));
      },
      std::move(task));
}

bool IsDeactivatedPasswordOrPasskey(const Suggestion& suggestion) {
  switch (GetFillingProductFromSuggestionType(suggestion.type)) {
    case FillingProduct::kPassword:
    case FillingProduct::kPasskey:
      return !suggestion.IsSelectable();
    case FillingProduct::kAddress:
    case FillingProduct::kCreditCard:
    case FillingProduct::kIban:
    case FillingProduct::kAutocomplete:
    case FillingProduct::kMerchantPromoCode:
    case FillingProduct::kCompose:
    case FillingProduct::kAutofillAi:
    case FillingProduct::kLoyaltyCard:
    case FillingProduct::kIdentityCredential:
    case FillingProduct::kDataList:
    case FillingProduct::kOneTimePassword:
    case FillingProduct::kAtMemory:
    case FillingProduct::kNone:
      return false;
  }
}

// Used to check if a suggestion should be displayed with styling specific to
// deactivated BNPL suggestions.
// TODO(crbug.com/503847790): Consolidate all deactivated suggestion styles,
// pending alignment from UX.
bool IsDeactivatedBnplSuggestion(const Suggestion& suggestion) {
  return suggestion.type == SuggestionType::kBnplEntry &&
         !suggestion.IsSelectable();
}

std::unique_ptr<views::BoxLayoutView> GetAlternativePaymentMethodBadge(
    std::u16string_view label) {
  // 100 is guaranteed to be larger than half the height of this view. Using 100
  // as `corner_radius`, the view will be created with maximum possible radius
  // which is half the height.
  // TODO(crbug.com/507868678) calculate the precise `corner_radius` value
  // instead of using a hard coded large number.
  return views::Builder<views::BoxLayoutView>()
      .AddChildren(
          views::Builder<views::Label>()
              .SetText(std::u16string(label))
              .SetTextStyle(kPopupBadgeTextStyle)
              .SetBorder(views::CreateRoundedRectBorder(
                  /*thickness=*/0, /*corner_radius=*/100,
                  kPopupBadgeBorderInsets, ui::kColorSysNeutralContainer))
              .SetBackground(views::CreateRoundedRectBackground(
                  ui::kColorSysNeutralContainer, 100)))
      .Build();
}

void FormatLabel(views::Label& label,
                 const Suggestion::Text& text,
                 FillingProduct main_filling_product,
                 int maximum_width_single_line) {
  switch (main_filling_product) {
    case FillingProduct::kAddress:
    case FillingProduct::kAutocomplete:
    case FillingProduct::kAutofillAi:
    case FillingProduct::kLoyaltyCard:
    case FillingProduct::kIdentityCredential:
      label.SetMaximumWidthSingleLine(maximum_width_single_line);
      break;
    case FillingProduct::kCreditCard:
      if (text.should_truncate.value()) {
        label.SetMaximumWidthSingleLine(maximum_width_single_line);
      }
      break;
    case FillingProduct::kCompose:
    case FillingProduct::kIban:
    case FillingProduct::kMerchantPromoCode:
    case FillingProduct::kPasskey:
    case FillingProduct::kPassword:
    case FillingProduct::kDataList:
    case FillingProduct::kNone:
    case FillingProduct::kOneTimePassword:
    case FillingProduct::kAtMemory:
      break;
  }
}

// Creates a label for the suggestion's main text.
std::unique_ptr<views::Label> CreateMainTextLabel(
    const Suggestion& suggestion,
    std::optional<user_education::DisplayNewBadge> show_new_badge,
    views::style::TextStyle primary_text_style = kMainTextStyle) {
  views::style::TextStyle main_text_label_style;
  if (ShouldApplyDeactivatedStyle(suggestion)) {
    main_text_label_style = kDisabledTextStyle;
  } else {
    main_text_label_style = suggestion.main_text.is_primary
                                ? primary_text_style
                                : kMainTextStyleLight;
  }

  auto label = std::make_unique<user_education::NewBadgeLabel>(
      suggestion.main_text.value, views::style::CONTEXT_DIALOG_BODY_TEXT,
      main_text_label_style);
  if (show_new_badge.has_value()) {
    label->SetDisplayNewBadge(show_new_badge.value());
  }

  if (!suggestion.main_text.is_primary) {
    label->SetEnabledColor(ui::kColorLabelForegroundSecondary);
  }
  if (IsDeactivatedBnplSuggestion(suggestion)) {
    label->SetEnabledColor(kColorAutofillPopupDeactivatedBnplForeground);
  }

  return label;
}

// Creates a label for the suggestion's minor text.
std::vector<std::unique_ptr<views::View>> CreateMinorTextLabels(
    const Suggestion& suggestion) {
  std::vector<std::unique_ptr<views::View>> minor_text_labels;
  for (const Suggestion::Text& text : suggestion.minor_texts) {
    if (text.value.empty()) {
      continue;
    }
    auto label = std::make_unique<views::Label>(
        text.value, views::style::CONTEXT_DIALOG_BODY_TEXT,
        ShouldApplyDeactivatedStyle(suggestion) ? kDisabledTextStyle
                                                : kMinorTextStyle);
    label->SetEnabledColor(ui::kColorLabelForegroundSecondary);
    minor_text_labels.push_back(std::move(label));
  }
  return minor_text_labels;
}

// Creates sub-text views and passes their references to `PopupRowContentView`
// for centralized style management.
std::vector<std::unique_ptr<views::View>> CreateSubtextViews(
    PopupRowContentView& content_view,
    const Suggestion& suggestion,
    FillingProduct main_filling_product) {
  std::vector<std::unique_ptr<views::View>> result;
  // TODO(crbug.com/550237412): Maybe this can be applied to all the
  // suggestion types.
  const int between_child_spacing =
      suggestion.type == SuggestionType::kAtMemorySearchResult
          ? kAtMemoryLabelHorizontalSpacing
          : ChromeLayoutProvider::Get()->GetDistanceMetric(
                DISTANCE_RELATED_LABEL_HORIZONTAL_LIST);

  for (const std::vector<Suggestion::Text>& label_row : suggestion.labels) {
    if (std::ranges::all_of(label_row, &std::u16string::empty,
                            &Suggestion::Text::value)) {
      // If a row is empty, do not include any further rows.
      return result;
    }

    auto label_row_container_view =
        views::Builder<views::BoxLayoutView>()
            .SetOrientation(views::BoxLayout::Orientation::kHorizontal)
            .SetBetweenChildSpacing(between_child_spacing)
            .Build();
    for (const Suggestion::Text& label_text : label_row) {
      // If a column is empty, do not include any further columns.
      if (label_text.value.empty()) {
        break;
      }
      auto* label =
          label_row_container_view->AddChildView(std::make_unique<views::Label>(
              label_text.value,
              ChromeTextContext::CONTEXT_DIALOG_BODY_TEXT_SMALL,
              IsDeactivatedPasswordOrPasskey(suggestion) ? kDisabledTextStyle
                                                         : kMinorTextStyle));
      if (!IsDeactivatedPasswordOrPasskey(suggestion)) {
        label->SetEnabledColor(ui::kColorLabelForegroundSecondary);
      }
      if (IsDeactivatedBnplSuggestion(suggestion)) {
        label->SetEnabledColor(kColorAutofillPopupDeactivatedBnplForeground);
      }
      // To make sure the popup width will not exceed its maximum value,
      // divide the maximum label width by the number of labels.
      FormatLabel(*label, label_text, main_filling_product,
                  kAutofillSuggestionMaxWidth / label_row.size());
    }
    result.push_back(std::move(label_row_container_view));
  }

  return result;
}

std::unique_ptr<PopupRowContentView> CreateFooterPopupRowContentView(
    const Suggestion& suggestion) {
  auto view = std::make_unique<PopupRowContentView>();

  std::unique_ptr<views::ImageView> icon =
      popup_cell_utils::GetIconImageView(suggestion);

  const bool kUseLeadingIcon =
      kPopupItemTypesUsingLeadingIcons.contains(suggestion.type);

  if (suggestion.is_loading) {
    view->AddChildView(std::make_unique<views::Throbber>())->Start();
    popup_cell_utils::AddSpacerWithSize(*view,
                                        PopupBaseView::ArrowHorizontalMargin(),
                                        /*resize=*/false);
  } else if (icon && kUseLeadingIcon) {
    view->AddChildView(std::move(icon));
    popup_cell_utils::AddSpacerWithSize(*view,
                                        PopupBaseView::ArrowHorizontalMargin(),
                                        /*resize=*/false);
  }

  view->SetMinimumCrossAxisSize(
      views::MenuConfig::instance().touchable_menu_height);

  std::unique_ptr<views::Label> main_text_label = CreateMainTextLabel(
      suggestion, /*show_new_badge=*/std::nullopt, kMainTextStyleLight);
  // TODO(crbug.com/345709988): Move this to CreateMainTextLabel. See
  // https://crrev.com/c/5605735/comment/970405c2_cbb55e85
  if (suggestion.IsSelectable()) {
    main_text_label->SetEnabledColor(ui::kColorLabelForegroundSecondary);
  }
  main_text_label->SetEnabled(!suggestion.is_loading);

  if (suggestion.type == SuggestionType::kPendingStateSignin ||
      suggestion.type == SuggestionType::kFreeformFooter ||
      suggestion.type == SuggestionType::kAutofillAiSourceAttribution) {
    main_text_label->SetMultiLine(true);
    main_text_label->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
    view->SetInsideBorderInsets(
        gfx::Insets(view->GetInsideBorderInsets())
            .set_top_bottom(
                kAutofillMultilineSuggestionAdditionalVerticalMargin,
                kAutofillMultilineSuggestionAdditionalVerticalMargin));
  } else if (suggestion.type == SuggestionType::kRemoveAutofillAi) {
    main_text_label->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
  }

  view->AddChildView(std::move(main_text_label));

  popup_cell_utils::AddSpacerWithSize(*view,
                                      /*spacer_width=*/0,
                                      /*resize=*/true);

  if (icon && !kUseLeadingIcon) {
    popup_cell_utils::AddSpacerWithSize(*view,
                                        PopupBaseView::ArrowHorizontalMargin(),
                                        /*resize=*/false);
    view->AddChildView(std::move(icon));
  }

  std::unique_ptr<views::ImageView> trailing_icon =
      popup_cell_utils::GetTrailingIconImageView(suggestion);
  if (trailing_icon) {
    popup_cell_utils::AddSpacerWithSize(*view,
                                        PopupBaseView::ArrowHorizontalMargin(),
                                        /*resize=*/true);
    view->AddChildView(std::move(trailing_icon));
  }

  // Force a refresh to ensure all the labels' styles are correct.
  view->UpdateStyle(/*selected=*/false);

  return view;
}

std::unique_ptr<PopupRowContentView> CreateSaveAndFillRowContentView(
    const Suggestion& suggestion) {
  auto view = std::make_unique<PopupRowContentView>();

  view->AddChildView(popup_cell_utils::GetIconImageView(suggestion));
  popup_cell_utils::AddSpacerWithSize(
      *view, PopupBaseView::ArrowHorizontalMargin(), /*resize=*/false);

  auto* text_container =
      view->AddChildView(std::make_unique<views::BoxLayoutView>());
  text_container->SetOrientation(views::BoxLayout::Orientation::kVertical);
  text_container->SetCrossAxisAlignment(
      views::BoxLayout::CrossAxisAlignment::kStart);
  text_container->SetInsideBorderInsets(
      gfx::Insets(view->GetInsideBorderInsets())
          .set_top_bottom(
              kAutofillMultilineSuggestionAdditionalVerticalMargin,
              kAutofillMultilineSuggestionAdditionalVerticalMargin));

  text_container->AddChildView(
      CreateMainTextLabel(suggestion, /*show_new_badge=*/std::nullopt));

  auto* description_label =
      text_container->AddChildView(std::make_unique<views::Label>(
          suggestion.labels[0][0].value, views::style::CONTEXT_DIALOG_BODY_TEXT,
          kMinorTextStyle));
  description_label->SetMultiLine(true);
  description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  description_label->SetEnabledColor(ui::kColorLabelForegroundSecondary);
  return view;
}

std::unique_ptr<views::Label> CreatePasswordDescriptionLabel(
    const Suggestion& suggestion) {
  if (suggestion.additional_label.empty()) {
    return nullptr;
  }
  auto label = std::make_unique<views::Label>(
      suggestion.additional_label, views::style::CONTEXT_DIALOG_BODY_TEXT,
      views::style::STYLE_SECONDARY);
  label->SetElideBehavior(gfx::ELIDE_HEAD);
  label->SetMaximumWidthSingleLine(kAutofillPopupUsernameMaxWidth);
  return label;
}

std::unique_ptr<views::View> CreatePasswordSubtextView(
    const Suggestion& suggestion) {
  CHECK_EQ(suggestion.labels.size(), 1u);
  CHECK_EQ(suggestion.labels[0].size(), 1u);

  const auto& label = suggestion.labels[0][0].value;
  auto label_view = std::make_unique<views::Label>(
      label, views::style::CONTEXT_DIALOG_BODY_TEXT,
      IsDeactivatedPasswordOrPasskey(suggestion)
          ? kDisabledTextStyle
          : views::style::STYLE_SECONDARY);
  // Password labels are obfuscated using password replacement character. Manual
  // fallback suggestions display credential username, which is not obfuscated
  // and should not be truncated.
  const bool is_password_label =
      label.find_first_not_of(
          password_manager::constants::kPasswordReplacementChar) ==
      std::u16string::npos;
  if (is_password_label) {
    label_view->SetElideBehavior(gfx::TRUNCATE);
    label_view->SetMaximumWidthSingleLine(kAutofillPopupPasswordMaxWidth);
  } else {
    label_view->SetElideBehavior(gfx::ELIDE_HEAD);
    label_view->SetMaximumWidthSingleLine(kAutofillPopupUsernameMaxWidth);
  }
  return label_view;
}

// If the `Suggestion::custom_icon` holds the `FaviconDetails` alternative,
// the icon should be loaded lazily. For this case, this method creates a
// `LazyLoadingImageView` to be passed to `CreatePasswordPopupRowContentView()`
// as the `icon`. Otherwise, it returns an icon created by `GetIconImageView()`.
std::unique_ptr<views::View> GetPasswordIconView(
    const Suggestion& suggestion,
    PasswordFaviconLoader* favicon_loader) {
  if (!std::holds_alternative<Suggestion::FaviconDetails>(
          suggestion.custom_icon)) {
    return popup_cell_utils::GetIconImageView(suggestion);
  }

  CHECK(favicon_loader);
  std::optional<ui::ImageModel> suggestion_icon_model =
      popup_cell_utils::GetIconImageModelFromIcon(suggestion.icon);
  ui::ImageModel placeholder_icon =
      suggestion_icon_model
          ? std::move(*suggestion_icon_model)
          : popup_cell_utils::ImageModelFromVectorIcon(
                ::features::IsRoundedIconsEnabled() ? kGlobeIcon
                                                    : kGlobeOldIcon,
                kCustomIconSize);

  return std::make_unique<LazyLoadingImageView>(
      gfx::Size(kCustomIconSize, kCustomIconSize), std::move(placeholder_icon),
      base::BindOnce(
          &PasswordFaviconLoader::Load, base::Unretained(favicon_loader),
          std::get<Suggestion::FaviconDetails>(suggestion.custom_icon)));
}

std::unique_ptr<PopupRowContentView> CreatePasswordPopupRowContentView(
    const Suggestion& suggestion,
    std::optional<user_education::DisplayNewBadge> show_new_badge,
    std::optional<AutofillPopupController::SuggestionFilterMatch> filter_match,
    PasswordFaviconLoader* favicon_loader) {
  auto view = std::make_unique<PopupRowContentView>();

  // Add the actual views.
  std::unique_ptr<views::Label> main_text_label =
      CreateMainTextLabel(suggestion, show_new_badge);
  main_text_label->SetMaximumWidthSingleLine(kAutofillPopupUsernameMaxWidth);
  if (filter_match) {
    main_text_label->SetTextStyleRange(kMainTextStyleHighlighted,
                                       filter_match->main_text_match);
  }

  std::vector<std::unique_ptr<views::View>> subtext_views;
  if (!suggestion.labels.empty()) {
    subtext_views.push_back(CreatePasswordSubtextView(suggestion));
  }
  popup_cell_utils::AddSuggestionContentToView(
      suggestion, std::move(main_text_label), CreateMinorTextLabels(suggestion),
      CreatePasswordDescriptionLabel(suggestion), std::move(subtext_views),
      GetPasswordIconView(suggestion, favicon_loader), *view);

  return view;
}

std::unique_ptr<PopupRowContentView> CreateComposePopupRowContentView(
    const Suggestion& suggestion,
    std::optional<user_education::DisplayNewBadge> show_new_badge) {
  auto view = std::make_unique<PopupRowContentView>();
  auto main_text_label = std::make_unique<user_education::NewBadgeLabel>(
      suggestion.main_text.value, views::style::CONTEXT_DIALOG_BODY_TEXT,
      views::style::STYLE_BODY_3_MEDIUM);
  if (show_new_badge.has_value()) {
    main_text_label->SetDisplayNewBadge(show_new_badge.value());
    main_text_label->SetPadAfterNewBadge(false);
  }
  popup_cell_utils::AddSuggestionContentToView(
      suggestion, std::move(main_text_label),
      /*minor_text_labels=*/{},
      /*description_label=*/nullptr, /*subtext_views=*/
      CreateSubtextViews(*view, suggestion, FillingProduct::kCompose),
      popup_cell_utils::GetIconImageView(suggestion), *view);

  return view;
}

std::unique_ptr<PopupRowContentView> CreateBnplPopupRowContentView(
    const Suggestion& suggestion,
    FillingProduct main_filling_product) {
  std::unique_ptr<views::Label> main_text_label =
      CreateMainTextLabel(suggestion, /*show_new_badge=*/std::nullopt);
  FormatLabel(*main_text_label, suggestion.main_text, main_filling_product,
              kAutofillSuggestionMaxWidth);

  // If the BNPL issuer is linked, add `BnplLinkedIssuerPill` to minor texts so
  // that it appears on the first line of the suggestion with the BNPL issuer
  // name.
  std::vector<std::unique_ptr<views::View>> minor_texts;
  if (payments::ShouldShowBnplLinkedPill(suggestion)) {
    minor_texts.reserve(2);

    // Add spacer to ensure the linked pill appears on the right side of the
    // first line of the suggestion.
    auto spacer = std::make_unique<views::View>();
    spacer->SetProperty(
        views::kFlexBehaviorKey,
        views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
                                 views::MaximumFlexSizeRule::kUnbounded)
            .WithOrder(2));
    minor_texts.push_back(std::move(spacer));

    auto linked_pill = std::make_unique<payments::BnplLinkedIssuerPill>();
    linked_pill->SetEnabled(suggestion.IsSelectable());
    minor_texts.push_back(std::move(linked_pill));
  }

  auto view = std::make_unique<PopupRowContentView>();

  std::vector<std::unique_ptr<views::View>> subtexts =
      CreateSubtextViews(*view, suggestion, main_filling_product);

  std::unique_ptr<views::ImageView> icon =
      popup_cell_utils::GetIconImageView(suggestion);
  if (!suggestion.IsSelectable()) {
    if (icon) {
      icon->SetPaintToLayer();
      icon->layer()->SetFillsBoundsOpaquely(false);
      icon->layer()->SetOpacity(kDisabledBnplOpacity);
    }
  }

  popup_cell_utils::AddSuggestionContentToView(
      suggestion, std::move(main_text_label), std::move(minor_texts),
      /*description_label=*/nullptr, std::move(subtexts), std::move(icon),
      *view);

  return view;
}

// Creates the row for an Autocomplete entry with a delete button.
std::unique_ptr<PopupRowWithButtonView> CreateAutocompleteRowWithDeleteButton(
    base::WeakPtr<AutofillPopupController> controller,
    PopupRowView::AccessibilitySelectionDelegate& a11y_selection_delegate,
    PopupRowView::SelectionDelegate& selection_delegate,
    int line_number) {
  auto view = std::make_unique<PopupRowContentView>();

  const Suggestion& suggestion = controller->GetSuggestionAt(line_number);
  std::unique_ptr<views::Label> main_text_label =
      CreateMainTextLabel(suggestion, /*show_new_badge=*/std::nullopt);
  FormatLabel(*main_text_label, suggestion.main_text,
              FillingProduct::kAutocomplete, kAutofillSuggestionMaxWidth);
  popup_cell_utils::AddSuggestionContentToView(
      suggestion, std::move(main_text_label), CreateMinorTextLabels(suggestion),
      /*description_label=*/nullptr,
      CreateSubtextViews(*view, suggestion, FillingProduct::kAutocomplete),
      popup_cell_utils::GetIconImageView(suggestion), *view);

  // The closure that actually attempts to delete an entry and record metrics
  // for it.
  base::RepeatingClosure deletion_action = base::BindRepeating(
      base::IgnoreResult(&AutofillPopupController::RemoveSuggestion),
      controller, line_number,
      AutofillMetrics::SingleEntryRemovalMethod::kDeleteButtonClicked);
  std::unique_ptr<views::ImageButton> button =
      views::CreateVectorImageButtonWithNativeTheme(
          CreateExecuteSoonWrapper(std::move(deletion_action)),
          ::features::IsRoundedIconsEnabled() ? views::kCloseIcon
                                              : views::kIcCloseOldIcon,
          kCloseIconSize);

  // We are making sure that the vertical distance from the delete button edges
  // to the cell border is the same as the horizontal distance.
  // 1. Take the current horizontal distance.
  int horizontal_margin = view->GetInsideBorderInsets().right();
  // 2. Take the height of the cell.
  int cell_height = view->GetMinimumCrossAxisSize();
  // 3. The diameter needs to be the height - 2 * the desired margin.
  int radius = (cell_height - horizontal_margin * 2) / 2;
  views::InstallFixedSizeCircleHighlightPathGenerator(button.get(), radius);
  button->SetPreferredSize(gfx::Size(radius * 2, radius * 2));
  button->SetTooltipText(l10n_util::GetStringUTF16(
      IDS_AUTOFILL_DELETE_AUTOCOMPLETE_SUGGESTION_TOOLTIP));
  button->GetViewAccessibility().SetRole(ax::mojom::Role::kMenuItem);
  button->GetViewAccessibility().SetName(l10n_util::GetStringFUTF16(
      IDS_AUTOFILL_DELETE_AUTOCOMPLETE_SUGGESTION_A11Y_HINT,
      popup_cell_utils::GetVoiceOverStringFromSuggestion(
          controller->GetSuggestionAt(line_number))));
  button->SetVisible(false);

  return std::make_unique<PopupRowWithButtonView>(
      a11y_selection_delegate, selection_delegate, controller, line_number,
      std::move(view), std::move(button),
      PopupRowWithButtonView::ButtonVisibility::kShowOnHoverOrSelect,
      PopupRowWithButtonView::ButtonSelectBehavior::kUnselectSuggestion);
}

// Checks if the text of a Label fits in one line.
// Assumes that a non-empty text has been set on a label before calling this
// function.
[[nodiscard]] bool FitsInOneLine(const views::Label& label) {
  CHECK(!label.GetText().empty());
  const int line_height = label.GetLineHeight();
  CHECK(line_height > 0);
  return label.GetHeightForWidth(kAtMemorySuggestionWidth) == line_height;
}

std::unique_ptr<PopupRowContentView>
CreateAtMemorySearchResultPopupRowContentView(
    const Suggestion& suggestion,
    std::optional<user_education::DisplayNewBadge> show_new_badge,
    FillingProduct main_filling_product) {
  std::unique_ptr<PopupRowContentView> view =
      std::make_unique<PopupRowContentView>();
  std::unique_ptr<views::Label> main_text_label =
      CreateMainTextLabel(suggestion, show_new_badge);

  main_text_label->SetMultiLine(true);
  main_text_label->SetMaxLines(kAtMemorySuggestionMaxLines);
  main_text_label->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
  main_text_label->SetMaximumWidth(kAtMemorySuggestionWidth);

  const bool main_label_fits_in_one_line = FitsInOneLine(*main_text_label);

  popup_cell_utils::AddSuggestionContentToView(
      suggestion, std::move(main_text_label), CreateMinorTextLabels(suggestion),
      /*description_label=*/nullptr,
      CreateSubtextViews(*view, suggestion, main_filling_product),
      popup_cell_utils::GetIconImageView(suggestion), *view);

  if (!main_label_fits_in_one_line) {
    view->SetInsideBorderInsets(
        gfx::Insets(view->GetInsideBorderInsets())
            .set_top_bottom(
                kAutofillMultilineSuggestionAdditionalVerticalMargin,
                kAutofillMultilineSuggestionAdditionalVerticalMargin));
  }

  return view;
}

}  // namespace

std::unique_ptr<PopupRowContentView> CreatePopupRowContentView(
    const Suggestion& suggestion,
    std::optional<user_education::DisplayNewBadge> show_new_badge,
    FillingProduct main_filling_product,
    std::optional<AutofillPopupController::SuggestionFilterMatch>
        filter_match) {
  auto view = std::make_unique<PopupRowContentView>();
  std::unique_ptr<views::Label> main_text_label =
      CreateMainTextLabel(suggestion, show_new_badge);
  if (filter_match) {
    main_text_label->SetTextStyleRange(kMainTextStyleHighlighted,
                                       filter_match->main_text_match);
  }

  FormatLabel(*main_text_label, suggestion.main_text, main_filling_product,
              kAutofillSuggestionMaxWidth);
  popup_cell_utils::AddSuggestionContentToView(
      suggestion, std::move(main_text_label), CreateMinorTextLabels(suggestion),
      /*description_label=*/nullptr,
      CreateSubtextViews(*view, suggestion, main_filling_product),
      popup_cell_utils::GetIconImageView(suggestion), *view);
  return view;
}

std::unique_ptr<PopupRowContentView>
CreateAlternativePaymentMethodPopupRowContentView(
    const Suggestion& suggestion,
    std::optional<user_education::DisplayNewBadge> show_new_badge,
    FillingProduct main_filling_product,
    std::optional<AutofillPopupController::SuggestionFilterMatch>
        filter_match) {
  auto view = std::make_unique<PopupRowContentView>();
  std::unique_ptr<views::Label> main_text_label =
      CreateMainTextLabel(suggestion, show_new_badge);
  if (filter_match) {
    main_text_label->SetTextStyleRange(kMainTextStyleHighlighted,
                                       filter_match->main_text_match);
  }

  FormatLabel(*main_text_label, suggestion.main_text, main_filling_product,
              kAutofillSuggestionMaxWidth);
  std::vector<std::unique_ptr<views::View>> minor_labels =
      CreateMinorTextLabels(suggestion);

  std::unique_ptr<views::BoxLayoutView> badge_view =
      GetAlternativePaymentMethodBadge(l10n_util::GetStringUTF16(
          suggestion.type == SuggestionType::kVirtualCreditCardEntry
              ? IDS_AUTOFILL_VIRTUAL_CARD_SUGGESTION_OPTION_VALUE
              : IDS_AUTOFILL_IBAN_SUGGESTION_OPTION_VALUE));

  std::vector<std::unique_ptr<views::View>> subtext_views =
      CreateSubtextViews(*view, suggestion, main_filling_product);
  if (suggestion.type == SuggestionType::kVirtualCreditCardEntry &&
      !minor_labels.empty()) {
    // If this is a virtual card suggestion with non-empty minor_labels,
    // it means that the minor_text represents expiration date and should be
    // replaced with a badge label.
    minor_labels.clear();
    minor_labels.push_back(std::move(badge_view));
  } else if (suggestion.type == SuggestionType::kIbanEntry &&
             subtext_views.empty()) {
    // If this is an IBAN suggestion with an empty subtext_views,
    // it means that the main_text represents IBAN's masked value and a
    // badge label should be put as the minor_text.
    minor_labels.clear();
    minor_labels.push_back(std::move(badge_view));
  } else if (!subtext_views.empty()) {
    views::View* layout_view = subtext_views.front().get();
    layout_view->AddChildView(std::move(badge_view));
  }

  popup_cell_utils::AddSuggestionContentToView(
      suggestion, std::move(main_text_label), std::move(minor_labels),
      /*description_label=*/nullptr, std::move(subtext_views),
      popup_cell_utils::GetIconImageView(suggestion), *view);
  return view;
}

std::unique_ptr<PopupRowView> CreatePopupRowView(
    base::WeakPtr<AutofillPopupController> controller,
    PopupRowView::AccessibilitySelectionDelegate& a11y_selection_delegate,
    PopupRowView::SelectionDelegate& selection_delegate,
    int line_number,
    std::optional<AutofillPopupController::SuggestionFilterMatch> filter_match,
    PasswordFaviconLoader* favicon_loader) {
  CHECK(controller);

  const Suggestion& suggestion = controller->GetSuggestionAt(line_number);
  SuggestionType type = suggestion.type;
  FillingProduct main_filling_product = controller->GetMainFillingProduct();

  if (type == SuggestionType::kAutocompleteEntry) {
    return CreateAutocompleteRowWithDeleteButton(
        controller, a11y_selection_delegate, selection_delegate, line_number);
  }

  if (IsFooterSuggestionType(type)) {
    return std::make_unique<PopupRowView>(
        a11y_selection_delegate, selection_delegate, controller, line_number,
        CreateFooterPopupRowContentView(suggestion));
  }

  const auto show_new_badge =
      suggestion.feature_for_new_badge
          ? std::optional<user_education::DisplayNewBadge>(
                UserEducationService::MaybeShowNewBadge(
                    controller->GetWebContents()->GetBrowserContext(),
                    *suggestion.feature_for_new_badge))
          : std::nullopt;

  switch (type) {
    // These `type` should never be displayed in a `PopupRowView`.
    case SuggestionType::kAtMemoryAiDisclosure:
    case SuggestionType::kInsecureContextPaymentDisabledMessage:
    case SuggestionType::kSeparator:
      NOTREACHED();
    case SuggestionType::kWebauthnPasskeyQrCode:
      return std::make_unique<PopupRowView>(
          a11y_selection_delegate, selection_delegate, controller, line_number,
          popup_cell_utils::CreatePasskeyQrCodePopupRowContentView(suggestion));
    case SuggestionType::kAccountStoragePasswordEntry:
    case SuggestionType::kBackupPasswordEntry:
    case SuggestionType::kPasswordEntry:
    case SuggestionType::kTroubleSigningInEntry:
      return std::make_unique<PopupRowView>(
          a11y_selection_delegate, selection_delegate, controller, line_number,
          CreatePasswordPopupRowContentView(suggestion, show_new_badge,
                                            std::move(filter_match),
                                            favicon_loader));
    case SuggestionType::kSaveAndFillCreditCardEntry: {
      return std::make_unique<PopupRowView>(
          a11y_selection_delegate, selection_delegate, controller, line_number,
          CreateSaveAndFillRowContentView(suggestion));
    }
    case SuggestionType::kComposeResumeNudge:
    case SuggestionType::kComposeSavedStateNotification: {
      return std::make_unique<PopupRowView>(
          a11y_selection_delegate, selection_delegate, controller, line_number,
          CreateComposePopupRowContentView(suggestion, show_new_badge));
    }
    case SuggestionType::kComposeProactiveNudge: {
      return std::make_unique<PopupRowView>(
          a11y_selection_delegate, selection_delegate, controller, line_number,
          CreateComposePopupRowContentView(suggestion, show_new_badge));
    }
    case SuggestionType::kIbanEntry:
    case SuggestionType::kVirtualCreditCardEntry: {
      return std::make_unique<PopupRowView>(
          a11y_selection_delegate, selection_delegate, controller, line_number,
          CreateAlternativePaymentMethodPopupRowContentView(
              suggestion, show_new_badge, main_filling_product,
              std::move(filter_match)));
    }
    case SuggestionType::kBnplEntry: {
      if (base::FeatureList::IsEnabled(
              features::kAutofillEnablePayNowPayLaterTabs)) {
        return std::make_unique<PopupRowView>(
            a11y_selection_delegate, selection_delegate, controller,
            line_number,
            CreateBnplPopupRowContentView(suggestion, main_filling_product));
      }
      // If flag is disabled, fall-through to default to create the generic
      // BNPL suggestion.
      [[fallthrough]];
    }
    case SuggestionType::kAtMemorySearchResult: {
      return std::make_unique<PopupRowView>(
          a11y_selection_delegate, selection_delegate, controller, line_number,
          CreateAtMemorySearchResultPopupRowContentView(
              suggestion, show_new_badge, main_filling_product));
    }
    // AtMemory suggestions do not apply filter match bolding to the main text.
    case SuggestionType::kAtMemoryGenericError:
    case SuggestionType::kAtMemoryFetching:
    case SuggestionType::kAtMemoryInactivityNudge:
    case SuggestionType::kAtMemoryNoConnection:
    case SuggestionType::kAtMemoryOpenGemini:
    case SuggestionType::kAtMemorySearchAffordance:
    case SuggestionType::kAtMemorySourceAttribution: {
      return std::make_unique<PopupRowView>(
          a11y_selection_delegate, selection_delegate, controller, line_number,
          CreatePopupRowContentView(suggestion, show_new_badge,
                                    main_filling_product,
                                    /*filter_match=*/std::nullopt));
    }

    case SuggestionType::kAddressEntry:
    case SuggestionType::kAddressEntryOnTyping:
    case SuggestionType::kAddressFieldByFieldFilling:
    case SuggestionType::kAllLoyaltyCardsEntry:
    case SuggestionType::kAllSavedPasswordsEntry:
    case SuggestionType::kAutocompleteAtMemoryButton:
    case SuggestionType::kAutocompleteEntry:
    case SuggestionType::kAutofillAiOtherOrders:
    case SuggestionType::kAutofillAiOtherShipments:
    case SuggestionType::kAutofillAiPrivateInferenceNotice:
    case SuggestionType::kAutofillAiSourceAttribution:
    case SuggestionType::kBnplFootnote:
    case SuggestionType::kComposeDisable:
    case SuggestionType::kComposeGoToSettings:
    case SuggestionType::kComposeNeverShowOnThisSiteAgain:
    case SuggestionType::kCreditCardEntry:
    case SuggestionType::kDatalistEntry:
    case SuggestionType::kDevtoolsTestAddressByCountry:
    case SuggestionType::kDevtoolsTestAddressEntry:
    case SuggestionType::kDevtoolsTestAddresses:
    case SuggestionType::kFetchingAmbientData:
    case SuggestionType::kFillAutofillAi:
    case SuggestionType::kFillPassword:
    case SuggestionType::kFreeformFooter:
    case SuggestionType::kGeneratePasswordEntry:
    case SuggestionType::kIdentityCredential:
    case SuggestionType::kLoadingThrobber:
    case SuggestionType::kLoyaltyCardEntry:
    case SuggestionType::kManageAddress:
    case SuggestionType::kManageAutofillAi:
    case SuggestionType::kManageAutofillAiIdentityDocs:
    case SuggestionType::kManageAutofillAiShopping:
    case SuggestionType::kManageAutofillAiTravel:
    case SuggestionType::kManageCreditCard:
    case SuggestionType::kManageEnhancedAutofill:
    case SuggestionType::kManageIban:
    case SuggestionType::kManageLoyaltyCard:
    case SuggestionType::kMaximizeCreditCardBenefitsEntry:
    case SuggestionType::kMerchantPromoCodeEntry:
    case SuggestionType::kOneTimePasswordEntry:
    case SuggestionType::kPasswordFieldByFieldFilling:
    case SuggestionType::kPendingStateSignin:
    case SuggestionType::kPersonalContextNotice:
    case SuggestionType::kRemoveAutofillAi:
    case SuggestionType::kScanCreditCard:
    case SuggestionType::kSeePromoCodeDetails:
    case SuggestionType::kTitle:
    case SuggestionType::kUndo:
    case SuggestionType::kViewPasswordDetails:
    case SuggestionType::kWebauthnCredential:
    case SuggestionType::kWebauthnSignInWithAnotherDevice:
      return std::make_unique<PopupRowView>(
          a11y_selection_delegate, selection_delegate, controller, line_number,
          CreatePopupRowContentView(suggestion, show_new_badge,
                                    main_filling_product,
                                    std::move(filter_match)));
  }
}

}  // namespace autofill
