// Copyright 2025 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_SIDE_PANEL_SIDE_PANEL_UI_BASE_H_
#define CHROME_BROWSER_UI_SIDE_PANEL_SIDE_PANEL_UI_BASE_H_

#include <memory>
#include <optional>

#include "base/callback_list.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/ui/side_panel/side_panel_entry.h"
#include "chrome/browser/ui/side_panel/side_panel_entry_id.h"
#include "chrome/browser/ui/side_panel/side_panel_entry_key.h"
#include "chrome/browser/ui/side_panel/side_panel_enums.h"
#include "chrome/browser/ui/side_panel/side_panel_native_view.h"
#include "chrome/browser/ui/side_panel/side_panel_registry.h"
#include "chrome/browser/ui/side_panel/side_panel_ui.h"
#include "components/tabs/public/tab_interface.h"
#include "ui/base/unowned_user_data/scoped_unowned_user_data.h"

class BrowserWindowInterface;
class SidePanelEntry;
class SidePanelEntryWaiter;

// Base class for Side Panel UIs that contains the common logic for managing
// side panel entries and state.
class SidePanelUIBase : public SidePanelUI {
 public:
  explicit SidePanelUIBase(BrowserWindowInterface* browser);
  ~SidePanelUIBase() override;

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

  // The side panel entry to be shown is uniquely specified via a tuple:
  //  (tab or window-scoped registry, SidePanelEntry::Key). `tab_handle` is
  //  necessary since it's possible for a Key to be present in both the
  //  tab-scoped and window-scoped registry, or in multiple different tab-scoped
  //  registries.
  struct UniqueKey {
    std::optional<tabs::TabHandle> tab_handle;
    SidePanelEntry::Key key;
    friend bool operator==(const UniqueKey&, const UniqueKey&) = default;
    friend std::ostream& operator<<(std::ostream& os,
                                    const UniqueKey& unique_key) {
      os << "UniqueKey{tab_handle: "
         << (unique_key.tab_handle
                 ? ("TabHandle@" +
                    base::NumberToString(unique_key.tab_handle->raw_value()))
                 : "null")
         << ", key: " << unique_key.key.ToString() << "}";
      return os;
    }
  };

  // SidePanelUI:
  using SidePanelUI::Close;
  using SidePanelUI::Show;
  void Show(SidePanelEntry::Id entry_id,
            std::optional<SidePanelOpenTrigger> open_trigger,
            bool suppress_animations) override;
  void Show(SidePanelEntry::Key entry_key,
            std::optional<SidePanelOpenTrigger> open_trigger,
            bool suppress_animations) override;
  std::optional<SidePanelEntry::Id> GetCurrentEntryId() const override;
  int GetCurrentEntryDefaultContentWidth() const override;
  bool IsSidePanelShowing() const override;
  bool IsSidePanelEntryShowing(
      const SidePanelEntry::Key& entry_key) const override;
  bool IsSidePanelEntryShowing(const SidePanelEntry::Key& entry_key,
                               bool for_tab) const override;
  base::CallbackListSubscription RegisterSidePanelShown(
      SidePanelUI::ShownCallback callback) override;
  void OnActiveTabChanged(content::WebContents* old_contents,
                          content::WebContents* new_contents,
                          bool tab_removed_for_deletion) override;

  BrowserWindowInterface* browser() const { return browser_; }

 protected:
  friend class SidePanelEntryWaiter;

  struct PanelData {
    PanelData();
    ~PanelData();

    // current_key_ uniquely identifies the SidePanelEntry that has its view
    // hosted by the side panel. At the time that it is set and for most code
    // paths, the SidePanelEntry is guaranteed to exist. It does not exist in
    // the following cases:
    //   * The active tab is switched, and UniqueKey is tab-scoped.
    //   * The entry is removed from tab or window-scoped registry.
    // The side-panel is showing if and only if current_key_ is set. That means
    // it must only be set in one place: PopulateSidePanel() and unset in one
    // place: OnViewVisibilityChanged()
    std::optional<SidePanelUIBase::UniqueKey> current_key;

    // Inner class that waits for side panel entries to load.
    std::unique_ptr<SidePanelEntryWaiter> waiter;

    // Timestamp of when the side panel was opened. Updated when the side panel
    // is triggered to be opened, not when visibility changes. These can differ
    // due to delays for loading content. This is used for metrics.
    base::TimeTicks opened_timestamp;

    // Callback list notified when the side panel opens or changes.
    base::RepeatingCallbackList<void()> shown_callback_list;
  };

  // This method does not show the side panel. Instead, it queues the side panel
  // to be shown once the contents have been loaded. This process may be either
  // synchronous or asynchronous.
  virtual void Show(const UniqueKey& entry,
                    std::optional<SidePanelOpenTrigger> open_trigger,
                    bool suppress_animations) = 0;

  // Removes existing SidePanelEntry contents from the side panel if any exist
  // and populates the side panel with the provided SidePanelEntry and
  // `content_view` if provided, otherwise get the content_view from the
  // provided SidePanelEntry.
  virtual void PopulateSidePanel(
      bool suppress_animations,
      const UniqueKey& unique_key,
      std::optional<SidePanelOpenTrigger> open_trigger,
      SidePanelEntry* entry,
      std::optional<SidePanelNativeView> content_view) = 0;

  // Shows an entry in the following fallback order: new contextual registry's
  // active entry > active global entry > none (close the side panel).
  virtual void MaybeShowEntryOnTabStripModelChanged(
      SidePanelRegistry* old_contextual_registry,
      SidePanelRegistry* new_contextual_registry) = 0;

  void SetOpenedTimestamp(base::TimeTicks timestamp);
  base::TimeTicks opened_timestamp() { return panel_data_->opened_timestamp; }

  void NotifyShownCallbacks();

  std::optional<UniqueKey> current_key() const {
    return panel_data_->current_key;
  }
  void SetCurrentKey(std::optional<UniqueKey> new_key);

  std::optional<UniqueKey> GetUniqueKeyForKey(
      const SidePanelEntry::Key& entry_key) const;

  // Returns the SidePanelEntry uniquely specified by UniqueKey.
  SidePanelEntry* GetEntryForUniqueKey(const UniqueKey& unique_key) const;

  SidePanelRegistry* GetActiveContextualRegistry() const;

  SidePanelEntry* GetActiveContextualEntryForKey(
      const SidePanelEntry::Key& entry_key) const;

  // Returns the new entry key to be shown after the active tab has changed, or
  // nullopt if no suitable entry is found. Called from
  // `OnTabStripModelChanged()` when there's an active entry being shown in the
  // side panel.
  std::optional<UniqueKey> GetNewActiveKeyOnTabChanged();

  SidePanelEntryWaiter* waiter() const;

 private:
  const raw_ptr<BrowserWindowInterface> browser_;
  std::unique_ptr<PanelData> panel_data_;
  ui::ScopedUnownedUserData<SidePanelUI> scoped_unowned_user_data_;
};

#endif  // CHROME_BROWSER_UI_SIDE_PANEL_SIDE_PANEL_UI_BASE_H_
