// Copyright 2024 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/digital_credentials/digital_identity_safety_interstitial_controller_desktop.h"

#include <string>

#include "base/metrics/histogram_functions.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/digital_credentials/digital_identity_interstitial_closed_reason.h"
#include "chrome/browser/ui/views/extensions/security_dialog_tracker.h"
#include "chrome/grit/generated_resources.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/url_formatter/elide_url.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/digital_identity_interstitial_type.h"
#include "content/public/browser/digital_identity_provider.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/dialog_model.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/style/typography.h"
#include "ui/views/style/typography_provider.h"
#include "ui/views/widget/widget.h"

using DialogButton = ui::DialogModel::Button;
using InterstitialType = content::DigitalIdentityInterstitialType;
using RequestStatusForMetrics =
    content::DigitalIdentityProvider::RequestStatusForMetrics;
using web_modal::WebContentsModalDialogManager;

DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kContinueButtonId);

DigitalIdentitySafetyInterstitialControllerDesktop::CloseOnNavigationObserver::
    CloseOnNavigationObserver() = default;

DigitalIdentitySafetyInterstitialControllerDesktop::CloseOnNavigationObserver::
    ~CloseOnNavigationObserver() {
  if (!web_contents_) {
    return;
  }
  WebContentsModalDialogManager::FromWebContents(web_contents_.get())
      ->RemoveObserver(this);
}

void DigitalIdentitySafetyInterstitialControllerDesktop::
    CloseOnNavigationObserver::Observe(content::WebContents& web_contents) {
  web_contents_ = web_contents.GetWeakPtr();
  WebContentsModalDialogManager::FromWebContents(web_contents_.get())
      ->AddObserver(this);
}

void DigitalIdentitySafetyInterstitialControllerDesktop::
    CloseOnNavigationObserver::OnWillCloseOnNavigation() {
  will_close_due_to_navigation_ = true;
}

DigitalIdentitySafetyInterstitialControllerDesktop::
    DigitalIdentitySafetyInterstitialControllerDesktop() = default;
DigitalIdentitySafetyInterstitialControllerDesktop::
    ~DigitalIdentitySafetyInterstitialControllerDesktop() = default;

content::DigitalIdentityProvider::DigitalIdentityInterstitialAbortCallback
DigitalIdentitySafetyInterstitialControllerDesktop::ShowInterstitial(
    content::WebContents& web_contents,
    const url::Origin& rp_origin,
    InterstitialType interstitial_type,
    content::DigitalIdentityProvider::DigitalIdentityInterstitialCallback
        callback) {
  web_contents_ = web_contents.GetWeakPtr();
  rp_origin_ = rp_origin;
  interstitial_type_ = interstitial_type;
  callback_ = std::move(callback);

  ShowInterstitialImpl(/*was_request_aborted=*/false);
  return base::BindOnce(
      &DigitalIdentitySafetyInterstitialControllerDesktop::Abort,
      weak_ptr_factory_.GetWeakPtr());
}

void DigitalIdentitySafetyInterstitialControllerDesktop::Abort() {
  if (!web_contents_) {
    return;
  }

  dialog_widget_->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
  ShowInterstitialImpl(/*was_request_aborted=*/true);
}

void DigitalIdentitySafetyInterstitialControllerDesktop::ShowInterstitialImpl(
    bool was_request_aborted) {
  if (!web_contents_) {
    return;
  }
  int body_resource_id = 0;
  int negative_button_label_resource_id = 0;
  switch (interstitial_type_) {
    case InterstitialType::kHighRisk:
      body_resource_id =
          IDS_WEB_DIGITAL_CREDENTIALS_INTERSTITIAL_HIGH_RISK_DIALOG_TEXT;
      negative_button_label_resource_id =
          IDS_WEB_DIGITAL_CREDENTIALS_INTERSTITIAL_HIGH_RISK_NEGATIVE_BUTTON_TEXT;
      break;
    case InterstitialType::kLowRisk:
      body_resource_id =
          IDS_WEB_DIGITAL_CREDENTIALS_INTERSTITIAL_LOW_RISK_DIALOG_TEXT;
      negative_button_label_resource_id =
          IDS_WEB_DIGITAL_CREDENTIALS_INTERSTITIAL_LOW_RISK_NEGATIVE_BUTTON_TEXT;
      break;
  }

  bool positive_button_enabled = !was_request_aborted;

  const views::LayoutProvider* layout_provider = views::LayoutProvider::Get();
  const int dialog_width = layout_provider->GetDistanceMetric(
      views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH);
  const gfx::Insets dialog_insets =
      layout_provider->GetInsetsMetric(views::INSETS_DIALOG);
  const float content_width = dialog_width - dialog_insets.width();

  const gfx::FontList& font_list = views::TypographyProvider::Get().GetFont(
      views::style::CONTEXT_DIALOG_BODY_TEXT, views::style::STYLE_PRIMARY);
  std::u16string formatted_origin =
      url_formatter::ElideUrl(rp_origin_.GetURL(), font_list, content_width);
  std::u16string body_text =
      l10n_util::GetStringFUTF16(body_resource_id, formatted_origin);
  std::u16string positive_button_label = l10n_util::GetStringUTF16(
      IDS_WEB_DIGITAL_CREDENTIALS_INTERSTITIAL_POSITIVE_BUTTON_TEXT);
  std::u16string negative_button_label =
      l10n_util::GetStringUTF16(negative_button_label_resource_id);

  ui::DialogModel::Builder dialog_model_builder(
      std::make_unique<ui::DialogModelDelegate>());
  dialog_model_builder
      .AddOkButton(
          base::BindOnce(&DigitalIdentitySafetyInterstitialControllerDesktop::
                             OnDialogClosed,
                         weak_ptr_factory_.GetWeakPtr(),
                         DigitalIdentityInterstitialClosedReason::kOkButton),
          DialogButton::Params()
              .SetId(kContinueButtonId)
              .SetLabel(positive_button_label)
              .SetStyle(ui::ButtonStyle::kDefault)
              .SetEnabled(positive_button_enabled))
      .AddCancelButton(
          base::BindOnce(
              &DigitalIdentitySafetyInterstitialControllerDesktop::
                  OnDialogClosed,
              weak_ptr_factory_.GetWeakPtr(),
              DigitalIdentityInterstitialClosedReason::kCancelButton),
          DialogButton::Params()
              .SetLabel(negative_button_label)
              .SetStyle(ui::ButtonStyle::kDefault))
      .OverrideDefaultButton(ui::mojom::DialogButton::kNone)
      .SetDialogDestroyingCallback(base::BindOnce(
          &DigitalIdentitySafetyInterstitialControllerDesktop::OnDialogClosed,
          weak_ptr_factory_.GetWeakPtr(),
          DigitalIdentityInterstitialClosedReason::kOther))
      .SetTitle(l10n_util::GetStringUTF16(
          IDS_WEB_DIGITAL_CREDENTIALS_INTERSTITIAL_DIALOG_TITLE))
      .AddParagraph(ui::DialogModelLabel(body_text))
      .SetInitiallyFocusedField(kContinueButtonId)
      .SetEnableInputProtection(true);

  if (was_request_aborted) {
    dialog_model_builder.AddParagraph(
        ui::DialogModelLabel(l10n_util::GetStringFUTF16(
            IDS_WEB_DIGITAL_CREDENTIALS_INTERSTITIAL_REQUEST_ABORTED_DIALOG_TEXT,
            formatted_origin)));
  }
  dialog_widget_ = constrained_window::ShowWebModal(
      dialog_model_builder.Build(), web_contents_.get());
  extensions::SecurityDialogTracker::GetInstance()->AddSecurityDialog(
      dialog_widget_);

  // ShowWebModal() can synchronously drop fullscreen mode. On macOS, this
  // can spin a nested run loop that processes a tab close, potentially
  // destroying the WebContents. Check liveness before observing it.
  if (!web_contents_) {
    return;
  }
  close_on_navigation_observer_ = std::make_unique<CloseOnNavigationObserver>();
  close_on_navigation_observer_->Observe(*web_contents_);
}

void DigitalIdentitySafetyInterstitialControllerDesktop::OnDialogClosed(
    DigitalIdentityInterstitialClosedReason closed_reason) {
  dialog_widget_ = nullptr;

  if (!callback_) {
    return;
  }

  if (close_on_navigation_observer_ &&
      close_on_navigation_observer_->WillCloseOnNavigation()) {
    closed_reason = DigitalIdentityInterstitialClosedReason::kPageNavigated;
  }

  base::UmaHistogramEnumeration(
      "Blink.DigitalIdentityRequest.InterstitialClosedReason", closed_reason);

  // Run async to ensure it's impossible to tear down the controller while our
  // caller may still be using it.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          std::move(callback_),
          closed_reason == DigitalIdentityInterstitialClosedReason::kOkButton
              ? RequestStatusForMetrics::kSuccess
              : RequestStatusForMetrics::kErrorUserDeclined));
}
