// Copyright 2023 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/document_scan/start_scan_runner.h"

#include "ash/constants/ash_pref_names.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/scanning/lorgnette_scanner_manager.h"
#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/extensions/dialogs/document_scanner_start_scan_confirmation_dialog.h"
#include "components/prefs/pref_service.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/extension.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/native_window_tracker/native_window_tracker.h"

namespace extensions {

namespace {

// Icon size for confirmation dialogs.
constexpr int kIconSize = 64;

// There is no easy way to interact with UI dialogs that are generated by Chrome
// itself, so we need to have a way to bypass this for testing.
std::optional<bool> g_start_scan_confirmation_result;

bool CanSkipConfirmation(content::BrowserContext* browser_context,
                         const ExtensionId& extension_id) {
  const base::ListValue& list =
      Profile::FromBrowserContext(browser_context)
          ->GetPrefs()
          ->GetList(ash::prefs::kDocumentScanAPITrustedExtensions);
  return list.contains(extension_id);

  // TODO(b/312740272): Add a way for the user to make their consent permanent.
  // Note that this needs to be per device.
}

}  // namespace

StartScanRunner::StartScanRunner(gfx::NativeWindow native_window,
                                 content::BrowserContext* browser_context,
                                 scoped_refptr<const Extension> extension)
    : native_window_(native_window),
      browser_context_(browser_context),
      extension_(std::move(extension)),
      approved_(false) {
  CHECK(extension_);
  if (native_window_) {
    native_window_tracker_ = ui::NativeWindowTracker::Create(native_window_);
  }
}

StartScanRunner::~StartScanRunner() = default;

// static
base::AutoReset<std::optional<bool>>
StartScanRunner::SetStartScanConfirmationResultForTesting(bool val) {
  return base::AutoReset<std::optional<bool>>(&g_start_scan_confirmation_result,
                                              val);
}

void StartScanRunner::Start(bool is_approved,
                            const std::string& scanner_name,
                            const std::string& scanner_handle,
                            api::document_scan::StartScanOptions options,
                            StartScanCallback callback) {
  CHECK(!callback_) << "start scan call already in progress";
  callback_ = std::move(callback);
  options_ = std::move(options);
  scanner_handle_ = std::move(scanner_handle);

  // TODO(b/312740272): Skip confirmation prompt if previous consent was within
  // the recent past (specific timeout TBD).  Note that confirmation needs to be
  // per device.
  if (is_approved || CanSkipConfirmation(browser_context_, extension_->id())) {
    SendStartScanRequest();
    return;
  }

  // If a test has set the confirmation result, go directly to the end handler
  // instead of displaying the dialog.
  if (g_start_scan_confirmation_result) {
    OnConfirmationDialogClosed(g_start_scan_confirmation_result.value());
    return;
  }

  ImageLoader::Get(browser_context_)
      ->LoadImageAtEveryScaleFactorAsync(
          extension_.get(), gfx::Size(kIconSize, kIconSize),
          base::BindOnce(&StartScanRunner::ShowStartScanDialog,
                         weak_ptr_factory_.GetWeakPtr(), scanner_name));
}

const ExtensionId& StartScanRunner::extension_id() const {
  return extension_->id();
}

void StartScanRunner::ShowStartScanDialog(const std::string& scanner_name,
                                          const gfx::Image& icon) {
  // If the browser window was closed during API request handling, treat it the
  // same as if the user denied the request.
  if (native_window_tracker_ &&
      native_window_tracker_->WasNativeWindowDestroyed()) {
    OnConfirmationDialogClosed(false);
    return;
  }

  ShowDocumentScannerStartScanConfirmationDialog(
      native_window_, extension_->id(), base::UTF8ToUTF16(extension_->name()),
      base::UTF8ToUTF16(scanner_name), icon.AsImageSkia(),
      base::BindOnce(&StartScanRunner::OnConfirmationDialogClosed,
                     weak_ptr_factory_.GetWeakPtr()));
}

void StartScanRunner::OnConfirmationDialogClosed(bool approved) {
  if (approved) {
    SendStartScanRequest();
    return;
  }

  lorgnette::StartPreparedScanResponse response;
  response.set_result(lorgnette::OPERATION_RESULT_ACCESS_DENIED);
  response.mutable_scanner()->set_token(scanner_handle_);
  std::move(callback_).Run(std::move(response));
}

void StartScanRunner::SendStartScanRequest() {
  approved_ = true;
  lorgnette::StartPreparedScanRequest request;
  request.mutable_scanner()->set_token(scanner_handle_);
  request.set_image_format(options_.format);
  if (options_.max_read_size.has_value()) {
    request.set_max_read_size(*options_.max_read_size);
  }
  ash::LorgnetteScannerManagerFactory::GetForBrowserContext(browser_context_)
      ->StartPreparedScan(request,
                          base::BindOnce(&StartScanRunner::OnStartScanResponse,
                                         weak_ptr_factory_.GetWeakPtr()));
}

void StartScanRunner::OnStartScanResponse(
    const std::optional<lorgnette::StartPreparedScanResponse>& response) {
  std::move(callback_).Run(response);
}

}  // namespace extensions
