// 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.

#include "chrome/browser/ui/views/bookmarks/bookmark_context_menu.h"

#include <memory>

#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
#include "chrome/common/chrome_switches.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_model_adapter.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/widget/widget.h"

using bookmarks::BookmarkNode;

namespace {

base::OnceClosure& PreRunCallback() {
  static base::NoDestructor<base::OnceClosure> instance;
  return *instance;
}

// Returns true if |command_id| corresponds to a command that causes one or more
// bookmarks to be removed.
bool IsRemoveBookmarksCommand(int command_id) {
  return command_id == IDC_CUT || command_id == IDC_BOOKMARK_BAR_REMOVE;
}

}  // namespace

BookmarkContextMenuObserver::~BookmarkContextMenuObserver() = default;

////////////////////////////////////////////////////////////////////////////////
// BookmarkContextMenu, public:

BookmarkContextMenu::BookmarkContextMenu(
    views::Widget* parent_widget,
    BrowserWindowInterface* browser,
    Profile* profile,
    BookmarkLaunchLocation opened_from,
    const std::vector<raw_ptr<const BookmarkNode, VectorExperimental>>&
        selection,
    bool close_on_remove,
    bool can_paste)
    : controller_(new BookmarkContextMenuController(
          parent_widget ? parent_widget->GetNativeWindow()
                        : gfx::NativeWindow(),
          this,
          browser,
          profile,
          opened_from,
          selection,
          can_paste)),
      parent_widget_(parent_widget ? parent_widget->GetWeakPtr() : nullptr),
      menu_(new views::MenuItemView(this)),
      close_on_remove_(close_on_remove) {
  menu_runner_ = std::make_unique<views::MenuRunner>(
      base::WrapUnique<views::MenuItemView>(menu_),
      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::IS_NESTED |
          views::MenuRunner::MENU_ITEM_CONTEXT_MENU);
  ui::SimpleMenuModel* menu_model = controller_->menu_model();
  ui::MenuModel* submodel = nullptr;
  for (size_t i = 0; i < menu_model->GetItemCount(); ++i) {
    views::MenuModelAdapter::AppendMenuItemFromModel(
        menu_model, i, menu_, menu_model->GetCommandIdAt(i));
    if (menu_model->GetCommandIdAt(i) == IDC_BOOKMARK_BAR_SUBMENU) {
      submodel = menu_model->GetSubmenuModelAt(i);
    }
  }

  if (submodel) {
    views::MenuItemView* item =
        menu_->GetMenuItemByID(IDC_BOOKMARK_BAR_SUBMENU);
    for (size_t i = 0; i < submodel->GetItemCount(); ++i) {
      views::MenuModelAdapter::AppendMenuItemFromModel(
          submodel, i, item, submodel->GetCommandIdAt(i));
    }
  }
}

BookmarkContextMenu::~BookmarkContextMenu() = default;

void BookmarkContextMenu::InstallPreRunCallback(base::OnceClosure callback) {
  DCHECK(PreRunCallback().is_null());
  PreRunCallback() = std::move(callback);
}

void BookmarkContextMenu::RunMenuAt(const gfx::Point& point,
                                    ui::mojom::MenuSourceType source_type) {
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode)) {
    return;
  }

  if (!parent_widget_) {
    return;
  }

  if (!PreRunCallback().is_null()) {
    std::move(PreRunCallback()).Run();
  }

  // width/height don't matter here.
  menu_runner_->RunMenuAt(parent_widget_.get(), nullptr,
                          gfx::Rect(point.x(), point.y(), 0, 0),
                          views::MenuAnchorPosition::kTopLeft, source_type);
}

void BookmarkContextMenu::AddObserver(BookmarkContextMenuObserver* observer) {
  observers_.AddObserver(observer);
}

void BookmarkContextMenu::RemoveObserver(
    BookmarkContextMenuObserver* observer) {
  observers_.RemoveObserver(observer);
}

////////////////////////////////////////////////////////////////////////////////
// BookmarkContextMenu, views::MenuDelegate implementation:

void BookmarkContextMenu::ExecuteCommand(int command_id, int event_flags) {
  chrome::UpdateBookmarkBarVisibilityPrefOnUserAction(controller_->profile());
  controller_->ExecuteCommand(command_id, event_flags);
}

bool BookmarkContextMenu::IsItemChecked(int command_id) const {
  return controller_->IsCommandIdChecked(command_id);
}

bool BookmarkContextMenu::IsCommandEnabled(int command_id) const {
  return controller_->IsCommandIdEnabled(command_id);
}

bool BookmarkContextMenu::IsCommandVisible(int command_id) const {
  return controller_->IsCommandIdVisible(command_id);
}

bool BookmarkContextMenu::ShouldCloseAllMenusOnExecute(int id) {
  return (id != IDC_BOOKMARK_BAR_REMOVE) || close_on_remove_;
}

bool BookmarkContextMenu::ShouldExecuteCommandWithoutClosingMenu(
    int id,
    const ui::Event& e) {
  if (id == IDC_BOOKMARK_BAR_SUBMENU_ALWAYS_SHOW ||
      id == IDC_BOOKMARK_BAR_SUBMENU_ALWAYS_HIDE ||
      id == IDC_BOOKMARK_BAR_SUBMENU_ONLY_ON_NTP) {
    return true;
  }
  return false;
}

void BookmarkContextMenu::OnMenuClosed(views::MenuItemView* menu) {
  observers_.Notify(&BookmarkContextMenuObserver::OnContextMenuClosed);
}

////////////////////////////////////////////////////////////////////////////////
// BookmarkContextMenuControllerDelegate
// implementation:

void BookmarkContextMenu::CloseMenu() {
  menu_->Cancel();
}

void BookmarkContextMenu::WillExecuteCommand(
    int command_id,
    const std::vector<raw_ptr<const BookmarkNode, VectorExperimental>>&
        bookmarks) {
  if (IsRemoveBookmarksCommand(command_id)) {
    observers_.Notify(&BookmarkContextMenuObserver::WillRemoveBookmarks,
                      bookmarks);
  }
}

void BookmarkContextMenu::DidExecuteCommand(int command_id) {
  if (IsRemoveBookmarksCommand(command_id)) {
    observers_.Notify(&BookmarkContextMenuObserver::DidRemoveBookmarks);
  }

  if (command_id == IDC_BOOKMARK_BAR_SUBMENU_ALWAYS_SHOW ||
      command_id == IDC_BOOKMARK_BAR_SUBMENU_ALWAYS_HIDE ||
      command_id == IDC_BOOKMARK_BAR_SUBMENU_ONLY_ON_NTP) {
    UpdateSubMenuState();
  }
}

void BookmarkContextMenu::UpdateSubMenuState() {
  for (int id : {IDC_BOOKMARK_BAR_SUBMENU_ALWAYS_SHOW,
                 IDC_BOOKMARK_BAR_SUBMENU_ALWAYS_HIDE,
                 IDC_BOOKMARK_BAR_SUBMENU_ONLY_ON_NTP}) {
    views::MenuItemView* item = menu_->GetMenuItemByID(id);
    if (item) {
      item->RefreshCheckmarkState();
    }
  }
}
