// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/javascript_dialogs/app_modal_dialog_controller.h"

#include <utility>

#include "base/check_op.h"
#include "build/build_config.h"
#include "components/javascript_dialogs/app_modal_dialog_manager.h"
#include "components/javascript_dialogs/app_modal_dialog_queue.h"
#include "components/javascript_dialogs/app_modal_dialog_view.h"
#include "ui/gfx/text_elider.h"

namespace javascript_dialogs {
namespace {

AppModalDialogObserver* app_modal_dialog_observer = nullptr;

// Control maximum sizes of various texts passed to us from javascript.
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
// Two-dimensional eliding.  Reformat the text of the message dialog
// inserting line breaks because otherwise a single long line can overflow
// the message dialog (and crash/hang the GTK, depending on the version).
const int kMessageTextMaxRows = 32;
const int kMessageTextMaxCols = 132;
const int kDefaultPromptMaxRows = 24;
const int kDefaultPromptMaxCols = 132;

std::u16string EnforceMaxTextSize(const std::u16string& in_string) {
  std::u16string out_string;
  gfx::ElideRectangleString(in_string, kMessageTextMaxRows, kMessageTextMaxCols,
                            false, &out_string);
  return out_string;
}

std::u16string EnforceMaxPromptSize(const std::u16string& in_string) {
  std::u16string out_string;
  gfx::ElideRectangleString(in_string, kDefaultPromptMaxRows,
                            kDefaultPromptMaxCols, false, &out_string);
  return out_string;
}

#else
// One-dimensional eliding.  Trust the window system to break the string
// appropriately, but limit its overall length to something reasonable.
const size_t kMessageTextMaxSize = 2000;
const size_t kDefaultPromptMaxSize = 2000;

std::u16string EnforceMaxTextSize(const std::u16string& in_string) {
  std::u16string out_string;
  gfx::ElideString(in_string, kMessageTextMaxSize, &out_string);
  return out_string;
}

std::u16string EnforceMaxPromptSize(const std::u16string& in_string) {
  std::u16string out_string;
  gfx::ElideString(in_string, kDefaultPromptMaxSize, &out_string);
  return out_string;
}
#endif

}  // namespace

ChromeJavaScriptDialogExtraData::ChromeJavaScriptDialogExtraData() = default;

AppModalDialogController::AppModalDialogController(
    content::WebContents* web_contents,
    ExtraDataMap* extra_data_map,
    const std::u16string& title,
    content::JavaScriptDialogType javascript_dialog_type,
    const std::u16string& message_text,
    const std::u16string& default_prompt_text,
    bool display_suppress_checkbox,
    bool is_before_unload_dialog,
    bool is_reload,
    content::JavaScriptDialogManager::DialogClosedCallback callback)
    : content::WebContentsObserver(web_contents),
      title_(title),
      extra_data_map_(extra_data_map),
      javascript_dialog_type_(javascript_dialog_type),
      message_text_(EnforceMaxTextSize(message_text)),
      default_prompt_text_(EnforceMaxPromptSize(default_prompt_text)),
      display_suppress_checkbox_(display_suppress_checkbox),
      is_before_unload_dialog_(is_before_unload_dialog),
      is_reload_(is_reload),
      callback_(std::move(callback)) {}

AppModalDialogController::~AppModalDialogController() {
  CompleteDialog();
}

void AppModalDialogController::ShowModalDialog(
    std::unique_ptr<AppModalDialogController> controller) {
  CHECK_EQ(this, controller.get());
  view_ = AppModalDialogManager::GetInstance()->view_factory()->Run(
      std::move(controller));
  view_->ShowAppModalDialog();
  if (app_modal_dialog_observer) {
    app_modal_dialog_observer->Notify(this);
  }
}

void AppModalDialogController::ActivateModalDialog() {
  DCHECK(view_);
  view_->ActivateAppModalDialog();
}

void AppModalDialogController::CloseModalDialog() {
  DCHECK(view_);
  view_->CloseAppModalDialog();
}

void AppModalDialogController::CompleteDialog() {
  // If |view_| is non-null, then |this| is the active dialog and the next one
  // should be shown. Otherwise, |this| was never shown.
  if (view_) {
    view_ = nullptr;
    AppModalDialogQueue::GetInstance()->ShowNextDialog();
  } else {
    DCHECK(!valid_);
  }
}

bool AppModalDialogController::IsValid() {
  return valid_;
}

void AppModalDialogController::Invalidate() {
  if (!valid_) {
    return;
  }

  valid_ = false;
  CallDialogClosedCallback(false, std::u16string());
  if (view_) {
    CloseModalDialog();
  }
}

void AppModalDialogController::OnCancel(bool suppress_js_messages) {
  // We need to do this before WM_DESTROY (WindowClosing()) as any parent frame
  // will receive its activation messages before this dialog receives
  // WM_DESTROY. The parent frame would then try to activate any modal dialogs
  // that were still open in the ModalDialogQueue, which would send activation
  // back to this one. The framework should be improved to handle this, so this
  // is a temporary workaround.
  CompleteDialog();

  NotifyDelegate(/*success=*/false, std::u16string(), suppress_js_messages);
}

void AppModalDialogController::OnAccept(const std::u16string& prompt_text,
                                        bool suppress_js_messages) {
  CompleteDialog();

  NotifyDelegate(/*success=*/true, override_prompt_text_.value_or(prompt_text),
                 suppress_js_messages);
}

void AppModalDialogController::OnClose() {
  NotifyDelegate(false, std::u16string(), false);
}

void AppModalDialogController::SetOverridePromptText(
    const std::u16string& override_prompt_text) {
  override_prompt_text_ = override_prompt_text;
}

void AppModalDialogController::WebContentsDestroyed() {
  Invalidate();
}

void AppModalDialogController::NotifyDelegate(bool success,
                                              const std::u16string& user_input,
                                              bool suppress_js_messages) {
  if (!valid_) {
    return;
  }

  CallDialogClosedCallback(success, user_input);

  // The close callback above may delete web_contents_, thus removing the extra
  // data from the map owned by ::AppModalDialogManager. Make sure
  // to only use the data if still present. https://crbug.com/41009870
  if (auto* contents = web_contents()) {
    auto extra_data = extra_data_map_->find(contents);
    if (extra_data != extra_data_map_->end()) {
      extra_data->second.has_already_shown_a_dialog_ = true;
      extra_data->second.suppress_javascript_messages_ = suppress_js_messages;
    }
  }

  // On Views, we can end up coming through this code path twice :(.
  // See https://crbug.com/40085084.
  valid_ = false;
}

void AppModalDialogController::CallDialogClosedCallback(
    bool success,
    const std::u16string& user_input) {
  if (!callback_.is_null()) {
    std::move(callback_).Run(success, user_input);
  }
}

AppModalDialogObserver::AppModalDialogObserver() {
  DCHECK(!app_modal_dialog_observer);
  app_modal_dialog_observer = this;
}

AppModalDialogObserver::~AppModalDialogObserver() {
  DCHECK(app_modal_dialog_observer);
  app_modal_dialog_observer = nullptr;
}

}  // namespace javascript_dialogs
