// 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_cell_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/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "build/branding_buildflags.h"
#include "cc/paint/skia_paint_canvas.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/autofill/autofill_suggestion_controller_utils.h"
#include "chrome/browser/ui/passwords/ui_utils.h"
#include "chrome/browser/ui/views/autofill/payments/payments_view_util.h"
#include "chrome/browser/ui/views/autofill/popup/popup_base_view.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_view_utils.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/webid/identity_ui_utils.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/platform_locale_settings.h"
#include "components/autofill/core/browser/filling/filling_product.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/browser/ui/autofill_resource_utils.h"
#include "components/omnibox/browser/vector_icons.h"
#include "components/qr_code_generator/bitmap_generator.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/base/models/image_model_utils.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider_manager.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/monogram_utils.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.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/layout/flex_layout.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/layout/table_layout.h"
#include "ui/views/layout/table_layout_view.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"

namespace autofill::popup_cell_utils {

namespace {

// The default icon size used in the suggestion drop down.
constexpr int kIconSize = 16;
constexpr int kPersonCheckIconSize = 20;
constexpr int kRecoveryPasswordIconSize = 20;
constexpr int kChromeRefreshIconSize = 20;
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
constexpr int kGooglePayLogoWidth = 40;
// The icon size used in the suggestion dropdown for displaying the Google
// Wallet icon in the "Manage loyalty cards" entry and in all loyalty cards
// submenu entries.
constexpr int kGoogleWalletIconSize = 20;
#endif

// The additional height of the row in case it has two lines of text.
constexpr int kAutofillPopupAdditionalDoubleRowHeight = 16;

// The additional height of the row in case it has three lines of text.
constexpr int kAutofillPopupAdditionalTripleRowHeight = 24;

// The additional padding of the row in case it has three lines of text.
constexpr int kAutofillPopupAdditionalVerticalPadding = 16;

// The additional padding of the row in case it is an Autofill AI suggestion
// and has three lines of text.
constexpr int kAutofillAiPopupAdditionalVerticalPadding = 8;

// The additional right padding of the row in case it is an Autofill AI
// suggestion and has three lines of text.
constexpr int kAutofillAiPopupAdditionalRightPadding = 8;

// Vertical spacing between labels in one row.
constexpr int kAdjacentLabelsVerticalSpacing = 2;

// The icon size used in the suggestion dropdown for displaying the Google
// Password Manager icon in the Manager Passwords entry.
constexpr int kGooglePasswordManagerIconSize = 20;

// Metric to measure the duration of getting the image for the Autofill pop-up.
constexpr char kHistogramGetImageViewByName[] =
    "Autofill.PopupGetImageViewTime";

// The monochrome icon size used when rendering letter monochrome icons.
constexpr int kMonochromeIconSize = 24;

// The background color of the letter monochrome icons.
constexpr SkColor kMonochromeIconBgColor = SkColorSetARGB(255, 237, 242, 250);

// The text color of the letter monochrome icons.
constexpr SkColor kMonochromeIconTextColor = SkColorSetARGB(255, 71, 71, 71);

// Returns the name of the network for payment method icons, for home/work
// address a11y labels and empty string otherwise.
std::u16string GetIconAccessibleName(Suggestion::Icon icon) {
  switch (icon) {
    // kNoIcon is kept at the top of the list.
    case Suggestion::Icon::kNoIcon:
      return std::u16string();

    // 1P Google services start
    case Suggestion::Icon::kGmail:
    case Suggestion::Icon::kGoogleCalendar:
    case Suggestion::Icon::kGooglePhotos:
      return std::u16string();
    // 1P Google services end

    // Address profile icons start
    case Suggestion::Icon::kHome:
      return l10n_util::GetStringUTF16(
          IDS_AUTOFILL_HOME_PROFILE_ICON_ACCESSIBILITY_LABEL);
    case Suggestion::Icon::kWork:
      return l10n_util::GetStringUTF16(
          IDS_AUTOFILL_WORK_PROFILE_ICON_ACCESSIBILITY_LABEL);
    // Address profile icons end

    // Generic icons start
    case Suggestion::Icon::kAccount:
    case Suggestion::Icon::kAndroidMessages:
    case Suggestion::Icon::kClose:
    case Suggestion::Icon::kCode:
    case Suggestion::Icon::kDelete:
    case Suggestion::Icon::kDevice:
    case Suggestion::Icon::kEdit:
    case Suggestion::Icon::kEmail:
    case Suggestion::Icon::kError:
    case Suggestion::Icon::kFlight:
    case Suggestion::Icon::kFlightSpark:
    case Suggestion::Icon::kGlobe:
    case Suggestion::Icon::kGoogle:
    case Suggestion::Icon::kGoogleMonochrome:
    case Suggestion::Icon::kGooglePasswordManager:
    case Suggestion::Icon::kGooglePay:
    case Suggestion::Icon::kGoogleWallet:
    case Suggestion::Icon::kGoogleWalletMonochrome:
    case Suggestion::Icon::kIdCard:
    case Suggestion::Icon::kIdCard2:
    case Suggestion::Icon::kIdCard2Spark:
    case Suggestion::Icon::kIdCardSpark:
    case Suggestion::Icon::kKey:
    case Suggestion::Icon::kLocation:
    case Suggestion::Icon::kLocationSpark:
    case Suggestion::Icon::kLoyalty:
    case Suggestion::Icon::kMagic:
    case Suggestion::Icon::kOfferTag:
    case Suggestion::Icon::kOrder:
    case Suggestion::Icon::kOrderSpark:
    case Suggestion::Icon::kPassport:
    case Suggestion::Icon::kPassportSpark:
    case Suggestion::Icon::kPenSpark:
    case Suggestion::Icon::kPersonCheck:
    case Suggestion::Icon::kQuestionMark:
    case Suggestion::Icon::kRecoveryPassword:
    case Suggestion::Icon::kSadTab:
    case Suggestion::Icon::kScanCreditCard:
    case Suggestion::Icon::kSettings:
    case Suggestion::Icon::kShipment:
    case Suggestion::Icon::kShipmentSpark:
    case Suggestion::Icon::kSpark:
    case Suggestion::Icon::kTextSpark:
    case Suggestion::Icon::kUndo:
    case Suggestion::Icon::kVehicle:
    case Suggestion::Icon::kVehicleSpark:
      return std::u16string();
    // Generic icons end

    // Payment method icons start
    // Credit card networks.
    case Suggestion::Icon::kCardAmericanExpress:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_AMEX);
    case Suggestion::Icon::kCardDiners:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DINERS);
    case Suggestion::Icon::kCardDiscover:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DISCOVER);
    case Suggestion::Icon::kCardElo:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_ELO);
    case Suggestion::Icon::kCardJCB:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_JCB);
    case Suggestion::Icon::kCardMasterCard:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_MASTERCARD);
    case Suggestion::Icon::kCardMir:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_MIR);
    case Suggestion::Icon::kCardTroy:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_TROY);
    case Suggestion::Icon::kCardUnionPay:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_UNION_PAY);
    case Suggestion::Icon::kCardVerve:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_VERVE);
    case Suggestion::Icon::kCardVisa:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_VISA);
    // Other networks.
    case Suggestion::Icon::kCardGeneric:
    case Suggestion::Icon::kCardGenericSpark:
    case Suggestion::Icon::kCardGenericVector:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_GENERIC);
    case Suggestion::Icon::kIban:
      return l10n_util::GetStringUTF16(IDS_AUTOFILL_IBAN_GENERIC);
    case Suggestion::Icon::kBnplAffirm:
    case Suggestion::Icon::kBnplAfterpay:
    case Suggestion::Icon::kBnplGeneric:
    case Suggestion::Icon::kBnplKlarna:
    case Suggestion::Icon::kBnplZip:
    case Suggestion::Icon::kSaveAndFill:
      return std::u16string();
      // Payment method icons end
  }
  NOTREACHED();
}

std::optional<ui::ImageModel> ImageModelFromImageSkia(
    const gfx::ImageSkia& image_skia) {
  if (image_skia.isNull()) {
    return std::nullopt;
  }
  auto image_model = ui::ImageModel::FromImageSkia(image_skia);
  return image_model;
}

// Converts the `image_model` to an `ImageView`. If `apply_deactivated_style` is
// true, the image will be converted to a disabled image.
std::unique_ptr<views::ImageView> ConvertModelToImageView(
    std::optional<ui::ImageModel> image_model,
    bool apply_deactivated_style) {
  if (!image_model) {
    return nullptr;
  }
  if (apply_deactivated_style) {
    image_model = ui::GetDefaultDisabledIconFromImageModel(
        image_model.value(),
        ui::ColorProviderManager::Get().GetColorProviderFor(
            ui::NativeTheme::GetInstanceForNativeUi()->GetColorProviderKey(
                nullptr)));
  }
  return std::make_unique<views::ImageView>(image_model.value());
}

// Creates the table in which all the Autofill suggestion content apart from
// leading and trailing icons is contained.
// If `stretch_first_column` is true, the first column will expand to take up
// all available horizontal space.
std::unique_ptr<views::TableLayoutView> CreateSuggestionContentTable(
    std::unique_ptr<views::Label> main_text_label,
    std::vector<std::unique_ptr<views::View>> minor_text_labels,
    std::unique_ptr<views::Label> description_label,
    std::vector<std::unique_ptr<views::View>> subtext_views,
    bool align_description_label_to_right,
    bool stretch_first_column) {
  const bool has_two_columns = !!description_label;
  auto table =
      views::Builder<views::TableLayoutView>()
          .AddColumn(
              stretch_first_column ? views::LayoutAlignment::kStretch
                                   : views::LayoutAlignment::kStart,
              views::LayoutAlignment::kStretch,
              stretch_first_column ? 1.0f : views::TableLayout::kFixedSize,
              views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
          .Build();
  if (has_two_columns) {
    const views::LayoutAlignment kHorizontalAlignment =
        align_description_label_to_right ? views::LayoutAlignment::kEnd
                                         : views::LayoutAlignment::kStart;
    const float kHorizontalResize = align_description_label_to_right
                                        ? 1.0f
                                        : views::TableLayout::kFixedSize;
    const int kDividerSpacing = ChromeLayoutProvider::Get()->GetDistanceMetric(
        DISTANCE_RELATED_LABEL_HORIZONTAL_LIST);
    table->AddPaddingColumn(views::TableLayout::kFixedSize, kDividerSpacing);
    table->AddColumn(kHorizontalAlignment, views::LayoutAlignment::kStretch,
                     kHorizontalResize,
                     views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
  }

  // Major and minor text go into the first row, first column.
  table->AddRows(1, 0);
  if (!minor_text_labels.empty()) {
    auto first_line_container = std::make_unique<views::View>();
    first_line_container
        ->SetLayoutManager(std::make_unique<views::FlexLayout>())
        ->SetOrientation(views::LayoutOrientation::kHorizontal)
        .SetMainAxisAlignment(views::LayoutAlignment::kStart)
        .SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
        .SetIgnoreDefaultMainAxisMargins(true)
        .SetCollapseMargins(true)
        .SetDefault(
            views::kMarginsKey,
            gfx::Insets::VH(0, ChromeLayoutProvider::Get()->GetDistanceMetric(
                                   DISTANCE_RELATED_LABEL_HORIZONTAL_LIST)));

    first_line_container->AddChildView(std::move(main_text_label));
    for (auto& minor_text : minor_text_labels) {
      first_line_container->AddChildView(std::move(minor_text));
    }
    table->AddChildView(std::move(first_line_container));
  } else {
    table->AddChildView(std::move(main_text_label));
  }

  // The description goes into the first row, second column.
  if (has_two_columns) {
    table->AddChildView(std::move(description_label));
  }

  // Every subtext label goes into an additional row.
  for (std::unique_ptr<views::View>& subtext_view : subtext_views) {
    table->AddPaddingRow(0, kAdjacentLabelsVerticalSpacing).AddRows(1, 0);
    table->AddChildView(std::move(subtext_view));
    if (has_two_columns) {
      table->AddChildView(std::make_unique<views::View>());
    }
  }
  return table;
}

bool IsPaymentMethodSuggestion(const Suggestion& suggestion) {
  switch (suggestion.type) {
    case SuggestionType::kBnplEntry:
    case SuggestionType::kCreditCardEntry:
    case SuggestionType::kIbanEntry:
    case SuggestionType::kMaximizeCreditCardBenefitsEntry:
    case SuggestionType::kSaveAndFillCreditCardEntry:
    case SuggestionType::kVirtualCreditCardEntry:
      return true;
    case SuggestionType::kAccountStoragePasswordEntry:
    case SuggestionType::kAddressEntry:
    case SuggestionType::kAddressEntryOnTyping:
    case SuggestionType::kAddressFieldByFieldFilling:
    case SuggestionType::kAllLoyaltyCardsEntry:
    case SuggestionType::kAllSavedPasswordsEntry:
    case SuggestionType::kAtMemoryAiDisclosure:
    case SuggestionType::kAtMemoryFetching:
    case SuggestionType::kAtMemoryGenericError:
    case SuggestionType::kAtMemoryInactivityNudge:
    case SuggestionType::kAtMemoryNoConnection:
    case SuggestionType::kAtMemoryOpenGemini:
    case SuggestionType::kAtMemorySearchAffordance:
    case SuggestionType::kAtMemorySearchResult:
    case SuggestionType::kAtMemorySourceAttribution:
    case SuggestionType::kAutocompleteAtMemoryButton:
    case SuggestionType::kAutocompleteEntry:
    case SuggestionType::kAutofillAiOtherOrders:
    case SuggestionType::kAutofillAiOtherShipments:
    case SuggestionType::kAutofillAiPrivateInferenceNotice:
    case SuggestionType::kAutofillAiSourceAttribution:
    case SuggestionType::kRemoveAutofillAi:
    case SuggestionType::kBackupPasswordEntry:
    case SuggestionType::kBnplFootnote:
    case SuggestionType::kComposeDisable:
    case SuggestionType::kComposeGoToSettings:
    case SuggestionType::kComposeNeverShowOnThisSiteAgain:
    case SuggestionType::kComposeProactiveNudge:
    case SuggestionType::kComposeResumeNudge:
    case SuggestionType::kComposeSavedStateNotification:
    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::kInsecureContextPaymentDisabledMessage:
    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::kManageIban:
    case SuggestionType::kManageLoyaltyCard:
    case SuggestionType::kManageEnhancedAutofill:
    case SuggestionType::kMerchantPromoCodeEntry:
    case SuggestionType::kOneTimePasswordEntry:
    case SuggestionType::kPasswordEntry:
    case SuggestionType::kPasswordFieldByFieldFilling:
    case SuggestionType::kPendingStateSignin:
    case SuggestionType::kPersonalContextNotice:
    case SuggestionType::kScanCreditCard:
    case SuggestionType::kSeePromoCodeDetails:
    case SuggestionType::kSeparator:
    case SuggestionType::kTitle:
    case SuggestionType::kTroubleSigningInEntry:
    case SuggestionType::kUndo:
    case SuggestionType::kViewPasswordDetails:
    case SuggestionType::kWebauthnCredential:
    case SuggestionType::kWebauthnPasskeyQrCode:
    case SuggestionType::kWebauthnSignInWithAnotherDevice:
      return false;
  }
}

}  // namespace

std::optional<ui::ImageModel> GetIconImageModelFromIcon(Suggestion::Icon icon) {
  switch (icon) {
    case Suggestion::Icon::kNoIcon:
      return std::nullopt;
    case Suggestion::Icon::kHome:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kHomeIcon
                                          : vector_icons::kHomeOldIcon,
                                      kIconSize);
    case Suggestion::Icon::kSpark:
      return ImageModelFromVectorIcon(omnibox::kSparkIcon, kIconSize);
    case Suggestion::Icon::kTextSpark:
      return ImageModelFromVectorIcon(kTextAnalysisIcon, kIconSize);
    case Suggestion::Icon::kWork:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kWorkIcon
                                          : vector_icons::kWorkOldIcon,
                                      kIconSize);
    case Suggestion::Icon::kAccount:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? kAccountCircleFilledIcon
                                          : kAccountCircleOldIcon,
                                      kIconSize);
    case Suggestion::Icon::kCardGenericSpark:
      return ImageModelFromVectorIcon(vector_icons::kCreditCardSparkIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kCardGenericVector:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? kCreditCardIcon
                                          : kCreditCardOldIcon,
                                      kIconSize);
    case Suggestion::Icon::kClose:
      return ImageModelFromVectorIcon(vector_icons::kCloseIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kCode:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kCodeIcon
                                          : vector_icons::kCodeOldIcon,
                                      kIconSize);
    case Suggestion::Icon::kDelete:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? kDeleteIcon
                                          : kTrashCanRefreshOldIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kDevice:
      return ImageModelFromVectorIcon(
          ::features::IsRoundedIconsEnabled() ? kDevicesIcon : kDevicesOldIcon,
          kIconSize);
    case Suggestion::Icon::kVehicle:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kDirectionsCarIcon
                                          : vector_icons::kDirectionsCarOldIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kVehicleSpark:
      return ImageModelFromVectorIcon(vector_icons::kCarSparkIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kEdit:
      return ImageModelFromVectorIcon(
          ::features::IsRoundedIconsEnabled()
              ? kEditIcon
              : vector_icons::kEditChromeRefreshOldIcon,
          kChromeRefreshIconSize);
    case Suggestion::Icon::kEmail:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kMailIcon
                                          : vector_icons::kEmailOutlineOldIcon,
                                      kIconSize);
    case Suggestion::Icon::kGmail:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
      return ImageModelFromVectorIcon(vector_icons::kGoogleGmailIcon,
                                      kIconSize);
#else
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kMailIcon
                                          : vector_icons::kEmailOutlineOldIcon,
                                      kIconSize);
#endif
    case Suggestion::Icon::kGooglePhotos:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
      return ImageModelFromVectorIcon(vector_icons::kGooglePhotosIcon,
                                      kIconSize);
#else
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kPhotoFilledIcon
                                          : vector_icons::kPhotoOldIcon,
                                      kIconSize);
#endif
    case Suggestion::Icon::kGoogleCalendar:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kCalendarTodayIcon
                                          : vector_icons::kCalendarTodayOldIcon,
                                      kIconSize);
    case Suggestion::Icon::kError:
      return ui::ImageModel::FromVectorIcon(::features::IsRoundedIconsEnabled()
                                                ? vector_icons::kErrorFilledIcon
                                                : vector_icons::kErrorOldIcon,
                                            ui::kColorSysError, kIconSize);
    case Suggestion::Icon::kSadTab:
      return ImageModelFromVectorIcon(
          ::features::IsRoundedIconsEnabled() ? kSadTabIcon : kSadTabOldIcon,
          kIconSize);
    case Suggestion::Icon::kFlight:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kFlightIcon
                                          : vector_icons::kFlightOldIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kFlightSpark:
      return ImageModelFromVectorIcon(vector_icons::kFlightSparkIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kGlobe:
      return ImageModelFromVectorIcon(
          ::features::IsRoundedIconsEnabled() ? kGlobeIcon : kGlobeOldIcon,
          kIconSize);
    case Suggestion::Icon::kGoogle:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
      return ImageModelFromImageSkia(gfx::CreateVectorIcon(
          vector_icons::kGoogleGLogoIcon, kIconSize, gfx::kPlaceholderColor));
#else
      return std::nullopt;
#endif
    case Suggestion::Icon::kGoogleMonochrome:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
      return ImageModelFromVectorIcon(vector_icons::kGoogleGLogoMonochromeIcon,
                                      kIconSize);
#else
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kMailFilledIcon
                                          : vector_icons::kEmailOldIcon,
                                      kIconSize);
#endif
    case Suggestion::Icon::kIdCard:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kIdCardIcon
                                          : vector_icons::kIdCardOldIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kIdCard2:
      return ImageModelFromVectorIcon(vector_icons::kIdCard2Icon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kIdCard2Spark:
      return ImageModelFromVectorIcon(vector_icons::kIdCard2SparkIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kIdCardSpark:
      return ImageModelFromVectorIcon(vector_icons::kIdCardSparkIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kKey:
      return ImageModelFromVectorIcon(
          ::features::IsRoundedIconsEnabled() ? kVpnKeyFilledIcon : kKeyOldIcon,
          kIconSize);
    case Suggestion::Icon::kLocation:
      return ImageModelFromVectorIcon(
          ::features::IsRoundedIconsEnabled()
              ? vector_icons::kLocationOnIcon
              : vector_icons::kLocationOnChromeRefreshOldIcon,
          kChromeRefreshIconSize);
    case Suggestion::Icon::kLocationSpark:
      return ImageModelFromVectorIcon(vector_icons::kLocationOnSparkIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kLoyalty:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kLoyaltyIcon
                                          : vector_icons::kLoyaltyOldIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kMagic:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kButtonMagicIcon
                                          : vector_icons::kMagicButtonOldIcon,
                                      kIconSize);
    case Suggestion::Icon::kOrder:
      return ImageModelFromVectorIcon(vector_icons::kShoppingBagIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kOrderSpark:
      return ImageModelFromVectorIcon(vector_icons::kShoppingBagSparkIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kPassport:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kPassportIcon
                                          : vector_icons::kPassportOldIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kPassportSpark:
      return ImageModelFromVectorIcon(vector_icons::kPassportSparkIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kPenSpark:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
      return ImageModelFromVectorIcon(vector_icons::kPenSparkIcon, kIconSize);
#else
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kEditFilledIcon
                                          : vector_icons::kEditOldIcon,
                                      kIconSize);
#endif
    case Suggestion::Icon::kPersonCheck:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kPersonCheckIcon
                                          : vector_icons::kPersonCheckOldIcon,
                                      kPersonCheckIconSize);
    case Suggestion::Icon::kQuestionMark:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kHelpIcon
                                          : vector_icons::kHelpOutlineOldIcon,
                                      kRecoveryPasswordIconSize);
    case Suggestion::Icon::kRecoveryPassword:
      return ImageModelFromVectorIcon(
          ::features::IsRoundedIconsEnabled()
              ? vector_icons::kHistoryIcon
              : vector_icons::kHistoryChromeRefreshOldIcon,
          kRecoveryPasswordIconSize);
    case Suggestion::Icon::kSaveAndFill:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? kCreditCardIcon
                                          : kCreditCardOldIcon,
                                      kIconSize);
    case Suggestion::Icon::kSettings:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? omnibox::kChromeProductIcon
                                          : omnibox::kProductOldIcon,
                                      kIconSize);
    case Suggestion::Icon::kShipment:
      return ImageModelFromVectorIcon(vector_icons::kLocalShippingIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kShipmentSpark:
      return ImageModelFromVectorIcon(vector_icons::kLocalShippingSparkIcon,
                                      kChromeRefreshIconSize);
    case Suggestion::Icon::kUndo:
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? vector_icons::kUndoIcon
                                          : vector_icons::kUndoOldIcon,
                                      kIconSize);
    case Suggestion::Icon::kGooglePasswordManager:
      return ImageModelFromVectorIcon(GooglePasswordManagerVectorIcon(),
                                      kGooglePasswordManagerIconSize);
    case Suggestion::Icon::kGooglePay:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
      return ui::ImageModel::FromImageGenerator(
          base::BindRepeating(&CreateTiledGooglePayLogo, kGooglePayLogoWidth,
                              kIconSize),
          gfx::Size(kGooglePayLogoWidth, kIconSize));
#else
      return ImageModelFromVectorIcon(::features::IsRoundedIconsEnabled()
                                          ? kCreditCardIcon
                                          : kCreditCardOldIcon,
                                      kIconSize);
#endif
    case Suggestion::Icon::kGoogleWallet:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
      return ImageModelFromVectorIcon(vector_icons::kGoogleWalletIcon,
                                      kGoogleWalletIconSize);
#else
      return std::nullopt;
#endif
    case Suggestion::Icon::kGoogleWalletMonochrome:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
      return ImageModelFromVectorIcon(vector_icons::kGoogleWalletMonochromeIcon,
                                      kGoogleWalletIconSize);
#else
      return std::nullopt;
#endif
    case Suggestion::Icon::kIban:
    case Suggestion::Icon::kOfferTag:
    case Suggestion::Icon::kScanCreditCard:
    case Suggestion::Icon::kCardGeneric:
    case Suggestion::Icon::kCardAmericanExpress:
    case Suggestion::Icon::kCardDiners:
    case Suggestion::Icon::kCardDiscover:
    case Suggestion::Icon::kCardElo:
    case Suggestion::Icon::kCardJCB:
    case Suggestion::Icon::kCardMasterCard:
    case Suggestion::Icon::kCardMir:
    case Suggestion::Icon::kCardTroy:
    case Suggestion::Icon::kCardUnionPay:
    case Suggestion::Icon::kCardVerve:
    case Suggestion::Icon::kCardVisa:
    case Suggestion::Icon::kBnplGeneric:
    case Suggestion::Icon::kBnplAffirm:
    case Suggestion::Icon::kBnplAfterpay:
    case Suggestion::Icon::kBnplKlarna:
    case Suggestion::Icon::kBnplZip:
    case Suggestion::Icon::kAndroidMessages: {
      // For other suggestion entries, get the icon from PNG files.
      int icon_id = GetIconResourceID(icon);
      DCHECK_NE(icon_id, 0);
      return ImageModelFromImageSkia(
          *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(icon_id));
    }
  }
  NOTREACHED();
}

std::u16string GetVoiceOverStringFromSuggestion(const Suggestion& suggestion) {
  if (suggestion.voice_over) {
    return *suggestion.voice_over;
  }

  std::vector<std::u16string> text;
  auto add_if_not_empty = [&text](std::u16string value) {
    if (!value.empty()) {
      text.push_back(std::move(value));
    }
  };

  add_if_not_empty(GetIconAccessibleName(suggestion.icon));
  text.push_back(suggestion.main_text.value);

  const bool is_vcn =
      suggestion.type == SuggestionType::kVirtualCreditCardEntry;
  const bool is_iban = suggestion.type == SuggestionType::kIbanEntry;
  const bool has_subtext =
      !suggestion.labels.empty() && !suggestion.labels[0][0].value.empty();

  std::u16string badge_text;
  if (is_vcn) {
    badge_text = l10n_util::GetStringUTF16(
        IDS_AUTOFILL_VIRTUAL_CARD_SUGGESTION_OPTION_VALUE);
  } else if (is_iban) {
    badge_text =
        l10n_util::GetStringUTF16(IDS_AUTOFILL_IBAN_SUGGESTION_OPTION_VALUE);
  }

  // A badge is applied as a label view for the following cases:
  // - A virtual card that does not have a product description or nickname.
  // - An IBAN that does not have a nickname.
  bool badge_used_as_minor_text = false;
  if ((is_vcn && !suggestion.minor_texts.empty()) ||
      (is_iban && !has_subtext)) {
    add_if_not_empty(badge_text);
    badge_used_as_minor_text = true;
  } else if (!suggestion.minor_texts.empty()) {
    std::vector<std::u16string> text_values;
    for (const auto& minor_text : suggestion.minor_texts) {
      text_values.push_back(minor_text.value);
    }
    std::u16string sublabel = base::JoinString(text_values, u" ");
    add_if_not_empty(sublabel);
  }

  if (payments::ShouldShowBnplLinkedPill(suggestion)) {
    text.push_back(l10n_util::GetStringUTF16(
        IDS_AUTOFILL_CARD_BNPL_LINKED_ISSUER_PILL_LABEL));
  }

  bool badge_added_to_labels = false;
  for (const std::vector<Suggestion::Text>& row : suggestion.labels) {
    std::vector<std::u16string> row_values;
    for (const Suggestion::Text& label : row) {
      // `label_text` is not populated for footers or autocomplete entries.
      if (!label.value.empty()) {
        row_values.push_back(label.value);
      }
    }
    // If a badge is present and not used as minor text, apply it as a
    // label view for these specific cases:
    // - Virtual card that has a product description or nickname.
    // - IBAN that has a nickname.
    if (!badge_text.empty() && !badge_used_as_minor_text &&
        !badge_added_to_labels) {
      row_values.push_back(badge_text);
      badge_added_to_labels = true;
    }

    add_if_not_empty(base::JoinString(row_values, u" "));
  }

  // `additional_label` is only populated in a passwords context.
  add_if_not_empty(suggestion.additional_label);

  return base::JoinString(text, u" ");
}

std::unique_ptr<views::ImageView> GetIconImageView(
    const Suggestion& suggestion) {
  base::TimeTicks start_time = base::TimeTicks::Now();
  // Check that icon and custom_icon are not set at the same time.
  CHECK(!(suggestion.icon != Suggestion::Icon::kNoIcon &&
          suggestion.custom_icon.index() != 0));
  if (auto* monochrome_icon = std::get_if<Suggestion::LetterMonochromeIcon>(
          &suggestion.custom_icon)) {
    SkBitmap bitmap;
    bitmap.allocN32Pixels(kMonochromeIconSize, kMonochromeIconSize, false);
    cc::SkiaPaintCanvas paint_canvas(bitmap);
    gfx::Canvas canvas(&paint_canvas, 1.f);
    const std::vector<std::string> font_names = {
        l10n_util::GetStringUTF8(IDS_NTP_FONT_FAMILY)};
    gfx::DrawMonogramInCanvas(&canvas, kMonochromeIconSize, kMonochromeIconSize,
                              monochrome_icon->monogram_text, font_names,
                              kMonochromeIconTextColor, kMonochromeIconBgColor);
    return ConvertModelToImageView(
        ImageModelFromImageSkia(
            gfx::Image::CreateFrom1xBitmap(bitmap).AsImageSkia()),
        ShouldApplyDeactivatedStyle(suggestion));
  }
  if (auto* image = std::get_if<gfx::Image>(&suggestion.custom_icon);
      image && !image->IsEmpty()) {
    gfx::ImageSkia image_skia = image->AsImageSkia();
    if (std::holds_alternative<Suggestion::IdentityCredentialPayload>(
            suggestion.payload)) {
      image_skia = webid::CreateCircleCroppedImage(
          image_skia, webid::kDesiredAvatarSizeInAutofillDropdown);
    }
    return ConvertModelToImageView(ImageModelFromImageSkia(image_skia),
                                   ShouldApplyDeactivatedStyle(suggestion));
  }
  std::unique_ptr<views::ImageView> icon_image_view =
      ConvertModelToImageView(GetIconImageModelFromIcon(suggestion.icon),
                              ShouldApplyDeactivatedStyle(suggestion));
  base::UmaHistogramTimes(kHistogramGetImageViewByName,
                          base::TimeTicks::Now() - start_time);

  if (icon_image_view) {
    // It is possible to have icons of different sizes (kChromeRefreshIconSize
    // and kIconSize) on the same popup. Setting the icon view width to
    // the largest value ensures that the icon occupies consistent horizontal
    // space and makes icons (and the text after them) aligned. It expands
    // the area of kIconSize icons only and doesn't change those that are bigger
    // by design (e.g. payment card icons) and have no alignment issues.
    gfx::Size size = icon_image_view->GetPreferredSize();
    size.set_width(std::max(kChromeRefreshIconSize, size.width()));
    icon_image_view->SetPreferredSize(size);
  }

  return icon_image_view;
}

std::unique_ptr<views::ImageView> GetTrailingIconImageView(
    const Suggestion& suggestion) {
  base::TimeTicks start_time = base::TimeTicks::Now();
  std::optional<ui::ImageModel> image_model =
      GetIconImageModelFromIcon(suggestion.trailing_icon);
  std::unique_ptr<views::ImageView> icon_image_view = ConvertModelToImageView(
      image_model, ShouldApplyDeactivatedStyle(suggestion));
  base::UmaHistogramTimes(kHistogramGetImageViewByName,
                          base::TimeTicks::Now() - start_time);

  return icon_image_view;
}

void AddSpacerWithSize(views::BoxLayoutView& view,
                       int spacer_width,
                       bool resize) {
  auto spacer = views::Builder<views::View>()
                    .SetPreferredSize(gfx::Size(spacer_width, 1))
                    .Build();
  view.SetFlexForView(view.AddChildView(std::move(spacer)),
                      /*flex=*/resize ? 1 : 0,
                      /*use_min_size=*/true);
}

void AddSuggestionContentToView(
    const Suggestion& suggestion,
    std::unique_ptr<views::Label> main_text_label,
    std::vector<std::unique_ptr<views::View>> minor_text_labels,
    std::unique_ptr<views::Label> description_label,
    std::vector<std::unique_ptr<views::View>> subtext_views,
    std::unique_ptr<views::View> icon,
    PopupRowContentView& content_view) {
  bool should_show_new_fop_format = IsPaymentMethodSuggestion(suggestion);
  // Adjust the row height based on the number of subtexts (lines of text).
  int row_height = views::MenuConfig::instance().touchable_menu_height;
  if (!subtext_views.empty() || should_show_new_fop_format) {
    row_height += kAutofillPopupAdditionalDoubleRowHeight;
  }

  // If there are three rows in total, add extra padding to avoid cramming.
  DCHECK_LE(subtext_views.size(), 2u);
  int vertical_padding = 0;
  const int left_padding = content_view.GetInsideBorderInsets().left();
  int right_padding = left_padding;
  if (subtext_views.size() == 2u) {
    if (should_show_new_fop_format) {
      row_height += kAutofillPopupAdditionalTripleRowHeight;
    } else {
      // TODO(crbug.com/535568421): Investigate if these spacing adjustments
      // should be applied to all 3-row suggestions rather than special-casing
      // Autofill AI.
      vertical_padding = (suggestion.type == SuggestionType::kFillAutofillAi)
                             ? kAutofillAiPopupAdditionalVerticalPadding
                             : kAutofillPopupAdditionalVerticalPadding;
    }
    if (suggestion.type == SuggestionType::kFillAutofillAi) {
      right_padding += kAutofillAiPopupAdditionalRightPadding;
    }
  }

  content_view.SetInsideBorderInsets(gfx::Insets::TLBR(
      vertical_padding, left_padding, vertical_padding, right_padding));

  content_view.SetMinimumCrossAxisSize(row_height);

  // The leading icon.
  if (suggestion.is_loading) {
    views::Throbber* throbber =
        content_view.AddChildView(std::make_unique<views::Throbber>());
    if (icon) {
      // Prevent that the layout is shifted when transitioning from throbber
      // to icon and vice versa when there is a width difference.
      const int size_delta =
          icon->GetMinimumSize().width() - throbber->GetMinimumSize().width();
      throbber->SetProperty(views::kMarginsKey,
                            gfx::Insets::VH(0, std::max(size_delta, 0) / 2));
    }
    throbber->Start();
    AddSpacerWithSize(content_view, PopupBaseView::ArrowHorizontalMargin(),
                      /*resize=*/false);
    content_view.SetEnabled(false);
  } else if (icon) {
    content_view.AddChildView(std::move(icon));
    AddSpacerWithSize(content_view, PopupBaseView::ArrowHorizontalMargin(),
                      /*resize=*/false);
  }

  // The actual content table.
  content_view.SetFlexForView(
      content_view.AddChildView(CreateSuggestionContentTable(
          std::move(main_text_label), std::move(minor_text_labels),
          std::move(description_label), std::move(subtext_views),
          suggestion.additional_label_alignment_right,
          /*stretch_first_column=*/
          payments::ShouldShowBnplLinkedPill(suggestion))),
      1);

  // The trailing icon.
  if (std::unique_ptr<views::ImageView> trailing_icon =
          GetTrailingIconImageView(suggestion)) {
    AddSpacerWithSize(content_view, PopupBaseView::ArrowHorizontalMargin(),
                      /*resize=*/true);
    content_view.AddChildView(std::move(trailing_icon));
  }

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

std::unique_ptr<PopupRowContentView> CreatePasskeyQrCodePopupRowContentView(
    const Suggestion& suggestion) {
  auto view = std::make_unique<PopupRowContentView>();
  view->SetOrientation(views::BoxLayout::Orientation::kVertical);
  view->SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kCenter);

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

  auto main_text_label = std::make_unique<views::Label>(
      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_PASSKEY_QR_CODE_TITLE),
      views::style::CONTEXT_DIALOG_BODY_TEXT,
      views::style::STYLE_BODY_4_MEDIUM);
  text_container->AddChildView(std::move(main_text_label));

  std::string qr_string =
      std::holds_alternative<Suggestion::Guid>(suggestion.payload)
          ? suggestion.GetPayload<Suggestion::Guid>().value()
          : std::string();

  if (qr_string.empty()) {
    LOG(ERROR) << "Passkey QR code string is empty";
    view->UpdateStyle(/*selected=*/false);
    return view;
  }

  base::expected<gfx::ImageSkia, qr_code_generator::Error> qr_code =
      qr_code_generator::GenerateImage(
          base::as_byte_span(qr_string),
          qr_code_generator::ModuleStyle::kCircles,
          qr_code_generator::LocatorStyle::kRounded,
          qr_code_generator::CenterImage::kPasskey,
          qr_code_generator::QuietZone::kIncluded);
  if (!qr_code.has_value()) {
    LOG(ERROR) << "Failed to generate Passkey QR code image.";
    view->UpdateStyle(/*selected=*/false);
    return view;
  }

  auto qr_image_view = std::make_unique<views::ImageView>();
  qr_image_view->SetImage(ui::ImageModel::FromImageSkia(qr_code.value()));
  qr_image_view->SetImageSize(gfx::Size(140, 140));
  qr_image_view->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
  qr_image_view->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
  qr_image_view->SetProperty(views::kMarginsKey,
                             gfx::Insets::TLBR(0, 0, 16, 0));
  qr_image_view->GetViewAccessibility().SetName(
      l10n_util::GetStringUTF16(IDS_WEBAUTHN_QR_CODE_ALT_TEXT));
  view->AddChildView(std::move(qr_image_view));

  view->UpdateStyle(/*selected=*/false);
  return view;
}

ui::ImageModel ImageModelFromVectorIcon(const gfx::VectorIcon& vector_icon,
                                        int icon_size = kIconSize) {
  return ui::ImageModel::FromVectorIcon(vector_icon, ui::kColorIcon, icon_size);
}

const gfx::VectorIcon& GetExpandableMenuIcon(SuggestionType type) {
  CHECK(IsExpandableSuggestionType(type));
  // Only compose suggestions have a different expandable icon.
  return GetFillingProductFromSuggestionType(type) == FillingProduct::kCompose
             ? ::features::IsRoundedIconsEnabled()
                   ? kMoreVertIcon
                   : kBrowserToolsChromeRefreshOldIcon
         : ::features::IsRoundedIconsEnabled()
             ? vector_icons::kKeyboardArrowRightFlippableIcon
             : vector_icons::kSubmenuArrowChromeRefreshOldIcon;
}

}  // namespace autofill::popup_cell_utils
