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

#include "chrome/browser/ui/webui_browser/webui_browser_page_handler.h"

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/notimplemented.h"
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/interaction/browser_elements.h"
#include "chrome/browser/ui/location_bar/location_bar.h"
#include "chrome/browser/ui/side_panel/side_panel_entry.h"
#include "chrome/browser/ui/toolbar/app_menu_model.h"
#include "chrome/browser/ui/toolbar/back_forward_menu_model.h"
#include "chrome/browser/ui/views/bubble_anchor_util_views.h"
#include "chrome/browser/ui/views/page_info/page_info_bubble_specification.h"
#include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
#include "chrome/browser/ui/views/profiles/profile_menu_coordinator.h"
#include "chrome/browser/ui/views/tab_search_bubble_host.h"
#include "chrome/browser/ui/views/toolbar/app_menu.h"
#include "chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h"
#include "chrome/browser/ui/webui_browser/webui_browser_ui.h"
#include "components/browser_apis/tab_strip/types/node_id.h"
#include "components/guest_contents/browser/guest_contents_handle.h"
#include "components/omnibox/browser/location_bar_model.h"
#include "components/omnibox/browser/vector_icons.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/ui_base_features.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/views/controls/menu/menu_model_adapter.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/interaction/element_tracker_views.h"

class WebUIBrowserGuestHandler
    : public content::DocumentService<webui_browser::mojom::GuestHandler> {
 public:
  WebUIBrowserGuestHandler(
      content::RenderFrameHost& render_frame_host,
      mojo::PendingReceiver<webui_browser::mojom::GuestHandler> receiver,
      content::WebContents* web_contents,
      WebUIBrowserWindow* window)
      : content::DocumentService<webui_browser::mojom::GuestHandler>(
            render_frame_host,
            std::move(receiver)),
        window_(window),
        web_contents_(web_contents->GetWeakPtr()) {}
  WebUIBrowserGuestHandler(const WebUIBrowserGuestHandler&) = delete;
  WebUIBrowserGuestHandler& operator=(const WebUIBrowserGuestHandler&) = delete;
  ~WebUIBrowserGuestHandler() override = default;

 private:
  // webui_browser::mojom::GuestHandler
  void Navigate(const GURL& src) override {
    content::NavigationController::LoadURLParams load_url_params(src);
    web_contents_->GetController().LoadURLWithParams(load_url_params);
  }

  void CanGoBack(CanGoBackCallback callback) override {
    if (!web_contents_.get()) {
      std::move(callback).Run(/*can_go_back=*/false);
      return;
    }
    std::move(callback).Run(web_contents_->GetController().CanGoBack());
  }

  void GoBack() override {
    if (web_contents_->GetController().CanGoBack()) {
      web_contents_->GetController().GoBack();
    }
  }

  void CanGoForward(CanGoForwardCallback callback) override {
    if (!web_contents_.get()) {
      std::move(callback).Run(/*can_go_forward=*/false);
      return;
    }
    std::move(callback).Run(web_contents_->GetController().CanGoForward());
  }

  void GoForward() override {
    if (web_contents_->GetController().CanGoForward()) {
      web_contents_->GetController().GoForward();
    }
  }

  void Reload() override {
    web_contents_->GetController().Reload(content::ReloadType::NORMAL, true);
  }

  void StopLoading() override { web_contents_->Stop(); }

  void OpenPageInfoMenu() override {
    std::unique_ptr<PageInfoBubbleSpecification> specification =
        PageInfoBubbleSpecification::Builder(
            window_->GetLocationBar()->GetChipAnchor()->anchor,
            window_->GetNativeWindow(), web_contents_.get(),
            web_contents_->GetLastCommittedURL())
            .Build();

    views::BubbleDialogDelegateView* const bubble =
        PageInfoBubbleView::CreatePageInfoBubble(std::move(specification));
    bubble->GetWidget()->Show();
  }

  void GetSecurityIcon(GetSecurityIconCallback callback) override {
    auto* icon = &window_->browser()
                      ->GetFeatures()
                      .location_bar_model()
                      ->GetVectorIcon();
    webui_browser::mojom::SecurityIcon icon_type;
    if (icon == &(features::IsRoundedIconsEnabled()
                      ? omnibox::kInfoIcon
                      : omnibox::kHttpChromeRefreshOldIcon)) {
      icon_type = webui_browser::mojom::SecurityIcon::HttpChromeRefresh;
    } else if (icon == &(features::IsRoundedIconsEnabled()
                             ? omnibox::kPageInfoCustomIcon
                             : omnibox::kSecurePageInfoChromeRefreshOldIcon)) {
      icon_type =
          webui_browser::mojom::SecurityIcon::SecurePageInfoChromeRefresh;
    } else if (icon == &(features::IsRoundedIconsEnabled()
                             ? vector_icons::kNoEncryptionIcon
                             : vector_icons::kNoEncryptionOldIcon)) {
      icon_type = webui_browser::mojom::SecurityIcon::NoEncryption;
    } else if (icon ==
               &(features::IsRoundedIconsEnabled()
                     ? vector_icons::kWarningIcon
                     : vector_icons::kNotSecureWarningChromeRefreshOldIcon)) {
      icon_type =
          webui_browser::mojom::SecurityIcon::NotSecureWarningChromeRefresh;
    } else if (icon == &(features::IsRoundedIconsEnabled()
                             ? vector_icons::kDomainIcon
                             : vector_icons::kBusinessChromeRefreshOldIcon)) {
      icon_type = webui_browser::mojom::SecurityIcon::BusinessChromeRefresh;
    } else if (icon == &(features::IsRoundedIconsEnabled()
                             ? vector_icons::kDangerousFilledIcon
                             : vector_icons::kDangerousChromeRefreshOldIcon)) {
      icon_type = webui_browser::mojom::SecurityIcon::DangerousChromeRefresh;
    } else if (icon == &(features::IsRoundedIconsEnabled()
                             ? omnibox::kChromeProductIcon
                             : omnibox::kProductChromeRefreshOldIcon)) {
      icon_type = webui_browser::mojom::SecurityIcon::ProductChromeRefresh;
    } else if (icon == &(features::IsRoundedIconsEnabled()
                             ? vector_icons::kChromeExtensionIcon
                             : vector_icons::kExtensionChromeRefreshOldIcon)) {
      icon_type = webui_browser::mojom::SecurityIcon::ExtensionChromeRefresh;
    } else if (icon == &(features::IsRoundedIconsEnabled()
                             ? omnibox::kOfflinePinFilledIcon
                             : omnibox::kOfflinePinOldIcon)) {
      icon_type = webui_browser::mojom::SecurityIcon::OfflinePin;
    } else {
      CHECK(false) << "Add new icon to webui_browsers's browser.mojom and "
                   << "app.ts and icons.html.ts.";
    }
    std::move(callback).Run(icon_type);
  }

  raw_ptr<WebUIBrowserWindow> window_;

  // The WebContents is destroyed before document
  // services, causing a raw_ptr of WebContents dangling here, so use a weak
  // ptr instead.
  base::WeakPtr<content::WebContents> web_contents_;
};

WebUIBrowserPageHandler::~WebUIBrowserPageHandler() = default;

// static
void WebUIBrowserPageHandler::CreateForRenderFrameHost(
    content::RenderFrameHost& render_frame_host,
    mojo::PendingReceiver<webui_browser::mojom::PageHandler> receiver,
    WebUIBrowserUI* controller) {
  // The RenderFrameHost takes ownership of this object via the DocumentService.
  new WebUIBrowserPageHandler(render_frame_host, std::move(receiver),
                              controller);
}

void WebUIBrowserPageHandler::GetGuestIdForTabId(
    const tabs_api::NodeId& tab_id,
    mojo::PendingReceiver<webui_browser::mojom::GuestHandler> receiver,
    GetGuestIdForTabIdCallback callback) {
  tabs::TabInterface* tab = nullptr;
  std::optional<tabs::TabHandle> maybe_tab_handle = tab_id.ToTabHandle();
  if (maybe_tab_handle) {
    tab = maybe_tab_handle->Get();
  }

  if (!tab) {
    mojo::ReportBadMessage("Invalid tab id");
    return;
  }
  content::WebContents* tab_contents = tab->GetContents();
  if (tab_contents == nullptr) {
    mojo::ReportBadMessage("Tab has no contents");
    return;
  }

  // The RenderFrameHost takes ownership of this object via the DocumentService.
  new WebUIBrowserGuestHandler(render_frame_host(), std::move(receiver),
                               tab_contents, GetBrowserWindow());

  guest_contents::GuestContentsHandle::CreateForWebContents(tab_contents);
  auto* guest_handle =
      guest_contents::GuestContentsHandle::FromWebContents(tab_contents);
  std::move(callback).Run(guest_handle->id());
}

void WebUIBrowserPageHandler::LoadTabSearch(LoadTabSearchCallback callback) {
  content::WebContents::CreateParams params(GetBrowser()->GetProfile());
  tab_search_contents_ = content::WebContents::Create(params);
  tab_search_contents_->SetColorProviderSource(GetBrowserWindow());
  content::NavigationController::LoadURLParams url_params{
      GURL(chrome::kChromeUITabSearchURL)};
  tab_search_contents_->GetController().LoadURLWithParams(url_params);
  guest_contents::GuestContentsHandle::CreateForWebContents(
      tab_search_contents_.get());
  auto* guest_handle = guest_contents::GuestContentsHandle::FromWebContents(
      tab_search_contents_.get());
  std::move(callback).Run(guest_handle->id());
}

void WebUIBrowserPageHandler::ShowTabSearchBubble(
    const std::string& anchor_name) {
  // TODO(webium): Call TabSearchBubbleHost::ShowTabSearchBubble().
  NOTIMPLEMENTED();
}

void WebUIBrowserPageHandler::OpenAppMenu() {
  menu_.reset();

  ui::TrackedElement* app_menu_button =
      BrowserElements::From(GetBrowser())
          ->GetElement(kToolbarAppMenuButtonElementId);
  CHECK(app_menu_button) << "App menu button not found";
  menu_model_ =
      std::make_unique<AppMenuModel>(GetBrowserWindow(), GetBrowser());
  menu_model_->Init();
  menu_ = std::make_unique<AppMenu>(
      GetBrowser(), menu_model_.get(), views::MenuRunner::NO_FLAGS,
      // The WebUI browser architecture doesn't currently use BrowserView or
      // an AppMenuControl that needs to be notified when the menu closes.
      base::DoNothing());
  menu_->RunMenu(GetBrowserWindow()->widget(),
                 app_menu_button->GetScreenBounds());
}

void WebUIBrowserPageHandler::OpenProfileMenu() {
  GetBrowser()->GetFeatures().profile_menu_coordinator()->Show(
      /*is_source_accelerator=*/false);
}

void WebUIBrowserPageHandler::LaunchDevToolsForBrowser() {
  content::WebContents* webcontents_to_inspect =
      content::WebContents::FromRenderFrameHost(&render_frame_host());
  DevToolsWindow::OpenDevToolsWindow(
      webcontents_to_inspect, DevToolsOpenedByAction::kMainMenuOrMainShortcut);
}

void WebUIBrowserPageHandler::OnSidePanelClosed() {
  GetBrowserWindow()->GetWebUIBrowserSidePanelUI()->OnSidePanelClosed();
}

void WebUIBrowserPageHandler::Minimize() {
  GetBrowserWindow()->Minimize();
}

void WebUIBrowserPageHandler::Maximize() {
  GetBrowserWindow()->Maximize();
}

void WebUIBrowserPageHandler::Restore() {
  GetBrowserWindow()->Restore();
}

void WebUIBrowserPageHandler::Close() {
  GetBrowserWindow()->Close();
}

void WebUIBrowserPageHandler::ShowBackForwardMenu(bool is_back) {
  back_forward_menu_runner_.reset();
  back_forward_menu_model_adapter_.reset();
  back_forward_menu_model_.reset();

  content::WebContents* web_contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  if (!web_contents) {
    return;
  }

  ui::ElementIdentifier button_id =
      is_back ? kToolbarBackButtonElementId : kToolbarForwardButtonElementId;
  ui::TrackedElement* button_element =
      BrowserElements::From(GetBrowser())->GetElement(button_id);
  if (!button_element) {
    return;
  }

  back_forward_menu_model_ = std::make_unique<BackForwardMenuModel>(
      GetBrowser(), is_back ? BackForwardMenuModel::ModelType::kBackward
                            : BackForwardMenuModel::ModelType::kForward);
  back_forward_menu_model_adapter_ =
      std::make_unique<views::MenuModelAdapter>(back_forward_menu_model_.get());
  back_forward_menu_runner_ = std::make_unique<views::MenuRunner>(
      back_forward_menu_model_adapter_->CreateMenu(),
      views::MenuRunner::HAS_MNEMONICS);
  back_forward_menu_runner_->RunMenuAt(
      GetBrowserWindow()->widget(), nullptr, button_element->GetScreenBounds(),
      views::MenuAnchorPosition::kTopLeft, ui::mojom::MenuSourceType::kMouse);
}

// static
int WebUIBrowserPageHandler::GetTabStripInsetWidth() {
#if BUILDFLAG(IS_MAC)
  // Values from BrowserFrameViewMac::GetCaptionButtonBounds()
  return (base::mac::MacOSVersion() >= 26'00'00) ? 76 : 82;
#else
  return 0;
#endif
}

void WebUIBrowserPageHandler::GetTabStripInset(
    GetTabStripInsetCallback callback) {
  std::move(callback).Run(GetTabStripInsetWidth());
}

WebUIBrowserPageHandler::WebUIBrowserPageHandler(
    content::RenderFrameHost& render_frame_host,
    mojo::PendingReceiver<webui_browser::mojom::PageHandler> receiver,
    WebUIBrowserUI* controller)
    : content::DocumentService<webui_browser::mojom::PageHandler>(
          render_frame_host,
          std::move(receiver)),
      controller_(controller->GetWeakPtr()) {}

Browser* WebUIBrowserPageHandler::GetBrowser() {
  if (!controller_) {
    return nullptr;
  }

  return controller_->browser();
}

WebUIBrowserWindow* WebUIBrowserPageHandler::GetBrowserWindow() {
  if (!controller_) {
    return nullptr;
  }

  return controller_->browser_window();
}
