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

#ifndef COMPONENTS_TABS_PUBLIC_TAB_INTERFACE_H_
#define COMPONENTS_TABS_PUBLIC_TAB_INTERFACE_H_

#include <memory>
#include <string>

#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tabs/public/tab_handle_factory.h"
#include "content/public/browser/web_contents_user_data.h"
#include "url/gurl.h"

namespace ui {
class UnownedUserDataHost;
}

namespace base {
class CallbackListSubscription;
}  // namespace base

namespace content {
class WebContents;
}  // namespace content

class BrowserWindowInterface;
class Profile;

namespace split_tabs {
class SplitTabId;
}  // namespace split_tabs

namespace tabs {

class TabCollection;
class TabFeatures;

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

// This class exists to allow consumers to look up a TabInterface from an
// instance of WebContents. This is necessary while transitioning features to
// use TabInterface instead of WebContents.
class TabLookupFromWebContents
    : public content::WebContentsUserData<TabLookupFromWebContents> {
 public:
  ~TabLookupFromWebContents() override = default;

  tabs::TabInterface* model() { return tab_interface_; }
  const tabs::TabInterface* model() const { return tab_interface_; }

 private:
  friend WebContentsUserData;
  TabLookupFromWebContents(content::WebContents* contents,
                           tabs::TabInterface* tab_interface);

  // Semantically owns this class.
  raw_ptr<tabs::TabInterface> tab_interface_;
  WEB_CONTENTS_USER_DATA_KEY_DECL();
};

// This is the public interface for tabs in a desktop browser. Most features in
// //chrome/browser depend on this interface, and thus to prevent circular
// dependencies this interface should not depend on anything else in //chrome.
// Ping erikchen for assistance if this class does not have the functionality
// your feature needs. This comment will be deleted after there are 10+ features
// in TabFeatures.
class TabInterface : public SupportsTabHandles {
 public:
  ~TabInterface() override = default;

  // Called when a tab collection drops ownership of this tab.
  // Defaults to `delete this;`. Implementations with external lifecycles (such
  // as Android's Java-owned TabAndroid) can override this method to no-op.
  virtual void DeleteSelf();

  // This method exists to ease the transition from WebContents to TabInterface.
  // This method should only be called on instances of WebContents that are
  // known to be tabs. Calling this on a non-tab will crash.
  static TabInterface* GetFromContents(content::WebContents* web_contents);
  static const TabInterface* GetFromContents(
      const content::WebContents* web_contents);

  // Code that references a WebContents should already know whether the
  // WebContents is a tab, and thus should use GetFromContents(). For historical
  // reasons, there is code in the repository that typically lives in or below
  // //content which does not know whether it's being invoked in the context of
  // a tab. This is an anti-pattern that should be avoided. When it is
  // unavoidable, this method may be used. Features that live entirely above the
  // //content layer should not use this method.
  static TabInterface* MaybeGetFromContents(content::WebContents* web_contents);
  static const TabInterface* MaybeGetFromContents(
      const content::WebContents* web_contents);

  // Returns a weak pointer to `this`.
  //
  // WARNING: Many uses of base::WeakPtr are inappropriate and lead to bugs.
  // An appropriate use case is as a variable passed to an asynchronously
  // invoked PostTask.
  // An inappropriate use case is to store as a member of an object that can
  // outlive TabInterface. This leads to inconsistent state machines.
  // For example (don't do this):
  // class FooOutlivesTab{
  //   base::WeakPtr<TabInterface> tab_;
  //   // Conceptually, this member should only be set if tab_ is set.
  //   std::optional<SkColor> color_of_tab_;
  // };
  // For example (do this):
  // class FooOutlivesTab {
  //   // Use RegisterWillDetach() to clear both tab_ and color_of_tab_ prior
  //   // to tab_ destruction.
  //   raw_ptr<TabInterface> tab_;
  //   std::optional<SkColor> color_of_tab_;
  // };
  virtual base::WeakPtr<TabInterface> GetWeakPtr() = 0;

  // Returns the WebContents that is currently associated with this tab.
  //
  // Windows/Mac/Linux:
  // * The returned pointer is guaranteed to be non-null.
  // * The WebContents object *itself* can be replaced, most notably when a
  //   background tab's contents are discarded to save memory. Callers who need
  //   to observe the tab for its entire lifetime should not cache the
  //   WebContents pointer directly. Instead, they should hold a reference to
  //   the TabInterface and call GetContents() when needed, or use
  //   RegisterWillDiscardContents() to be notified of swaps.
  //
  // Android:
  // * This may return nullptr for tabs that have not loaded in the current
  //   session. If kLoadAllTabsOnStartup is enabled, this will be non-null for
  //   all tabs in models with TabModelType::kStandard.
  // * The WebContents object will never change after being populated.
  //   Discarding WebContents does not change the WebContents pointer and
  //   swapping WebContents is not supported. That said, for portability with
  //   Windows/Mac/Linux, the recommendation is to hold a pointer to the
  //   TabInterface and call GetContents() when needed as described above.
  virtual content::WebContents* GetContents() const = 0;

  // Loads the WebContents of the tab if it needs to be loaded/reloaded. On
  // Android calling this will guarantee subsequent calls to `GetContents` will
  // return a non-nullptr value. However, the cost of invoking this is high.
  // Invoke this only in response to a user request that should load the tab.
  virtual void LoadIfNeeded() = 0;

  // Return the tab title. On Android, prefer this to getting the title from
  // WebContents as it can return the title even if there is no WebContents or
  // there is a pending navigation.
  virtual std::u16string GetTitle() const = 0;

  // Return the tab url. On Android, prefer this to getting the URL from
  // WebContents as it can return the URL even if there is no WebContents or
  // there is a pending navigation.
  virtual GURL GetURL() const = 0;

  // Returns the time at which the tab was last active (or shown, on Android if
  // the tab is not loaded).
  virtual base::Time GetLastActiveTime() const = 0;

  // Returns the Profile associated with this tab.
  virtual Profile* GetProfile() const = 0;

  // Closes the tab.
  virtual void Close() = 0;

  // Register for this callback to detect changes to GetContents(). The first
  // WebContents is the contents that will be discarded. The second WebContents
  // is the new contents. The tab is guaranteed to be in the background.
  using WillDiscardContentsCallback = base::RepeatingCallback<
      void(TabInterface*, content::WebContents*, content::WebContents*)>;
  virtual base::CallbackListSubscription RegisterWillDiscardContents(
      WillDiscardContentsCallback callback) = 0;

  // Whether the tab is the Active Tab in its browser window (see
  // TabStripModel's SelectionModel's active index for more details about the
  // active state). When a tab is in the foreground, this class guarantees that
  // GetContents() will return a non-nullptr WebContents, and this WebContents
  // will not change. If a tab is dragged out of a window (creating a new
  // window), it will briefly be deactivated, and then reactivate. To see if
  // this is happened, check the BrowserWindowInterface's session id.
  virtual bool IsActivated() const = 0;

  // Register for these two callbacks to detect changes to IsActivated().
  using DidActivateCallback = base::RepeatingCallback<void(TabInterface*)>;
  virtual base::CallbackListSubscription RegisterDidActivate(
      DidActivateCallback callback) = 0;

  using WillDeactivateCallback = base::RepeatingCallback<void(TabInterface*)>;
  virtual base::CallbackListSubscription RegisterWillDeactivate(
      WillDeactivateCallback callback) = 0;

  // ONLY USE THIS IF YOUR FEATURE NEEDS MORE SPECIFICITY THAN IsActivated().
  // Whether the tab is visible in the contents area of the browser window.
  // This will directly match the attached state until there are features that
  // provide multiple visible tabs per window. This state is not related to
  // widget visibility or occlusion of the window.
  virtual bool IsVisible() const = 0;

  // Returns true if the tab is selected in its browser window. Note that
  // "selected" is distinct from "activated" -- multiple tabs may be selected at
  // a time, and a selected tab is not necessarily active.
  virtual bool IsSelected() const = 0;

  // Register for these two callbacks to detect changes to IsVisible().
  using DidBecomeVisibleCallback = base::RepeatingCallback<void(TabInterface*)>;
  virtual base::CallbackListSubscription RegisterDidBecomeVisible(
      DidBecomeVisibleCallback callback) = 0;

  using WillBecomeHiddenCallback = base::RepeatingCallback<void(TabInterface*)>;
  virtual base::CallbackListSubscription RegisterWillBecomeHidden(
      WillBecomeHiddenCallback callback) = 0;

  // Register for this callback to detect when a tab will be detached from a
  // window.
  // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.tabs
  enum class DetachReason {
    // The tab is about to be deleted.
    kDelete,
    // The tab is going to be removed, in order to be inserted into another
    // window.
    kInsertIntoOtherWindow
  };
  using WillDetach = base::RepeatingCallback<void(TabInterface*, DetachReason)>;
  virtual base::CallbackListSubscription RegisterWillDetach(
      WillDetach callback) = 0;

  // Register for this callback to detect when a tab has been inserted into a
  // window's tab strip. Registered callbacks will fire for all tab strip
  // insertion events, including when the tab is first created and added to the
  // tab strip if a callback has been registered early enough in the tab's
  // lifecycle.
  using DidInsertCallback = base::RepeatingCallback<void(TabInterface*)>;
  virtual base::CallbackListSubscription RegisterDidInsert(
      DidInsertCallback callback) = 0;

  // Register for this callback to detect when the pinned state changes.
  using PinnedStateChangedCallback =
      base::RepeatingCallback<void(TabInterface*, bool new_pinned_state)>;
  virtual base::CallbackListSubscription RegisterPinnedStateChanged(
      PinnedStateChangedCallback callback) = 0;

  // Register for this callback to detect when the group changes.
  using GroupChangedCallback = base::RepeatingCallback<
      void(TabInterface*, std::optional<tab_groups::TabGroupId> new_group)>;
  virtual base::CallbackListSubscription RegisterGroupChanged(
      GroupChangedCallback callback) = 0;

  // Register for this callback to detect when the blocked state changes.
  using BlockedStateChangedCallback =
      base::RepeatingCallback<void(TabInterface*, bool new_blocked_state)>;
  virtual base::CallbackListSubscription RegisterBlockedStateChanged(
      BlockedStateChangedCallback callback) = 0;

  // Features that want to show tab-modal UI are mutually exclusive. Before
  // showing a modal UI first check `CanShowModal`. Then call ShowModal() and
  // keep `ScopedTabModal` alive to prevent other features from showing
  // tab-modal UI.
  virtual bool CanShowModalUI() const = 0;
  virtual std::unique_ptr<ScopedTabModalUI> ShowModalUI() = 0;

  // Register for this callback to detect when a modal is shown or hidden.
  using TabInterfaceCallback = base::RepeatingCallback<void(TabInterface*)>;
  virtual base::CallbackListSubscription RegisterModalUIChanged(
      TabInterfaceCallback callback) = 0;

  // A normal browser window has a tab strip and an omnibox. The returned value
  // never changes.
  virtual bool IsInNormalWindow() const = 0;

  // Always valid in production code. Exceptions are:
  //  (1) Tabs briefly do not have a BrowserWindowInterface when they are
  //  detached from one window and moved to another. That is an implementation
  //  detail of tab dragging and should not affect code outside of the tab-strip
  //  implementation.
  //  (2) Some tab-related unit tests create a TabInterface but do not create
  //  TabFeatures, BrowserWindowInterface or BrowserWindowFeatures. Most code
  //  that accesses this method should typically be scoped to TabFeatures or
  //  BrowserWindowFeatures, but some code (e.g. tab_helpers) are currently
  //  created in these unit tests. The proper solution is to convert these
  //  tab_helpers to TabFeatures, which is tracked in
  //  https://crbug.com/369319589.
  // This is a long winded way of saying: if you are using this code from
  // TabFeatures or BrowserWindowFeatures, you can safely assume that this is
  // always non-nullptr.
  //
  // Android specific notes:
  // * Tabs that are reparenting, closing or in undoable closure state will
  //   return nullptr.
  // * Prior to associating the tab with a browser window (i.e. early in
  //   startup), this will be nullptr.
  // * After dissociation from a browser window (i.e. during activity/profile
  //   destruction), this will be nullptr.
  virtual BrowserWindowInterface* GetBrowserWindowInterface() = 0;
  virtual const BrowserWindowInterface* GetBrowserWindowInterface() const = 0;

  // Returns the feature controllers scoped to this tab.
  // TabFeatures that depend on other TabFeatures should not use this method.
  // Instead they should use use dependency injection to pass dependencies at
  // construction or initialization. This method exists for three reasons:
  //   (1) BrowserWindowFeatures often depend on state of TabFeatures for the
  //   active tab, which can change. BrowserWindowFeatures need a way to
  //   dynamically fetch TabFeatures.
  //   (2) To expose TabFeatures for tests.
  //   (3) It is not possible to perform dependency injection for legacy code
  //   that is conceptually a TabFeature and needs access to other TabFeatures.
  virtual tabs::TabFeatures* GetTabFeatures() = 0;
  virtual const tabs::TabFeatures* GetTabFeatures() const = 0;

  // Return true if the tab is pinned in its tabstrip, or false otherwise.
  virtual bool IsPinned() const = 0;

  // Whether the tab is blocked by a modal dialog.
  virtual bool IsBlocked() const = 0;

  // Return true if the tab is part of a split view, or false otherwise.
  virtual bool IsSplit() const = 0;

  // Returns the id of the tab group this tab belongs to, or nullopt if the tab
  // is not grouped.
  virtual std::optional<tab_groups::TabGroupId> GetGroup() const = 0;

  // Returns the id of the split tab this tab belongs to, or nullopt if the tab
  // is not part of a split tab.
  virtual std::optional<split_tabs::SplitTabId> GetSplit() const = 0;

  // Returns a pointer to the parent TabCollection.
  virtual TabCollection* GetParentCollection(
      base::PassKey<TabCollection>) const = 0;

  virtual const TabCollection* GetParentCollection() const = 0;

  // Updates the parent collection of the TabModel in response to structural
  // changes such as pinning, grouping, or moving the tab between collections.
  // This method ensures the TabModel remains correctly associated within the
  // tab hierarchy, maintaining consistent organization.
  virtual void OnReparented(TabCollection* parent,
                            base::PassKey<TabCollection>) = 0;

  // Must be called whenever any of this tab's ancestor collections change.
  virtual void OnAncestorChanged(base::PassKey<TabCollection>) = 0;

  // Returns the UnownedUserDataHost associated with this tab. This is used to
  // retrieve arbitrary features from the tab without requiring TabModel to have
  // knowledge of them.
  virtual ui::UnownedUserDataHost& GetUnownedUserDataHost() = 0;
  virtual const ui::UnownedUserDataHost& GetUnownedUserDataHost() const = 0;
};

using TabHandle = TabInterface::Handle;

}  // namespace tabs

#endif  // COMPONENTS_TABS_PUBLIC_TAB_INTERFACE_H_
