// 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/toolbar/toolbar_action_hover_card_bubble_view.h"

#include <string>
#include <string_view>

#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "chrome/browser/extensions/extension_ui_util.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_model.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/extensions/extension_view_utils.h"
#include "chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/extension_features.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/style/typography.h"
#include "ui/views/view_class_properties.h"

namespace {

using HoverCardState = ToolbarActionViewModel::HoverCardState;

// Hover card fixed width. Toolbar actions are not visible when window is too
// small to display them, therefore hover cards wouldn't be displayed if the
// window is not big enough.
constexpr int kHoverCardWidth = 240;

// Hover card margins.
// TODO(crbug.com/40857356): Move to a base hover card class.
constexpr int kHorizontalMargin = 12;
constexpr int kVerticalMargin = 12;

// Maximum number of lines that a label occupies.
constexpr int kHoverCardLabelMaxLines = 3;

}  // namespace

ToolbarActionHoverCardBubbleView::ToolbarActionHoverCardBubbleView(
    ToolbarActionView* action_view,
    base::WeakPtr<ToolbarActionHoverCardController> controller)
    : BubbleDialogDelegateView(action_view,
                               views::BubbleBorder::TOP_LEFT,
                               views::BubbleBorder::STANDARD_SHADOW),
      action_view_model_(action_view->view_model()),
      controller_(controller) {
  DCHECK(base::FeatureList::IsEnabled(
      extensions_features::kExtensionsMenuAccessControl));

  // Remove dialog's default buttons.
  SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone));

  // Remove the accessible role so that hover cards are not read when they
  // appear because tabs handle accessibility text.
  SetAccessibleWindowRole(ax::mojom::Role::kNone);

  // We'll do all of our own layout inside the bubble, so no need to inset this
  // view inside the client view.
  set_margins(gfx::Insets());

  // Set so that when hovering over a toolbar action in a inactive window that
  // window will not become active. Setting this to false creates the need to
  // explicitly hide the hovercard on press, touch, and keyboard events.
  SetCanActivate(false);

  // Set so that the toolbar action hover card is not focus traversable when
  // keyboard navigating through the tab strip.
  set_focus_traversable_from_anchor_view(false);

  set_fixed_width(kHoverCardWidth);

  // Let anchor point handle its own highlight, since the hover card is the
  // same for multiple anchor points.
  set_highlight_button_when_shown(false);

  // Set up layout.
  views::FlexLayout* const layout =
      SetLayoutManager(std::make_unique<views::FlexLayout>());
  layout->SetOrientation(views::LayoutOrientation::kVertical);
  layout->SetMainAxisAlignment(views::LayoutAlignment::kStart);
  layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
  layout->SetCollapseMargins(true);

  // Set up content.
  auto create_label = [](int context, int text_style,
                         std::optional<ui::ColorId> color_id,
                         gfx::Insets insets) {
    auto label = std::make_unique<FadeLabelView>(kHoverCardLabelMaxLines,
                                                 context, text_style);
    if (color_id) {
      label->SetEnabledColor(color_id.value());
    }
    label->SetProperty(views::kMarginsKey, insets);
    label->SetProperty(
        views::kFlexBehaviorKey,
        views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
                                 views::MaximumFlexSizeRule::kScaleToMaximum));
    return label;
  };

  auto create_separator = []() {
    auto separator = std::make_unique<views::Separator>();
    separator->SetProperty(views::kMarginsKey,
                           gfx::Insets::VH(kVerticalMargin, 0));
    return separator;
  };

  title_label_ = AddChildView(create_label(
      CONTEXT_TAB_HOVER_CARD_TITLE, views::style::STYLE_BODY_3_EMPHASIS,
      /*color_id=*/std::nullopt,
      gfx::Insets::VH(kVerticalMargin, kHorizontalMargin)));
  action_title_label_ = AddChildView(create_label(
      views::style::CONTEXT_DIALOG_BODY_TEXT, views::style::STYLE_BODY_4,
      /*color_id=*/kColorTabHoverCardSecondaryText,
      gfx::Insets::TLBR(0, kHorizontalMargin, kVerticalMargin,
                        kHorizontalMargin)));

  site_access_separator_ = AddChildView(create_separator());
  site_access_title_label_ = AddChildView(create_label(
      CONTEXT_TAB_HOVER_CARD_TITLE, views::style::STYLE_BODY_3_EMPHASIS,
      /*color_id=*/std::nullopt,
      gfx::Insets::TLBR(kVerticalMargin, kHorizontalMargin, 0,
                        kHorizontalMargin)));
  site_access_description_label_ = AddChildView(create_label(
      views::style::CONTEXT_DIALOG_BODY_TEXT, views::style::STYLE_BODY_4,
      /*color_id=*/kColorTabHoverCardSecondaryText,
      gfx::Insets::TLBR(0, kHorizontalMargin, kVerticalMargin,
                        kHorizontalMargin)));

  policy_separator_ = AddChildView(create_separator());
  policy_label_ = AddChildView(create_label(
      views::style::CONTEXT_DIALOG_BODY_TEXT, views::style::STYLE_BODY_4,
      /*color_id=*/kColorTabHoverCardSecondaryText,
      gfx::Insets::VH(kVerticalMargin, kHorizontalMargin)));

  // Set up widget.
  views::BubbleDialogDelegateView::CreateBubble(this);
  set_adjust_if_offscreen(true);

  GetBubbleFrameView()->SetPreferredArrowAdjustment(
      views::BubbleFrameView::PreferredArrowAdjustment::kOffset);

  const int corner_radius = ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
      views::Emphasis::kHigh);
  GetBubbleFrameView()->SetRoundedCorners(gfx::RoundedCornersF(corner_radius));

  // Start in the fully "faded-in" position so that whatever text we initially
  // display is visible.
  SetTextFade(1.0);
}

void ToolbarActionHoverCardBubbleView::UpdateCardContent(
    const std::u16string& extension_name,
    const std::u16string& action_title,
    ToolbarActionViewModel::HoverCardState state,
    content::WebContents* web_contents) {
  title_label_->SetData({extension_name, /*is_filename=*/false});

  // We need to adjust the bottom margin of `title_label_` depending on
  // `action_title_` visibility.
  if (action_title.empty()) {
    title_label_->SetProperty(
        views::kMarginsKey,
        gfx::Insets::VH(kVerticalMargin, kHorizontalMargin));
    action_title_label_->SetVisible(false);
  } else {
    title_label_->SetProperty(
        views::kMarginsKey,
        gfx::Insets::TLBR(kVerticalMargin, kHorizontalMargin, 0,
                          kHorizontalMargin));
    action_title_label_->SetData({action_title, /*is_filename=*/false});
    action_title_label_->SetVisible(true);
  }

  ToolbarActionViewModel::HoverCardUiState ui_state =
      action_view_model_->GetHoverCardUiState(state, web_contents);

  DCHECK(ui_state.site_access_title.has_value() ==
         ui_state.site_access_description.has_value());
  bool show_site_access_labels = ui_state.site_access_title.has_value();

  bool show_policy_label = ui_state.policy_text.has_value();

  site_access_separator_->SetVisible(show_site_access_labels);
  site_access_title_label_->SetVisible(show_site_access_labels);
  site_access_description_label_->SetVisible(show_site_access_labels);
  if (show_site_access_labels) {
    site_access_title_label_->SetData(
        {ui_state.site_access_title.value(), /*is_filename=*/false});
    site_access_description_label_->SetData(
        {ui_state.site_access_description.value(), /*is_filename=*/false});
  }

  policy_separator_->SetVisible(show_policy_label);
  policy_label_->SetVisible(show_policy_label);
  if (show_policy_label) {
    policy_label_->SetData(
        {ui_state.policy_text.value(), /*is_filename=*/false});
  }
}

void ToolbarActionHoverCardBubbleView::SetTextFade(double percent) {
  title_label_->SetFade(percent);
  action_title_label_->SetFade(percent);
  site_access_title_label_->SetFade(percent);
  site_access_description_label_->SetFade(percent);
  policy_label_->SetFade(percent);
}

std::u16string_view ToolbarActionHoverCardBubbleView::GetTitleTextForTesting()
    const {
  return title_label_->GetText();
}

std::u16string_view
ToolbarActionHoverCardBubbleView::GetActionTitleTextForTesting() const {
  return action_title_label_->GetText();
}

std::u16string_view
ToolbarActionHoverCardBubbleView::GetSiteAccessTitleTextForTesting() const {
  return site_access_title_label_->GetText();
}

std::u16string_view
ToolbarActionHoverCardBubbleView::GetSiteAccessDescriptionTextForTesting()
    const {
  return site_access_description_label_->GetText();
}

bool ToolbarActionHoverCardBubbleView::IsActionTitleVisible() const {
  return action_title_label_->GetVisible();
}

bool ToolbarActionHoverCardBubbleView::IsSiteAccessSeparatorVisible() const {
  return site_access_separator_->GetVisible();
}

bool ToolbarActionHoverCardBubbleView::IsSiteAccessTitleVisible() const {
  return site_access_title_label_->GetVisible();
}

bool ToolbarActionHoverCardBubbleView::IsSiteAccessDescriptionVisible() const {
  return site_access_description_label_->GetVisible();
}

bool ToolbarActionHoverCardBubbleView::IsPolicySeparatorVisible() const {
  return policy_separator_->GetVisible();
}

bool ToolbarActionHoverCardBubbleView::IsPolicyLabelVisible() const {
  return policy_label_->GetVisible();
}

void ToolbarActionHoverCardBubbleView::OnMouseEntered(
    const ui::MouseEvent& event) {
  BubbleDialogDelegateView::OnMouseEntered(event);
  if (controller_) {
    controller_->OnHoverCardMouseEntered();
  }
}

void ToolbarActionHoverCardBubbleView::OnMouseExited(
    const ui::MouseEvent& event) {
  BubbleDialogDelegateView::OnMouseExited(event);
  if (controller_) {
    controller_->OnHoverCardMouseExited();
  }
}

ToolbarActionHoverCardBubbleView::~ToolbarActionHoverCardBubbleView() = default;

BEGIN_METADATA(ToolbarActionHoverCardBubbleView)
END_METADATA
