// 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/extensions/api/pdf_viewer_private/pdf_viewer_private_api.h"

#include <cmath>
#include <memory>

#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/glic/public/glic_enabling.h"
#include "chrome/browser/glic/public/glic_keyed_service.h"
#include "chrome/browser/glic/public/glic_keyed_service_factory.h"
#include "chrome/browser/pdf/pdf_pref_names.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/user_education/browser_user_education_interface.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/extensions/api/pdf_viewer_private.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/pdf/common/constants.h"
#include "components/prefs/pref_service.h"
#include "components/tabs/public/tab_interface.h"
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
#include "extensions/browser/mime_handler/mime_handler_stream_manager.h"
#include "extensions/browser/mime_handler/stream_container.h"
#include "pdf/buildflags.h"
#include "pdf/pdf_features.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/url_constants.h"

#if BUILDFLAG(ENABLE_PDF_SAVE_TO_DRIVE)
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/save_to_drive/content_reader.h"
#include "chrome/browser/save_to_drive/pdf_content_reader.h"
#include "chrome/browser/save_to_drive/save_to_drive_event_dispatcher.h"
#include "chrome/browser/save_to_drive/save_to_drive_flow.h"
#include "chrome/browser/save_to_drive/save_to_drive_utils.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"  // nogncheck
#include "chrome/browser/ui/save_to_drive/get_account.h"
#include "extensions/common/error_utils.h"
#endif  // BUILDFLAG(ENABLE_PDF_SAVE_TO_DRIVE)

namespace extensions {

namespace {

namespace IsAllowedLocalFileAccess =
    api::pdf_viewer_private::IsAllowedLocalFileAccess;

namespace SaveToDrive = api::pdf_viewer_private::SaveToDrive;

namespace SetPdfPluginAttributes =
    api::pdf_viewer_private::SetPdfPluginAttributes;

namespace SetPdfDocumentTitle = api::pdf_viewer_private::SetPdfDocumentTitle;

// Check if the current URL is allowed based on a list of allowlisted domains.
bool IsUrlAllowedToEmbedLocalFiles(const GURL& current_url,
                                   const base::ListValue& allowlisted_domains) {
  if (!current_url.is_valid() || !current_url.SchemeIs(url::kHttpsScheme)) {
    return false;
  }

  for (auto const& value : allowlisted_domains) {
    const std::string* domain = value.GetIfString();
    if (!domain) {
      continue;
    }

    if (current_url.DomainIs(*domain)) {
      return true;
    }
  }
  return false;
}

// Get the `StreamContainer` associated with the `extension_host`.
base::WeakPtr<StreamContainer> GetStreamContainer(
    content::RenderFrameHost* extension_host) {
  content::RenderFrameHost* embedder_host = extension_host->GetParent();
  if (!embedder_host) {
    return nullptr;
  }

  auto* mime_handler_stream_manager =
      mime_handler::MimeHandlerStreamManager::FromRenderFrameHost(
          embedder_host);
  if (!mime_handler_stream_manager) {
    return nullptr;
  }

  return mime_handler_stream_manager->GetStreamContainer(embedder_host);
}

#if BUILDFLAG(ENABLE_PDF_SAVE_TO_DRIVE)
// Converts the `SaveRequestType` enum from the extension API to the mojom enum.
pdf::mojom::SaveRequestType ToMojomSaveRequestType(
    api::pdf_viewer_private::SaveRequestType request_type) {
  switch (request_type) {
    case api::pdf_viewer_private::SaveRequestType::kOriginal:
      return pdf::mojom::SaveRequestType::kOriginal;
    case api::pdf_viewer_private::SaveRequestType::kEdited:
      return pdf::mojom::SaveRequestType::kEdited;
    case api::pdf_viewer_private::SaveRequestType::kSearchified:
      return pdf::mojom::SaveRequestType::kSearchified;
    case api::pdf_viewer_private::SaveRequestType::kAnnotation:
      return pdf::mojom::SaveRequestType::kAnnotation;
    case api::pdf_viewer_private::SaveRequestType::kNone:
      // It should not be called with `kNone`.
      NOTREACHED();
  }
  NOTREACHED();
}
#endif  // BUILDFLAG(ENABLE_PDF_SAVE_TO_DRIVE)

}  // namespace

PdfViewerPrivateGetStreamInfoFunction::PdfViewerPrivateGetStreamInfoFunction() =
    default;

PdfViewerPrivateGetStreamInfoFunction::
    ~PdfViewerPrivateGetStreamInfoFunction() = default;

ExtensionFunction::ResponseAction PdfViewerPrivateGetStreamInfoFunction::Run() {
  base::WeakPtr<StreamContainer> stream =
      GetStreamContainer(render_frame_host());
  if (!stream) {
    return RespondNow(Error("Failed to get StreamContainer"));
  }

  api::pdf_viewer_private::StreamInfo stream_info;
  stream_info.original_url = stream->original_url().spec();
  stream_info.stream_url = stream->stream_url().spec();
  stream_info.tab_id = stream->tab_id();
  stream_info.embedded = stream->embedded();
  return RespondNow(WithArguments(stream_info.ToValue()));
}

PdfViewerPrivateIsAllowedLocalFileAccessFunction::
    PdfViewerPrivateIsAllowedLocalFileAccessFunction() = default;

PdfViewerPrivateIsAllowedLocalFileAccessFunction::
    ~PdfViewerPrivateIsAllowedLocalFileAccessFunction() = default;

ExtensionFunction::ResponseAction
PdfViewerPrivateIsAllowedLocalFileAccessFunction::Run() {
  std::optional<IsAllowedLocalFileAccess::Params> params =
      IsAllowedLocalFileAccess::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  PrefService* prefs =
      Profile::FromBrowserContext(browser_context())->GetPrefs();

  return RespondNow(WithArguments(IsUrlAllowedToEmbedLocalFiles(
      GURL(params->url),
      prefs->GetList(prefs::kPdfLocalFileAccessAllowedForDomains))));
}

PdfViewerPrivateSaveToDriveFunction::PdfViewerPrivateSaveToDriveFunction() =
    default;

PdfViewerPrivateSaveToDriveFunction::~PdfViewerPrivateSaveToDriveFunction() =
    default;

ExtensionFunction::ResponseAction PdfViewerPrivateSaveToDriveFunction::Run() {
#if BUILDFLAG(ENABLE_PDF_SAVE_TO_DRIVE)
  std::optional<SaveToDrive::Params> params =
      SaveToDrive::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);
  if (params->save_request_type !=
      api::pdf_viewer_private::SaveRequestType::kNone) {
    return RunSaveToDriveFlow(params->save_request_type);
  }
  return StopSaveToDriveFlow();
#else
  return RespondNow(Error("Not supported"));
#endif  // BUILDFLAG(ENABLE_PDF_SAVE_TO_DRIVE)
}

#if BUILDFLAG(ENABLE_PDF_SAVE_TO_DRIVE)
ExtensionFunction::ResponseAction
PdfViewerPrivateSaveToDriveFunction::RunSaveToDriveFlow(
    api::pdf_viewer_private::SaveRequestType request_type) {
  using SaveToDriveFlow = save_to_drive::SaveToDriveFlow;

  if (SaveToDriveFlow::GetForCurrentDocument(render_frame_host())) {
    return RespondNow(Error("An upload is already in progress"));
  }
  auto event_dispatcher =
      save_to_drive::SaveToDriveEventDispatcher::Create(render_frame_host());
  if (!event_dispatcher) {
    return RespondNow(Error("Failed to create event dispatcher"));
  }

  // It is possible the tab associated with this call has been closed.
  if (!SaveToDriveFlow::HasValidTabId(render_frame_host())) {
    return RespondNow(Error(ErrorUtils::FormatErrorMessage(
        ExtensionTabUtil::kTabNotFoundError,
        base::NumberToString(save_to_drive::GetTabId(render_frame_host())))));
  }

  auto content_reader = std::make_unique<save_to_drive::PDFContentReader>(
      render_frame_host(), ToMojomSaveRequestType(request_type));
  auto account_chooser = std::make_unique<save_to_drive::AccountChooser>();
  HatsService* hats_service = HatsServiceFactory::GetForProfile(
      Profile::FromBrowserContext(browser_context()),
      /*create_if_necessary=*/true);
  auto* flow = SaveToDriveFlow::Create(
      render_frame_host(), std::move(event_dispatcher),
      std::move(content_reader), std::move(account_chooser), hats_service);
  flow->Run();
  return RespondNow(NoArguments());
}

ExtensionFunction::ResponseAction
PdfViewerPrivateSaveToDriveFunction::StopSaveToDriveFlow() {
  using SaveToDriveFlow = save_to_drive::SaveToDriveFlow;

  auto* flow = SaveToDriveFlow::GetForCurrentDocument(render_frame_host());
  if (!flow) {
    return RespondNow(Error("Failed to get SaveToDriveFlow"));
  }
  flow->Stop();
  return RespondNow(NoArguments());
}
#endif  // BUILDFLAG(ENABLE_PDF_SAVE_TO_DRIVE)

PdfViewerPrivateSetPdfDocumentTitleFunction::
    PdfViewerPrivateSetPdfDocumentTitleFunction() = default;

PdfViewerPrivateSetPdfDocumentTitleFunction::
    ~PdfViewerPrivateSetPdfDocumentTitleFunction() = default;

// This function is only called for full-page PDFs.
ExtensionFunction::ResponseAction
PdfViewerPrivateSetPdfDocumentTitleFunction::Run() {
  content::WebContents* web_contents = GetSenderWebContents();
  if (!web_contents) {
    return RespondNow(Error("Could not find a valid web contents."));
  }

  // Title should only be set for full-page PDFs.
  // MIME type associated with sender `WebContents` must be `application/pdf`
  // for a full-page PDF.
  EXTENSION_FUNCTION_VALIDATE(web_contents->GetContentsMimeType() ==
                              pdf::kPDFMimeType);

  std::optional<SetPdfDocumentTitle::Params> params =
      SetPdfDocumentTitle::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  web_contents->UpdateTitleForEntry(
      web_contents->GetController().GetLastCommittedEntry(),
      base::UTF8ToUTF16(params->title));

  return RespondNow(NoArguments());
}

PdfViewerPrivateSetPdfPluginAttributesFunction::
    PdfViewerPrivateSetPdfPluginAttributesFunction() = default;

PdfViewerPrivateSetPdfPluginAttributesFunction::
    ~PdfViewerPrivateSetPdfPluginAttributesFunction() = default;

ExtensionFunction::ResponseAction
PdfViewerPrivateSetPdfPluginAttributesFunction::Run() {
  std::optional<SetPdfPluginAttributes::Params> params =
      SetPdfPluginAttributes::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  base::WeakPtr<StreamContainer> stream =
      GetStreamContainer(render_frame_host());
  if (!stream) {
    return RespondNow(Error("Failed to get StreamContainer"));
  }

  const api::pdf_viewer_private::PdfPluginAttributes& attributes =
      params->attributes;
  // Check the `background_color` is an integer.
  double whole = 0.0;
  if (std::modf(attributes.background_color, &whole) != 0.0) {
    return RespondNow(Error("Background color is not an integer"));
  }

  // Check the `background_color` is within the range of a uint32_t.
  if (!base::IsValueInRangeForNumericType<uint32_t>(
          attributes.background_color)) {
    return RespondNow(Error("Background color out of bounds"));
  }

  stream->set_pdf_plugin_attributes(mime_handler::PdfPluginAttributes::New(
      /*background_color=*/attributes.background_color,
      /*allow_javascript=*/attributes.allow_javascript));
  return RespondNow(NoArguments());
}

PdfViewerPrivateGlicSummarizeFunction::PdfViewerPrivateGlicSummarizeFunction() =
    default;

PdfViewerPrivateGlicSummarizeFunction::
    ~PdfViewerPrivateGlicSummarizeFunction() = default;

ExtensionFunction::ResponseAction PdfViewerPrivateGlicSummarizeFunction::Run() {
  bool success = false;
  auto cleanup = absl::MakeCleanup([&success] {
    base::UmaHistogramBoolean("PDF.GlicSummarizeButtonClicked", success);
  });

  content::WebContents* contents = GetSenderWebContents();
  if (!contents) {
    return RespondNow(Error("No web contents."));
  }

  // When the PDF viewer is hosted in a MimeHandlerViewGuest (legacy GuestView,
  // e.g. on ChromeOS where kPdfOopif is disabled), the sender WebContents is
  // the inner guest contents which has no TabInterface attached. Walk up to
  // the embedder WebContents so the tab lookup succeeds.
  if (!chrome_pdf::features::IsOopifPdfEnabled()) {
    if (auto* guest = MimeHandlerViewGuest::FromWebContents(contents)) {
      contents = guest->embedder_web_contents();
      if (!contents) {
        return RespondNow(Error("No web contents."));
      }
    }
  }

  tabs::TabInterface* tab_interface =
      tabs::TabInterface::MaybeGetFromContents(contents);
  if (!tab_interface) {
    return RespondNow(Error("No tab."));
  }

  if (!glic::GlicEnabling::IsEnabledForProfile(
          Profile::FromBrowserContext(contents->GetBrowserContext()))) {
    return RespondNow(Error("Glic is not enabled."));
  }

  glic::GlicKeyedService* glic_service =
      glic::GlicKeyedServiceFactory::GetGlicKeyedService(
          contents->GetBrowserContext());
  CHECK(glic_service);

  int arm = features::kPdfGlicSummarizeArm.Get();
  bool has_consented = glic::GlicEnabling::HasConsentedForProfile(
      Profile::FromBrowserContext(contents->GetBrowserContext()));

  glic::GlicInvokeOptions options(
      glic::Target(*tab_interface, glic::NewConversation()),
      glic::mojom::InvocationSource::kPdfSummarizeButton);
  options.prompts.push_back(
      l10n_util::GetStringUTF8(IDS_PDF_GLIC_SUMMARIZE_PROMPT));

  if (has_consented) {
    glic_service->InvokeWithAutoSubmit(
        glic::InvokeWithAutoSubmitPasskeyProvider::GetPassKey(),
        std::move(options));
  } else {
    if (arm == 3) {
      options.fre_override = glic::mojom::FreOverride::kTrustFirstInline;
      glic_service->InvokeWithAutoSubmit(
          glic::InvokeWithAutoSubmitPasskeyProvider::GetPassKey(),
          std::move(options));
    } else {
      options.fre_override = glic::mojom::FreOverride::kTrustFirstText;
      glic_service->Invoke(std::move(options));
    }
  }

  if (auto* user_education =
          BrowserUserEducationInterface::MaybeGetForWebContentsInTab(
              contents)) {
    user_education->NotifyFeaturePromoFeatureUsed(
        feature_engagement::kIPHPdfGlicSummarizeFeature,
        FeaturePromoFeatureUsedAction::kClosePromoIfPresent);
  }

  success = true;
  return RespondNow(NoArguments());
}

}  // namespace extensions
