// Copyright 2019 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/sharing/sharing_ui_controller.h"

#include <utility>

#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/sharing/sharing_service_factory.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/browser/ui/views/sharing/sharing_window_controller.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/branded_strings.h"
#include "components/sharing_message/features.h"
#include "components/sharing_message/sharing_constants.h"
#include "components/sharing_message/sharing_dialog.h"
#include "components/sharing_message/sharing_dialog_data.h"
#include "components/sharing_message/sharing_target_device_info.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/strings/grit/ui_strings.h"

namespace {

content::WebContents* GetCurrentWebContents(
    content::WebContents* web_contents) {
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(web_contents);
  return browser ? browser->GetTabStripModel()->GetActiveWebContents()
                 : nullptr;
}

SharingDialogType GetSharingDialogType(bool has_devices, bool has_apps) {
  if (has_devices)
    return SharingDialogType::kDialogWithDevicesMaybeApps;
  if (has_apps)
    return SharingDialogType::kDialogWithoutDevicesWithApp;
  return SharingDialogType::kEducationalDialog;
}

}  // namespace

SharingUiController::SharingUiController(content::WebContents* web_contents)
    : web_contents_(web_contents),
      sharing_service_(SharingServiceFactory::GetForBrowserContext(
          web_contents->GetBrowserContext())) {}

SharingUiController::~SharingUiController() = default;

std::u16string SharingUiController::GetTitle(SharingDialogType dialog_type) {
  // We only handle error messages generically.
  DCHECK_EQ(SharingDialogType::kErrorDialog, dialog_type);
  switch (send_result()) {
    case SharingSendMessageResult::kDeviceNotFound:
    case SharingSendMessageResult::kNetworkError:
    case SharingSendMessageResult::kAckTimeout:
    case SharingSendMessageResult::kCommitTimeout:
      return l10n_util::GetStringFUTF16(
          IDS_BROWSER_SHARING_ERROR_DIALOG_TITLE_GENERIC_ERROR,
          GetContentType());

    case SharingSendMessageResult::kSuccessful:
    case SharingSendMessageResult::kCancelled:
      NOTREACHED();

    case SharingSendMessageResult::kPayloadTooLarge:
    case SharingSendMessageResult::kInternalError:
    case SharingSendMessageResult::kEncryptionError:
      return l10n_util::GetStringFUTF16(
          IDS_BROWSER_SHARING_ERROR_DIALOG_TITLE_INTERNAL_ERROR,
          GetContentType());
  }
}

std::u16string SharingUiController::GetErrorDialogText() const {
  switch (send_result()) {
    case SharingSendMessageResult::kDeviceNotFound:
      return l10n_util::GetStringFUTF16(
          IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND,
          GetTargetDeviceName());

    case SharingSendMessageResult::kCommitTimeout:
    case SharingSendMessageResult::kNetworkError:
      return l10n_util::GetStringUTF16(
          IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_NETWORK_ERROR);

    case SharingSendMessageResult::kAckTimeout:
      return l10n_util::GetStringFUTF16(
          IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_ACK_TIMEOUT,
          GetTargetDeviceName());

    case SharingSendMessageResult::kSuccessful:
    case SharingSendMessageResult::kCancelled:
      return std::u16string();

    case SharingSendMessageResult::kPayloadTooLarge:
    case SharingSendMessageResult::kInternalError:
    case SharingSendMessageResult::kEncryptionError:
      return l10n_util::GetStringUTF16(
          IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_INTERNAL_ERROR);
  }
}

void SharingUiController::OnDialogClosed(SharingDialog* dialog) {
  // Ignore already replaced dialogs.
  if (dialog != dialog_)
    return;

  dialog_ = nullptr;
}

void SharingUiController::OnDialogShown(bool has_devices, bool has_apps) {
  if (on_dialog_shown_closure_for_testing_)
    std::move(on_dialog_shown_closure_for_testing_).Run();
}

void SharingUiController::ClearLastDialog() {
  last_dialog_id_++;
  is_loading_ = false;
  send_result_ = SharingSendMessageResult::kSuccessful;
  CloseDialog();
}

void SharingUiController::UpdateAndShowDialog(
    const std::optional<url::Origin>& initiating_origin) {
  ClearLastDialog();
  DoUpdateApps(base::BindOnce(&SharingUiController::OnAppsReceived,
                              weak_ptr_factory_.GetWeakPtr(), last_dialog_id_,
                              initiating_origin));
}

std::vector<SharingTargetDeviceInfo> SharingUiController::GetDevices() const {
  return sharing_service_->GetDeviceCandidates(GetRequiredFeature());
}

bool SharingUiController::HasSendFailed() const {
  return send_result_ != SharingSendMessageResult::kSuccessful &&
         send_result_ != SharingSendMessageResult::kCancelled;
}

void SharingUiController::MaybeShowErrorDialog() {
  if (HasSendFailed() && web_contents_ == GetCurrentWebContents(web_contents_))
    ShowNewDialog(CreateDialogData(SharingDialogType::kErrorDialog));
}

SharingDialogData SharingUiController::CreateDialogData(
    SharingDialogType dialog_type) {
  SharingDialogData data;

  data.type = dialog_type;
  data.prefix = GetFeatureMetricsPrefix();
  data.title = GetTitle(data.type);
  data.error_text = GetErrorDialogText();

  auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
  data.device_callback =
      base::BindOnce(&SharingUiController::OnDeviceChosen, weak_ptr);
  data.app_callback =
      base::BindOnce(&SharingUiController::OnAppChosen, weak_ptr);
  data.close_callback =
      base::BindOnce(&SharingUiController::OnDialogClosed, weak_ptr);
  return data;
}

bool SharingUiController::ShouldShowLoadingIcon() const {
  return true;
}

bool SharingUiController::HasAccessibleUi() const {
  return true;
}

base::OnceClosure SharingUiController::SendMessageToDevice(
    const SharingTargetDeviceInfo& device,
    std::optional<base::TimeDelta> response_timeout,
    components_sharing_message::SharingMessage sharing_message,
    std::optional<SharingMessageSender::ResponseCallback> custom_callback) {
  send_result_ = SharingSendMessageResult::kSuccessful;
  target_device_name_ = device.client_name();
  if (ShouldShowLoadingIcon()) {
    last_dialog_id_++;
    is_loading_ = true;
  }

  SharingMessageSender::ResponseCallback response_callback = base::BindOnce(
      &SharingUiController::OnResponse, weak_ptr_factory_.GetWeakPtr(),
      last_dialog_id_, std::move(custom_callback));
  return sharing_service_->SendMessageToDevice(
      device, response_timeout.value_or(kSharingMessageTTL),
      std::move(sharing_message), std::move(response_callback));
}

void SharingUiController::CloseDialog() {
  if (!dialog_)
    return;

  dialog_->Hide();

  // SharingDialog::Hide may close the dialog asynchronously, and therefore not
  // call OnDialogClosed immediately. If that is the case, call OnDialogClosed
  // now to notify subclasses and clear |dialog_|.
  if (dialog_)
    OnDialogClosed(dialog_);

  DCHECK(!dialog_);
}

void SharingUiController::ShowNewDialog(SharingDialogData dialog_data) {
  CloseDialog();
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(web_contents_);
  if (!browser) {
    return;
  }
  bool has_devices = !dialog_data.devices.empty();
  bool has_apps = !dialog_data.apps.empty();
  dialog_ = SharingWindowController::From(browser)->ShowSharingDialog(
      web_contents(), std::move(dialog_data));
  OnDialogShown(has_devices, has_apps);
}

std::u16string SharingUiController::GetTargetDeviceName() const {
  return base::UTF8ToUTF16(target_device_name_);
}

void SharingUiController::OnResponse(
    int dialog_id,
    std::optional<SharingMessageSender::ResponseCallback> custom_callback,
    SharingSendMessageResult result,
    std::unique_ptr<components_sharing_message::ResponseMessage> response) {
  if (custom_callback)
    std::move(custom_callback.value()).Run(result, std::move(response));
  if (dialog_id != last_dialog_id_)
    return;

  send_result_ = result;
  if (ShouldShowLoadingIcon()) {
    is_loading_ = false;
  }
}

void SharingUiController::OnAppsReceived(
    int dialog_id,
    const std::optional<url::Origin>& initiating_origin,
    std::vector<SharingApp> apps) {
  if (dialog_id != last_dialog_id_)
    return;

  auto devices = GetDevices();

  SharingDialogData dialog_data =
      CreateDialogData(GetSharingDialogType(!devices.empty(), !apps.empty()));
  dialog_data.devices = std::move(devices);
  dialog_data.apps = std::move(apps);
  dialog_data.initiating_origin = initiating_origin;

  ShowNewDialog(std::move(dialog_data));
}
