// Copyright 2022 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/extensions_request_access_button.h"

#include <memory>

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "chrome/browser/extensions/extension_action_runner.h"
#include "chrome/browser/extensions/extension_ui_util.h"
#include "chrome/browser/tab_list/tab_list_interface.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/extensions/extensions_toolbar_view_model.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_model.h"
#include "chrome/browser/ui/user_education/browser_user_education_interface.h"
#include "chrome/browser/ui/views/extensions/extensions_container_views.h"
#include "chrome/browser/ui/views/extensions/extensions_request_access_hover_card_coordinator.h"
#include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
#include "chrome/browser/ui/views/extensions/extensions_toolbar_desktop.h"
#include "chrome/browser/ui/views/permissions/chip/permission_chip_constants.h"
#include "chrome/browser/ui/views/toolbar/toolbar_chip_button.h"
#include "chrome/grit/generated_resources.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "content/public/browser/web_contents.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"

ExtensionsRequestAccessButton::ExtensionsRequestAccessButton(
    BrowserWindowInterface* browser,
    ExtensionsToolbarViewModel* extensions_toolbar_view_model,
    ExtensionsContainerViews* extensions_container_views)
    : ToolbarChipButton(
          base::BindRepeating(&ExtensionsRequestAccessButton::OnButtonPressed,
                              base::Unretained(this)),
          ToolbarChipButton::Edge::kRight),
      browser_(browser),
      extensions_toolbar_view_model_(extensions_toolbar_view_model),
      extensions_container_views_(extensions_container_views),
      hover_card_coordinator_(
          std::make_unique<ExtensionsRequestAccessHoverCardCoordinator>()),
      input_protector_(
          std::make_unique<views::InputEventActivationProtector>()) {
  // Set button for IPH.
  SetProperty(views::kElementIdentifierKey,
              kExtensionsRequestAccessButtonElementId);
}

ExtensionsRequestAccessButton::~ExtensionsRequestAccessButton() = default;

void ExtensionsRequestAccessButton::Update(
    const ExtensionsToolbarViewModel::RequestAccessButtonParams&
        request_access_button_params) {
  CHECK(!IsShowingConfirmation());
  extension_ids_ = request_access_button_params.extension_ids;

  SetVisible(!request_access_button_params.extension_ids.empty());
  SetTooltipText(request_access_button_params.tooltip_text);

  // TODO(crbug.com/40784980): Set the label and background color without
  // borders separately to match the mocks. For now, using SetHighlight to
  // display that adds a border and highlight color in addition to the label.
  std::optional<SkColor> color;
  SetHighlight(
      l10n_util::GetStringFUTF16Int(
          IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON,
          static_cast<int>(request_access_button_params.extension_ids.size())),
      color);
  SetEnabled(true);
}

// TODO(crbug.com/40879945): Remove hover card once
// kExtensionsMenuAccessControlWithPermittedSites is rolled out. We are keeping
// it for now since we may bring the hover card back.
void ExtensionsRequestAccessButton::MaybeShowHoverCard() {
  if (hover_card_coordinator_->IsShowing() ||
      !GetWidget()->IsMouseEventsEnabled()) {
    return;
  }

  hover_card_coordinator_->ShowBubble(GetActiveWebContents(), this,
                                      extensions_toolbar_view_model_,
                                      extension_ids_);
}

void ExtensionsRequestAccessButton::ResetConfirmation() {
  SetVisible(false);
  confirmation_origin_ = std::nullopt;
  collapse_timer_.Stop();
}

bool ExtensionsRequestAccessButton::IsShowingConfirmation() const {
  if (!confirmation_origin_.has_value()) {
    return false;
  }

  CHECK(GetVisible());
  return confirmation_origin_.has_value();
}

size_t ExtensionsRequestAccessButton::GetExtensionsCount() const {
  return extension_ids_.size();
}

bool ExtensionsRequestAccessButton::IsShowingConfirmationFor(
    const url::Origin& origin) const {
  if (!confirmation_origin_.has_value()) {
    return false;
  }

  CHECK(GetVisible());
  return confirmation_origin_ == origin;
}

bool ExtensionsRequestAccessButton::ShouldShowInkdropAfterIphInteraction() {
  return false;
}

void ExtensionsRequestAccessButton::VisibilityChanged(
    views::View* starting_from,
    bool is_visible) {
  views::View::VisibilityChanged(starting_from, is_visible);
  input_protector_->VisibilityChanged(is_visible);
}

void ExtensionsRequestAccessButton::OnBoundsChanged(
    const gfx::Rect& previous_bounds) {
  ToolbarChipButton::OnBoundsChanged(previous_bounds);
  input_protector_->MaybeUpdateViewProtectedTimeStamp();

  auto* extensions_toolbar =
      views::AsViewClass<ExtensionsToolbarDesktop>(parent());
  if (extensions_toolbar) {
    views::View* extensions_button = extensions_toolbar->GetExtensionsButton();
    if (extensions_button) {
      UpdateClipPath(extensions_button);
    }
  }
}

void ExtensionsRequestAccessButton::AddedToWidget() {
  ToolbarChipButton::AddedToWidget();
  auto* extensions_toolbar =
      views::AsViewClass<ExtensionsToolbarDesktop>(parent());
  if (extensions_toolbar) {
    views::View* extensions_button = extensions_toolbar->GetExtensionsButton();
    if (extensions_button && !sibling_observation_.IsObserving()) {
      sibling_observation_.Observe(extensions_button);
      UpdateClipPath(extensions_button);
    }
  }
}

void ExtensionsRequestAccessButton::RemovedFromWidget() {
  sibling_observation_.Reset();
  ToolbarChipButton::RemovedFromWidget();
}

ExtensionsRequestAccessButton::SiblingObserver::SiblingObserver(
    ExtensionsRequestAccessButton* button)
    : button_(button) {}

ExtensionsRequestAccessButton::SiblingObserver::~SiblingObserver() = default;

void ExtensionsRequestAccessButton::SiblingObserver::OnViewBoundsChanged(
    views::View* observed_view) {
  button_->UpdateClipPath(observed_view);
}

void ExtensionsRequestAccessButton::SiblingObserver::OnViewIsDeleting(
    views::View* observed_view) {
  button_->OnSiblingDeleting();
}

void ExtensionsRequestAccessButton::OnSiblingDeleting() {
  sibling_observation_.Reset();
}

void ExtensionsRequestAccessButton::UpdateClipPath(
    views::View* extensions_button) {
  if (GetVisible() && extensions_button && extensions_button->GetVisible()) {
    int clip_width = std::max(0, extensions_button->x() - x());

    gfx::Rect clip_rect =
        base::i18n::IsRTL()
            ? gfx::Rect(width() - clip_width, 0, clip_width, height())
            : gfx::Rect(0, 0, clip_width, height());

    SetClipPath(SkPath::Rect(gfx::RectToSkRect(clip_rect)));
  } else {
    SetClipPath(SkPath());
  }
}

void ExtensionsRequestAccessButton::OnButtonPressed(const ui::Event& event) {
  if (input_protector_->IsPossiblyUnintendedInteraction(
          event, /*allow_key_events=*/false)) {
    return;
  }
  // Record IPH usage.
  BrowserUserEducationInterface::From(browser_)->NotifyFeaturePromoFeatureUsed(
      feature_engagement::kIPHExtensionsRequestAccessButtonFeature,
      FeaturePromoFeatureUsedAction::kClosePromoIfPresent);
  content::WebContents* web_contents = GetActiveWebContents();
  extensions::ExtensionActionRunner* action_runner =
      extensions::ExtensionActionRunner::GetForWebContents(web_contents);
  if (!action_runner) {
    return;
  }

  // Make sure we set this before granting tab permissions, since that will
  // trigger an update to the request access button for each extension that is
  // granted access.
  confirmation_origin_ =
      web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();

  // Always grant access to this site to all extensions.
  DCHECK_GT(extension_ids_.size(), 0u);
  extensions_toolbar_view_model_->GrantSiteAccess(web_contents, extension_ids_);

  // Show confirmation message, and disable the button, for a specific duration.
  std::optional<SkColor> color;
  SetHighlight(l10n_util::GetStringUTF16(
                   IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT),
               color);
  SetEnabled(false);

  base::TimeDelta collapse_duration =
      remove_confirmation_for_testing_ ? base::Seconds(0)
                                       : kPermissionConfirmationDisplayDuration;
  // base::Unretained() below is safe because this view is tied to the
  // lifetime of `extensions_toolbar_view_model_`.
  collapse_timer_.Start(
      FROM_HERE, collapse_duration,
      base::BindOnce(&ExtensionsContainerViews::CollapseConfirmation,
                     base::Unretained(extensions_container_views_)));

  base::RecordAction(base::UserMetricsAction(
      "Extensions.Toolbar.ExtensionsActivatedFromRequestAccessButton"));
  UMA_HISTOGRAM_COUNTS_100(
      "Extensions.Toolbar.ExtensionsActivatedFromRequestAccessButton",
      extension_ids_.size());
}

content::WebContents* ExtensionsRequestAccessButton::GetActiveWebContents()
    const {
  auto* tab = TabListInterface::From(browser_)->GetActiveTab();
  return tab ? tab->GetContents() : nullptr;
}

BEGIN_METADATA(ExtensionsRequestAccessButton)
END_METADATA
