// 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_EXTENSIONS_EXTENSION_CONTEXT_MENU_MODEL_H_
#define CHROME_BROWSER_EXTENSIONS_EXTENSION_CONTEXT_MENU_MODEL_H_

#include <memory>
#include <optional>
#include <string>

#include "base/memory/raw_ptr.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_usage.h"
#include "ui/menus/simple_menu_model.h"
#include "url/origin.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

class BrowserWindowInterface;
class Profile;

namespace content {
class WebContents;
}

namespace extensions {
class ContextMenuMatcher;
class Extension;
class ExtensionAction;

#if !BUILDFLAG(IS_ANDROID)
class SidePanelService;
#endif

// The context menu model for extension icons.
class ExtensionContextMenuModel : public ui::SimpleMenuModel,
                                  public ui::SimpleMenuModel::Delegate {
 public:
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kHomePageMenuItem);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kToggleVisibilityMenuItem);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kPageAccessMenuItem);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kPageAccessRunOnClickSubmenuItem);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kPageAccessRunOnSiteSubmenuItem);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kPageAccessRunOnAllSitesSubmenuItem);

  enum MenuEntries {
    HOME_PAGE = 0,
    OPTIONS,
    TOGGLE_VISIBILITY,
    UNINSTALL,
    MANAGE_EXTENSIONS,
    INSPECT_POPUP,
    PAGE_ACCESS_CANT_ACCESS,
    PAGE_ACCESS_SUBMENU,
    PAGE_ACCESS_RUN_ON_CLICK,
    PAGE_ACCESS_RUN_ON_SITE,
    PAGE_ACCESS_RUN_ON_ALL_SITES,
    PAGE_ACCESS_LEARN_MORE,
    PAGE_ACCESS_ALL_EXTENSIONS_GRANTED,
    PAGE_ACCESS_ALL_EXTENSIONS_BLOCKED,
    PAGE_ACCESS_PERMISSIONS_PAGE,
    VIEW_WEB_PERMISSIONS,
    POLICY_INSTALLED,
    TOGGLE_SIDE_PANEL_VISIBILITY,
    RATE_EXTENSION,
    // NOTE: If you update this, you probably need to update the
    // ContextMenuAction enum below.
  };

  // A separate enum to indicate the action taken on the menu. We have two
  // enums (this and MenuEntries above) to avoid needing to have a single one
  // with both action-specific values (like kNoAction) and menu-specific values
  // (like PAGE_ACCESS_SUBMENU).
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused. New values should be added before
  // kMaxValue.
  enum class ContextMenuAction {
    kNoAction = 0,
    kCustomCommand = 1,
    kHomePage = 2,
    kOptions = 3,
    kToggleVisibility = 4,
    kUninstall = 5,
    kManageExtensions = 6,
    kInspectPopup = 7,
    kPageAccessRunOnClick = 8,
    kPageAccessRunOnSite = 9,
    kPageAccessRunOnAllSites = 10,
    kPageAccessLearnMore = 11,
    kPageAccessPermissionsPage = 12,
    kViewWebPermissions = 13,
    kPolicyInstalled = 14,
    kToggleSidePanelVisibility = 15,
    kRateExtension = 16,
    kMaxValue = kRateExtension,
    // NOTE: Please update ExtensionContextMenuAction in enums.xml if you modify
    // this enum.
  };

  // Location where the context menu is open from.
  // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.extensions
  enum class ContextMenuSource { kToolbarAction = 0, kMenuItem = 1 };

  // Delegate to handle showing an ExtensionAction popup.
  class PopupDelegate {
   public:
    // Called when the user selects the menu item which requests that the
    // popup be shown and inspected.
    // The delegate should know which popup to display.
    virtual void InspectPopup() = 0;

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

  // Creates a menu model for the given extension. If
  // prefs::kExtensionsUIDeveloperMode is enabled then a menu item
  // will be shown for "Inspect Popup" which, when selected, will cause
  // ShowPopupForDevToolsWindow() to be called on `delegate`.
  ExtensionContextMenuModel(const Extension* extension,
                            BrowserWindowInterface* browser,
                            bool is_pinned,
                            PopupDelegate* delegate,
                            bool can_show_icon_in_toolbar,
                            ContextMenuSource source);

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

  ~ExtensionContextMenuModel() override;

  // SimpleMenuModel::Delegate:
  bool IsCommandIdChecked(int command_id) const override;
  bool IsCommandIdVisible(int command_id) const override;
  bool IsCommandIdEnabled(int command_id) const override;
  void ExecuteCommand(int command_id, int event_flags) override;
  void OnMenuWillShow(ui::SimpleMenuModel* source) override;
  void MenuClosed(ui::SimpleMenuModel* source) override;

  ui::SimpleMenuModel* page_access_submenu_for_testing() {
    return page_access_submenu_.get();
  }

 private:
  void Init(const Extension* extension, bool can_show_icon_in_toolbar);

  void InitMenu(const Extension* extension, bool can_show_icon_in_toolbar);

  // Constructs the menu when `kExtensionsMenuAccessControl` is enabled.
  void InitMenuWithFeature(const Extension* extension,
                           bool can_show_icon_in_toolbar);

  // Adds the page access items based on the current site setting pointed by
  // `web_contents`.
  void CreatePageAccessItems(const Extension* extension,
                             content::WebContents* web_contents);

  // Emits a UKM record for the extension associated with `extension_url` and
  // the corresponding `action`.
  void RecordUkmForExtension(const GURL& extension_url,
                             ExtensionUsageAction action);

  // Gets the extension we are displaying the menu for. Returns NULL if the
  // extension has been uninstalled and no longer exists.
  const Extension* GetExtension() const;

  // Returns the active web contents.
  content::WebContents* GetActiveWebContents() const;

  // Returns the side panel service for the current profile.
#if !BUILDFLAG(IS_ANDROID)
  SidePanelService* GetSidePanelService() const;
#endif

  // Appends the extension's context menu items.
  void AppendExtensionItems();

#if !BUILDFLAG(IS_ANDROID)
  // Appends the side panel menu item to the context menu if `extension` has one
  // it can open.
  void AddSidePanelEntryIfPresent(const Extension& extension);
#endif

  // A copy of the extension's id.
  ExtensionId extension_id_;

  // Whether the menu is for a component extension.
  bool is_component_;

  // The extension action of the extension we are displaying the menu for (if
  // it has one, otherwise NULL).
  raw_ptr<ExtensionAction, DanglingUntriaged> extension_action_;

  const raw_ptr<BrowserWindowInterface> browser_;

  raw_ptr<Profile> profile_;

  // The delegate which handles the 'inspect popup' menu command (or NULL).
  raw_ptr<PopupDelegate> delegate_;

  // Whether the extension icon is pinned at the time the menu opened.
  bool is_pinned_;

  // Menu matcher for context menu items specified by the extension.
  std::unique_ptr<ContextMenuMatcher> extension_items_;

  std::unique_ptr<ui::SimpleMenuModel> page_access_submenu_;

  // The action taken by the menu. Has a valid value when the menu is being
  // shown.
  std::optional<ContextMenuAction> action_taken_;

  ContextMenuSource source_;

  // The origin used to populate the context menu's content.
  // TODO(crbug.com/40265043): Web contents may change while the menu is open,
  // which may affect the context menu contents. We should dynamically update
  // the context menu, or close it when this happens.
  url::Origin origin_;
};

}  // namespace extensions

#endif  // CHROME_BROWSER_EXTENSIONS_EXTENSION_CONTEXT_MENU_MODEL_H_
