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

#ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_LAYOUT_HELPER_H_
#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_LAYOUT_HELPER_H_

#include <optional>
#include <vector>

#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ref.h"
#include "chrome/browser/ui/tabs/tab_types.h"
#include "chrome/browser/ui/views/tabs/tab_layout_state.h"
#include "chrome/browser/ui/views/tabs/tab_slot_view.h"
#include "chrome/browser/ui/views/tabs/tab_strip_layout.h"
#include "chrome/browser/ui/views/tabs/tab_strip_layout_types.h"
#include "chrome/browser/ui/views/tabs/tab_width_constraints.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/view_model.h"

class Tab;
class TabGroupHeader;
class TabContainerController;

namespace tab_groups {
class TabGroupId;
}

// Helper class for TabStrip, that is responsible for calculating and assigning
// layouts for tabs and group headers. It tracks animations and changes to the
// model so that it has all necessary information for layout purposes.
class TabStripLayoutHelper {
 public:
  using GetTabsCallback = base::RepeatingCallback<views::ViewModelT<Tab>*()>;

  TabStripLayoutHelper(const TabContainerController& controller,
                       GetTabsCallback get_tabs_callback);
  TabStripLayoutHelper(const TabStripLayoutHelper&) = delete;
  TabStripLayoutHelper& operator=(const TabStripLayoutHelper&) = delete;
  ~TabStripLayoutHelper();

  // Returns a vector of all tabs in the strip, including both closing tabs
  // and tabs still in the model.
  std::vector<Tab*> GetTabs() const;

  // Get all tab slot views in visual order, including all tabs from
  // GetTabs() and all tab group headers.
  std::vector<TabSlotView*> GetTabSlotViews() const;

  LayoutDomain layout_domain() { return tab_strip_layout_domain_; }

  // Returns the number of pinned tabs in the tabstrip.
  size_t GetPinnedTabCount() const;

  // Returns a map of all tab groups and their bounds.
  const std::map<tab_groups::TabGroupId, gfx::Rect>& group_header_ideal_bounds()
      const {
    return group_header_ideal_bounds_;
  }

  // Inserts a new tab at `index`.
  void InsertTabAt(int model_index, Tab* tab, TabPinned pinned);

  // Marks the tab at `model_index` as closing, but does not remove it from
  // `slots_`.
  void MarkTabAsClosing(int model_index, Tab* tab);

  // Removes `tab` from `slots_`.
  void RemoveTab(Tab* tab);

  // Moves the tab at `prev_index` with group `moving_tab_group` to `new_index`.
  // Also updates the group header's location if necessary.
  void MoveTab(std::optional<tab_groups::TabGroupId> moving_tab_group,
               int prev_index,
               int new_index);

  // Sets the tab at `index`'s pinned state to `pinned`.
  void SetTabPinned(int model_index, TabPinned pinned);

  // Inserts a new group header for `group`.
  void InsertGroupHeader(tab_groups::TabGroupId group, TabGroupHeader* header);

  // Removes the group header for `group`.
  void RemoveGroupHeader(tab_groups::TabGroupId group);

  // Ensures the group header for `group` is at the correct index. Should be
  // called externally when group membership changes but nothing else about the
  // layout does.
  void UpdateGroupHeaderIndex(tab_groups::TabGroupId group);

  // Changes the active tab from `prev_active_index` to `new_active_index`.
  void SetActiveTab(std::optional<size_t> prev_active_index,
                    std::optional<size_t> new_active_index);

  // Calculates the smallest width the tabs can occupy.
  int CalculateMinimumWidth();

  // Calculates the width the tabs would occupy if they have enough space.
  int CalculatePreferredWidth();

  // Generates and sets the ideal bounds for the views in `tabs` and
  // `group_headers`. Returns the total width occupied by the new ideal bounds.
  int UpdateIdealBounds(int available_width);

 private:
  struct TabSlot;

  // Calculates the bounds each tab should occupy, subject to the provided
  // width constraint.
  std::pair<std::vector<gfx::Rect>, LayoutDomain> CalculateIdealBounds(
      std::optional<int> available_width);

  // Given `model_index` for a tab already present in `slots_`, return
  // the corresponding index in `slots_`.
  int GetSlotIndexForExistingTab(int model_index) const;

  // For a new tab at `new_model_index`, get the insertion index in
  // `slots_`. `group` is the new tab's group.
  int GetSlotInsertionIndexForNewTab(
      int new_model_index,
      std::optional<tab_groups::TabGroupId> group) const;

  // Returns the slot corresponding to the first tab in a group
  // in the view.
  std::optional<int> GetFirstTabSlotForGroup(
      tab_groups::TabGroupId group) const;

  // Used internally in the above two functions. For a tabstrip with N
  // tabs, this takes 0 <= `model_index` <= N and returns the first
  // possible slot corresponding to this model index.
  //
  // This means that if `model_index` is the first tab in a group, the
  // returned slot index will point to the group header. For other tabs,
  // the slot index corresponding to that tab will be returned. Finally,
  // if `model_index` = N, slots_.size() will be returned.
  int GetFirstSlotIndexForTabModelIndex(int model_index) const;

  // Given a group ID, returns the index of its header's corresponding TabSlot
  // in `slots_`.
  int GetSlotIndexForGroupHeader(tab_groups::TabGroupId group) const;

  // If the tab at `slot_index` is split, returns the index of the adjacent
  // split tab. Otherwise returns `std::nullopt`.
  std::optional<int> GetAdjacentSplitTab(int slot_index) const;

  // True iff the slot at index `i` is a tab that is in a collapsed group.
  bool SlotIsCollapsedTab(int i) const;

  // The owning TabContainer's controller.
  const raw_ref<const TabContainerController, DanglingUntriaged> controller_;

  // Callback to get the necessary View objects from the owning tabstrip.
  GetTabsCallback get_tabs_callback_;

  // Current collation of tabs and group headers, along with necessary data to
  // run layout and animations for those Views.
  std::vector<TabSlot> slots_;

  // Contains the ideal bounds of tab group headers.
  std::map<tab_groups::TabGroupId, gfx::Rect> group_header_ideal_bounds_;

  LayoutDomain tab_strip_layout_domain_;
};

#endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_LAYOUT_HELPER_H_
