// 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_EXTENSIONS_EXTENSIONS_TOOLBAR_VIEW_MODEL_H_
#define CHROME_BROWSER_UI_EXTENSIONS_EXTENSIONS_TOOLBAR_VIEW_MODEL_H_

#include "base/containers/flat_map.h"
#include "base/scoped_observation.h"
#include "chrome/browser/tab_list/tab_list_interface_observer.h"
#include "chrome/browser/ui/extensions/extension_action_delegate.h"
#include "chrome/browser/ui/extensions/extension_action_view_model.h"
#include "chrome/browser/ui/extensions/extensions_container.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_model.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
#include "content/public/browser/web_contents_observer.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/buildflags/buildflags.h"
#include "ui/gfx/vector_icon_types.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

class TabListInterface;

// ViewModel for the ExtensionsToolbarDesktop. This class manages the business
// logic for the order and state of extension actions in the toolbar. It serves
// as the single source of truth for the ordering of the list of actions.
class ExtensionsToolbarViewModel
    : public ExtensionsContainer,
      public ToolbarActionsModel::Observer,
      public content::WebContentsObserver,
      public TabListInterfaceObserver,
      public extensions::PermissionsManager::Observer {
 public:
  // Delegate used to retrieve platform-specific information.
  class Delegate {
   public:
    // Creates the platform-specific action view model.
    virtual std::unique_ptr<ExtensionActionViewModel> CreateActionViewModel(
        const ToolbarActionsModel::ActionId& action_id,
        ExtensionsContainer* extensions_container) = 0;
    // Hides any actively showing popups.
    virtual void HideActivePopup() = 0;

    // Closes the extensions menu, if it was open.
    virtual void CloseExtensionsMenuIfOpen() = 0;

    // Returns whether a popup can be shown.
    virtual bool CanShowToolbarActionPopupForAPICall(
        const ToolbarActionsModel::ActionId& action_id) = 0;

    // Toggle the Extensions menu (as if the user clicked the puzzle piece
    // icon).
    // TODO(crbug.com/473701535): Determine whether this method belongs in the
    // delegate or the observer.
    virtual void ToggleExtensionsMenu() = 0;

    // Triggers the manage extensions IPH.
    virtual void ShowManageExtensionsIPH() {}

    // Triggers the pinned by default IPH.
    virtual void ShowPinnedByDefaultIPH(const std::string& extension_id) {}

   protected:
    virtual ~Delegate() = default;
  };

  // Observer used to notify platforms about changes to the model.
  class Observer : public base::CheckedObserver {
   public:
    // Called after all actions are added to the model.
    virtual void OnActionsInitialized() = 0;

    // Called when an action is added to the model.
    virtual void OnActionAdded(
        const ToolbarActionsModel::ActionId& action_id) = 0;

    // Called when an action is removed from the model.
    virtual void OnActionRemoved(
        const ToolbarActionsModel::ActionId& action_id) = 0;

    // Called when an action in the model is updated.
    virtual void OnActionUpdated(
        const ToolbarActionsModel::ActionId& action_id) = 0;

    // Called when the pinned actions in the model are changed.
    virtual void OnPinnedActionsChanged() = 0;

    // Called when the active WebContents is changed (e.g. tab change or page
    // navigation). `is_same_document` is true if the change was due to a
    // same-document navigation.
    virtual void OnActiveWebContentsChanged(
        bool is_same_document,
        content::WebContents* web_contents) = 0;

    // Called when the extensions that should be displayed in the request
    // access button to be recomputed.
    virtual void OnRequestAccessButtonParamsChanged(
        content::WebContents* web_contents) {}

    // Called when both the extensions button and the request access button
    // should be updated.
    virtual void OnToolbarControlStateUpdated() {}
  };

  enum class ExtensionsToolbarButtonState {
    // All extensions have blocked access to the current site.
    kAllExtensionsBlocked,
    // At least one extension has access to the current site.
    kAnyExtensionHasAccess,
    kDefault,
  };

  // Holds the information for the request access button.
  struct RequestAccessButtonParams {
    RequestAccessButtonParams();
    RequestAccessButtonParams(RequestAccessButtonParams&&);
    RequestAccessButtonParams& operator=(RequestAccessButtonParams&&);
    ~RequestAccessButtonParams();

    std::vector<extensions::ExtensionId> extension_ids;
    std::u16string tooltip_text;
  };

  ExtensionsToolbarViewModel(Delegate* delegate,
                             BrowserWindowInterface* browser,
                             ToolbarActionsModel* actions_model);
  ExtensionsToolbarViewModel(const ExtensionsToolbarViewModel&) = delete;
  ExtensionsToolbarViewModel& operator=(ExtensionsToolbarViewModel&) = delete;
  ~ExtensionsToolbarViewModel() override;

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  // Returns the view model of the action if it exists, else a nullptr.
  ToolbarActionViewModel* GetActionModelForId(
      const ToolbarActionsModel::ActionId& action_id) const;

  // Returns whether a drag can be started on an action.
  bool IsActionDraggable(const ToolbarActionsModel::ActionId& action_id) const;

  // Move the pinned action `action_id` to `target_index`.
  void MovePinnedAction(const ToolbarActionsModel::ActionId& action_id,
                        size_t target_index);

  // Move this pinned action `action_id` by the specified `move_by` amount.
  void MovePinnedActionBy(const std::string& action_id, int move_by);

  // Returns the sorted list of the IDs of all installed actions.
  const base::flat_set<ToolbarActionsModel::ActionId>& GetAllActionIds() const;

  // Returns the ordered list of ids of pinned actions.
  const std::vector<ToolbarActionsModel::ActionId>& GetPinnedActionIds() const;

  // Returns whether the actions are initialized.
  bool AreActionsInitialized();

  // Returns the icon for the toolbar button for the given state.
  static const gfx::VectorIcon& GetToolbarButtonIcon(
      ExtensionsToolbarButtonState state);

  // Returns the accessible text for the toolbar button for the given state.
  static std::u16string GetToolbarButtonAccessibleText(
      ExtensionsToolbarButtonState state);

  // Returns the tooltip text for the toolbar button for the given state.
  static std::u16string GetToolbarButtonTooltipText(
      ExtensionsToolbarButtonState state);

  // Returns the state of the extensions toolbar button based on 'web_contents'
  // given the provided extensions state.
  static ExtensionsToolbarButtonState GetButtonState(
      BrowserWindowInterface* browser,
      content::WebContents& web_contents,
      const ToolbarActionsModel* actions_model,
      // Callback that returns whether any of extensions have access to the
      // WebContents.
      base::OnceCallback<bool(content::WebContents&)> has_access_callback);

  // Returns the state of the extensions toolbar button based on 'web_contents'.
  ExtensionsToolbarButtonState GetButtonState(
      content::WebContents& web_contents) const;

  // Executes the default behavior associated with the action. This should only
  // be called as a result of a user action.
  void ExecuteUserAction(const ToolbarActionsModel::ActionId& action_id,
                         ToolbarActionViewModel::InvocationSource source);

  // Grants site access to the given `extension_ids` for the `web_contents`.
  void GrantSiteAccess(
      content::WebContents* web_contents,
      const std::vector<extensions::ExtensionId>& extension_ids);

  // Returns RequestAccessButtonParams which contain information to be used in
  // the button's tooltip.
  RequestAccessButtonParams GetRequestAccessButtonParams(
      content::WebContents* web_contents) const;

  // ExtensionsContainer:
  ToolbarActionViewModel* GetActionForId(const std::string& action_id) override;
  void HideActivePopup() override;
  void CloseExtensionsMenuIfOpen() override;
  bool ShowToolbarActionPopupForAPICall(const std::string& action_id,
                                        ShowPopupCallback callback) override;
  void ToggleExtensionsMenu() override;
  void ShowManageExtensionsIPH() override;
  void ShowPinnedByDefaultIPH(const std::string& extension_id) override;
  bool HasAnyExtensions() const override;

  // ToolbarActionsModel::Observer:
  void OnToolbarModelInitialized() override;
  void OnToolbarActionAdded(
      const ToolbarActionsModel::ActionId& action_id) override;
  void OnToolbarActionRemoved(
      const ToolbarActionsModel::ActionId& action_id) override;
  void OnToolbarActionUpdated(
      const ToolbarActionsModel::ActionId& action_id) override;
  void OnToolbarPinnedActionsChanged() override;
  void OnToolbarActionsModelShutdown() override;

  // content::WebContentsObserver:
  void DidFinishNavigation(content::NavigationHandle* handle) override;

  // TabListInterfaceObserver:
  void OnActiveTabChanged(TabListInterface& tab_list,
                          tabs::TabInterface* tab) override;
  void OnWebContentsReplaced(TabListInterface& tab_list,
                             tabs::TabInterface* tab,
                             content::WebContents* old_contents,
                             content::WebContents* new_contents) override;
  void OnTabListDestroyed(TabListInterface& tab_list) override;

  // extensions::PermissionsManager::Observer:
  void OnHostAccessRequestAdded(const extensions::ExtensionId& extension_id,
                                int tab_id) override;
  void OnHostAccessRequestUpdated(const extensions::ExtensionId& extension_id,
                                  int tab_id) override;
  void OnHostAccessRequestRemoved(const extensions::ExtensionId& extension_id,
                                  int tab_id) override;
  void OnHostAccessRequestsCleared(int tab_id) override;
  void OnHostAccessRequestDismissedByUser(const extensions::ExtensionId& id,
                                          const url::Origin& origin) override;
  void OnUserPermissionsSettingsChanged(
      const extensions::PermissionsManager::UserPermissionsSettings& settings)
      override;
  void OnShowAccessRequestsInToolbarChanged(
      const extensions::ExtensionId& extension_id,
      bool can_show_requests) override;
  void OnPermissionsManagerShutdown() override;

 private:
  // Returns whether any of `actions` given have access to the `web_contents`.
  bool AnyActionHasCurrentSiteAccess(content::WebContents& web_contents) const;

  // Creates and appends an action model to `actions_` vector.
  void AppendActionModel(const ToolbarActionsModel::ActionId& action_id);

  // Returns the current web contents.
  content::WebContents* GetCurrentWebContents() const;

  // The corresponding browser window.
  const raw_ptr<BrowserWindowInterface> browser_;

  // The delegate to retrieve platform-specific information.
  const raw_ptr<Delegate> delegate_;

  raw_ptr<ToolbarActionsModel> actions_model_;
  base::ScopedObservation<ToolbarActionsModel, ToolbarActionsModel::Observer>
      actions_model_observation_{this};

  base::ScopedObservation<TabListInterface, TabListInterfaceObserver>
      tab_list_observation_{this};

  base::ScopedObservation<extensions::PermissionsManager,
                          extensions::PermissionsManager::Observer>
      permissions_manager_observation_{this};

  // The observers that handles platform-specific UI.
  base::ObserverList<Observer> observers_;

  // Actions for all extensions.
  base::flat_map<ToolbarActionsModel::ActionId,
                 std::unique_ptr<ToolbarActionViewModel>>
      actions_;
};

#endif  // CHROME_BROWSER_UI_EXTENSIONS_EXTENSIONS_TOOLBAR_VIEW_MODEL_H_
