// Copyright 2017 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_STRIP_H_
#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_H_

#include <map>
#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/tabs/tab_types.h"
#include "chrome/browser/ui/views/frame/browser_root_view.h"
#include "chrome/browser/ui/views/tabs/dragging/tab_drag_context.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_container.h"
#include "chrome/browser/ui/views/tabs/tab_container_controller.h"
#include "chrome/browser/ui/views/tabs/tab_group_header.h"
#include "chrome/browser/ui/views/tabs/tab_group_views.h"
#include "chrome/browser/ui/views/tabs/tab_slot_controller.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/base/pointer/touch_ui_controller.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/view.h"
#include "ui/views/view_model.h"
#include "ui/views/widget/widget_observer.h"

class BrowserWindowInterface;
class Tab;
class TabGroup;
class TabHoverCardController;
class TabStripController;
class TabStripObserver;
class TabStyle;

namespace gfx {
class Rect;
}

namespace tab_groups {
class TabGroupId;
}

namespace ui {
class ListSelectionModel;
}

// A View that represents the TabStripModel. The TabStrip has the
// following responsibilities:
//
//  - It implements the TabStripModelObserver interface, and acts as a
//    container for Tabs, and is also responsible for creating them.
//
//  - It takes part in Tab Drag & Drop with Tab, TabDragHelper and
//    DraggedTab, focusing on tasks that require reshuffling other tabs
//    in response to dragged tabs.
class TabStrip : public views::View,
                 public views::WidgetObserver,
                 public TabContainerController,
                 public TabSlotController,
                 public BrowserRootView::DropTarget {
  METADATA_HEADER(TabStrip, views::View)

 public:
  TabStrip(std::unique_ptr<TabStripController> tab_strip_controller,
           std::unique_ptr<TabHoverCardController> hover_card_controller);

  TabStrip(const TabStrip&) = delete;
  TabStrip& operator=(const TabStrip&) = delete;
  ~TabStrip() override;

  // Initializes the `tab_container_` so tabs can be added to it.
  void Initialize();

  // Resets the tab strip by removing the tabs in `tab_container_` .
  void Reset();

  void SetAvailableWidthCallback(
      base::RepeatingCallback<int()> available_width_callback);

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

  // Returns the size needed for the specified views. This is invoked during
  // drag and drop to calculate offsets and positioning.
  static int GetSizeNeededForViews(const std::vector<TabSlotView*>& views);

  // Sets the observer to be notified of changes within this TabStrip.
  void SetTabStripObserver(TabStripObserver* observer);

  void SetIsGlassFrame(bool is_glass);

  // Returns true if the specified rect (in TabStrip coordinates) intersects
  // the window caption area of the browser window.
  bool IsRectInWindowCaption(const gfx::Rect& rect);

  // Returns false when there is a drag operation in progress so that the frame
  // doesn't close.
  bool IsTabStripCloseable() const;

  base::WeakPtr<TabStrip> AsWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }

  // Returns true if the tab strip is editable. Returns false if the tab strip
  // is being dragged or animated to prevent extensions from messing things up
  // while that's happening.
  bool IsTabStripEditable() const;

  // Returns information about tabs at given indices.
  bool IsTabCrashed(int tab_index) const;
  bool TabHasNetworkError(int tab_index) const;
  std::optional<tabs::TabAlert> GetTabAlertState(int tab_index) const;

  // Updates the loading animations displayed by tabs in the tabstrip to the
  // next frame. The `elapsed_time` parameter is shared between tabs and used to
  // keep the throbbers in sync.
  void UpdateLoadingAnimations(const base::TimeDelta& elapsed_time);

  // Adds tabs at the specified indices.
  struct AddTabData {
    int index;
    tabs::TabHandle handle;
    bool is_pinned;
  };
  void AddTabsAt(const std::vector<AddTabData>& tabs_data);

  // Moves a tab.
  void MoveTab(int from_model_index, int to_model_index);

  // Removes a tab at the specified index. If the tab with `contents` is being
  // dragged then the drag is completed.
  void RemoveTabAt(content::WebContents* contents,
                   int model_index,
                   bool was_active);

  void OnTabWillBeRemoved(content::WebContents* contents, int model_index);

  void OnTabPinnedStateChanged(int model_index, bool is_pinned);

  // Sets the tab group at the specified model index.
  void AddTabToGroup(std::optional<tab_groups::TabGroupId> group,
                     int model_index);

  // Creates the views associated with a newly-created tab group.
  void OnGroupCreated(const tab_groups::TabGroupId& group);

  // Opens the editor bubble for the tab `group` as a result of an explicit user
  // action to create the `group`.
  void OnGroupEditorOpened(const tab_groups::TabGroupId& group);

  // Updates the group's tabs and header when its associated TabGroupVisualData
  // changes. This should be called when the result of
  // `TabStripController::GetGroupTitle(group)` or
  // `TabStripController::GetGroupColorId(group)` changes.
  void OnGroupVisualsChanged(const tab_groups::TabGroupId& group,
                             const tab_groups::TabGroupVisualData* old_visuals,
                             const tab_groups::TabGroupVisualData* new_visuals);

  // Handles animations relating to toggling the collapsed state of a group.
  void ToggleTabGroup(const tab_groups::TabGroupId& group,
                      bool is_collapsing,
                      ToggleTabGroupCollapsedStateOrigin origin);

  // Updates the ordering of the group header when the whole group is moved.
  // Needed to ensure display and focus order of the group header view.
  void OnGroupMoved(const tab_groups::TabGroupId& group);

  // Destroys the views associated with a recently deleted tab group.
  void OnGroupClosed(const tab_groups::TabGroupId& group);

  void OnTabGroupFocusChanged(
      std::optional<tab_groups::TabGroupId> new_focused_group,
      std::optional<tab_groups::TabGroupId> old_focused_group);

  // Updates the tab slot view split state and animates to bounds.
  void OnSplitCreated(const std::vector<int>& split_indices,
                      split_tabs::SplitTabId split_id);

  // Updates the tab slot view split state and animates to bounds.
  void OnSplitRemoved(const std::vector<int>& split_indices);

  // Updates the tab slot view split state and animates to bounds.
  void OnSplitContentsChanged(const std::vector<int>& split_indices);

  // Invoked when the selection is updated.
  void SetSelection(const ui::ListSelectionModel& new_selection);

  // Returns the TabGroupHeader with ID `id`.
  TabGroupHeader* group_header(const tab_groups::TabGroupId& id) const {
    return tab_container_->GetGroupViews(id)->header();
  }

  // Returns the TabGroup with ID `id`.
  TabGroup* GetTabGroup(const tab_groups::TabGroupId& id) const override;

  // Returns the index of the specified view in the model coordinate system, or
  // std::nullopt if view is closing not a tab, or is not in this tabstrip.
  // TODO(tbergquist): This should return an optional<size_t>.
  std::optional<int> GetModelIndexOf(const TabSlotView* view) const;

  // Cover method for TabStripController::GetCount.
  int GetModelCount() const;

  TabStripController* controller() const { return controller_.get(); }
  TabContainer* tab_container_for_testing() { return tab_container_; }

  TabDragContext* GetDragContext();

  // Stops any ongoing animations and lays out the tabstrip.
  void StopAnimating();

  // Returns a view for anchoring an in-product help promo. `index_hint`
  // indicates at which tab the promo should be displayed, but is not
  // binding.
  views::View* GetTabViewForPromoAnchor(int index_hint);

  // Gets the default focusable child view in the TabStrip.
  views::View* GetDefaultFocusableChild();

  // The browser window interface for the hosting browser.
  BrowserWindowInterface* GetBrowserWindowInterface() const;

  // TabContainerController:
  bool IsValidModelIndex(int index) const override;
  std::optional<int> GetActiveIndex() const override;
  int NumPinnedTabsInModel() const override;
  void OnDropIndexUpdate(std::optional<int> index, bool drop_before) override;
  bool IsBrowserClosing() const override;
  std::optional<int> GetFirstTabInGroup(
      const tab_groups::TabGroupId& group) const override;
  gfx::Range ListTabsInGroup(
      const tab_groups::TabGroupId& group) const override;
  bool CanExtendDragHandle() const override;
  const views::View* GetTabClosingModeMouseWatcherHostView() const override;
  bool IsAnimatingInTabStrip() const override;
  void UpdateAnimationTarget(
      TabSlotView* tab_slot_view,
      gfx::Rect target_bounds_in_tab_container_coords) override;
  std::optional<tab_groups::TabGroupId> GetFocusedGroup() const override;

  // TabContainerController AND TabSlotController:
  bool IsGroupCollapsed(const tab_groups::TabGroupId& group) const override;

  // TabSlotController:
  ui::ListSelectionModel GetSelectionModel() const override;
  Tab* tab_at(int index) const override;
  void SelectTab(Tab* tab, const ui::Event& event) override;
  void ExtendSelectionTo(Tab* tab) override;
  void ToggleSelected(Tab* tab) override;
  void AddSelectionFromAnchorTo(Tab* tab) override;
  void CloseTab(Tab* tab, CloseTabSource source) override;
  void ToggleTabAudioMute(Tab* tab) override;
  void ShiftTabNext(Tab* tab) override;
  void ShiftTabPrevious(Tab* tab) override;
  void MoveTabFirst(Tab* tab) override;
  void MoveTabLast(Tab* tab) override;
  using TabSlotController::ToggleTabGroupCollapsedState;
  void ToggleTabGroupCollapsedState(
      const tab_groups::TabGroupId group,
      ToggleTabGroupCollapsedStateOrigin origin) override;
  void NotifyTabstripBubbleOpened() override;
  void NotifyTabstripBubbleClosed() override;
  void ShowContextMenuForTab(Tab* tab,
                             const gfx::Point& p,
                             ui::mojom::MenuSourceType source_type) override;
  void TabKeyboardFocusChangedTo(const tabs::TabInterface* tab) override;
  int GetTabCount() const override;
  bool IsActiveTab(const TabSlotView* tab) const override;
  bool IsTabSelected(const TabSlotView* tab) const override;
  bool IsFocusInTabStrip() const override;

  void MaybeStartDrag(TabSlotView* source,
                      const ui::LocatedEvent& event,
                      ui::ListSelectionModel original_selection) override;
  [[nodiscard]] Liveness ContinueDrag(views::View* view,
                                      const ui::LocatedEvent& event) override;
  bool EndDrag(EndDragReason reason) override;
  Tab* GetTabAt(const gfx::Point& point) override;
  Tab* GetAdjacentTab(const Tab* tab, int offset) override;
  std::vector<Tab*> GetTabsInSplit(const Tab* tab) override;
  void OnMouseEventInTab(views::View* source,
                         const ui::MouseEvent& event) override;
  void OnGroupContentsChanged(const tab_groups::TabGroupId& group) override;
  void UpdateHoverCard(HoverCardAnchorTarget* anchor_target,
                       HoverCardUpdateType update_type) override;
  bool HoverCardIsShowing(HoverCardAnchorTarget* anchor_target) override;
  void ShowHover(Tab* tab, TabStyle::ShowHoverStyle style) override;
  void HideHover(Tab* tab, TabStyle::HideHoverStyle style) override;
  int GetStrokeThickness() const override;
  bool CanPaintThrobberToLayer() const override;
  bool IsGlassFrame() const override;
  SkColor GetTabSeparatorColor() const override;
  std::u16string GetAccessibleTabName(const Tab* tab) const override;
  float GetHoverOpacityForTab(float range_parameter) const override;
  float GetHoverOpacityForRadialHighlight() const override;
  std::u16string GetGroupTitle(
      const tab_groups::TabGroupId& group) const override;
  std::u16string GetGroupContentString(
      const tab_groups::TabGroupId& group) const override;
  tab_groups::TabGroupColorId GetGroupColorId(
      const tab_groups::TabGroupId& group) const override;
  SkColor GetPaintedGroupColor(
      const tab_groups::TabGroupColorId& color_id) const override;
  void ShiftGroupLeft(const tab_groups::TabGroupId& group) override;
  void ShiftGroupRight(const tab_groups::TabGroupId& group) override;
  BrowserWindowInterface* GetBrowserWindowInterface() override;

  // views::View:
  views::SizeBounds GetAvailableSize(const View* child) const override;
  gfx::Size GetMinimumSize() const override;
  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override;
  void Layout(PassKey) override;
  void ChildPreferredSizeChanged(views::View* child) override;

  // BrowserRootView::DropTarget:
  // These methods handle link drag & drop.
  // TODO(https://crbug.com/40828528): Use the standard views::View drag and
  // drop methods instead.
  std::optional<BrowserRootView::DropIndex> GetDropIndex(
      const ui::DropTargetEvent& event) override;
  BrowserRootView::DropTarget* GetDropTarget(
      gfx::Point loc_in_local_coords) override;
  views::View* GetViewForDrop() override;

  void DisableTabStripEditingForTesting();
  TabHoverCardController* hover_card_controller() {
    return hover_card_controller_.get();
  }

 private:
  class TabDragContextImpl;

  friend class TabDragControllerTest;
  friend class TabDragContextImpl;
  friend class TabGroupEditorBubbleViewDialogBrowserTest;
  friend class TabStripTestBase;
  friend class HorizontalTabStripRegionViewTestBase;

  class TabContextMenuController : public views::ContextMenuController {
   public:
    explicit TabContextMenuController(TabStrip* parent);
    // views::ContextMenuController:
    void ShowContextMenuForViewImpl(
        views::View* source,
        const gfx::Point& point,
        ui::mojom::MenuSourceType source_type) override;

   private:
    const raw_ptr<TabStrip> parent_;
  };

  void Init();

  std::map<tab_groups::TabGroupId, TabGroupHeader*> GetGroupHeaders();

  // Returns whether the close button should be highlighted after a remove.
  bool ShouldHighlightCloseButtonAfterRemove();

  // Returns whether the window background behind the tabstrip is transparent.
  bool TitlebarBackgroundIsTransparent() const;

  // Returns the last tab in the strip that's actually visible.  This will be
  // the actual last tab unless the strip is in the overflow node_data.
  const Tab* GetLastVisibleTab() const;

  // Closes the tab at `model_index`.
  void CloseTabInternal(CloseTabSource source);

  // Computes and stores values derived from contrast ratios.
  void UpdateContrastRatioValues();

  // Determines whether a tab can be shifted by one in the direction of `offset`
  // and moves it if possible.
  void ShiftTabRelative(Tab* tab, int offset);

  // Determines whether a group can be shifted by one in the direction of
  // `offset` and moves it if possible.
  void ShiftGroupRelative(const tab_groups::TabGroupId& group, int offset);

  // views::View:
  void OnMouseEntered(const ui::MouseEvent& event) override;
  void OnMouseExited(const ui::MouseEvent& event) override;
  void AddedToWidget() override;
  void RemovedFromWidget() override;
  void OnThemeChanged() override;

  // ui::EventHandler:
  void OnGestureEvent(ui::GestureEvent* event) override;

  // views::WidgetObserver:
  void OnWidgetActivationChanged(views::Widget* widget, bool active) override;

  void OnTouchUiChanged();

  // Screen-reader-only announcements that depend on tab group titles.
  void AnnounceTabAddedToGroup(tab_groups::TabGroupId group_id);
  void AnnounceTabRemovedFromGroup(tab_groups::TabGroupId group_id);

  // -- Member Variables ------------------------------------------------------

  raw_ptr<TabStripObserver> observer_;

  std::unique_ptr<TabStripController> controller_;

  std::unique_ptr<TabHoverCardController> hover_card_controller_;

  raw_ref<TabDragContextImpl, AcrossTasksDanglingUntriaged> drag_context_;

  // The View parent for the tabs and the various group views.
  raw_ptr<TabContainer, AcrossTasksDanglingUntriaged> tab_container_;

  // Location of the mouse at the time of the last move.
  gfx::Point last_mouse_move_location_;

  // Used to track the time needed to create a new tab from the new tab button.
  std::optional<base::TimeTicks> new_tab_button_pressed_start_time_;

  // Used for seek time metrics from the time the mouse enters the tabstrip.
  std::optional<base::TimeTicks> mouse_entered_tabstrip_time_;

  // Used to track if the time from mouse entered to tab switch has been
  // reported.
  bool has_reported_time_mouse_entered_to_switch_ = false;

  const raw_ptr<const TabStyle> style_;

  // Number of mouse moves.
  int mouse_move_count_ = 0;

  // This represents the Tabs in `tabs_` that have been selected.
  //
  // Each time tab selection should change, this class will receive a
  // SetSelection() callback with the new tab selection. That callback only
  // includes the new selection model. This keeps track of the previous
  // selection model, and is always consistent with `tabs_`. This must be
  // updated to account for tab insertions/removals/moves.
  ui::ListSelectionModel selected_tabs_;

  // When tabs are hovered, a radial highlight is shown and the tab opacity is
  // adjusted using some value between `hover_opacity_min_` and
  // `hover_opacity_max_` (depending on tab width). All these opacities depend
  // on contrast ratios and are updated when colors or active state changes,
  // so for efficiency's sake they are computed and stored once here instead
  // of with each tab. Note: these defaults will be overwritten at construction
  // except in cases where a unit test provides no controller_.
  float hover_opacity_min_ = 1.0f;
  float hover_opacity_max_ = 1.0f;
  float radial_highlight_opacity_ = 1.0f;

  SkColor separator_color_ = gfx::kPlaceholderColor;

  // If false simulates a non-editable tab strip for testing.
  bool tab_strip_editable_for_testing_ = true;

  bool is_glass_ = false;

  base::CallbackListSubscription paint_as_active_subscription_;

  const base::CallbackListSubscription subscription_ =
      ui::TouchUiController::Get()->RegisterCallback(
          base::BindRepeating(&TabStrip::OnTouchUiChanged,
                              base::Unretained(this)));

  TabContextMenuController context_menu_controller_{this};

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

#endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_H_
