// 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_TABS_TAB_STRIP_MODEL_H_
#define CHROME_BROWSER_UI_TABS_TAB_STRIP_MODEL_H_

#include <stddef.h>
#include <stdint.h>

#include <iterator>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include <vector>

#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_multi_source_observation.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/tab_list/tab_removed_reason.h"
#include "chrome/browser/ui/tabs/tab_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/browser/ui/tabs/tab_strip_model_selection_state.h"
#include "chrome/browser/ui/tabs/tab_strip_scrubbing_metrics.h"
#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
#include "components/sessions/core/session_id.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "components/tabs/public/tab_collection.h"
#include "components/tabs/public/tab_collection_types.h"
#include "components/tabs/public/tab_context_menu_command.h"
#include "components/tabs/public/tab_interface.h"
#include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
#include "ui/base/models/list_selection_model.h"
#include "ui/base/page_transition_types.h"

#if BUILDFLAG(IS_ANDROID)
#error This file should only be included on desktop.
#endif

class DraggingTabsSession;
class Profile;
class TabGroupModel;
class TabStripModelDelegate;
class TabStripModelObserver;

namespace content {
class WebContents;
}

namespace gfx {
class Range;
}

namespace split_tabs {
class SplitTabData;
class SplitTabVisualData;
enum class SplitTabLayout;
enum class SplitTabCreatedSource;
enum class SplitTabOrientationChangeSource;
}  // namespace split_tabs

namespace tabs {
class SplitTabCollection;
class TabStripCollection;
class TabGroupTabCollection;
}  // namespace tabs

namespace tabs_api {
class TabStripModelAdapterImpl;
}

class TabGroupModelFactory {
 public:
  TabGroupModelFactory();
  TabGroupModelFactory(const TabGroupModelFactory&) = delete;
  TabGroupModelFactory& operator=(const TabGroupModelFactory&) = delete;

  static TabGroupModelFactory* GetInstance();
  std::unique_ptr<TabGroupModel> Create();
};

// Have DetachedTabCollection object as a container of the `collection_` so
// client does not need to worry or deal with the collection object.
struct DetachedTabCollection {
  DetachedTabCollection(
      std::variant<std::unique_ptr<tabs::TabGroupTabCollection>,
                   std::unique_ptr<tabs::SplitTabCollection>> collection,
      std::optional<int> active_index,
      bool pinned_);
  DetachedTabCollection(const DetachedTabCollection&) = delete;
  DetachedTabCollection& operator=(const DetachedTabCollection&) = delete;
  ~DetachedTabCollection();
  DetachedTabCollection(DetachedTabCollection&&);
  std::variant<std::unique_ptr<tabs::TabGroupTabCollection>,
               std::unique_ptr<tabs::SplitTabCollection>>
      collection_;
  // Store the index of tab that was active in the detached group.
  std::optional<int> active_index_;
  bool pinned_ = false;
};

// Holds state for a tab that has been detached from the tab strip.
// Will also handle tab deletion if `remove_reason` is kDeleted.
struct DetachedTab {
  DetachedTab(int index_before_any_removals,
              int index_at_time_of_removal,
              bool was_pinned_at_time_of_removal,
              std::unique_ptr<tabs::TabModel> tab,
              TabRemovedReason remove_reason,
              tabs::TabInterface::DetachReason tab_detach_reason,
              std::optional<SessionID> id);
  DetachedTab(const DetachedTab&) = delete;
  DetachedTab& operator=(const DetachedTab&) = delete;
  ~DetachedTab();
  DetachedTab(DetachedTab&&);

  std::unique_ptr<tabs::TabModel> tab;

  // The index of the tab in the original selection model of the tab
  // strip [prior to any tabs being removed, if multiple tabs are being
  // simultaneously removed].
  const int index_before_any_removals;

  // The index of the tab at the time it is being removed. If multiple
  // tabs are being simultaneously removed, the index reflects previously
  // removed tabs in this batch.
  const int index_at_time_of_removal;

  // True if this tab was pinned when it was removed from the tab strip.
  const bool was_pinned_at_time_of_removal;

  // Reasons for detaching a tab. These may differ, for e.g. when a
  // tab is detached for re-insertion into a browser of different type,
  // in which case the TabInterface is destroyed but the WebContents is
  // retained.
  TabRemovedReason remove_reason;
  tabs::TabInterface::DetachReason tab_detach_reason;

  // The `contents` associated optional SessionID, used as key for
  // ClosedTabCache. We only cache `contents` if `remove_reason` is kCached.
  //
  // TODO(crbug.com/377537302): The ClosedTabCache feature is gone, but it's
  // unclear if the session ID is needed for other things as well.
  std::optional<SessionID> id;
};

// A feature which wants to show tabstrip-modal UI should call
// TabStripController::ShowModalUI and keep alive the instance of
// ScopedTabStripModalUI for the duration of the tabstrip-modal UI.
class ScopedTabStripModalUI {
 public:
  ScopedTabStripModalUI() = default;
  virtual ~ScopedTabStripModalUI() = default;
};

////////////////////////////////////////////////////////////////////////////////
//
// TabStripModel
//
// A model & low level controller of a Browser Window tabstrip. Holds a vector
// of WebContents, and provides an API for adding, removing and
// shuffling them, as well as a higher level API for doing specific Browser-
// related tasks like adding new Tabs from just a URL, etc.
//
// Each tab may be pinned. Pinned tabs are locked to the left side of the tab
// strip and rendered differently (small tabs with only a favicon). The model
// makes sure all pinned tabs are at the beginning of the tab strip. For
// example, if a non-pinned tab is added it is forced to be with non-pinned
// tabs. Requests to move tabs outside the range of the tab type are ignored.
// For example, a request to move a pinned tab after non-pinned tabs is ignored.
//
// A TabStripModel has one delegate that it relies on to perform certain tasks
// like creating new TabStripModels (probably hosted in Browser windows) when
// required. See TabStripDelegate above for more information.
//
// A TabStripModel also has N observers (see TabStripModelObserver above),
// which can be registered via Add/RemoveObserver. An Observer is notified of
// tab creations, removals, moves, and other interesting events. The
// TabStrip implements this interface to know when to create new tabs in
// the View, and the Browser object likewise implements to be able to update
// its bookkeeping when such events happen.
//
// This implementation of TabStripModel is not thread-safe and should only be
// accessed on the UI thread.
//
////////////////////////////////////////////////////////////////////////////////
class TabStripModel {
 public:
  using TabIterator = tabs::TabCollection::TabIterator;
  using iterator = TabIterator;
  using const_iterator = TabIterator;
  using reverse_iterator = std::reverse_iterator<TabIterator>;
  using const_reverse_iterator = std::reverse_iterator<TabIterator>;

  static constexpr int kNoTab = -1;

  TabStripModel() = delete;

  // Construct a TabStripModel with a delegate to help it do certain things
  // (see the TabStripModelDelegate documentation). `delegate` cannot be NULL.
  // the TabGroupModelFactory can be replaced with a nullptr to set the
  // group_model to null in cases where groups are not supported.
  explicit TabStripModel(TabStripModelDelegate* delegate,
                         Profile* profile,
                         TabGroupModelFactory* group_model_factory =
                             TabGroupModelFactory::GetInstance());

  TabStripModel(const TabStripModel&) = delete;
  TabStripModel& operator=(const TabStripModel&) = delete;

  ~TabStripModel();

  // Retrieves the TabStripModelDelegate associated with this TabStripModel.
  TabStripModelDelegate* delegate() const { return delegate_; }

  // Sets the TabStripModelObserver used by the UI showing the tabs. As other
  // observers may query the UI for state, the UI's observer must be first.
  void SetTabStripUI(TabStripModelObserver* observer);

  // Add and remove observers to changes within this TabStripModel.
  void AddObserver(TabStripModelObserver* observer);
  void RemoveObserver(TabStripModelObserver* observer);

  // Retrieve the number of WebContentses/emptiness of the TabStripModel.
  int count() const;

  bool empty() const;

  // Retrieve the Profile associated with this TabStripModel.
  Profile* profile() const { return profile_; }

  // Retrieve the index of the currently active WebContents. The only time this
  // is kNoTab is if the tab strip is being initialized or destroyed. Note that
  // tab strip destruction is an asynchronous process.
  int active_index() const {
    return selection_model_.active_tab()
               ? GetIndexOfTab(selection_model_.active_tab())
               : kNoTab;
  }

  // Returns true if the tabstrip is currently closing all open tabs (via a
  // call to CloseAllTabs). As tabs close, the selection in the tabstrip
  // changes which notifies observers, which can use this as an optimization to
  // avoid doing meaningless or unhelpful work.
  bool closing_all() const { return closing_all_; }

  // Basic API /////////////////////////////////////////////////////////////////

  // Determines if the specified index is contained within the TabStripModel.
  bool ContainsIndex(int index) const;

  // Adds the specified WebContents in the default location. Tabs opened
  // in the foreground inherit the opener of the previously active tab.
  // Use of the detached tab is preferred over webcontents, so when possible
  // use AppendTab instead of this method.
  void AppendWebContents(std::unique_ptr<content::WebContents> contents,
                         bool foreground);

  // Adds the specified Tab at the end of the Tabstrip. Tabs opened
  // in the foreground inherit the opener of the previously active tab and
  // become the active tab.
  void AppendTab(std::unique_ptr<tabs::TabModel> tab, bool foreground);

  // Adds the specified WebContents at the specified location.
  // `add_types` is a bitmask of AddTabTypes; see it for details.
  //
  // All append/insert methods end up in this method.
  //
  // NOTE: adding a tab using this method does NOT query the order controller,
  // as such the ADD_FORCE_INDEX AddTabTypes is meaningless here. The only time
  // the `index` is changed is if using the index would result in breaking the
  // constraint that all pinned tabs occur before non-pinned tabs. It returns
  // the index the web contents is actually inserted to. See also
  // AddWebContents.
  int InsertWebContentsAt(
      int index,
      std::unique_ptr<content::WebContents> contents,
      int add_types,
      std::optional<tab_groups::TabGroupId> group = std::nullopt);

  // Adds a TabModel from another tabstrip at the specified location. See
  // InsertWebContentsAt.
  int InsertDetachedTabAt(
      int index,
      std::unique_ptr<tabs::TabModel> tab,
      int add_types,
      std::optional<tab_groups::TabGroupId> group = std::nullopt);

  // Removes the group collection from the collection hierarchy and passes it to
  // the client. The client can re-insert into another tabstrip using
  // `InsertDetachedGroupAt` without destroying the group.
  std::unique_ptr<DetachedTabCollection> DetachTabGroupForInsertion(
      const tab_groups::TabGroupId group_id);

  // Inserts a detached tab group into the tabstrip starting at `index`.
  gfx::Range InsertDetachedTabGroupAt(
      std::unique_ptr<DetachedTabCollection> group,
      int index);

  // Removes the split collection from the collection hierarchy and passes it to
  // the client. The client can re-insert into another tabstrip using
  // `InsertDetachedSplitTabAt` without destroying the split.
  std::unique_ptr<DetachedTabCollection> DetachSplitTabForInsertion(
      const split_tabs::SplitTabId split_id);

  // Inserts a detached split tab into the tabstrip starting at `index`.
  // `pinned` and `group` information are used to insert it in the right place
  // in the collection hierarchy.
  gfx::Range InsertDetachedSplitTabAt(
      std::unique_ptr<DetachedTabCollection> split,
      int index,
      bool pinned,
      std::optional<tab_groups::TabGroupId> group_id = std::nullopt);

  // Closes the specified WebContents. This causes the WebContents to be
  // destroyed, but it may not happen immediately.
  // `close_types` is a bitmask of CloseTypes. Prefer this over
  // CloseWebContentsAt() when a WebContents* is already available.
  void CloseWebContents(content::WebContents* contents, uint32_t close_types);

  // Closes the WebContents at the specified index. This causes the
  // WebContents to be destroyed, but it may not happen immediately.
  // `close_types` is a bitmask of CloseTypes.
  void CloseWebContentsAt(int index, uint32_t close_types);

  // Discards `contents` and replaces it with `new_contents`.
  // The WebContents that was discarded is returned and its ownership returns
  // to the caller.
  std::unique_ptr<content::WebContents> DiscardWebContents(
      content::WebContents* contents,
      std::unique_ptr<content::WebContents> new_contents);

  // Detaches the tab at the specified index for reinsertion into another tab
  // strip. Returns the detached tab.
  std::unique_ptr<tabs::TabModel> DetachTabAtForInsertion(int index);

  // Detaches the WebContents at the specified index for re-insertion into
  // another browser of a different type, destroying the owning TabModel in the
  // process.
  //
  // This works as follows:
  //   - the contents is extracted from the source browser and the owning tab is
  //     destroyed (performed by DetachWebContentsAtForInsertion())
  //   - the contents is added to the new browser, creating a new tab model
  //
  // TODO(crbug.com/334281979): This is done to avoid TabFeatures having to deal
  // with changing browser types during tab moves. This should no longer be
  // necessary once non-normal browser windows do not use Browser, TabStripModel
  // or TabModel.
  std::unique_ptr<content::WebContents> DetachWebContentsAtForInsertion(
      int index,
      TabRemovedReason reason = TabRemovedReason::kInsertedIntoOtherTabStrip);

  // Detaches the WebContents at the specified index and immediately deletes it.
  void DetachAndDeleteWebContentsAt(int index);

  std::vector<std::variant<std::unique_ptr<DetachedTab>,
                           std::unique_ptr<DetachedTabCollection>>>
  DetachTabsAndCollectionsForInsertion(const std::vector<int>& tab_indices);

  // Activates `tab`, which must be in this tab strip model. Prefer this over
  // ActivateTabAt() when a TabInterface* is already available.
  void ActivateTab(
      tabs::TabInterface* tab,
      TabStripUserGestureDetails user_gesture = TabStripUserGestureDetails(
          TabStripUserGestureDetails::GestureType::kNone));

  // Makes the tab at the specified index the active tab. |gesture_detail.type|
  // contains the gesture type that triggers the tab activation.
  // |gesture_detail.time_stamp| contains the timestamp of the user gesture, if
  // any.
  void ActivateTabAt(
      int index,
      TabStripUserGestureDetails gesture_detail = TabStripUserGestureDetails(
          TabStripUserGestureDetails::GestureType::kNone));

  // Move the WebContents at the specified index to another index. This
  // method does NOT send Detached/Attached notifications, rather it moves the
  // WebContents inline and sends a Moved notification instead.
  // EnsureGroupContiguity() is called after the move, so this will never result
  // in non-contiguous group (though the moved tab's group may change).
  // If `select_after_move` is false, whatever tab was selected before the move
  // will still be selected, but its index may have incremented or decremented
  // one slot. It returns the index the web contents is actually moved to.
  int MoveWebContentsAt(int index, int to_position, bool select_after_move);

  // This is similar to `MoveWebContentsAt` but takes in an additional `group`
  // parameter that the tab is assigned to with the move. This does not make a
  // best case effort to ensure group contiguity and would rather CHECK if it
  // breaks group contiguity.
  int MoveWebContentsAt(int index,
                        int to_position,
                        bool select_after_move,
                        std::optional<tab_groups::TabGroupId> group);

  // Moves the selected tabs to `index`. `index` is treated as if the tab strip
  // did not contain any of the selected tabs. For example, if the tabstrip
  // contains [A b c D E f] (upper case selected) and this is invoked with 1 the
  // result is [b A D E c f].
  // This method maintains that all pinned tabs occur before non-pinned tabs.
  // When pinned tabs are selected the move is processed in two chunks: first
  // pinned tabs are moved, then non-pinned tabs are moved. If the index is
  // after (pinned-tab-count - selected-pinned-tab-count), then the index the
  // non-pinned selected tabs are moved to is (index +
  // selected-pinned-tab-count). For example, if the model consists of
  // [A b c D E f] (A b c are pinned) and this is invoked with 2, the result is
  // [b c A D E f]. In this example nothing special happened because the target
  // index was <= (pinned-tab-count - selected-pinned-tab-count). If the target
  // index were 3, then the result would be [b c A f D F]. A, being pinned, can
  // move no further than index 2. The non-pinned tabs are moved to the target
  // index + selected-pinned tab-count (3 + 1).
  void MoveSelectedTabsTo(int index,
                          std::optional<tab_groups::TabGroupId> group);

  // Moves all tabs in `group` to `to_index`. This has no checks to make sure
  // the position is valid for a group to move to.
  void MoveGroupTo(const tab_groups::TabGroupId& group, int to_index);

  // Moves all tabs in split with `split_id` to `to_index` with  properties
  // `pinned` and `group_id`. This has no checks to make sure the position is
  // valid for a split to move to.
  void MoveSplitTo(const split_tabs::SplitTabId& split_id,
                   int to_index,
                   bool pinned,
                   std::optional<tab_groups::TabGroupId> group_id);

  // Returns the currently active WebContents, or NULL if there is none.
  content::WebContents* GetActiveWebContents() const;

  // Returns the currently active Tab, or NULL if there is none.
  tabs::TabInterface* GetActiveTab() const;

  // Returns the currently active tab or if it is a split tab, all the tabs in
  // that split. Doesn't take into account occlusion.
  std::vector<tabs::TabInterface*> GetForegroundTabs() const;

  // Returns the WebContents at the specified index, or NULL if there is
  // none.
  content::WebContents* GetWebContentsAt(int index) const;

  // Returns the index of the specified WebContents, or TabStripModel::kNoTab
  // if the WebContents is not in this TabStripModel.
  int GetIndexOfWebContents(const content::WebContents* contents) const;

  // Notify any observers that the tab has changed in some way. See
  // TabChangeType for details of `change_type`.
  void NotifyTabChanged(tabs::TabInterface* tab,
                        TabChangeType change_type);

  // Notify any observers that the WebContents has changed in some way. See
  // TabChangeType for details of `change_type`.
  void UpdateWebContentsState(content::WebContents* contents,
                              TabChangeType change_type);

  // Cause a tab to display a UI indication to the user that it needs their
  // attention.
  void SetTabNeedsAttention(content::WebContents* contents, bool attention);

  // Close all tabs at once. Code can use closing_all() above to defer
  // operations that might otherwise by invoked by the flurry of detach/select
  // notifications this method causes.
  void CloseAllTabs();

  // Closes all tabs in the given `group`. If the group is focused, focus is
  // unset before closing. If all tabs in the tabstrip are in `group`, a new
  // fallback tab is added outside the group to prevent the browser window from
  // closing.
  void CloseAllTabsInGroup(const tab_groups::TabGroupId& group);

  // Returns true if there are any WebContentses that are currently loading
  // and should be shown on the UI.
  bool TabsNeedLoadingUI() const;

  // Returns the WebContents that opened the WebContents at `index`, or NULL if
  // there is no opener on record.
  tabs::TabInterface* GetOpenerOfTabAt(const int index) const;

  // Changes the `opener` of the tab at `index`.
  // Note: A tab must not be its own opener.
  void SetOpenerOfTabAt(int index, tabs::TabInterface* opener);

  // Returns the index of the last WebContents in the model opened by the
  // specified opener, starting at `start_index`.
  int GetIndexOfLastWebContentsOpenedBy(const content::WebContents* opener,
                                        int start_index) const;

  // To be called when a navigation is about to occur in the specified
  // WebContents. Depending on the tab, and the transition type of the
  // navigation, the TabStripModel may adjust its selection behavior and opener
  // inheritance.
  void TabNavigating(content::WebContents* contents,
                     ui::PageTransition transition);

  // Changes the blocked state of the tab at `index`.
  void SetTabBlocked(int index, bool blocked);

  // Changes the pinned state of the tab at `index`. See description above
  // class for details on this. Returns the index the tab is now at (it may have
  // been moved to maintain contiguity of pinned tabs at the beginning of the
  // tabstrip).)
  int SetTabPinned(int index, bool pinned);

  // Returns true if the tab at `index` is pinned.
  // See description above class for details on pinned tabs.
  bool IsTabPinned(int index) const;

  bool IsTabCollapsed(int index) const;

  bool IsGroupCollapsed(const tab_groups::TabGroupId& group) const;

  // Returns true if the tab at `index` is blocked by a tab modal dialog.
  bool IsTabBlocked(int index) const;

  // Returns true if the tab at `index` is in the foreground.
  bool IsTabInForeground(int index) const;

  // Returns true if the tab corresponding to `tab` is allowed to be
  // closed.
  bool IsTabClosable(const tabs::TabInterface* tab) const;

  split_tabs::SplitTabData* GetSplitData(split_tabs::SplitTabId split_id) const;

  // Returns the set of SplitTabIds for the split tabs found in this
  // TabStripModel. These ids are globally unique and randomly generated across
  // all windows.
  std::set<split_tabs::SplitTabId> ListSplits() const;

  bool ContainsSplit(split_tabs::SplitTabId split_id) const;

  std::optional<split_tabs::SplitTabId> GetSplitForTab(int index) const;

  // Returns the group that contains the tab at `index`, or nullopt if the tab
  // index is invalid or not grouped.
  std::optional<tab_groups::TabGroupId> GetTabGroupForTab(int index) const;

  // If a tab inserted at `index` would be within a tab group, return that
  // group's ID. Otherwise, return nullopt. If `index` points to the first tab
  // in a group, it will return nullopt since a new tab would be either between
  // two different groups or just after a non-grouped tab.
  std::optional<tab_groups::TabGroupId> GetSurroundingTabGroup(int index) const;

  // Returns the index of the first tab that is not a pinned tab. This returns
  // `count()` if all of the tabs are pinned tabs, and 0 if none of the tabs are
  // pinned tabs.
  int IndexOfFirstNonPinnedTab() const;

  // Extends the selection from the anchor to `index`.
  void ExtendSelectionTo(int index);

  // This can fail if the tabstrip is not editable.
  void SelectTabAt(int index);

  // This can fail if the tabstrip is not editable.
  void DeselectTabAt(int index);

  // Makes sure the tabs from the anchor to `index` are selected. This adds to
  // the selection if there is an anchor and resets the selection to `index` if
  // there is not an anchor.
  void AddSelectionFromAnchorTo(int index);

  // Returns true if the tab at `index` is selected.
  bool IsTabSelected(int index) const;

  // Sets the selection to match that of `source`.
  void SetSelectionFromModel(ui::ListSelectionModel source);
  void SetSelectionFromModel(tabs::TabStripModelSelectionState source);

  const tabs::TabStripModelSelectionState& selection_model() const {
    return selection_model_;
  }

  // Features that want to show tabstrip-modal UI are mutually exclusive.
  // Before showing a modal UI first check `CanShowModalUI`. Then call
  // ShowModalUI() and keep `ScopedTabStripModal` alive to prevent other
  // features from showing tabstrip-modal UI.
  bool CanShowModalUI() const;
  std::unique_ptr<ScopedTabStripModalUI> ShowModalUI();
  void ForceShowingModalUIForTesting(bool showing);

  // Command level API /////////////////////////////////////////////////////////

  // Adds a WebContents at the best position in the TabStripModel given
  // the specified insertion index, transition, etc. `add_types` is a bitmask of
  // AddTabTypes; see it for details. This method ends up calling into
  // InsertWebContentsAt to do the actual insertion. Pass kNoTab for `index` to
  // append the contents to the end of the tab strip.
  void AddWebContents(
      std::unique_ptr<content::WebContents> contents,
      int index,
      ui::PageTransition transition,
      int add_types,
      std::optional<tab_groups::TabGroupId> group = std::nullopt);
  void AddTab(std::unique_ptr<tabs::TabModel> tab,
              int index,
              ui::PageTransition transition,
              int add_types,
              std::optional<tab_groups::TabGroupId> group = std::nullopt);

  // Closes the selected tabs.
  void CloseSelectedTabs();

  // Select adjacent tabs
  void SelectNextTab(
      TabStripUserGestureDetails detail = TabStripUserGestureDetails(
          TabStripUserGestureDetails::GestureType::kOther));
  void SelectPreviousTab(
      TabStripUserGestureDetails detail = TabStripUserGestureDetails(
          TabStripUserGestureDetails::GestureType::kOther));

  // Selects the last tab in the tab strip.
  void SelectLastTab(
      TabStripUserGestureDetails detail = TabStripUserGestureDetails(
          TabStripUserGestureDetails::GestureType::kOther));

  // Moves the active in the specified direction. Respects group boundaries.
  void MoveTabNext();
  void MoveTabPrevious();

  // This is used by the `tab_drag_controller` to help precompute the group the
  // selected tabs would be a part of if they moved to a destination index. It
  // returns the index of the tabs in the current model that would end up being
  // the adjacent tabs of the selected unpinned tabs post move operation.
  std::pair<std::optional<int>, std::optional<int>>
  GetAdjacentTabsAfterSelectedMove(base::PassKey<DraggingTabsSession>,
                                   int destination_index);

  // Updates the layout for the tabs with `split_id` and notifies observers.
  void UpdateSplitLayout(
      split_tabs::SplitTabId split_id,
      split_tabs::SplitTabLayout tab_layout,
      std::optional<split_tabs::SplitTabOrientationChangeSource> source =
          std::nullopt);

  // Updates the ratio for the tabs with `split_id` and notifies observers.
  void UpdateSplitRatio(split_tabs::SplitTabId split_id,
                        double start_content_ratio,
                        bool is_intermediate = false);

  // Updates the split tab at index `split_index` with the tab at
  // `update_index`. The split that includes `split_index` must include the
  // active tab in the tab strip.
  enum class SplitUpdateType { kReplace, kSwap };
  void UpdateTabInSplit(tabs::TabInterface* split_tab,
                        int update_index,
                        SplitUpdateType update_type);

  // Reverses the order of tabs with `split_id`.
  void ReverseTabsInSplit(split_tabs::SplitTabId split_id);

  // Create a new split view with the first entry in `indices` (or the active
  // tab if there is only one element in `indices`) as the pivot index and add
  // the rest of the tabs pointed to by `indices` to it. Reorders the tabs so
  // they are contiguous.
  split_tabs::SplitTabId AddToNewSplit(
      std::vector<int> indices,
      split_tabs::SplitTabVisualData visual_data,
      split_tabs::SplitTabCreatedSource source);

  // Adds all the tabs in `indices` to a split with `split_id` and
  // `visual_data`. `pivot_index` is the index in indices to determine the
  // properties of the split like group, pin, destination index.
  void RestoreSplit(split_tabs::SplitTabId split_id,
                    const std::vector<int>& indices,
                    split_tabs::SplitTabVisualData visual_data);

  // Create a new tab group and add the set of tabs pointed to be `indices` to
  // it. Pins all of the tabs if any of them were pinned, and reorders the tabs
  // so they are contiguous and do not split an existing group in half. Returns
  // the new group. This may unsplit split tabs if they are only partially
  // contained in `indices`. `indices` must be sorted in ascending order.
  tab_groups::TabGroupId AddToNewGroup(const std::vector<int> indices);

  // Add the set of tabs pointed to by `indices` to the given tab group `group`.
  // The tabs take on the pinnedness of the tabs already in the group. Tabs
  // before the group will move to the start, while tabs after the group will
  // move to the end. If `add_to_end` is true, all tabs will instead move to
  // the end. This may unsplit split tabs if they are only partially contained
  // in `indices`. `indices` must be sorted in ascending order.
  void AddToExistingGroup(const std::vector<int> indices,
                          const tab_groups::TabGroupId group,
                          const bool add_to_end = false);

  // Similar to AddToExistingGroup(), but creates a group with id `group` if it
  // doesn't exist. This is only intended to be called from session restore
  // code.
  void AddToGroupForRestore(const std::vector<int>& indices,
                            const tab_groups::TabGroupId& group);

  // Removes the set of tabs pointed to by `indices` from the the groups they
  // are in, if any. The tabs are moved out of the group if necessary. This
  // may unsplit split tabs if they are only partially contained in `indices`.
  // `indices` must be sorted in ascending order.
  void RemoveFromGroup(const std::vector<int>& indices);

  // Unsplits all the tabs that are part of the split with `split_id`. The tabs
  // maintain their group and pin properties.
  void RemoveSplit(split_tabs::SplitTabId split_id);

  TabGroupModel* group_model() const { return group_model_.get(); }

  bool SupportsTabGroups() const { return group_model_.get() != nullptr; }

  // Returns the ID of the group that is focused. If no group is focused,
  // returns nullopt.
  std::optional<tab_groups::TabGroupId> GetFocusedGroup() const;

  // Sets the group to be focused.
  void SetFocusedGroup(std::optional<tab_groups::TabGroupId> group);

  // Rotates the focused tab group between the unfocused state and active tab
  // groups in the strip. Requires `features::kTabGroupsFocusing` to be enabled
  // (CHECKs). If there are no tab groups, this is a no-op. When `forward` is
  // true, rotates towards higher-indexed groups; otherwise rotates in reverse
  // order.
  void RotateFocusedGroup(bool forward);

  // Returns true if one or more of the tabs pointed to by `indices` are
  // supported by read later.
  bool IsReadLaterSupportedForAny(const std::vector<int>& indices);

  // Saves tabs with url supported by Read Later.
  void AddToReadLater(const std::vector<int>& indices);

  // Notifies all group observers that the TabGroupEditor is opening. This is
  // used by Views that want to force the editor to open without having to find
  // the group's header view in the Tab Strip.
  void OpenTabGroupEditor(const tab_groups::TabGroupId& group);

  // Updates the group visuals and notifies observers.
  void ChangeTabGroupVisuals(const tab_groups::TabGroupId& group,
                             tab_groups::TabGroupVisualData visual_data,
                             bool is_customized = false);

  // Returns iterators for traversing through all the tabs in the tabstrip.
  TabIterator begin() const;
  TabIterator end() const;
  reverse_iterator rbegin() const;
  reverse_iterator rend() const;
  TabIterator at(tabs::TabInterface* tab) const;

  // Gets the root of the tab strip model. Used to traverse the tab topology.
  const tabs::TabCollection* Root() const;

  // Finds the group id for a tab collection. Note that this API can be error
  // prone. Make sure to read and understand the potential problems with
  // relying on group id.
  std::optional<const tab_groups::TabGroupId> FindGroupIdFor(
      const tabs::TabCollection::Handle& collection_handle,
      base::PassKey<tabs_api::TabStripModelAdapterImpl>) const;

  tabs::TabCollectionHandle GetPinnedTabsCollectionHandle(
      base::PassKey<tabs_api::TabStripModelAdapterImpl>) const;
  tabs::TabCollectionHandle GetUnpinnedTabsCollectionHandle(
      base::PassKey<tabs_api::TabStripModelAdapterImpl>) const;

  // View API //////////////////////////////////////////////////////////////////

  using ContextMenuCommand = tabs::TabContextMenuCommand;
  using enum tabs::TabContextMenuCommand;

  // Returns true if the specified command is enabled. If `context_index` is
  // selected the response applies to all selected tabs.
  bool IsContextMenuCommandEnabled(int context_index,
                                   ContextMenuCommand command_id) const;

  // Performs the action associated with the specified command for the given
  // TabStripModel index `context_index`.  If `context_index` is selected the
  // command applies to all selected tabs.
  void ExecuteContextMenuCommand(int context_index,
                                 ContextMenuCommand command_id);

  // Returns a list of the group ids that are going to be deleted if a given
  // list of tab indexes are removed from the group. used by context menu
  // commands to decide whether to confirm group deletion.
  std::vector<tab_groups::TabGroupId> GetGroupsDestroyedFromRemovingIndices(
      const std::vector<int>& indices) const;

  // Returns a list of the group ids that are going to be deleted if a given
  // list of tabs are removed.
  std::vector<tab_groups::TabGroupId> GetGroupsDestroyedFromRemovingTabs(
      const std::vector<tabs::TabInterface*>& tabs_to_remove) const;

  // This should be called after GetGroupsDestroyedFromRemovingIndices(). Marks
  // all groups in `group_ids` as closing. This is useful in the event you need
  // to know if a group is currently closing or not such as when a grouped tab
  // is closed which has an unload handler.
  void MarkTabGroupsForClosing(
      const std::vector<tab_groups::TabGroupId> group_ids);

  // There are multiple commands that close tabs. They all must check the
  // Group affiliation of the tabs, confirm that they can delete groups, and
  // then perform the close of the tabs. When true `delete_groups` also
  // deletes any saved groups that are closing. When false, groups will close
  // normally but continue to be saved.
  void ExecuteCloseTabsCommand(
      base::RepeatingCallback<std::vector<tabs::TabInterface*>()>
          get_tabs_to_close,
      bool delete_groups);

  // Adds the tab at `context_index` to the given tab group `group`. If
  // `context_index` is selected the command applies to all selected tabs.
  void ExecuteAddToExistingGroupCommand(int context_index,
                                        const tab_groups::TabGroupId& group);

  // Adds the tab at `context_index` to the browser window at `browser_index`.
  // If `context_index` is selected the command applies to all selected tabs.
  void ExecuteAddToExistingWindowCommand(int context_index, int browser_index);

  // Adds the tab at `context_index` to a new split with the current active tab.
  // If `context_index` is active, the currently selected tabs are added to a
  // new split.
  void ExecuteAddToNewSplitCommand(int context_index,
                                   split_tabs::SplitTabLayout layout);

  // Returns true if 'CommandToggleSiteMuted' will mute. `index` is the
  // index supplied to `ExecuteContextMenuCommand`.
  bool WillContextMenuMuteSites(int index);

  // Returns true if 'CommandTogglePinned' will pin. `index` is the index
  // supplied to `ExecuteContextMenuCommand`.
  bool WillContextMenuPin(int index);

  // Returns true if 'CommandToggleGrouped' will group. `index` is the index
  // supplied to `ExecuteContextMenuCommand`.
  bool WillContextMenuGroup(int index);

  // Convert a ContextMenuCommand into a browser command. Returns true if a
  // corresponding browser command exists, false otherwise.
  static bool ContextMenuCommandToBrowserCommand(int cmd_id, int* browser_cmd);

  // Returns the index of the next tab spawned by the specified tabs in
  // `block_tab_range`.
  int GetIndexOfNextWebContentsOpenedBy(
      const gfx::Range& block_tab_range) const;

  // Returns the index of the next tab spawned by the opener of the specified
  // tabs in `block_tab_range`.
  int GetIndexOfNextWebContentsOpenedByOpenerOf(
      const gfx::Range& block_tab_range) const;

  // Finds the next available tab to switch to as the active tab starting at
  // a block of tabs. The methods will check the indices to
  // the right of the block before checking the indices to the left of the
  // block. Index within the block cannot be returned. Returns std::nullopt if
  // there are no valid tabs.
  std::optional<int> GetNextExpandedActiveTab(
      const gfx::Range& block_tab_range) const;
  std::optional<int> GetNextExpandedActiveTab(
      tab_groups::TabGroupId collapsing_group) const;

  // Forget all opener relationships, to reduce unpredictable tab switching
  // behavior in complex session states.
  void ForgetAllOpeners();

  // Forgets the opener relationship of the specified WebContents.
  void ForgetOpener(content::WebContents* contents);

  // Determine where to place a newly opened tab by using the supplied
  // transition and foreground flag to figure out how it was opened.
  int DetermineInsertionIndex(ui::PageTransition transition, bool foreground);

  // If a tab is in a group and the tab failed to close, this method will be
  // called from the unload_controller. Ungroup the group to maintain
  // consistency with the user's intended action (to get rid of the group).
  void GroupCloseStopped(const tab_groups::TabGroupId& group);

  // Serialise this object into a trace.
  void WriteIntoTrace(perfetto::TracedValue context) const;

  // Convert between tabs and indices.
  int GetIndexOfTab(const tabs::TabInterface* tab) const;
  tabs::TabInterface* GetTabAtIndex(int index) const;
  // Gets the tabs at the specified indices using two pointers instead of
  // calling GetTabAtIndex repeatedly, making the runtime O(count()) instead of
  // O(indices.size() * count()). Requires indices to be strictly ascending.
  std::vector<tabs::TabInterface*> GetTabsAtIndices(
      const std::vector<int>& indices) const;

  tabs::TabInterface* GetTabForWebContents(
      const content::WebContents* contents) const;

 private:
  FRIEND_TEST_ALL_PREFIXES(TabStripModelTest, GetIndicesClosedByCommand);
  // Temporary private API.
  FRIEND_TEST_ALL_PREFIXES(TabStripModelTest, FindGroupIdFor);

  struct DetachNotifications;
  struct MoveNotification {
    int initial_index;
    std::optional<tab_groups::TabGroupId> intial_group;
    bool initial_pinned;
    raw_ptr<const tabs::TabInterface> tab;
    TabStripSelectionChange selection_change;
  };

  // Tracks whether a tabstrip-modal UI is showing.
  class ScopedTabStripModalUIImpl : public ScopedTabStripModalUI {
   public:
    explicit ScopedTabStripModalUIImpl(TabStripModel* model);
    ~ScopedTabStripModalUIImpl() override;

   private:
    // Owns this.
    raw_ptr<TabStripModel> model_;
  };

  tabs::TabModel* GetTabModelAtIndex(int index) const;

  tabs::TabModel* GetActiveTabModel() const;

  // Perform tasks associated with changes to the model. Change the Active Index
  // and notify observers.
  void OnChange(const TabStripModelChange& change,
                const TabStripSelectionChange& selection);

  // Notify observers if the focused tab group has changed.
  void NotifyTabGroupFocusChanged(
      const std::optional<tab_groups::TabGroupId>& old_focused_group);

  // Logs metrics when the focused tab group changes.
  void LogTabGroupFocusMetrics(
      const std::optional<tab_groups::TabGroupId>& old_focused_group,
      const std::optional<tab_groups::TabGroupId>& new_focused_group);

  // Notify observers that a `group` was created.
  void NotifyTabGroupVisualsChanged(const tab_groups::TabGroupId& group_id,
                                    TabGroupChange::VisualsChange visuals);

  // Notify observers that a `group` was created.
  void NotifyTabGroupCreated(const tab_groups::TabGroupId& group);

  // Notify observers that a `group` was closed.
  void NotifyTabGroupClosed(const tab_groups::TabGroupId& group);

  // Notify observers that `group` is moved.
  void NotifyTabGroupMoved(const tab_groups::TabGroupId& group);

  // Notify observers that `group` is detached from the model. This also sends
  // split related observations within the group.
  void NotifyTabGroupDetached(
      tabs::TabGroupTabCollection* group_collection,
      std::map<split_tabs::SplitTabId,
               std::vector<std::pair<tabs::TabInterface*, int>>>
          splits_in_group);

  // Notify observers that `group` is attached to the model. This also sends
  // split related observations within the group.
  void NotifyTabGroupAttached(tabs::TabGroupTabCollection* group_collection);

  // Notify observers that split with `split_id` has been created.
  void NotifySplitTabCreated(
      split_tabs::SplitTabId split_id,
      const std::vector<std::pair<tabs::TabInterface*, int>>& tabs_with_indices,
      SplitTabChange::SplitTabAddReason reason,
      const split_tabs::SplitTabVisualData& visual_data);

  // Notify observers that visual data for a split has changed.
  void NotifySplitTabVisualsChanged(
      split_tabs::SplitTabId split_id,
      const split_tabs::SplitTabVisualData& old_visual_data,
      const split_tabs::SplitTabVisualData& new_visual_data,
      const SplitTabChange::SplitVisualChangeReason reason,
      bool is_intermediate = false);

  // Notify observers that contents of a split has been reordered.
  void NotifySplitTabContentsUpdated(
      split_tabs::SplitTabId split_id,
      const std::vector<std::pair<tabs::TabInterface*, int>>& prev_tabs,
      const std::vector<std::pair<tabs::TabInterface*, int>>& new_tabs);

  // Notify observers that split with `split_id` has been removed.
  void NotifySplitTabRemoved(
      split_tabs::SplitTabId split_id,
      const std::vector<std::pair<tabs::TabInterface*, int>>& tabs_with_indices,
      SplitTabChange::SplitTabRemoveReason reason);

  // Notify observers that a split was detached from this tabstrip model.
  // This also sends any group related notification.
  void NotifySplitTabDetached(
      tabs::SplitTabCollection* split_collection,
      std::vector<std::pair<tabs::TabInterface*, int>> tabs_in_split,
      std::optional<tab_groups::TabGroupId> previous_group_state);

  // Notify observers that a split was attached to this tabstrip model.
  // This also sends any group related notification.
  void NotifySplitTabAttached(tabs::SplitTabCollection* split_collection);

  // Detaches the tab at the specified `index` from this strip.
  // `web_contents_remove_reason` is used to indicate to observers what is going
  // to happen to the WebContents (i.e. deleted or reinserted into another tab
  // strip). `tab_detach_reason` is used to indicate to observers what is going
  // to happen to the TabModel owning the WebContents. These reasons may not
  // always match (a WebContents may be retained for re-insertion while its
  // owning TabModel may be destroyed).
  std::unique_ptr<DetachedTab> DetachTabWithReasonAt(
      int index,
      TabRemovedReason web_contents_remove_reason,
      tabs::TabInterface::DetachReason tab_detach_reason);

  // Performs all the work to detach a TabModel instance but avoids sending
  // most notifications. TabClosingAt() and TabDetachedAt() are sent because
  // observers are reliant on the selection model being accurate at the time
  // that TabDetachedAt() is called.
  std::unique_ptr<DetachedTab> DetachTabImpl(
      int index_before_any_removals,
      int index_at_time_of_removal,
      bool create_historical_tab,
      TabRemovedReason web_contents_remove_reason,
      tabs::TabInterface::DetachReason tab_detach_reason);

  // Removes a tab collection from `contents_data_` using
  // `execute_detach_collection_operation`. Also sends collection specific
  // observation using `execute_tabs_notify_observer_operation` like group and
  // split related observation calls. `TabStripModelChange` and
  // `TabStripSelectionChange` observation calls are handled as common code.
  std::unique_ptr<tabs::TabCollection> DetachTabCollectionImpl(
      tabs::TabCollection* collection,
      base::OnceCallback<std::unique_ptr<tabs::TabCollection>()>
          execute_detach_collection_operation,
      base::OnceClosure execute_tabs_notify_observer_operation);

  // Helper method performing tasks like notification, fixing opener and
  // returning back a Remove struct before actually detaching the set of
  // tab_indices.
  TabStripModelChange::Remove ProcessTabsForDetach(gfx::Range tab_indices);

  // Helper method for updating the selection model after detaching a collection
  // from `contents_data_`.
  void UpdateSelectionModelForCollectionDetach(
      const std::vector<tabs::TabInterface*>& already_detached_tabs,
      int detach_start_index,
      std::optional<int> next_selected_index,
      bool active_tab_removed);

  // Attaches a tab collection to `contents_data_` using
  // `execute_insert_detached_tabs_operation`. Also sends collection specific
  // observation using `execute_tabs_notify_observer_operation` like group and
  // split related observation calls. `TabStripModelChange` and
  // `TabStripSelectionChange` observation calls are handled as common code.
  gfx::Range InsertDetachedCollectionImpl(
      tabs::TabCollection* collection,
      std::optional<int> active_index,
      base::OnceClosure execute_insert_detached_tabs_operation,
      base::OnceClosure execute_tabs_notify_observer_operation);

  // This is the callback used as `execute_insert_detached_tabs_operation` in
  // `InsertDetachedCollectionImpl` when a group is inserted into a tabstrip. It
  // updates the `group_model_` and inserts the `group_collection` into
  // `contents_data_`.
  void InsertDetachedTabGroupImpl(
      std::unique_ptr<tabs::TabGroupTabCollection> group_collection,
      int index);

  // We batch send notifications. This has two benefits:
  //   1) This allows us to send the minimal number of necessary notifications.
  //   This is important because some notifications cause the main thread to
  //   synchronously communicate with the GPU process and cause jank.
  //   https://crbug.com/41379209.
  //   2) This allows us to avoid some problems caused by re-entrancy [e.g.
  //   using destroyed WebContents instances]. Ideally, this second check
  //   wouldn't be necessary because we would enforce that there is no
  //   re-entrancy in the TabStripModel, but that condition is currently
  //   violated in tests [and possibly in the wild as well].
  void SendDetachWebContentsNotifications(DetachNotifications* notifications);

  bool RunUnloadListenerBeforeClosing(content::WebContents* contents);
  bool ShouldRunUnloadListenerBeforeClosing(content::WebContents* contents);

  int ConstrainInsertionIndex(int index, bool pinned_tab) const;

  int ConstrainMoveIndex(int index, bool pinned_tab) const;

  // If `index` is selected all the selected indices are returned, otherwise a
  // vector with `index` is returned. This is used when executing commands to
  // determine which indices the command applies to. Indices are sorted in
  // increasing order.
  std::vector<int> GetIndicesForCommand(int index) const;

  // Returns a vector of indices of the tabs that will close when executing the
  // command `id` for the tab at `index`. The returned indices are sorted in
  // descending order.
  std::vector<int> GetIndicesClosedByCommand(int index,
                                             ContextMenuCommand id) const;

  std::vector<tabs::TabInterface*> GetTabsForCommand(int index) const;
  std::vector<tabs::TabInterface*> GetTabsClosedByCommand(
      int index,
      ContextMenuCommand id) const;
  // Returns true if the specified WebContents is a New Tab at the end of
  // the tabstrip. We check for this because opener relationships are _not_
  // forgotten for the New Tab page opened as a result of a New Tab gesture
  // (e.g. Ctrl+T, etc) since the user may open a tab transiently to look up
  // something related to their current activity.
  bool IsNewTabAtEndOfTabStrip(content::WebContents* contents) const;

  // Adds the specified TabModel at the specified location.
  // `add_types` is a bitmask of AddTabTypes; see it for details.
  //
  // All append/insert methods end up in this method.
  //
  // NOTE: adding a tab using this method does NOT query the order controller,
  // as such the ADD_FORCE_INDEX AddTabTypes is meaningless here. The only time
  // the `index` is changed is if using the index would result in breaking the
  // constraint that all pinned tabs occur before non-pinned tabs. It returns
  // the index the tab is actually inserted to. See also AddWebContents.
  int InsertTabAtImpl(int index,
                      std::unique_ptr<tabs::TabModel> tab,
                      int add_types,
                      std::optional<tab_groups::TabGroupId> group);

  // Closes the WebContentses at the specified indices. This causes the
  // WebContentses to be destroyed, but it may not happen immediately. If
  // the page in question has an unload event the WebContents will not be
  // destroyed until after the event has completed, which will then call back
  // into this method.
  void CloseTabs(base::span<content::WebContents* const> items,
                 uint32_t close_types);

  // Executes a call to CloseTabs on the web contentses contained in tabs
  // returned from `get_tabs_to_close`. This is a helper method
  // bound by ExecuteCloseTabsCommand in order to properly
  // protect the stack from reentrancy.
  void ExecuteCloseTabs(
      base::RepeatingCallback<std::vector<tabs::TabInterface*>()>
          get_tabs_to_close,
      uint32_t close_types);

  // `close_types` is a bitmask of the types in CloseTypes.
  // Returns true if all the tabs have been deleted. A return value of false
  // means some portion (potentially none) of the WebContents were deleted.
  // WebContents not deleted by this function are processing unload handlers
  // which may eventually be deleted based on the results of the unload handler.
  // Additionally processing the unload handlers may result in needing to show
  // UI for the WebContents. See UnloadController for details on how unload
  // handlers are processed.
  bool CloseWebContentses(base::span<content::WebContents* const> items,
                          uint32_t close_types,
                          DetachNotifications* notifications);

  // Returns the WebContentses at the specified indices. Requires indices to be
  // strictly ascending or descending.
  std::vector<content::WebContents*> GetWebContentsesByIndices(
      std::vector<int> indices) const;

  // Sets the selection to `new_model` and notifies any observers.
  // Note: This function might end up sending 0 to 3 notifications in the
  // following order: TabDeactivated, ActiveTabChanged, TabSelectionChanged.
  // `selection` will be filled with information corresponding to 3 notification
  // above. When it's `triggered_by_other_operation`, This won't notify
  // observers that selection was changed. Callers should notify it by
  // themselves.
  TabStripSelectionChange SetSelection(
      const tabs::TabStripModelSelectionState& new_model,
      TabStripModelObserver::ChangeReason reason,
      bool triggered_by_other_operation,
      bool notify_focus_change = true);

  // Close all tabs in the given `group` at once.
  void CloseAllTabsInGroupImpl(const tab_groups::TabGroupId& group);

  // Direction of relative tab movements or selections. kNext indicates moving
  // forward (positive increment) in the tab strip. kPrevious indicates
  // backward (negative increment).
  enum class TabRelativeDirection {
    kNext,
    kPrevious,
  };

  // Selects either the next tab (kNext), or the previous tab (kPrevious).
  void SelectRelativeTab(TabRelativeDirection direction,
                         TabStripUserGestureDetails detail);

  // Moves the active tab (or its split if it is in one) into the next slot
  // (kNext), or the previous slot (kPrevious). Respects group boundaries and
  // creates movement slots into and out of groups. Treats split tabs as a
  // single slot.
  void MoveTabRelative(TabRelativeDirection direction);

  // Implementation of MoveSelectedTabsTo. Moves `length` of the selected tabs
  // starting at `start` to `index`. See MoveSelectedTabsTo for more details.
  void MoveSelectedTabsToImpl(int index, size_t start, size_t length);

  std::vector<int> GetSelectedPinnedTabs();
  std::vector<int> GetSelectedUnpinnedTabs();

  split_tabs::SplitTabId AddToSplitImpl(
      split_tabs::SplitTabId split_id,
      const std::vector<int>& indices,
      int pivot_index,
      split_tabs::SplitTabVisualData visual_data,
      SplitTabChange::SplitTabAddReason reasons);

  void RemoveSplitImpl(split_tabs::SplitTabId split_id,
                       SplitTabChange::SplitTabRemoveReason reason);

  void UpdateTabInSplitImpl(tabs::TabInterface* split_tab,
                            int update_index,
                            SplitUpdateType update_type);

  // Adds tabs to newly-allocated group id `new_group`. This group must be new
  // and have no tabs in it.
  void AddToNewGroupImpl(
      const std::vector<int>& indices,
      const tab_groups::TabGroupId& new_group,
      std::optional<tab_groups::TabGroupVisualData> visual_data = std::nullopt);

  void MoveGroupToImpl(const tab_groups::TabGroupId& group, int to_index);

  // Adds tabs to existing group `group`. This group must have been initialized
  // by a previous call to `AddToNewGroupImpl()`.
  void AddToExistingGroupImpl(const std::vector<int>& indices,
                              const tab_groups::TabGroupId& group,
                              const bool add_to_end = false);

  // Adds all selected indices provided by `context_index` into a new tab group.
  void AddToNewGroupFromContextIndex(int context_index);

  // Implementation of MoveTabsAndSetPropertiesImpl. Moves the set of tabs in
  // `indices` to the `destination_index` and updates the tabs to the
  // appropriate `group` and `pinned` properties.
  // Note: `destination_index` refers to a place in the tabstrip prior to the
  // move operation.
  void MoveTabsAndSetPropertiesImpl(const std::vector<int>& indices,
                                    int destination_index,
                                    std::optional<tab_groups::TabGroupId> group,
                                    bool pinned);

  void AddToReadLaterImpl(const std::vector<int>& indices);

  // Updates the `contents_data_` and sends out observer notifications for
  // inserting a new tab in  the tabstrip.
  void InsertTabAtIndexImpl(std::unique_ptr<tabs::TabModel> tab_model,
                            int index,
                            std::optional<tab_groups::TabGroupId> group,
                            bool pin,
                            bool active);

  // Updates the `contents_data_` and sends out observer notifications for
  // removing an existing tab in  the tabstrip.
  std::unique_ptr<tabs::TabModel> RemoveTabFromIndexImpl(
      int index,
      tabs::TabInterface::DetachReason tab_detach_reason,
      int index_before_any_removals);

  // Updates the `contents_data_` and sends out observer notifications for
  // updating the index, pinned state or group property.
  void MoveTabToIndexImpl(int initial_index,
                          int final_index,
                          const std::optional<tab_groups::TabGroupId> group,
                          bool pin,
                          bool select_after_move);

  // Similar to `MoveTabToIndexImpl` but this is used for multiple tabs either
  // being moved or having their group updated. `tab_indices` should be sorted.
  // Tabs are inserted at `destination_index` after they are removed.
  void MoveTabsToIndexImpl(const std::vector<int>& tab_indices,
                           int destination_index,
                           const std::optional<tab_groups::TabGroupId> group);

  // Sends group notifications for a tab at `index` based on its initial_group
  // and `final_group` and updates the `group_model_`.
  void TabGroupStateChanged(
      int index,
      tabs::TabInterface* tab,
      const std::optional<tab_groups::TabGroupId> initial_group,
      const std::optional<tab_groups::TabGroupId> new_group);

  // Exits or updates focus mode when a moved or reparented tab was the active
  // tab in the focused group.
  void MaybeUpdateFocusModeForMovedTab(
      tabs::TabInterface* tab,
      bool initial_pinned_state,
      const std::optional<tab_groups::TabGroupId>& initial_focused_group);

  // Updates the `group_model` by incrementing the tab count of `group`.
  void AddTabToGroupModel(const tab_groups::TabGroupId& group);

  // Checks if the `contents_data_` is in a valid order. This checks for
  // pinned tabs placement, group contiguity and selected tabs validity.
  void CompleteModelUpdateTransaction();

  void SendMoveNotificationForTab(
      int index,
      int to_position,
      tabs::TabInterface* tab,
      const TabStripSelectionChange& selection_change);

  void UpdateSelectionModelForMove(int initial_index,
                                   int final_index,
                                   bool select_after_move);

  void UpdateSelectionModelForMoves(const std::vector<int>& tab_indices,
                                    int destination_index);

  // Clears any previous selection and sets the selected index. This takes into
  // account split tabs. Namely, if the tab at `index` is a split tab then
  // all the tabs in the split will be selected.
  void SetSelectedIndex(tabs::TabStripModelSelectionState& selection_state,
                        int index);
  void SetSelectedTab(tabs::TabStripModelSelectionState& selection_state,
                      tabs::TabInterface* tab);

  // Returns a vector of tabs between the anchor and a provided index, in
  // traversal order, that takes into account split tabs. If the anchor or
  // the tab at index is part of a split, the range will include that split.
  std::vector<tabs::TabInterface*> GetSelectionRangeFromAnchorToIndex(
      int index);

  // Generates the MoveNotifications for `MoveTabsToIndexImpl` and updates the
  // selection model and openers.
  std::vector<TabStripModel::MoveNotification> PrepareTabsToMoveToIndex(
      const std::vector<int>& tab_indices,
      int destination_index);

  // Generates a sequence of initial and destination index for tabs in
  // `tab_indices` when the tabs need to move to `destination_index`.
  std::vector<std::pair<int, int>> CalculateIncrementalTabMoves(
      const std::vector<int>& tab_indices,
      int destination_index) const;

  // Changes the pinned state of all tabs at `indices`, moving them in the
  // process if necessary. If indices contains all tabs in a split, the whole
  // split is pinned/unpinned. Otherwise, the tabs will be individually
  // processed, resulting in the split being unsplit.
  void SetTabsPinned(const std::vector<int> indices, bool pinned);

  // Implementation for setting the pinned state of the tab at `index`.
  int SetTabPinnedImpl(int indices, bool pinned);

  // Changes the pinned state of a split collection, moving it in the process if
  // necessary.
  void SetSplitPinnedImpl(tabs::SplitTabCollection* split, bool pinned);

  // Wrapper for bulk move operations to make them send out the appropriate
  // change notifications.
  void MoveTabsWithNotifications(std::vector<int> tab_indices,
                                 int destination_index,
                                 base::OnceClosure execute_tabs_move_operation);

  // Sets the sound content setting for each site at the `indices`.
  void SetSitesMuted(const std::vector<int>& indices, bool mute) const;

  // Sets the opener of any tabs that reference the tab at `index` to that tab's
  // opener or null if there's a cycle.
  void FixOpeners(int index);

  // Returns a group when the index of a tab is updated from `index` to
  // `to_position` that would not break group contiguity. The group returned
  // keeps the original group if it is valid at the `to_position` and otherwise
  // returns a valid group.
  std::optional<tab_groups::TabGroupId> GetGroupToAssign(int index,
                                                         int to_position);

  // Private API for now, because this API can be difficult to use correctly.
  // Interim stop gap until we have a handle based API. Use PassKey to access
  // this.
  // One notable deficiencies is that it doesn't work for all tab collection
  // types (e.g.: unpinned collection, tab strip collection, and split tab
  // collection). The onus is on the caller to handle those cases correctly.
  std::optional<const tab_groups::TabGroupId> FindGroupIdFor(
      const tabs::TabCollection::Handle& collection_handle) const;

  // Returns a valid index to be selected after the tabs in `block_tabs` are
  // closed. If index is after the block, index is adjusted to reflect the fact
  // that the block is going away.
  int GetTabIndexAfterClosing(int index, const gfx::Range& block_tabs) const;

  // Takes the `selection` change and decides whether to forget the openers.
  void OnActiveTabChanged(const TabStripSelectionChange& selection);

  // Determine where to shift selection after a tab or collection is closed.
  std::optional<int> DetermineNewSelectedIndex(
      std::variant<tabs::DanglingUntriagedTabInterface,
                   tabs::DanglingUntriagedTabCollection> tab_or_collection)
      const;

  std::vector<std::pair<tabs::TabInterface*, int>> GetTabsAndIndicesInSplit(
      split_tabs::SplitTabId split_id);

  // Returns [start, end) where the leftmost tab in the split has index start
  // and the rightmost tab in the split has index end - 1.
  gfx::Range GetIndexRangeOfSplit(split_tabs::SplitTabId split_id) const;

  // If inserting at `index` breaks a split, returns its id, otherwise nullopt.
  std::optional<split_tabs::SplitTabId> InsertionBreaksSplitContiguity(
      int index);

  // Helper to determine if moving a block of tabs from `start_index` with block
  // size `length` to `final_index` breaks contiguity.
  std::optional<split_tabs::SplitTabId>
  MoveBreaksSplitContiguity(int start_index, int length, int final_index);

  void MaybeRemoveSplitsForMove(
      int initial_index,
      int final_index,
      const std::optional<tab_groups::TabGroupId> group,
      bool pin);

  // For each split that has tabs in `indices`, remove any of them that contain
  // tabs not in `indices`.
  void MaybeRemoveSplitsForUpdate(const std::vector<int>& indices);

  // Checks if both tabs of a split view are closing simultaneously and, if so,
  // creates a historical split entry in the tab restore service.
  void CreateHistoricalSplitIfClosing(
      const std::vector<tabs::TabInterface*>& tabs,
      uint32_t close_types);

  void NotifyForegroundTabsWillEnterBackground();

  // Prior to a split being removed, if the split is currently active, notify
  // the inactive tab(s) in the split will become hidden as a result of the
  // split being removed.
  void NotifyInactiveSplitTabWillBecomeHidden(split_tabs::SplitTabId split_id);

  // Assues `left` and `right` have the same root tab collection, and that
  // `left` comes before `right` in traversal order. Returns a vector of tabs
  // ordered by the traversal order starting from `left` and ending at `right`.
  // The range includes both endpoints `left` and `right`.
  std::vector<tabs::TabInterface*> GetTabRange(tabs::TabInterface* left,
                                               tabs::TabInterface* right);

  // Returns the tab in selection_model_ with the lowest index.
  tabs::TabInterface* GetFirstSelectedTab() const;

  // Returns the tab in selection_model_ with the highest index.
  tabs::TabInterface* GetLastSelectedTab() const;

  // Returns a TabStripModelSelectionState with the same active, anchor, and
  // selected tabs as the given ListSelectionModel.
  tabs::TabStripModelSelectionState GetSelectionStateFrom(
      const ui::ListSelectionModel&);

  // The WebContents data currently hosted within this TabStripModel. This must
  // be kept in sync with `selection_model_`.
  std::unique_ptr<tabs::TabStripCollection> contents_data_;

  // The model for tab groups hosted within this TabStripModel.
  std::unique_ptr<TabGroupModel> group_model_;

  raw_ptr<TabStripModelDelegate> delegate_;

  bool tab_strip_ui_was_set_ = false;

  // TODO(crbug.com/483152816): Investigate if this can be made non-reentrant.
  base::ObserverList<
      TabStripModelObserver,
      /*check_empty=*/false,
      base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged>::
      UncheckedAndDanglingUntriaged observers_;

  // A profile associated with this TabStripModel.
  raw_ptr<Profile, AcrossTasksDanglingUntriaged> profile_;

  // True if all tabs are currently being closed via CloseAllTabs.
  bool closing_all_ = false;

  // This must be kept in sync with `contents_data_`.
  tabs::TabStripModelSelectionState selection_model_;

  // TabStripModel is not re-entrancy safe. This member is used to guard public
  // methods that mutate state of `selection_model_` or `contents_data_`.
  bool reentrancy_guard_ = false;

  TabStripScrubbingMetrics scrubbing_metrics_;

  // Tracks whether a modal UI is showing.
  bool showing_modal_ui_ = false;

  // Timestamp when the current focus mode session began, if active.
  std::optional<base::TimeTicks> focus_mode_session_start_time_;

  // Number of times a pinned tab was activated during the current focus mode
  // session.
  int focus_mode_pinned_tab_activations_ = 0;

  // Tracks whether any pinned tabs were present during the current focus mode
  // session.
  bool had_pinned_tabs_in_focus_session_ = false;

  base::WeakPtrFactory<TabStripModel> weak_factory_{this};
};

// Forbid construction of ScopedObservation and ScopedMultiSourceObservation
// with TabStripModel: TabStripModelObserver already implements their
// functionality natively.
namespace base {

template <>
class ScopedObservation<TabStripModel, TabStripModelObserver> {
 public:
  // Deleting the constructor gives a clear error message traceable back to
  // here.
  explicit ScopedObservation(TabStripModelObserver* observer) = delete;
};

template <>
class ScopedMultiSourceObservation<TabStripModel, TabStripModelObserver> {
 public:
  // Deleting the constructor gives a clear error message traceable back to
  // here.
  explicit ScopedMultiSourceObservation(TabStripModelObserver* observer) =
      delete;
};

}  // namespace base

#endif  // CHROME_BROWSER_UI_TABS_TAB_STRIP_MODEL_H_
