// Copyright 2014 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/extensions/extension_action_delegate_desktop.h"

#include <utility>

#include "base/check.h"
#include "base/check_deref.h"
#include "chrome/browser/extensions/extension_view_host.h"
#include "chrome/browser/extensions/extension_view_host_factory.h"
#include "chrome/browser/tab_list/tab_list_interface.h"
#include "chrome/browser/ui/extensions/accelerator_priority.h"
#include "chrome/browser/ui/extensions/extension_action_view_model.h"
#include "chrome/browser/ui/views/extensions/extension_popup.h"
#include "chrome/browser/ui/views/extensions/extensions_container_views.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "extensions/common/api/extension_action/action_info.h"
#include "extensions/common/command.h"
#include "ui/views/view.h"

using extensions::ActionInfo;

ExtensionActionDelegateDesktop::ExtensionActionDelegateDesktop(
    BrowserWindowInterface* browser,
    ExtensionsContainer* extensions_container,
    ExtensionsContainerViews* extensions_container_views)
    : browser_(browser),
      extensions_container_(CHECK_DEREF(extensions_container)),
      extensions_container_views_(extensions_container_views) {}

ExtensionActionDelegateDesktop::~ExtensionActionDelegateDesktop() {
  // Should have already unregistered.
  DCHECK(!action_keybinding_);
}

ExtensionActionDelegateDesktop*
ExtensionActionDelegateDesktop::GetPopupOwnerDelegate() {
  ExtensionActionViewModel* owner_model =
      static_cast<ExtensionActionViewModel*>(
          extensions_container_->GetActionForId(model_->GetId()));
  return static_cast<ExtensionActionDelegateDesktop*>(owner_model->delegate());
}

void ExtensionActionDelegateDesktop::DoTriggerPopup(
    std::unique_ptr<extensions::ExtensionViewHost> host,
    PopupShowAction show_action,
    bool by_user,
    ShowPopupCallback callback) {
  DCHECK_EQ(this, GetPopupOwnerDelegate());

  // Always hide the current popup, even if it's not owned by this extension.
  // Only one popup should be visible at a time.
  extensions_container_->HideActivePopup();

  extensions_container_->CloseExtensionsMenuIfOpen();

  popup_host_ = host.get();
  popup_host_observation_.Observe(popup_host_.get());
  extensions_container_views_->SetPopupOwner(model_);

  extensions_container_views_->PopOutAction(
      model_->GetId(),
      base::BindOnce(&ExtensionActionDelegateDesktop::ShowPopup,
                     weak_factory_.GetWeakPtr(), std::move(host), show_action,
                     by_user, std::move(callback)));
}

void ExtensionActionDelegateDesktop::ShowPopup(
    std::unique_ptr<extensions::ExtensionViewHost> host,
    PopupShowAction show_action,
    bool by_user,
    ShowPopupCallback callback) {
  // It's possible that the popup should be closed before it finishes opening
  // (since it can open asynchronously). Check before proceeding.
  if (!popup_host_) {
    if (callback) {
      std::move(callback).Run(nullptr);
    }
    return;
  }

  // NOTE: Today, ShowPopup() always synchronously creates the platform-specific
  // popup class, which is what we care most about (since `has_opened_popup_`
  // is used to determine whether we need to manually close the
  // ExtensionViewHost). This doesn't necessarily mean that the popup has
  // completed rendering on the screen.
  has_opened_popup_ = true;

  // TOP_RIGHT is correct for both RTL and LTR, because the views platform
  // performs the flipping in RTL cases.
  views::BubbleBorder::Arrow arrow = views::BubbleBorder::TOP_RIGHT;
  ExtensionPopup::ShowPopup(
      browser_, std::move(host),
      extensions_container_views_->GetReferenceButtonForPopup(model_->GetId()),
      arrow, show_action, std::move(callback));

  extensions_container_views_->OnPopupShown(model_->GetId(), by_user);
}

void ExtensionActionDelegateDesktop::OnPopupClosed() {
  DCHECK(popup_host_observation_.IsObservingSource(popup_host_.get()));
  popup_host_observation_.Reset();
  popup_host_ = nullptr;
  has_opened_popup_ = false;
  extensions_container_views_->SetPopupOwner(nullptr);
  if (extensions_container_views_->GetPoppedOutActionId() == model_->GetId()) {
    extensions_container_views_->UndoPopOut();
  }
  extensions_container_views_->OnPopupClosed(model_->GetId());
}

void ExtensionActionDelegateDesktop::AttachToModel(
    ExtensionActionViewModel* model) {
  CHECK(model);
  CHECK(!model_);
  model_ = model;
}

void ExtensionActionDelegateDesktop::DetachFromModel() {
  CHECK(model_);
  model_ = nullptr;
}

void ExtensionActionDelegateDesktop::RegisterCommand() {
  // If we've already registered, do nothing.
  if (action_keybinding_) {
    return;
  }

  extensions::Command extension_command;
  views::FocusManager* focus_manager =
      extensions_container_views_->GetFocusManagerForAccelerator();
  if (focus_manager && model_->GetExtensionCommand(&extension_command)) {
    action_keybinding_ =
        std::make_unique<ui::Accelerator>(extension_command.accelerator());
    focus_manager->RegisterAccelerator(*action_keybinding_,
                                       kExtensionAcceleratorPriority, this);
  }
}

void ExtensionActionDelegateDesktop::UnregisterCommand() {
  // If we've already unregistered, do nothing.
  if (!action_keybinding_) {
    return;
  }

  views::FocusManager* focus_manager =
      extensions_container_views_->GetFocusManagerForAccelerator();
  if (focus_manager) {
    focus_manager->UnregisterAccelerator(*action_keybinding_, this);
    action_keybinding_.reset();
  }
}

bool ExtensionActionDelegateDesktop::IsShowingPopup() const {
  return popup_host_ != nullptr;
}

void ExtensionActionDelegateDesktop::HidePopup() {
  if (!IsShowingPopup()) {
    return;
  }

  // Only call Close() on the popup if it's been shown; otherwise, the popup
  // will be cleaned up in ShowPopup().
  if (has_opened_popup_) {
    popup_host_->Close();
  }

  // We need to do these actions synchronously (instead of closing and then
  // performing the rest of the cleanup in OnExtensionHostDestroyed()) because
  // the extension host may close asynchronously, and we need to keep the view
  // delegate up to date.
  if (popup_host_) {
    OnPopupClosed();
  }
}

gfx::NativeView ExtensionActionDelegateDesktop::GetPopupNativeViewForTesting() {
  return popup_host_ ? popup_host_->view()->GetNativeView() : gfx::NativeView();
}

void ExtensionActionDelegateDesktop::TriggerPopup(
    std::unique_ptr<extensions::ExtensionViewHost> host,
    PopupShowAction show_action,
    bool by_user,
    ShowPopupCallback callback) {
  GetPopupOwnerDelegate()->DoTriggerPopup(std::move(host), show_action, by_user,
                                          std::move(callback));
}

void ExtensionActionDelegateDesktop::ShowContextMenuAsFallback() {
  extensions_container_views_->ShowContextMenuAsFallback(model_->GetId());
}

void ExtensionActionDelegateDesktop::CloseExtensionsMenuIfOpen() {
  extensions_container_->CloseExtensionsMenuIfOpen();
}

bool ExtensionActionDelegateDesktop::AcceleratorPressed(
    const ui::Accelerator& accelerator) {
  return model_->TryHandleAcceleratorPress();
}

bool ExtensionActionDelegateDesktop::CanHandleAccelerators() const {
  return model_->CanHandleAccelerators();
}

void ExtensionActionDelegateDesktop::OnExtensionHostDestroyed(
    extensions::ExtensionHost* host) {
  OnPopupClosed();
}
