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

#ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_H_
#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_H_

#include <memory>
#include <optional>
#include <string>

#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ui/tabs/tab_data.h"
#include "chrome/browser/ui/tabs/tab_enums.h"
#include "chrome/browser/ui/views/tabs/hovercard/hover_card_anchor_target.h"
#include "chrome/browser/ui/views/tabs/shared/tab_strip_types.h"
#include "chrome/browser/ui/views/tabs/tab/alert_indicator_button.h"
#include "chrome/browser/ui/views/tabs/tab_slot_view.h"
#include "chrome/browser/ui/views/tabs/tab_style_views.h"
#include "chrome/common/buildflags.h"
#include "components/performance_manager/public/freezing/freezing.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tabs/public/tab_interface.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/paint_throbber.h"
#include "ui/views/context_menu_controller.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/masked_targeter_delegate.h"
#include "ui/views/view_observer.h"

enum class TabChangeType;
class TabCloseButton;
class TabSlotController;
class TabIcon;
struct TabSizeInfo;
class TabTitle;
class TabStyleViewDelegate;

namespace gfx {
class Animation;
class LinearAnimation;
}  // namespace gfx
namespace views {
class View;
}  // namespace views

namespace tabs {
enum class TabAlert;
class TabDataObserver;
struct TabData;
}

namespace glic {
class TabUnderlineView;
}  // namespace glic

///////////////////////////////////////////////////////////////////////////////
//
//  A View that renders a Tab in a TabStrip.
//
///////////////////////////////////////////////////////////////////////////////
class Tab : public gfx::AnimationDelegate,
            public views::MaskedTargeterDelegate,
            public views::ViewObserver,
            public TabSlotView,
            public HoverCardAnchorTarget,
            public AlertIndicatorButton::Delegate {
  METADATA_HEADER(Tab, TabSlotView)

 public:
  // When the content's width of the tab shrinks to below this size we should
  // hide the close button on inactive tabs. Any smaller and they're too easy
  // to hit on accident.
  static constexpr int kMinimumContentsWidthForCloseButtons = 68;
  static constexpr int kTouchMinimumContentsWidthForCloseButtons = 100;

  // Sets whether hover cards should appear on mouse hover. Used in browser
  // tests to prevent them from interfering with unrelated tests.
  static void SetShowHoverCardOnMouseHoverForTesting(bool value);

  explicit Tab(tabs::TabHandle handle, TabSlotController* controller);
  Tab(const Tab&) = delete;
  Tab& operator=(const Tab&) = delete;
  ~Tab() override;

  bool IsActive() const;

  // gfx::AnimationDelegate:
  void AnimationEnded(const gfx::Animation* animation) override;
  void AnimationProgressed(const gfx::Animation* animation) override;

  // views::MaskedTargeterDelegate:
  bool GetHitTestMask(SkPath* mask) const override;

  // TabSlotView:
  void Layout(PassKey) override;
  bool OnKeyPressed(const ui::KeyEvent& event) override;
  bool OnKeyReleased(const ui::KeyEvent& event) override;
  bool OnMousePressed(const ui::MouseEvent& event) override;
  bool OnMouseDragged(const ui::MouseEvent& event) override;
  void OnMouseReleased(const ui::MouseEvent& event) override;
  void OnMouseCaptureLost() override;
  void OnMouseMoved(const ui::MouseEvent& event) override;
  void OnMouseEntered(const ui::MouseEvent& event) override;
  void OnMouseExited(const ui::MouseEvent& event) override;
  void OnGestureEvent(ui::GestureEvent* event) override;
  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override;
  void PaintChildren(const views::PaintInfo& info) override;
  void OnPaint(gfx::Canvas* canvas) override;
  void AddedToWidget() override;
  void RemovedFromWidget() override;
  void OnFocus() override;
  void OnBlur() override;
  void OnThemeChanged() override;
  TabSlotView::ViewType GetTabSlotViewType() const override;
  TabSizeInfo GetTabSizeInfo() const override;
  void SetGroup(std::optional<tab_groups::TabGroupId> group) override;
  void SetSplit(std::optional<split_tabs::SplitTabId> split) override;
  void UpdateAccessibleName();

  void OnAXNameChanged(ax::mojom::StringAttribute attribute,
                       const std::optional<std::string>& name);

  TabSlotController* controller() const { return controller_; }
  const tabs::TabHandle& tab_handle() const { return tab_handle_; }

  // Used to set/check whether this Tab is being animated closed.
  void SetClosing(bool closing);
  bool closing() const { return closing_; }

  // Returns the color for the tab's group, if any.
  std::optional<SkColor> GetGroupColor() const;

  const tabs::TabData& data() const { return data_; }

  // HoverCardAnchorTarget:
  bool NeedsToShowThumbnail() const override;
  bool IsValidHoverCardTarget() const override;
  views::BubbleAnchor GetAnchor() override;
  views::BubbleBorder::Arrow GetAnchorPosition() const override;

  // Notifies the AlertIndicatorButton that the active state of this tab has
  // changed.
  void ActiveStateChanged();

  // AlertIndicatorButton::Delegate
  bool ShouldEnableMuteToggle(int required_width) override;
  void ToggleTabAudioMute() override;
  bool IsApparentlyActive() const override;
  void AlertStateChanged() override;

  // Called when the selected state changes.
  void SelectedStateChanged();

  // Returns true if the tab is selected.
  bool IsSelected() const;

  // Returns true if this tab is discarded.
  bool IsDiscarded() const;

  // Returns true if this tab has captured a thumbnail.
  bool HasThumbnail() const;

  // Sets the data this tabs displays. Should only be called after Tab is added
  // to widget hierarchy.
  void SetDataForTesting(tabs::TabData data);

  // Redraws the loading animation if one is visible. Otherwise, no-op. The
  // `elapsed_time` parameter is shared between tabs and used to keep the
  // throbbers in sync.
  void StepLoadingAnimation(const base::TimeDelta& elapsed_time);

  void CreateFreezingVote(FreezingVoteReason reason,
                          content::WebContents* contents);
  void ReleaseFreezingVote(FreezingVoteReason reason);
  bool HasFreezingVote(FreezingVoteReason reason) const;
  bool HasFreezingVote() const;

  bool mouse_hovered() const { return mouse_hovered_; }

  void ShowHover(TabStyle::ShowHoverStyle style);
  void HideHover(TabStyle::HideHoverStyle style);

  // Returns the progress (0 to 1) of the hover animation.
  double GetHoverAnimationValue() const;
  float GetHoverOpacity() const;
  bool IsHoverAnimationActive() const;
  bool IsHovering() const;

  // Returns the z-value of the tab, which should be used to paint them in
  // ascending order. Return values are in the range (0,
  // TabStyle::GetMaximumZValue()).
  float GetZValue() const;

  GlowHoverController* GetHoverControllerForTesting() {
    return hover_controller_.get();
  }

  // Returns the TabStyle associated with this tab.
  TabStyleViews* tab_style_views() { return tab_style_views_.get(); }
  const TabStyleViews* tab_style_views() const {
    return tab_style_views_.get();
  }
  const TabStyle* tab_style() const { return tab_style_views_->tab_style(); }
  bool should_fill_background_tab_color() const {
    return should_fill_background_tab_color_;
  }

  // Returns the text to show in a tab's tooltip: The contents `title`, followed
  // by a break, followed by a localized string describing the `alert_state`.
  // Exposed publicly for tests.
  static std::u16string GetTooltipText(
      const std::u16string& title,
      std::optional<tabs::TabAlert> alert_state);

  bool showing_close_button_for_testing() const {
    return showing_close_button_;
  }

  bool showing_icon() const { return showing_icon_; }
  bool showing_alert_indicator() const { return showing_alert_indicator_; }
  bool showing_close_button() const { return showing_close_button_; }

  raw_ptr<TabCloseButton> close_button() { return close_button_; }

  TabIcon* GetTabIconForTesting() const { return icon_; }

  AlertIndicatorButton* alert_indicator_button_for_testing() {
    return alert_indicator_button_;
  }

  void UpdateInsets();

  glic::TabUnderlineView* glic_underline() const {
    return glic_tab_underline_view_;
  }

 private:
  class TabCloseButtonObserver;
  friend class TabContentsTest;
  friend class TabTest;
  friend class TabStripTestBase;
#if BUILDFLAG(IS_CHROMEOS)
  FRIEND_TEST_ALL_PREFIXES(TabStripTest, CloseButtonHiddenWhenLockedForOnTask);
#endif
  FRIEND_TEST_ALL_PREFIXES(TabStripTest, TabCloseButtonVisibility);
  FRIEND_TEST_ALL_PREFIXES(TabTest, TitleTextHasSufficientContrast);
  FRIEND_TEST_ALL_PREFIXES(TabHoverCardInteractiveUiTest,
                           HoverCardVisibleOnTabCloseButtonFocusAfterTabFocus);
  FRIEND_TEST_ALL_PREFIXES(TabContentsTest, AccessibleNameChanged);
  FRIEND_TEST_ALL_PREFIXES(TabContentsTest,
                           AccessibleNameChangesWithCollaborationMessages);

  // Invoked from Layout to adjust the position of the favicon or alert
  // indicator for pinned tabs. The visual_width parameter is how wide the
  // icon looks (rather than how wide the bounds are).
  void MaybeAdjustLeftForPinnedTab(gfx::Rect* bounds, int visual_width) const;

  // Computes which icons are visible in the tab. Should be called everytime
  // before layout is performed.
  void UpdateIconVisibility();

  // Returns whether the tab should be rendered as a normal tab as opposed to a
  // pinned tab.
  bool ShouldRenderAsNormalTab() const;

  // Updates the attention state of the `icon_`. This only updates state; it is
  // the responsibility of the caller to request a paint.
  void UpdateTabIconAttention();

  // Returns the width of the largest part of the tab that is available for the
  // user to click to select/activate the tab.
  int GetWidthOfLargestSelectableRegion() const;

  // Selects, generates, and applies colors for various foreground elements to
  // ensure proper contrast. Elements affected include title text, close button
  // and alert icon.
  void UpdateForegroundColors();

  // Considers switching to hovered mode or [re-]showing the hover card based on
  // the mouse moving over the tab. If the tab is already hovered or mouse
  // events are disabled because of touch input, this is a no-op.
  void MaybeUpdateHoverStatus(const ui::MouseEvent& event);

  void CloseButtonPressed(const ui::Event& event);

  void OnTabDataChanged(TabChangeType tab_change_type,
                        const tabs::TabData& tab_data);

  static std::unique_ptr<TabStyleViewDelegate> CreateStyleDelegate(
      const Tab* tab);

  // The tab handle associated with the view.
  const tabs::TabHandle tab_handle_;

  // The controller, never nullptr.
  const raw_ptr<TabSlotController> controller_;

  tabs::TabData data_;

  std::unique_ptr<TabStyleViews> tab_style_views_;

  std::unique_ptr<GlowHoverController> hover_controller_;

  // True if the tab is being animated closed.
  bool closing_ = false;

  raw_ptr<glic::TabUnderlineView> glic_tab_underline_view_ = nullptr;

  raw_ptr<TabIcon> icon_ = nullptr;
  raw_ptr<AlertIndicatorButton> alert_indicator_button_ = nullptr;
  raw_ptr<TabCloseButton> close_button_ = nullptr;

  raw_ptr<TabTitle> title_;
  // The title's bounds are animated when switching between showing and hiding
  // the tab's favicon/throbber.
  gfx::Rect start_title_bounds_;
  gfx::Rect target_title_bounds_;
  gfx::LinearAnimation title_animation_;

  // For narrow tabs, we show the alert icon or, if there is no alert icon, the
  // favicon even if it won't completely fit. In this case, we need to center
  // the icon within the tab; it will be clipped to fit.
  bool center_icon_ = false;

  // Whether we're showing the icon. It is cached so that we can detect when it
  // changes and layout appropriately.
  bool showing_icon_ = false;

  // Whether we're showing the alert indicator. It is cached so that we can
  // detect when it changes and layout appropriately.
  bool showing_alert_indicator_ = false;

  // Whether we are showing the close button. It is cached so that we can
  // detect when it changes and layout appropriately.
  bool showing_close_button_ = false;

  // Whether the tab is currently animating from a pinned to an unpinned state.
  bool is_animating_from_pinned_ = false;

  // When both the close button and alert indicator are visible, we add extra
  // padding between them to space them out visually.
  bool extra_alert_indicator_padding_ = false;

  // Indicates whether the mouse is currently hovered over the tab. This is
  // different from View::IsMouseHovered() which does a naive intersection with
  // the view bounds.
  bool mouse_hovered_ = false;

  // Whether the shift key was pressed at the start of the click. Used on mouse
  // up.
  bool shift_pressed_on_mouse_down_ = false;
  bool should_fill_background_tab_color_ = false;

  std::unique_ptr<TabCloseButtonObserver> tab_close_button_observer_;

  std::unique_ptr<tabs::TabDataObserver> tab_data_observer_;

  std::optional<performance_manager::freezing::FreezingVote>& GetFreezingVote(
      FreezingVoteReason reason);

  // Freezing vote held while the tab's group is collapsed.
  std::optional<performance_manager::freezing::FreezingVote>
      collapsed_freezing_vote_;
  // Freezing vote held while another group is focused in focus mode.
  std::optional<performance_manager::freezing::FreezingVote>
      focus_mode_freezing_vote_;

  base::CallbackListSubscription paint_as_active_subscription_;

  base::CallbackListSubscription root_name_changed_subscription_;

  base::CallbackListSubscription tab_data_change_subscription_;

  base::WeakPtrFactory<Tab> weak_ptr_factory_{this};
};

#endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_H_
