// Copyright 2019 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/profiles/profile_menu_view_base.h"

#include <algorithm>
#include <memory>
#include <utility>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/metrics/histogram_functions.h"
#include "base/scoped_observation.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/signin_ui_util.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/profiles/profile_colors_util.h"
#include "chrome/browser/ui/profiles/profile_view_utils.h"
#include "chrome/browser/ui/views/controls/hover_button.h"
#include "chrome/browser/ui/views/profiles/avatar_badge_view.h"
#include "components/supervised_user/core/browser/family_link_user_capabilities.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.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/themed_vector_icon.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/compositor/layer.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/animation/ink_drop_host.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/layout_manager.h"
#include "ui/views/layout/table_layout.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/style/typography.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_tracker.h"

#if !BUILDFLAG(IS_CHROMEOS)
#endif  // !BUILDFLAG(IS_CHROMEOS)

namespace {

// Helpers --------------------------------------------------------------------

constexpr int kMenuWidth = 328;
constexpr int kMaxImageSize = ProfileMenuViewBase::kIdentityImageSize;
constexpr int kDefaultMargin = 8;
constexpr int kManagementHeaderIconLabelSpacing = 6;

// If the bubble is too large to fit on the screen, it still needs to be at
// least this tall to show one row.
constexpr int kMinimumScrollableContentHeight = 40;

// Spacing between the edge of the user menu and the top/bottom or left/right of
// the menu items.
constexpr int kMenuEdgeMargin = 16;

// Empty space between the rounded rectangle (outside) and menu edge.
constexpr int kIdentityContainerMargin = 12;

// Additional empty space between the menu item (e.g. icon or label) and the
// edge menu margin.
constexpr int kMenuItemLeftInternalPadding = 12;

constexpr char kProfileMenuClickedActionableItemHistogram[] =
    "Profile.Menu.ClickedActionableItem";
constexpr char kProfileMenuClickedActionableItemSupervisedHistogram[] =
    "Profile.Menu.ClickedActionableItem_Supervised";

gfx::ImageSkia SizeImage(const gfx::ImageSkia& image, int size) {
  return gfx::ImageSkiaOperations::CreateResizedImage(
      image, skia::ImageOperations::RESIZE_BEST, gfx::Size(size, size));
}

gfx::ImageSkia ColorImage(const gfx::ImageSkia& image, SkColor color) {
  return gfx::ImageSkiaOperations::CreateColorMask(image, color);
}

std::unique_ptr<views::BoxLayout> CreateBoxLayout(
    views::BoxLayout::Orientation orientation,
    views::BoxLayout::CrossAxisAlignment cross_axis_alignment,
    gfx::Insets insets = gfx::Insets()) {
  auto layout = std::make_unique<views::BoxLayout>(orientation, insets);
  layout->set_cross_axis_alignment(cross_axis_alignment);
  return layout;
}

const gfx::ImageSkia ImageForMenu(const gfx::VectorIcon& icon,
                                  float icon_to_image_ratio,
                                  SkColor color) {
  const int padding =
      static_cast<int>(kMaxImageSize * (1.0f - icon_to_image_ratio) / 2.0f);

  gfx::ImageSkia sized_icon =
      gfx::CreateVectorIcon(icon, kMaxImageSize - 2 * padding, color);
  return gfx::CanvasImageSource::CreatePadded(sized_icon, gfx::Insets(padding));
}

class FeatureButtonIconView : public views::ImageView {
  METADATA_HEADER(FeatureButtonIconView, views::ImageView)

 public:
  FeatureButtonIconView(const gfx::VectorIcon& icon, float icon_to_image_ratio)
      : icon_(icon), icon_to_image_ratio_(icon_to_image_ratio) {}
  ~FeatureButtonIconView() override = default;

  void UpdateColor(ui::ColorId color_id) {
    constexpr int kIconSize = 16;
    const SkColor icon_color = GetColorProvider()->GetColor(color_id);
    gfx::ImageSkia image =
        ImageForMenu(*icon_, icon_to_image_ratio_, icon_color);
    SetImage(ui::ImageModel::FromImageSkia(
        SizeImage(ColorImage(image, icon_color), kIconSize)));
  }

  // views::ImageView:
  void OnThemeChanged() override {
    views::ImageView::OnThemeChanged();
    UpdateColor(ui::kColorIcon);
  }

 private:
  const raw_ref<const gfx::VectorIcon> icon_;
  const float icon_to_image_ratio_;
};

BEGIN_METADATA(FeatureButtonIconView)
END_METADATA

// AvatarImageView is used to ensure avatar adornments are kept in sync with
// current theme colors.
class AvatarImageView : public views::ImageView {
  METADATA_HEADER(AvatarImageView, views::ImageView)

 public:
  AvatarImageView(const ui::ImageModel& avatar_image,
                  int image_size,
                  int border_size,
                  ProfileMenuViewBase::AvatarRingType avatar_ring)
      : avatar_image_(avatar_image),
        image_size_(image_size),
        border_size_(border_size),
        avatar_ring_(avatar_ring) {
    if (avatar_image_.IsEmpty()) {
      // This can happen if the account image hasn't been fetched yet, if there
      // is no image, or in tests.
      avatar_image_ = ui::ImageModel::FromVectorIcon(
          features::IsRoundedIconsEnabled() ? kAccountCircleIcon
                                            : kUserAccountAvatarOldIcon,
          ui::kColorMenuIcon, image_size_);
    }
  }

  // views::ImageView:
  void OnThemeChanged() override {
    ImageView::OnThemeChanged();
    DCHECK(!avatar_image_.IsEmpty());
    ui::ColorProvider* color_provider = GetColorProvider();
    CHECK(color_provider);

    gfx::ImageSkia sized_avatar_image;
    bool should_crop = true;

    switch (avatar_ring_) {
      case ProfileMenuViewBase::AvatarRingType::kDotted: {
        const int size_with_border = image_size_ + 2 * border_size_;
        sized_avatar_image = profiles::GetAvatarWithDottedRing(
            avatar_image_, size_with_border, /*has_padding=*/true,
            /*has_background=*/true, *color_provider);
        // Dotted ring avatar does not support a border, as the border is
        // already included with the dotted ring.
        CHECK_EQ(border_size_, 0);
        break;
      }
      case ProfileMenuViewBase::AvatarRingType::kGradient: {
        // Keep the avatar's size identical with the no-ring case, the ring
        // expands outwards.
        sized_avatar_image = AddLinearGradientRingToAvatar(
            avatar_image_, *color_provider, image_size_);
        should_crop = false;
        break;
      }
      case ProfileMenuViewBase::AvatarRingType::kNone: {
        if (border_size_ > 0) {
          // Total image size is `image_size_ + 2 * border_size_`.
          ui::ImageModel sized_avatar_image_without_border =
              ProfileMenuViewBase::GetCircularSizedImage(avatar_image_,
                                                         image_size_);
          sized_avatar_image = gfx::CanvasImageSource::CreatePadded(
              sized_avatar_image_without_border.Rasterize(color_provider),
              gfx::Insets(border_size_));
        } else {
          sized_avatar_image =
              profiles::GetSizedAvatarImageModel(avatar_image_, image_size_)
                  .Rasterize(color_provider);
        }
        sized_avatar_image = profiles::AddBackgroundToImage(
            sized_avatar_image, GetBackgroundColor());
        break;
      }
    }

    if (should_crop) {
      gfx::Image circular_sized_avatar_image = profiles::GetSizedAvatarIcon(
          gfx::Image(sized_avatar_image), sized_avatar_image.size().width(),
          sized_avatar_image.size().height(),
          profiles::AvatarShape::SHAPE_CIRCLE);
      sized_avatar_image = *circular_sized_avatar_image.ToImageSkia();
    }

    SetImage(ui::ImageModel::FromImageSkia(sized_avatar_image));
  }

 private:
  SkColor GetBackgroundColor() const {
    return GetColorProvider()->GetColor(ui::kColorBubbleBackground);
  }

  ui::ImageModel avatar_image_;
  const int image_size_;
  const int border_size_;
  const ProfileMenuViewBase::AvatarRingType avatar_ring_;
};

BEGIN_METADATA(AvatarImageView)
END_METADATA

// An extension of HoverButton so that the color pattern on focus matches
// the other menus' colors.
class MenuButtonRowView : public HoverButton {
  METADATA_HEADER(MenuButtonRowView, HoverButton)

 public:
  MenuButtonRowView(PressedCallback callback,
                    std::unique_ptr<views::View> icon_view,
                    const std::u16string& title_text,
                    int icon_offset,
                    std::unique_ptr<views::View> badge_view = nullptr)
      : HoverButton(std::move(callback), [&]() {
          HoverButton::Params params;
          params.icon_view = std::move(icon_view);
          params.title = title_text;
          params.secondary_view = std::move(badge_view);
          params.add_vertical_label_spacing = false;
          params.icon_vertical_offset = -icon_offset;
          params.icon_label_spacing -= icon_offset;
          return params;
        }()) {
    SetIconHorizontalMargins(kMenuItemLeftInternalPadding - icon_offset,
                             /*right=*/0);
    // Instead of creating the highlight with an InkDrop, which paints a layer
    // over this, we paint the highlight directly to the background.
    views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::OFF);
    title()->SetEnabledColor(ui::kColorMenuItemForeground);
    title()->SetBackgroundColor(kColorProfileMenuBackground);

    if (secondary_view()) {
      const gfx::Insets* margin =
          secondary_view()->GetProperty(views::kMarginsKey);
      if (margin) {
        secondary_view()->SetProperty(
            views::kMarginsKey,
            gfx::Insets::TLBR(0, margin->left(), 0, margin->right()));
      }
    }
  }
  ~MenuButtonRowView() override = default;

  // Note: only focus handling is necessary, because `HoverButton` automatically
  // requests focus when hovered.
  //
  // HoverButton:
  void OnFocus() override {
    HoverButton::OnFocus();
    title()->SetEnabledColor(ui::kColorMenuItemForegroundSelected);
    title()->SetBackgroundColor(ui::kColorMenuItemBackgroundSelected);
    if (auto* feature_icon =
            views::AsViewClass<FeatureButtonIconView>(icon_view())) {
      feature_icon->UpdateColor(ui::kColorMenuItemForegroundSelected);
    }
  }

  void OnBlur() override {
    HoverButton::OnBlur();
    title()->SetEnabledColor(ui::kColorMenuItemForeground);
    title()->SetBackgroundColor(kColorProfileMenuBackground);
    if (auto* feature_icon =
            views::AsViewClass<FeatureButtonIconView>(icon_view())) {
      feature_icon->UpdateColor(ui::kColorIcon);
    }
  }

  void OnPaintBackground(gfx::Canvas* canvas) override {
    if (HasFocus()) {
      cc::PaintFlags flags;
      flags.setAntiAlias(true);
      flags.setColor(
          GetColorProvider()->GetColor(ui::kColorMenuItemBackgroundSelected));
      canvas->DrawRect(GetLocalBounds(), flags);
    }
  }
};

BEGIN_METADATA(MenuButtonRowView)
END_METADATA

class ProfileMenuNewBadge : public views::View {
  METADATA_HEADER(ProfileMenuNewBadge, views::View)

 public:
  ProfileMenuNewBadge() {
    SetLayoutManager(std::make_unique<views::FillLayout>());
    auto* label = AddChildView(std::make_unique<views::Label>(
        l10n_util::GetStringUTF16(IDS_NEW_BADGE), views::style::CONTEXT_LABEL,
        views::style::STYLE_SECONDARY));

    // Use a smaller, lighter font for the badge to match standard app menu
    // styling.
    label->SetFontList(label->font_list().Derive(-1, gfx::Font::NORMAL,
                                                 gfx::Font::Weight::MEDIUM));
    label->SetLineHeight(label->font_list().GetHeight());
    label->SetEnabledColor(ui::kColorBadgeForeground);
    label->SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(0, 4)));

    SetBackground(views::CreateRoundedRectBackground(
        ui::kColorBadgeBackground,
        views::LayoutProvider::Get()->GetCornerRadiusMetric(
            views::ShapeContextTokens::kBadgeRadius)));
  }
};

BEGIN_METADATA(ProfileMenuNewBadge)
END_METADATA

}  // namespace

ui::ImageModel ProfileMenuViewBase::GetCircularSizedImage(
    const ui::ImageModel& image_model,
    int size) {
  // Resize.
  ui::ImageModel resized =
      profiles::GetSizedAvatarImageModel(image_model, size);
  // It is assumed that vector icons are already fitting in a circle. Only crop
  // images.
  if (!resized.IsImage()) {
    return resized;
  }
  return ui::ImageModel::FromImage(GetSizedAvatarIcon(
      resized.GetImage(), size, size, profiles::AvatarShape::SHAPE_CIRCLE));
}

ProfileMenuViewBase::IdentitySectionParams::IdentitySectionParams() = default;
ProfileMenuViewBase::IdentitySectionParams::~IdentitySectionParams() = default;
ProfileMenuViewBase::IdentitySectionParams::IdentitySectionParams(
    IdentitySectionParams&&) = default;
ProfileMenuViewBase::IdentitySectionParams&
ProfileMenuViewBase::IdentitySectionParams::operator=(IdentitySectionParams&&) =
    default;

// ProfileMenuViewBase ---------------------------------------------------------

// Despite ProfileMenuViewBase being a dialog, we are enforcing it to behave
// like a menu from the accessibility POV because it fits better with a menu UX.
// The dialog exposes the kMenuBar role, and the top-level container is kMenu.
// This class is responsible for emitting menu accessible events when the dialog
// is activated or deactivated.
class ProfileMenuViewBase::AXMenuWidgetObserver : public views::WidgetObserver {
 public:
  AXMenuWidgetObserver(ProfileMenuViewBase* owner, views::Widget* widget)
      : owner_(owner) {
    observation_.Observe(widget);
  }
  ~AXMenuWidgetObserver() override = default;

  void OnWidgetActivationChanged(views::Widget* widget, bool active) override {
    views::ViewTracker tracker(owner_.get());
    if (active) {
      owner_->NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuStart,
                                                 true);
      if (!tracker.view()) {
        return;
      }
      owner_->NotifyAccessibilityEventDeprecated(
          ax::mojom::Event::kMenuPopupStart, true);
    } else {
      owner_->NotifyAccessibilityEventDeprecated(
          ax::mojom::Event::kMenuPopupEnd, true);
      if (!tracker.view()) {
        return;
      }
      owner_->NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuEnd,
                                                 true);
    }
  }

 private:
  raw_ptr<ProfileMenuViewBase> owner_;
  base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{
      this};
};

ProfileMenuViewBase::ProfileMenuViewBase(views::BubbleAnchor anchor_element,
                                         BrowserWindowInterface* browser)
    : BubbleDialogDelegateView(anchor_element, views::BubbleBorder::TOP_RIGHT),
      profile_(raw_ref<Profile>::from_ptr(browser->GetProfile())),
      close_bubble_helper_(this, browser->GetTabStripModel()) {
  SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone));
  set_margins(gfx::Insets(0));
  DCHECK(!anchor_element.IsNull());

  if (ui::TrackedElement* tracked_element = anchor_element.GetIfElement();
      tracked_element && tracked_element->IsA<views::TrackedElementViews>()) {
    anchor_view_.SetView(
        tracked_element->AsA<views::TrackedElementViews>()->view());
  } else if (views::View* anchor_view = anchor_element.GetIfView()) {
    anchor_view_.SetView(anchor_view);
  }
  if (views::View* view = anchor_view_.view()) {
    views::InkDrop::Get(view)->AnimateToState(views::InkDropState::ACTIVATED,
                                              nullptr);
  }

  SetEnableArrowKeyTraversal(true);

  // TODO(crbug.com/40230528): Using `SetAccessibleWindowRole(kMenu)` here will
  // result in screenreader to announce the menu having only one item. This is
  // probably because this API sets the a11y role for the widget, but not root
  // view in it. This is confusing and prone to misuse. We should unify the two
  // sets of API for BubbleDialogDelegateView.
  GetViewAccessibility().SetRole(ax::mojom::Role::kMenu);

  RegisterWindowClosingCallback(base::BindOnce(
      &ProfileMenuViewBase::OnWindowClosing, weak_factory_.GetWeakPtr()));

  SetBackground(views::CreateSolidBackground(kColorProfileMenuBackground));
}

ProfileMenuViewBase::~ProfileMenuViewBase() = default;

void ProfileMenuViewBase::SetProfileIdentityWithCallToAction(
    IdentitySectionParams params) {
  constexpr int kHeaderVerticalSize = 36;
  constexpr int kHeaderImageSize = 16;
  constexpr int kIdentityContainerHorizontalPadding = 24;
  constexpr int kAvatarTopMargin = 24;
  constexpr int kTitleTopMargin = 8;
  constexpr int kBottomMarginWhenNoButton = 24;
  constexpr int kSubtitleBottomMarginInfoBelow = 4;
  constexpr int kSubtitleBottomMarginButtonBelow = 12;
  constexpr int kButtonBottomMargin = 28;
  constexpr int kAvatarBadgeTopMargin = 8;

  // Vertical view structure when all elements are present. Square brackets []
  // represent empty space:
  //
  // Optional header:
  //     HoverButton: (size: kHeaderVerticalSize)
  //     Horizontal Separator
  // [kAvatarTopMargin]
  // Image: Avatar (size: kIdentityInfoImageSize)
  // [kTitleTopMargin]
  // Label: Title
  // Optional:
  //     Label: Email Subtitle (optional)
  //     [kSubtitleBottomMarginInfoBelow] or [kSubtitleBottomMarginButtonBelow]
  //     Label: Subtitle (optional)
  //     [kSubtitleBottomMarginButtonBelow] or [kBottomMarginWhenNoButton]
  // Optional:
  //     Button: maybe with an image inside
  //     [kButtonBottomMargin]
  //
  // Note: If a button is present, a subtitle must also be present. The layout
  // does not support a button without subtitle.

  identity_info_container_->RemoveAllChildViews();
  // Vertical BoxLayout.
  auto box_layout =
      CreateBoxLayout(views::BoxLayout::Orientation::kVertical,
                      views::BoxLayout::CrossAxisAlignment::kCenter);
  box_layout->SetCollapseMarginsSpacing(true);
  identity_info_container_->SetLayoutManager(std::move(box_layout));

  // Paint to a layer with rounded corners. This ensures that no element can
  // draw outside of the rounded corners, even if they use layers. This is
  // needed in particular for the HoverButton highlight.
  identity_info_container_->SetPaintToLayer();
  identity_info_container_->layer()->SetRoundedCornerRadius(
      gfx::RoundedCornersF(views::LayoutProvider::Get()->GetCornerRadiusMetric(
          views::Emphasis::kHigh)));

  // No need to set rounded corners on the background, because the container
  // is painted in a layer that has rounded corners already.
  identity_info_container_->SetBackground(
      views::CreateSolidBackground(kColorProfileMenuIdentityInfoBackground));

  // Space around the rectangle, between the rectangle and the menu edge.
  identity_info_container_->SetProperty(views::kMarginsKey,
                                        gfx::Insets(kIdentityContainerMargin));

  if (!params.header_string.empty() && !params.header_image.IsEmpty()) {
    // Header.
    auto hover_button = std::make_unique<HoverButton>(
        std::move(params.header_action),
        std::make_unique<views::ImageView>(
            GetCircularSizedImage(params.header_image, kHeaderImageSize)),
        params.header_string, std::u16string(), nullptr, true, std::u16string(),
        kManagementHeaderIconLabelSpacing);
    hover_button->SetPreferredSize(gfx::Size(
        kMenuWidth - 2 * kIdentityContainerMargin, kHeaderVerticalSize));
    hover_button->SetIconHorizontalMargins(0, 0);
    hover_button->title()->SetTextStyle(views::style::STYLE_BODY_5);

    // Swap the layout manager so that the text is centered.
    auto hover_button_box_layout = std::make_unique<views::BoxLayout>(
        views::BoxLayout::Orientation::kHorizontal);
    hover_button_box_layout->set_main_axis_alignment(
        views::LayoutAlignment::kCenter);
    hover_button->SetLayoutManager(std::move(hover_button_box_layout));
    identity_info_container_->AddChildView(std::move(hover_button));

    // Separator.
    identity_info_container_->AddChildView(
        views::Builder<views::Separator>()
            .SetColorId(kColorProfileMenuBackground)
            .SetPreferredSize(
                gfx::Size(kMenuWidth, views::Separator::kThickness))
            .Build());
  }

  // Avatar.
  identity_info_container_->AddChildView(
      views::Builder<views::View>(
          std::make_unique<AvatarImageView>(
              params.profile_image,
              kIdentityInfoImageSize - 2 * params.profile_image_padding,
              params.profile_image_padding, params.avatar_ring))
          .SetProperty(views::kMarginsKey,
                       gfx::Insets().set_top(kAvatarTopMargin))
          .Build());

  // Optional subscription badge.
  if (params.avatar_ring == AvatarRingType::kGradient) {
    if (auto badge_view = GetAvatarBadgeView(params.badge_label)) {
      badge_view->SetProperty(
          views::kMarginsKey,
          gfx::Insets::TLBR(kAvatarBadgeTopMargin, 0, 0, 0));
      identity_info_container_->AddChildView(std::move(badge_view));
    }
  }

  // Title.
  const bool has_any_subtitle =
      !params.subtitle.empty() || !params.email_subtitle.empty();
  const bool has_button = !params.button_text.empty();
  const int title_bottom_margin =
      has_any_subtitle ? 0 : kBottomMarginWhenNoButton;
  identity_info_container_->AddChildView(
      views::Builder<views::Label>()
          .SetText(params.title)
          .SetTextContext(views::style::CONTEXT_LABEL)
          .SetTextStyle(views::style::STYLE_BODY_3_MEDIUM)
          .SetElideBehavior(gfx::ELIDE_TAIL)
          .SetProperty(views::kMarginsKey,
                       gfx::Insets::TLBR(kTitleTopMargin,
                                         kIdentityContainerHorizontalPadding,
                                         title_bottom_margin,
                                         kIdentityContainerHorizontalPadding))
          .SetEnabledColor(kColorProfileMenuIdentityInfoTitle)
          .Build());
  if (!has_any_subtitle) {
    CHECK(!has_button);
    return;
  }

  // Subtitle.

  // Set the subtitle as the name of the parent container, so accessibility
  // tools can read it together with the button text. The role change is
  // required by Windows ATs.
  identity_info_container_->GetViewAccessibility().SetRole(
      ax::mojom::Role::kGroup);
  identity_info_container_->GetViewAccessibility().SetName(
      !params.subtitle.empty() ? params.subtitle : params.email_subtitle,
      ax::mojom::NameFrom::kAttribute);

  const bool has_subtitle = !params.subtitle.empty();
  if (!params.email_subtitle.empty()) {
    const int email_subtitle_bottom_margin =
        has_subtitle ? kSubtitleBottomMarginInfoBelow
                     : (has_button ? kSubtitleBottomMarginButtonBelow
                                   : kBottomMarginWhenNoButton);
    identity_info_container_->AddChildView(
        views::Builder<views::Label>()
            .SetText(params.email_subtitle)
            .SetTextContext(views::style::CONTEXT_LABEL)
            .SetTextStyle(views::style::STYLE_BODY_4)
            .SetHandlesTooltips(false)
            .SetProperty(
                views::kMarginsKey,
                gfx::Insets::TLBR(0, kIdentityContainerHorizontalPadding,
                                  email_subtitle_bottom_margin,
                                  kIdentityContainerHorizontalPadding))
            .SetEnabledColor(kColorProfileMenuIdentityInfoSubtitle)
            .Build());
  }

  if (has_subtitle) {
    const int subtitle_bottom_margin = has_button
                                           ? kSubtitleBottomMarginButtonBelow
                                           : kBottomMarginWhenNoButton;
    identity_info_container_->AddChildView(
        views::Builder<views::Label>()
            .SetText(params.subtitle)
            .SetTextContext(views::style::CONTEXT_LABEL)
            .SetTextStyle(views::style::STYLE_BODY_4)
            .SetMultiLine(true)
            .SetHandlesTooltips(false)
            .SetProperty(
                views::kMarginsKey,
                gfx::Insets::TLBR(0, kIdentityContainerHorizontalPadding,
                                  subtitle_bottom_margin,
                                  kIdentityContainerHorizontalPadding))
            .SetEnabledColor(kColorProfileMenuIdentityInfoSubtitle)
            .Build());
  }

  if (!has_button) {
    return;
  }

  // Button.
  identity_button_ = identity_info_container_->AddChildView(
      views::Builder<views::MdTextButton>()
          .SetText(params.button_text)
          .SetCallback(base::BindRepeating(&ProfileMenuViewBase::ButtonPressed,
                                           base::Unretained(this),
                                           std::move(params.button_action)))
          .SetStyle(ui::ButtonStyle::kProminent)
          .SetProperty(views::kMarginsKey,
                       gfx::Insets().set_bottom(kButtonBottomMargin))
          .SetImageModel(views::Button::STATE_NORMAL, params.button_image)
          .Build());
}

void ProfileMenuViewBase::AddFeatureButton(const std::u16string& text,
                                           base::RepeatingClosure action,
                                           const gfx::VectorIcon& icon,
                                           float icon_to_image_ratio,
                                           bool is_new) {
  // Initialize layout if this is the first time a button is added.
  if (!features_container_->GetLayoutManager()) {
    features_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
        views::BoxLayout::Orientation::kVertical));
  }

  std::unique_ptr<views::View> secondary_view;
  if (is_new) {
    secondary_view = std::make_unique<ProfileMenuNewBadge>();
  }

  std::unique_ptr<HoverButton> button = CreateMenuRowButton(
      std::move(action),
      std::make_unique<FeatureButtonIconView>(icon, icon_to_image_ratio), text,
      /*icon_offset=*/0, std::move(secondary_view));

  if (is_new) {
    button->AddExtraAccessibleText(
        l10n_util::GetStringUTF16(IDS_NEW_BADGE_SCREEN_READER_MESSAGE));
  }

  features_container_->AddChildView(std::move(button));
}

void ProfileMenuViewBase::SetProfileManagementHeading(
    const std::u16string& heading) {
  profile_mgmt_heading_ = heading;

  // Add separator before heading.
  profile_mgmt_separator_container_->RemoveAllChildViews();
  profile_mgmt_separator_container_->SetLayoutManager(
      std::make_unique<views::FillLayout>());
  profile_mgmt_separator_container_->SetBorder(
      views::CreateEmptyBorder(gfx::Insets::VH(kDefaultMargin, 0)));
  profile_mgmt_separator_container_->AddChildView(
      std::make_unique<views::Separator>());

  // Initialize heading layout.
  profile_mgmt_heading_container_->RemoveAllChildViews();
  profile_mgmt_heading_container_->SetLayoutManager(
      std::make_unique<views::FillLayout>());
  profile_mgmt_heading_container_->SetBorder(
      views::CreateEmptyBorder(gfx::Insets::TLBR(
          kDefaultMargin, kMenuEdgeMargin + kMenuItemLeftInternalPadding,
          kDefaultMargin, kMenuEdgeMargin)));

  // Add heading.
  views::Label* label = profile_mgmt_heading_container_->AddChildView(
      std::make_unique<views::Label>(heading, views::style::CONTEXT_LABEL,
                                     views::style::STYLE_BODY_3_EMPHASIS));
  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  label->SetHandlesTooltips(false);
}

void ProfileMenuViewBase::AddAvailableProfile(
    const ui::ImageModel& image_model,
    const std::u16string& name,
    bool is_guest,
    base::RepeatingClosure action,
    const std::u16string& extra_accessible_text) {
  // Initialize layout if this is the first time a button is added.
  if (!selectable_profiles_container_->GetLayoutManager()) {
    selectable_profiles_container_->SetLayoutManager(
        std::make_unique<views::BoxLayout>(
            views::BoxLayout::Orientation::kVertical));
    // Give the container an accessible name so accessibility tools can provide
    // context for the buttons inside it. The role change is required by Windows
    // ATs.
    selectable_profiles_container_->GetViewAccessibility().SetRole(
        ax::mojom::Role::kGroup);
    selectable_profiles_container_->GetViewAccessibility().SetName(
        profile_mgmt_heading_, ax::mojom::NameFrom::kAttribute);
  }

  DCHECK(!image_model.IsEmpty());
  const int icon_offset =
      (image_model.Size().width() - kOtherProfileImageSize) / 2;

  std::unique_ptr<HoverButton> button = CreateMenuRowButton(
      std::move(action), std::make_unique<views::ImageView>(image_model), name,
      icon_offset);

  if (!extra_accessible_text.empty()) {
    button->AddExtraAccessibleText(extra_accessible_text);
  }

  views::Button* added_button =
      selectable_profiles_container_->AddChildView(std::move(button));

  if (!is_guest && !first_profile_button_) {
    first_profile_button_ = added_button;
  }
}

void ProfileMenuViewBase::AddProfileManagementFeaturesSeparator() {
  // Add separator before profile management features.
  profile_mgmt_features_separator_container_->RemoveAllChildViews();
  profile_mgmt_features_separator_container_->SetLayoutManager(
      std::make_unique<views::FillLayout>());
  profile_mgmt_features_separator_container_->SetBorder(
      views::CreateEmptyBorder(gfx::Insets::VH(kDefaultMargin, 0)));
  profile_mgmt_features_separator_container_->AddChildView(
      std::make_unique<views::Separator>());
}

void ProfileMenuViewBase::AddProfileManagementFeatureButton(
    const gfx::VectorIcon& icon,
    const std::u16string& text,
    base::RepeatingClosure action) {
  // Initialize layout if this is the first time a button is added.
  AddBottomMargin();

  auto icon_view =
      std::make_unique<FeatureButtonIconView>(icon, /*icon_to_image_ratio=*/1);
  profile_mgmt_features_container_->AddChildView(CreateMenuRowButton(
      std::move(action), std::move(icon_view), text, /*icon_offset=*/0));
}

void ProfileMenuViewBase::AddBottomMargin() {
  // Create an empty container with a bottom margin.
  if (!profile_mgmt_features_container_->GetLayoutManager()) {
    profile_mgmt_features_container_->SetLayoutManager(
        std::make_unique<views::BoxLayout>(
            views::BoxLayout::Orientation::kVertical));
    profile_mgmt_features_container_->SetBorder(
        views::CreateEmptyBorder(gfx::Insets::TLBR(0, 0, kDefaultMargin, 0)));
  }
}

void ProfileMenuViewBase::OnActionableItemClicked(ActionableItem item) {
  actionable_item_clicked_ = true;

  // TODO(tangltom): Separate metrics for incognito and guest menu.
  base::UmaHistogramEnumeration(kProfileMenuClickedActionableItemHistogram,
                                item);
  // Additionally output a version of the metric for supervised users, to allow
  // more fine-grained analysis.
  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForProfileIfExists(&profile());
  if (identity_manager &&
      supervised_user::IsPrimaryAccountSubjectToParentalControls(
          identity_manager) == signin::Tribool::kTrue) {
    base::UmaHistogramEnumeration(
        kProfileMenuClickedActionableItemSupervisedHistogram, item);
  }
}

int ProfileMenuViewBase::GetMaxHeight() const {
  gfx::Rect anchor_rect = GetAnchorRect();
  gfx::Rect screen_space =
      display::Screen::Get()
          ->GetDisplayNearestPoint(anchor_rect.CenterPoint())
          .work_area();
  int available_space = screen_space.bottom() - anchor_rect.bottom();
#if BUILDFLAG(IS_WIN)
  // On Windows the bubble can also be show to the top of the anchor.
  available_space =
      std::max(available_space, anchor_rect.y() - screen_space.y());
#endif
  return std::max(kMinimumScrollableContentHeight, available_space);
}

void ProfileMenuViewBase::Reset() {
  RemoveAllChildViews();

  auto components = std::make_unique<views::View>();
  components->SetLayoutManager(std::make_unique<views::FlexLayout>())
      ->SetOrientation(views::LayoutOrientation::kVertical);

  // Create and add new component containers in the correct order.
  // First, add the parts of the current profile.
  identity_info_container_ =
      components->AddChildView(std::make_unique<views::View>());
  features_container_ =
      components->AddChildView(std::make_unique<views::View>());
  profile_mgmt_separator_container_ =
      components->AddChildView(std::make_unique<views::View>());
  // Second, add the profile management header. This includes the heading and
  // the shortcut feature(s) next to it.
  auto profile_mgmt_header = std::make_unique<views::View>();
  views::BoxLayout* profile_mgmt_header_layout =
      profile_mgmt_header->SetLayoutManager(
          CreateBoxLayout(views::BoxLayout::Orientation::kHorizontal,
                          views::BoxLayout::CrossAxisAlignment::kCenter));
  profile_mgmt_heading_container_ =
      profile_mgmt_header->AddChildView(std::make_unique<views::View>());
  profile_mgmt_header_layout->SetFlexForView(profile_mgmt_heading_container_,
                                             1);
  components->AddChildView(std::move(profile_mgmt_header));
  // Third, add the profile management buttons.
  selectable_profiles_container_ =
      components->AddChildView(std::make_unique<views::View>());
  profile_mgmt_features_separator_container_ =
      components->AddChildView(std::make_unique<views::View>());
  profile_mgmt_features_container_ =
      components->AddChildView(std::make_unique<views::View>());
  first_profile_button_ = nullptr;

  // Create a scroll view to hold the components.
  auto scroll_view = std::make_unique<views::ScrollView>();
  scroll_view->SetHorizontalScrollBarMode(
      views::ScrollView::ScrollBarMode::kDisabled);
  // TODO(crbug.com/41406562): it's a workaround for the crash.
  scroll_view->SetDrawOverflowIndicator(false);
  scroll_view->ClipHeightTo(0, GetMaxHeight());
  scroll_view->SetContents(std::move(components));

  // Create a table layout to set the menu width.
  SetLayoutManager(std::make_unique<views::TableLayout>())
      ->AddColumn(
          views::LayoutAlignment::kStretch, views::LayoutAlignment::kStretch,
          views::TableLayout::kFixedSize,
          views::TableLayout::ColumnSize::kFixed, kMenuWidth, kMenuWidth)
      .AddRows(1, 1.0f);
  AddChildView(std::move(scroll_view));
}

void ProfileMenuViewBase::FocusFirstProfileButton() {
  if (first_profile_button_) {
    first_profile_button_->RequestFocus();
  }
}

void ProfileMenuViewBase::Init() {
  Reset();
  BuildMenu();
}

void ProfileMenuViewBase::OnWindowClosing() {
  if (!anchor_view_) {
    return;
  }

  if (views::View* view = anchor_view_.view()) {
    views::InkDrop::Get(view)->AnimateToState(views::InkDropState::DEACTIVATED,
                                              nullptr);
  }
}

bool ProfileMenuViewBase::HandleContextMenu(
    content::RenderFrameHost& render_frame_host,
    const content::ContextMenuParams& params) {
  // Suppresses the context menu because some features, such as inspecting
  // elements, are not appropriate in a bubble.
  return true;
}

void ProfileMenuViewBase::ButtonPressed(base::RepeatingClosure action) {
  DCHECK(action);
  signin_ui_util::RecordProfileMenuClick(profile());
  action.Run();
}

void ProfileMenuViewBase::CreateAXWidgetObserver(views::Widget* widget) {
  ax_widget_observer_ = std::make_unique<AXMenuWidgetObserver>(this, widget);
}

std::unique_ptr<HoverButton> ProfileMenuViewBase::CreateMenuRowButton(
    base::RepeatingClosure action,
    std::unique_ptr<views::View> icon_view,
    const std::u16string& text,
    int icon_offset,
    std::unique_ptr<views::View> badge_view) {
  CHECK(icon_view);
  return std::make_unique<MenuButtonRowView>(
      base::BindRepeating(&ProfileMenuViewBase::ButtonPressed,
                          base::Unretained(this), std::move(action)),
      std::move(icon_view), text, icon_offset, std::move(badge_view));
}

BEGIN_METADATA(ProfileMenuViewBase)
END_METADATA
