// Copyright 2022 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/webid/account_selection_bubble_view.h"

#include <algorithm>
#include <utility>

#include "base/feature_list.h"
#include "base/i18n/case_conversion.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/image_fetcher/image_decoder_impl.h"
#include "chrome/browser/ui/passwords/ui_utils.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/controls/hover_button.h"
#include "chrome/browser/ui/views/webid/account_selection_view_base.h"
#include "chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h"
#include "chrome/browser/ui/views/webid/webid_utils.h"
#include "chrome/grit/generated_resources.h"
#include "components/image_fetcher/core/image_fetcher_impl.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/common/content_features.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "skia/ext/image_operations.h"
#include "third_party/blink/public/mojom/webid/federated_request.mojom.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/accessibility/platform/ax_platform.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/base/ui_base_features.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/button/md_text_button.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/scroll_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/styled_label.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/flex_layout_types.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"

namespace webid {

namespace {

constexpr int kSingleIdpUseOtherAccountButtonIconMargin = 5;
constexpr int kLoginButtonSeparatorLeftMargin = 64;
constexpr int kLoginButtonSeparatorRightMargin = 15;
constexpr float kNumVisibleRows = 2.5f;

// views::MdTextButton which:
// - Uses the passed-in `brand_background_color` based on whether the button
//   background contrasts sufficiently with dialog background.
// - If `brand_text_color` is not provided, computes the text color such that it
//   contrasts sufficiently with `brand_background_color`.
// - If `extra_accessible_text` is passed, appends this to the button's
// accessible name. This is useful when the user logs in via a popup window and
// cannot easily navigate the rest of the text in the dialog to confirm which is
// the account being used to login via FedCM.
class ContinueButton : public views::MdTextButton {
  METADATA_HEADER(ContinueButton, views::MdTextButton)

 public:
  ContinueButton(views::MdTextButton::PressedCallback callback,
                 const std::u16string& text,
                 const content::IdentityProviderMetadata& idp_metadata,
                 std::optional<std::u16string> extra_accessible_text)
      : views::MdTextButton(std::move(callback), text),
        brand_background_color_(idp_metadata.brand_background_color),
        brand_text_color_(idp_metadata.brand_text_color) {
    SetCornerRadius(kButtonRadius);
    SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);
    SetStyle(ui::ButtonStyle::kProminent);
    if (extra_accessible_text.has_value()) {
      GetViewAccessibility().SetName(text + u", " + *extra_accessible_text);
    }
  }

  ContinueButton(const ContinueButton&) = delete;
  ContinueButton& operator=(const ContinueButton&) = delete;
  ~ContinueButton() override = default;

  void OnThemeChanged() override {
    views::MdTextButton::OnThemeChanged();
    if (!brand_background_color_) {
      return;
    }

    const SkColor dialog_background_color =
        GetWidget()
            ->widget_delegate()
            ->AsBubbleDialogDelegate()
            ->background_color()
            .ResolveToSkColor(GetColorProvider());
    if (color_utils::GetContrastRatio(dialog_background_color,
                                      *brand_background_color_) <
        color_utils::kMinimumVisibleContrastRatio) {
      SetBgColorOverrideDeprecated(std::nullopt);
      SetEnabledTextColors(std::nullopt);
      return;
    }

    SetBgColorOverrideDeprecated(*brand_background_color_);
    SkColor text_color;
    if (brand_text_color_) {
      // IdpNetworkRequestManager ensures that `brand_text_color_` is only set
      // if it sufficiently contrasts with `brand_background_color_`.
      text_color = *brand_text_color_;
    } else {
      text_color = color_utils::BlendForMinContrast(GetCurrentTextColor(),
                                                    *brand_background_color_)
                       .color;
    }
    SetEnabledTextColors(text_color);
  }

 private:
  std::optional<SkColor> brand_background_color_;
  std::optional<SkColor> brand_text_color_;
};

BEGIN_METADATA(ContinueButton)
END_METADATA

// Adds a login button separator to the given `scroller_content`. Returns the
// separator size.
int AddLoginButtonSeparator(views::View* scroller_content,
                            bool is_multi_idp,
                            const std::unique_ptr<views::View>& button) {
  auto separator = std::make_unique<views::Separator>();
  separator->SetBorder(views::CreateEmptyBorder(
      is_multi_idp ? gfx::Insets::TLBR(
                         kTopBottomPadding, kLoginButtonSeparatorLeftMargin,
                         kTopBottomPadding, kLoginButtonSeparatorRightMargin)
                   : gfx::Insets::VH(kVerticalSpacing + kTopBottomPadding, 0)));
  int separator_size = separator->GetPreferredSize().height();
  scroller_content->AddChildView(std::move(separator));
  return separator_size;
}

}  // namespace

AccountSelectionBubbleDelegate::AccountSelectionBubbleDelegate(
    std::unique_ptr<AccountSelectionBubbleView> account_selection_view,
    views::View* anchor_view)
    : views::BubbleDialogDelegate(
          anchor_view,
          // Note that TOP_RIGHT means the bubble's top and right are anchored
          // to the `anchor_view`. The final bubble positioning will be computed
          // in GetBubbleBounds.
          views::BubbleBorder::Arrow::TOP_RIGHT,
          views::BubbleBorder::DIALOG_SHADOW,
          /*autosize=*/true) {
  auto* selection_view = SetContentsView(std::move(account_selection_view));
  SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone));
  set_fixed_width(kBubbleWidth);
  SetShowTitle(false);
  SetShowCloseButton(false);
  set_close_on_deactivate(false);
  set_margins(gfx::Insets::VH(kTopBottomPadding + kVerticalSpacing, 0));
  SetAccessibleTitle(selection_view->dialog_title());
}

AccountSelectionBubbleDelegate::~AccountSelectionBubbleDelegate() = default;

gfx::Rect AccountSelectionBubbleDelegate::GetBubbleBounds() {
  gfx::Rect proposed_bubble_bounds =
      views::BubbleDialogDelegate::GetBubbleBounds();
  return GetAccountSelectionView()->GetBubbleBounds(proposed_bubble_bounds);
}

views::Widget* AccountSelectionBubbleDelegate::GetWidget() {
  return GetAccountSelectionView()->GetWidget();
}

const views::Widget* AccountSelectionBubbleDelegate::GetWidget() const {
  return const_cast<AccountSelectionBubbleDelegate*>(this)
      ->GetAccountSelectionView()
      ->GetWidget();
}

AccountSelectionBubbleView*
AccountSelectionBubbleDelegate::GetAccountSelectionView() {
  if (auto* account_selection_view_contents =
          views::AsViewClass<AccountSelectionBubbleView>(GetContentsView())) {
    return account_selection_view_contents;
  }
  NOTREACHED()
      << "Bubble ContentsView isn't of type AccountSelectionBubbleView!";
}

AccountSelectionBubbleView::AccountSelectionBubbleView(
    const content::RelyingPartyData& rp_data,
    const std::optional<std::u16string>& idp_title,
    blink::mojom::RpContext rp_context,
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
    FedCmAccountSelectionView* owner)
    : AccountSelectionViewBase(owner,
                               std::move(url_loader_factory),
                               rp_data,
                               owner->web_contents()
                                   ->GetPrimaryMainFrame()
                                   ->GetRenderWidgetHost()
                                   ->GetDeviceScaleFactor()),
      rp_context_(rp_context) {
  // Configure the BoxLayout
  SetOrientation(views::BoxLayout::Orientation::kVertical);
  SetBetweenChildSpacing(kTopBottomPadding);

  title_ = GetTitle(rp_data_, idp_title, rp_context);

  header_view_ = AddChildView(CreateHeaderView());
}

AccountSelectionBubbleView::~AccountSelectionBubbleView() = default;

void AccountSelectionBubbleView::ShowMultiAccountPicker(
    const std::vector<IdentityRequestAccountPtr>& accounts,
    const std::vector<IdentityProviderDataPtr>& idp_list,
    const gfx::Image& rp_icon,
    bool show_back_button) {
  bool is_multi_idp = idp_list.size() > 1u;
  std::u16string title = GetTitle(
      rp_data_,
      is_multi_idp ? std::nullopt
                   : std::make_optional<std::u16string>(
                         base::UTF8ToUTF16(idp_list[0]->idp_for_display)),
      rp_context_);
  UpdateHeader(
      is_multi_idp ? rp_icon : idp_list[0]->idp_metadata.brand_decoded_icon,
      title, webid::GetSubtitle(rp_data_), show_back_button,
      /*should_circle_crop_header_icon=*/!is_multi_idp);

  RemoveNonHeaderChildViews();
  AddSeparatorAndMultipleAccountChooser(accounts, idp_list);

  PreferredSizeChanged();
}

void AccountSelectionBubbleView::ShowVerifyingSheet(
    const IdentityRequestAccountPtr& account,
    const std::u16string& title) {
  UpdateHeader(account->identity_provider->idp_metadata.brand_decoded_icon,
               title, u"",
               /*show_back_button=*/false,
               /*should_circle_crop_header_icon=*/true);

  RemoveNonHeaderChildViews();
  views::ProgressBar* const progress_bar =
      AddChildView(std::make_unique<views::ProgressBar>());
  progress_bar->SetPreferredHeight(kProgressBarHeight);
  // Use an infinite animation: SetValue(-1).
  progress_bar->SetValue(-1);
  progress_bar->SetBackgroundColor(SK_ColorLTGRAY);
  auto row = std::make_unique<views::View>();
  row->SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical,
      gfx::Insets::VH(kTopBottomPadding, kLeftRightPadding)));
  CHECK(!account->is_filtered_out);
  row->AddChildView(CreateAccountRow(account,
                                     /*clickable_position=*/std::nullopt,
                                     /*should_include_idp=*/false));
  AddChildView(std::move(row));

  PreferredSizeChanged();
}

void AccountSelectionBubbleView::ShowSingleAccountConfirmDialog(
    const IdentityRequestAccountPtr& account,
    bool show_back_button) {
  std::u16string title = GetTitle(
      rp_data_, base::UTF8ToUTF16(account->identity_provider->idp_for_display),
      rp_context_);
  UpdateHeader(account->identity_provider->idp_metadata.brand_decoded_icon,
               title, webid::GetSubtitle(rp_data_), show_back_button,
               /*should_circle_crop_header_icon=*/true);

  RemoveNonHeaderChildViews();
  AddChildView(std::make_unique<views::Separator>());
  std::pair<std::unique_ptr<views::View>, views::MdTextButton*>
      chooser_and_button = CreateSingleAccountChooser(account);
  AddChildView(std::move(chooser_and_button.first));

  // If the screen reader is active, request focus so that the creation of
  // this button is announced to the user. Do not do this when screen reader
  // is not active because it looks bad.
  if (ui::AXPlatform::GetInstance().IsScreenReaderActive()) {
    chooser_and_button.second->RequestFocus();
  }
  PreferredSizeChanged();
}

void AccountSelectionBubbleView::ShowFailureDialog(
    const std::u16string& idp_for_display,
    const content::IdentityProviderMetadata& idp_metadata) {
  UpdateHeader(idp_metadata.brand_decoded_icon,
               GetTitle(rp_data_, idp_for_display, rp_context_),
               webid::GetSubtitle(rp_data_),
               /*show_back_button=*/false,
               /*should_circle_crop_header_icon=*/true);

  RemoveNonHeaderChildViews();
  AddChildView(std::make_unique<views::Separator>());
  auto row = std::make_unique<views::View>();
  row->SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical,
      gfx::Insets::VH(0, kLeftRightPadding)));

  // Add body.
  views::Label* const body = row->AddChildView(std::make_unique<views::Label>(
      l10n_util::GetStringFUTF16(IDS_IDP_SIGNIN_STATUS_MISMATCH_DIALOG_BODY,
                                 idp_for_display),
      views::style::CONTEXT_DIALOG_BODY_TEXT, views::style::STYLE_SECONDARY));
  body->SetMultiLine(true);
  body->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
  constexpr int kBodyLineHeight = 20;
  body->SetLineHeight(kBodyLineHeight);

  // Add space between the body and the separator and the body and the continue
  // button.
  constexpr int kBottomSpacing = 16;
  body->SetBorder(views::CreateEmptyBorder(
      gfx::Insets::TLBR(kVerticalSpacing, 0, kBottomSpacing, 0)));

  // Add continue button.
  auto button = std::make_unique<ContinueButton>(
      base::BindRepeating(&FedCmAccountSelectionView::OnLoginToIdP,
                          base::Unretained(owner_), idp_metadata.config_url,
                          idp_metadata.idp_login_url),
      l10n_util::GetStringUTF16(IDS_SIGNIN_CONTINUE), idp_metadata,
      // TODO (kylixrd@): Shouldn't the following be a localizable string?
      /*extra_accessible_text=*/u"Opens in a new tab");
  row->AddChildView(std::move(button));
  AddChildView(std::move(row));

  PreferredSizeChanged();
}

void AccountSelectionBubbleView::ShowErrorDialog(
    const std::u16string& idp_for_display,
    const content::IdentityProviderMetadata& idp_metadata,
    const std::optional<TokenError>& error) {
  std::u16string title = GetTitle(rp_data_, idp_for_display, rp_context_);
  UpdateHeader(idp_metadata.brand_decoded_icon, title,
               webid::GetSubtitle(rp_data_),
               /*show_back_button=*/false,
               /*should_circle_crop_header_icon=*/true);

  RemoveNonHeaderChildViews();
  AddChildView(std::make_unique<views::Separator>());
  auto row = std::make_unique<views::View>();
  row->SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical,
      gfx::Insets::VH(kTopBottomPadding, kLeftRightPadding)));

  std::u16string summary_text;
  std::u16string description_text;
  std::tie(summary_text, description_text) =
      GetErrorDialogText(error, idp_for_display);

  // Add error summary.
  views::Label* const summary =
      row->AddChildView(std::make_unique<views::Label>(
          summary_text, views::style::CONTEXT_DIALOG_TITLE,
          views::style::STYLE_PRIMARY));
  summary->SetMultiLine(true);
  summary->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
  constexpr int kSummaryLineHeight = 22;
  summary->SetLineHeight(kSummaryLineHeight);

  // Add error description.
  views::Label* const description =
      row->AddChildView(std::make_unique<views::Label>(
          description_text, views::style::CONTEXT_DIALOG_BODY_TEXT,
          views::style::STYLE_SECONDARY));
  description->SetMultiLine(true);
  description->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
  constexpr int kDescriptionLineHeight = 20;
  description->SetLineHeight(kDescriptionLineHeight);

  AddChildView(std::move(row));

  // Add row for buttons.
  auto button_row = std::make_unique<views::BoxLayoutView>();
  button_row->SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kEnd);
  constexpr int kButtonRowTopPadding = 6;
  button_row->SetInsideBorderInsets(
      gfx::Insets::TLBR(kButtonRowTopPadding, 0, 0, kLeftRightPadding));
  constexpr int kButtonRowChildSpacing = 8;
  button_row->SetBetweenChildSpacing(kButtonRowChildSpacing);

  // Add more details button.
  if (error && !error->url.is_empty()) {
    auto more_details_button = std::make_unique<views::MdTextButton>(
        base::BindRepeating(&FedCmAccountSelectionView::OnMoreDetails,
                            base::Unretained(owner_)),
        l10n_util::GetStringUTF16(IDS_SIGNIN_ERROR_DIALOG_MORE_DETAILS_BUTTON));
    button_row->AddChildView(std::move(more_details_button));
  }

  // Add got it button.
  auto got_it_button = std::make_unique<views::MdTextButton>(
      base::BindRepeating(&FedCmAccountSelectionView::OnGotIt,
                          base::Unretained(owner_)),
      l10n_util::GetStringUTF16(IDS_SIGNIN_ERROR_DIALOG_GOT_IT_BUTTON));
  button_row->AddChildView(std::move(got_it_button));

  AddChildView(std::move(button_row));

  PreferredSizeChanged();
}

void AccountSelectionBubbleView::ShowRequestPermissionDialog(
    const IdentityRequestAccountPtr& account) {
  NOTREACHED() << "ShowRequestPermissionDialog is only implemented for "
                  "AccountSelectionModalView";
}

std::string AccountSelectionBubbleView::GetDialogTitle() const {
  return base::UTF16ToUTF8(title_);
}

std::optional<std::string> AccountSelectionBubbleView::GetDialogSubtitle()
    const {
  if (subtitle_.empty()) {
    return std::nullopt;
  }
  return base::UTF16ToUTF8(subtitle_);
}

gfx::Rect AccountSelectionBubbleView::GetBubbleBounds(
    gfx::Rect proposed_bubble_bounds) {
  // Since the top right corner of the bubble is set as the arrow in the ctor,
  // the top right corner of the bubble will be anchored to the origin, which we
  // set to be the top right corner of the web contents container.
  //       |-------------------------|
  //       |                |        |
  //       |                | bubble |
  //       |                |        |
  //       |                |--------|
  //       |                         |
  //       |   contents_web_view     |
  //       |          ...            |
  //       |-------------------------|
  // We also need to inset the web contents bounds by kTopMargin at the top and
  // kRightMargin either at the left or right, depending on whether RTL is
  // enabled, in order to leave some space between the bubble and the edges of
  // the web contents.
  //       |-------------------------|
  //       |               kTopMargin|
  //       |         |--------|      |
  //       |         |        |kRight|
  //       |         | bubble |Margin|
  //       |         |--------|      |
  //       |                         |
  //       | contents_web_view       |
  //       |          ...            |
  //       |-------------------------|
  // In the RTL case, the bubble is aligned towards the left side of the screen
  // and the horizontal inset would apply to the left of the bubble.

  if (!owner_->web_contents()) {
    // Async autosize tasks may occur after the web_contents_ is destroyed.
    return proposed_bubble_bounds;
  }

  gfx::Rect web_contents_bounds = owner_->web_contents()->GetViewBounds();
  if (base::i18n::IsRTL()) {
    web_contents_bounds.Inset(gfx::Insets::TLBR(
        /*top=*/kTopMargin, /*left=*/kRightMargin, /*bottom=*/0,
        /*right=*/0));
    proposed_bubble_bounds.set_origin(
        owner_->web_contents()->GetViewBounds().origin());
  } else {
    web_contents_bounds.Inset(gfx::Insets::TLBR(
        /*top=*/kTopMargin, /*left=*/0, /*bottom=*/0,
        /*right=*/kRightMargin));
    proposed_bubble_bounds.set_origin(
        owner_->web_contents()->GetViewBounds().top_right());
  }
  proposed_bubble_bounds.AdjustToFit(web_contents_bounds);

  return proposed_bubble_bounds;
}

std::unique_ptr<views::View> AccountSelectionBubbleView::CreateHeaderView() {
  auto header = std::make_unique<views::View>();
  // Do not use a top margin as it has already been set in the bubble.
  header->SetLayoutManager(std::make_unique<views::FlexLayout>())
      ->SetInteriorMargin(gfx::Insets::TLBR(
          0, kLeftRightPadding, kVerticalSpacing, kLeftRightPadding));

  // Add the space for the icon.
  auto image_view = std::make_unique<BrandIconImageView>(kBubbleIdpIconSize);
  image_view->SetProperty(views::kMarginsKey,
                          gfx::Insets().set_right(kLeftRightPadding));
  header_icon_view_ = header->AddChildView(std::move(image_view));

  back_button_ =
      header->AddChildView(views::CreateVectorImageButtonWithNativeTheme(
          base::BindRepeating(&FedCmAccountSelectionView::OnBackButtonClicked,
                              base::Unretained(owner_)),
          features::IsRoundedIconsEnabled() ? vector_icons::kArrowBackIcon
          : features::IsRoundedIconsEnabled()
              ? vector_icons::kArrowBackIcon
              : vector_icons::kArrowBackOldIcon));
  views::InstallCircleHighlightPathGenerator(back_button_.get());
  back_button_->SetTooltipText(l10n_util::GetStringUTF16(IDS_ACCNAME_BACK));
  back_button_->SetVisible(false);

  int back_button_right_margin = kLeftRightPadding;
  // Set the right margin of the back button so that the back button and
  // the IDP brand icon have the same width. This ensures that the header
  // title does not shift when the user navigates to the consent screen.
  back_button_right_margin =
      std::max(0, back_button_right_margin +
                      header_icon_view_->GetPreferredSize().width() -
                      back_button_->GetPreferredSize().width());
  back_button_->SetProperty(views::kMarginsKey,
                            gfx::Insets().set_right(back_button_right_margin));

  auto* titles_container =
      header->AddChildView(std::make_unique<views::View>());
  titles_container
      ->SetLayoutManager(std::make_unique<views::BoxLayout>(
          views::BoxLayout::Orientation::kVertical))
      ->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
  views::FlexSpecification flex_spec(views::LayoutOrientation::kHorizontal,
                                     views::MinimumFlexSizeRule::kScaleToZero,
                                     views::MaximumFlexSizeRule::kUnbounded);
  titles_container->SetProperty(views::kFlexBehaviorKey, flex_spec);

  // Add the title.
  title_label_ = titles_container->AddChildView(std::make_unique<views::Label>(
      title_, views::style::CONTEXT_DIALOG_BODY_TEXT,
      views::style::STYLE_PRIMARY));
  SetLabelProperties(title_label_);

  // Add the subtitle.
  subtitle_label_ =
      titles_container->AddChildView(std::make_unique<views::Label>(
          subtitle_, views::style::CONTEXT_DIALOG_BODY_TEXT,
          views::style::STYLE_SECONDARY));
  SetLabelProperties(subtitle_label_);
  subtitle_label_->SetVisible(!subtitle_.empty());

  // Add the close button.
  std::unique_ptr<views::Button> close_button =
      views::BubbleFrameView::CreateCloseButton(
          base::BindRepeating(&FedCmAccountSelectionView::OnCloseButtonClicked,
                              base::Unretained(owner_)));
  close_button->SetVisible(true);
  header->AddChildView(std::move(close_button));

  return header;
}

std::pair<std::unique_ptr<views::View>, views::MdTextButton*>
AccountSelectionBubbleView::CreateSingleAccountChooser(
    const IdentityRequestAccountPtr& account) {
  auto row = std::make_unique<views::View>();
  row->SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical,
      gfx::Insets::VH(0, kLeftRightPadding), kVerticalSpacing));
  CHECK(!account->is_filtered_out);
  row->AddChildView(CreateAccountRow(account,
                                     /*clickable_position=*/std::nullopt,
                                     /*should_include_idp=*/false));

  // Prefer using the given name if it is provided, otherwise fallback to name,
  // unless that is disabled.
  std::u16string button_title = l10n_util::GetStringUTF16(IDS_SIGNIN_CONTINUE);
  if (!account->given_name.empty()) {
    button_title = l10n_util::GetStringFUTF16(
        IDS_ACCOUNT_SELECTION_CONTINUE, base::UTF8ToUTF16(account->given_name));
  }
  const content::IdentityProviderData& idp_data = *account->identity_provider;
  const content::IdentityProviderMetadata& idp_metadata = idp_data.idp_metadata;
  // We can pass crefs to OnAccountSelected because the `observer_` owns the
  // data.
  auto button = std::make_unique<ContinueButton>(
      base::BindRepeating(
          base::IgnoreResult(&FedCmAccountSelectionView::OnAccountSelected),
          base::Unretained(owner_), account),
      button_title, idp_metadata,
      base::UTF8ToUTF16(account->display_identifier));
  views::MdTextButton* button_ptr = button.get();
  row->AddChildView(std::move(button));

  // Do not add disclosure text if fields is empty.
  if (account->fields.empty()) {
    return std::make_pair(std::move(row), button_ptr);
  }

  // Add disclosure text.
  row->AddChildView(CreateDisclosureLabel(account));
  return std::make_pair(std::move(row), button_ptr);
}

void AccountSelectionBubbleView::AddSeparatorAndMultipleAccountChooser(
    const std::vector<IdentityRequestAccountPtr>& accounts,
    const std::vector<IdentityProviderDataPtr>& idp_list) {
  auto scroll_view = std::make_unique<views::ScrollView>();

  scroll_view->SetHorizontalScrollBarMode(
      views::ScrollView::ScrollBarMode::kDisabled);
  views::View* const scroller_content =
      scroll_view->SetContents(std::make_unique<views::View>());
  scroller_content->SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical));
  bool is_multi_idp = idp_list.size() > 1u;
  AddAccounts(accounts, scroller_content, is_multi_idp);
  size_t num_rows = accounts.size();
  std::optional<int> separator_size;
  // The size of the first button that prompts the user to login to IDP. This
  // may be either a "use a different account" due to filtered out accounts or a
  // mismatch login button.
  std::optional<int> first_login_button_size;

  // Add use other accounts due to filtered accounts or mismatches.
  for (const auto& idp_data : idp_list) {
    if (!idp_data->idp_metadata.has_filtered_out_account &&
        !idp_data->has_login_status_mismatch) {
      continue;
    }
    auto login_button =
        is_multi_idp
            ? CreateMultiIdpLoginRow(
                  base::UTF8ToUTF16(idp_data->idp_for_display), idp_data)
            : CreateSingleIdpUseOtherAccountButton(
                  idp_data->idp_metadata,
                  l10n_util::GetStringUTF16(
                      IDS_ACCOUNT_SELECTION_USE_OTHER_ACCOUNT),
                  kSingleIdpUseOtherAccountButtonIconMargin);
    if (accounts.size() > 0 && !first_login_button_size) {
      separator_size =
          AddLoginButtonSeparator(scroller_content, is_multi_idp, login_button);
      first_login_button_size = login_button->GetPreferredSize().height();
    }
    scroller_content->AddChildView(std::move(login_button));
    ++num_rows;
  }

  CHECK(num_rows > 0);
  bool starts_with_scroller = false;
  // The maximum height that the multi-account-picker can have. This value was
  // chosen so that if there are more than two accounts, the picker will show up
  // as a scrollbar showing 2 accounts plus half of the third one. Note that
  // this is an estimate if there are multiple IDPs, as IDP rows are not the
  // same height. That said, calling GetPreferredSize() is expensive so we are
  // ok with this estimate.
  float num_visible_rows = kNumVisibleRows;
  const int first_row_size =
      scroller_content->children()[0]->GetPreferredSize().height();
  int clipped_size = static_cast<int>(first_row_size * kNumVisibleRows);
  // When there are account rows but not enough to cover the visible rows, add
  // the mismatch size so that the scroller does not end awkwardly.
  if (0 < accounts.size() && accounts.size() < num_visible_rows &&
      first_login_button_size) {
    clipped_size += *separator_size + *first_login_button_size;
    ++num_visible_rows;
  }
  scroll_view->ClipHeightTo(0, clipped_size);
  if (num_rows > num_visible_rows) {
    starts_with_scroller = true;
  } else {
    // We will have some spacing between the scroller and the separator at the
    // top but we need some additional spacing to match the bottom margin,
    // which is slightly larger in single IDP case.
    scroll_view->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
        is_multi_idp ? kVerticalSpacing - kTopBottomPadding : kVerticalSpacing,
        0, 0, 0)));
  }

  // We use a container for most of the contents here. If there is a scroller at
  // the start, we include the separator so that there is no spacing between the
  // separator and the scroller. And we also always include the accounts
  // followed by the IDP mismatches.
  auto container = std::make_unique<views::View>();
  container->SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical));
  auto separator = std::make_unique<views::Separator>();
  if (!starts_with_scroller) {
    separator->SetBorder(views::CreateEmptyBorder(
        gfx::Insets::TLBR(0, 0, kTopBottomPadding, 0)));
  }
  container->AddChildView(std::move(separator));
  container->AddChildView(std::move(scroll_view));
  AddChildView(std::move(container));
}

void AccountSelectionBubbleView::AddAccounts(
    const std::vector<IdentityRequestAccountPtr>& accounts,
    views::View* accounts_content,
    bool is_multi_idp) {
  int out_position = 0;
  if (!is_multi_idp) {
    for (const auto& account : accounts) {
      accounts_content->AddChildView(
          CreateAccountRow(account, /*clickable_position=*/out_position++,
                           /*should_include_idp=*/false));
    }
    return;
  }
  for (const auto& account : accounts) {
    // We notify the user that the account has been used in the past based on
    // the IdP's knowledge, e.g. `approved_clients` (or the browser knowledge if
    // that one is not present).
    std::optional<std::u16string> used_string =
        account->idp_claimed_login_state.value_or(
            account->browser_trusted_login_state) ==
                Account::LoginState::kSignIn
            ? std::make_optional<std::u16string>(
                  l10n_util::GetStringUTF16(IDS_USED_ON_THIS_SITE))
            : std::nullopt;
    accounts_content->AddChildView(
        CreateAccountRow(account, /*clickable_position=*/out_position++,
                         /*should_include_idp=*/true, /*is_modal_dialog=*/false,
                         /*additional_vertical_padding=*/0, used_string));
  }
}

std::unique_ptr<views::View> AccountSelectionBubbleView::CreateMultiIdpLoginRow(
    const std::u16string& idp_for_display,
    const IdentityProviderDataPtr& idp_data) {
  auto image_view = std::make_unique<BrandIconImageView>(kMultiIdpIconSize);
  image_view->SetVisible(!idp_data->idp_metadata.brand_decoded_icon.IsEmpty());
  image_view->SetBrandIconImage(idp_data->idp_metadata.brand_decoded_icon,
                                /*should_circle_crop=*/true);

  auto button = std::make_unique<HoverButton>(
      base::BindRepeating(&FedCmAccountSelectionView::OnLoginToIdP,
                          base::Unretained(owner_),
                          idp_data->idp_metadata.config_url,
                          idp_data->idp_metadata.idp_login_url),
      std::move(image_view),
      l10n_util::GetStringFUTF16(
          IDS_ACCOUNT_SELECTION_USE_OTHER_ACCOUNT_MULTI_IDP, idp_for_display),
      /*subtitle=*/std::u16string(),
      /*secondary_view=*/
      std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
          features::IsRoundedIconsEnabled() ? kOpenInNewIcon
                                            : kOpenInNewOldIcon,
          ui::kColorMenuIcon, kBubbleIdpIconSize)));
  button->SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(
      /*vertical=*/kMultiIdpVerticalSpacing,
      /*horizontal=*/kLeftRightPadding)));
  button->SetIconHorizontalMargins(kMultiIdpIconLeftMargin,
                                   kMultiIdpIconRightMargin);
  button->AddExtraAccessibleText(
      l10n_util::GetStringUTF16(IDS_ACCOUNT_SELECTION_OPENS_IN_NEW_TAB));
  return button;
}

std::unique_ptr<views::View>
AccountSelectionBubbleView::CreateSingleIdpUseOtherAccountButton(
    const content::IdentityProviderMetadata& idp_metadata,
    const std::u16string& title,
    int icon_margin) {
  auto icon_view =
      std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
          features::IsRoundedIconsEnabled() ? kOpenInNewIcon
                                            : kOpenInNewOldIcon,
          ui::kColorMenuIcon, kIdpLoginIconSize));
  auto button = std::make_unique<HoverButton>(
      base::BindRepeating(&FedCmAccountSelectionView::OnLoginToIdP,
                          base::Unretained(owner_), idp_metadata.config_url,
                          idp_metadata.idp_login_url),
      std::move(icon_view), title);
  button->SetIconHorizontalMargins(icon_margin, icon_margin);
  button->AddExtraAccessibleText(
      l10n_util::GetStringUTF16(IDS_ACCOUNT_SELECTION_OPENS_IN_NEW_TAB));
  return button;
}

void AccountSelectionBubbleView::UpdateHeader(
    const gfx::Image& idp_image,
    const std::u16string& title,
    const std::u16string& subtitle,
    bool show_back_button,
    bool should_circle_crop_header_icon) {
  back_button_->SetVisible(show_back_button);
  // The back button takes the place of the brand icon, if it is shown. By
  // default, we show placeholder brand icon prior to brand icon being fetched
  // so that header text wrapping does not change when brand icon is fetched.
  // Therefore, we need to hide the brand icon if the image is empty.
  if (show_back_button || idp_image.IsEmpty()) {
    header_icon_view_->SetVisible(false);
  } else {
    header_icon_view_->SetBrandIconImage(idp_image,
                                         should_circle_crop_header_icon);
    header_icon_view_->SetVisible(true);
  }
  if (title != title_) {
    title_ = title;
    title_label_->SetText(title_);
    // TODO(crbug.com/390581529): Make this work properly with subtitles.
    if (auto* widget = GetWidget()) {
      widget->widget_delegate()->SetAccessibleTitle(title_);
    }
    // The title label is not destroyed, so announce it manually.
    SendAccessibilityEvent(GetWidget(), title_);
  }
  if (subtitle != subtitle_) {
    subtitle_ = subtitle;
    subtitle_label_->SetText(subtitle_);
    subtitle_label_->SetVisible(!subtitle_.empty());
  }
}

void AccountSelectionBubbleView::RemoveNonHeaderChildViews() {
  const std::vector<raw_ptr<views::View, VectorExperimental>> child_views =
      children();
  for (views::View* child_view : child_views) {
    if (child_view != header_view_) {
      RemoveChildViewT(child_view);
    }
  }
}

BEGIN_METADATA(AccountSelectionBubbleView)
END_METADATA

}  // namespace webid
