// 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 <string>

#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/blocked_content/popunder_preventer.h"
#include "chrome/browser/ui/dialogs/browser_dialogs.h"
#include "chrome/browser/ui/login/login_handler.h"
#include "chrome/browser/ui/views/login_view.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/password_manager/core/browser/password_manager.h"
#include "components/strings/grit/components_strings.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_delegate.h"

class LoginHandlerViewsDialog;

namespace {

// This class prompts the user for credentials and returns the result to
// `auth_required_callback`.
class LoginHandlerViews : public LoginHandler {
 public:
  LoginHandlerViews(
      const net::AuthChallengeInfo& auth_info,
      content::WebContents* web_contents,
      content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback);
  LoginHandlerViews(const LoginHandlerViews&) = delete;
  LoginHandlerViews& operator=(const LoginHandlerViews&) = delete;
  ~LoginHandlerViews() override;

  void OnDialogDestroyed();

 protected:
  // LoginHandler:
  bool BuildViewImpl(const std::u16string& authority,
                     const std::u16string& explanation,
                     LoginModelData* login_model_data) override;
  void CloseDialog() override;

 private:
  raw_ptr<LoginHandlerViewsDialog> dialog_ = nullptr;
  std::unique_ptr<PopunderPreventer> popunder_preventer_;
};

}  // namespace

// The DialogDelegate is a separate object from LoginHandlerViews so it can be
// owned by the views hierarchy (see DeleteDelegate).
class LoginHandlerViewsDialog : public views::DialogDelegate {
 public:
  // Creates a dialog which reports the results back to |handler|. Note the
  // dialog is responsible for its own lifetime, which may be independent of
  // |handler|. |handler| may decide to close the dialog, by calling
  // CloseDialog, or the dialog may have been destroyed by the views
  // hierarchy, in which case it will call handler->OnDialogDestroyed. When
  // one of these methods is called, whichever comes first, each object must
  // release pointers to the other.
  LoginHandlerViewsDialog(LoginHandlerViews* handler,
                          content::WebContents* web_contents,
                          const std::u16string& authority,
                          const std::u16string& explanation,
                          LoginHandler::LoginModelData* login_model_data);
  LoginHandlerViewsDialog(const LoginHandlerViewsDialog&) = delete;
  LoginHandlerViewsDialog& operator=(const LoginHandlerViewsDialog&) = delete;

  void CloseDialog();

  // views::DialogDelegate:
  bool ShouldShowCloseButton() const override;
  std::u16string GetWindowTitle() const override;
  void WindowClosing() override;
  views::View* GetInitiallyFocusedView() override;
  views::View* GetContentsView() override;
  views::Widget* GetWidget() override;
  const views::Widget* GetWidget() const override;

 private:
  ~LoginHandlerViewsDialog() override;

  raw_ptr<LoginHandlerViews> handler_;
  // The LoginView that contains the user's login information.
  raw_ptr<LoginView> login_view_ = nullptr;
  raw_ptr<views::Widget> widget_ = nullptr;
};

namespace {

LoginHandlerViews::LoginHandlerViews(
    const net::AuthChallengeInfo& auth_info,
    content::WebContents* web_contents,
    content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback)
    : LoginHandler(auth_info, web_contents, std::move(auth_required_callback)),
      popunder_preventer_(std::make_unique<PopunderPreventer>(web_contents)) {}

LoginHandlerViews::~LoginHandlerViews() {
  // LoginHandler cannot call CloseDialog because the subclass will already have
  // been destructed.
  CloseDialog();
}

void LoginHandlerViews::OnDialogDestroyed() {
  dialog_ = nullptr;
}

bool LoginHandlerViews::BuildViewImpl(const std::u16string& authority,
                                      const std::u16string& explanation,
                                      LoginModelData* login_model_data) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK(!dialog_);

  // A WebContentsModalDialogManager is necessary to show the Dialog. A manager
  // may not be available during the shutdown process of the WebContents, which
  // can trigger DidFinishNavigation events. See https://crbug.com/328462789.
  web_modal::WebContentsModalDialogManager* manager =
      web_modal::WebContentsModalDialogManager::FromWebContents(
          constrained_window::GetTopLevelWebContents(web_contents()));
  if (!manager || !manager->delegate()) {
    return false;
  }
  dialog_ = new LoginHandlerViewsDialog(this, web_contents(), authority,
                                        explanation, login_model_data);
  return true;
}

void LoginHandlerViews::CloseDialog() {
  // The hosting widget may have been freed.
  if (dialog_) {
    dialog_->CloseDialog();
    dialog_ = nullptr;
  }
  popunder_preventer_ = nullptr;
}

}  // namespace

LoginHandlerViewsDialog::LoginHandlerViewsDialog(
    LoginHandlerViews* handler,
    content::WebContents* web_contents,
    const std::u16string& authority,
    const std::u16string& explanation,
    LoginHandler::LoginModelData* login_model_data)
    : handler_(handler) {
  SetButtonLabel(ui::mojom::DialogButton::kOk,
                 l10n_util::GetStringUTF16(IDS_LOGIN_DIALOG_OK_BUTTON_LABEL));
  SetAcceptCallback(base::BindOnce(
      [](LoginHandlerViewsDialog* dialog) {
        if (!dialog->handler_) {
          return;
        }
        dialog->handler_->SetAuth(dialog->login_view_->GetUsername(),
                                  dialog->login_view_->GetPassword());
      },
      base::Unretained(this)));
  SetCancelCallback(base::BindOnce(
      [](LoginHandlerViewsDialog* dialog) {
        if (!dialog->handler_) {
          return;
        }
        dialog->handler_->CancelAuth(/*notify_others=*/true);
      },
      base::Unretained(this)));
  SetModalType(ui::mojom::ModalType::kChild);
  SetOwnedByWidget(OwnedByWidgetPassKey());

  // Create a new LoginView and set the model for it.  The model (password
  // manager) is owned by the WebContents, but the view is parented to the
  // browser window, so the view may be destroyed after the password manager.
  // The view listens for model destruction and unobserves accordingly.
  login_view_ = new LoginView(authority, explanation, login_model_data);
  widget_ = constrained_window::ShowWebModalDialogViews(this, web_contents);
}

void LoginHandlerViewsDialog::CloseDialog() {
  handler_ = nullptr;
  // The hosting widget may have been freed.
  if (widget_) {
    widget_->Close();
  }
}

bool LoginHandlerViewsDialog::ShouldShowCloseButton() const {
  return false;
}

std::u16string LoginHandlerViewsDialog::GetWindowTitle() const {
  return l10n_util::GetStringUTF16(IDS_LOGIN_DIALOG_TITLE);
}

void LoginHandlerViewsDialog::WindowClosing() {
  // Reference is no longer valid.
  widget_ = nullptr;
  if (handler_) {
    handler_->CancelAuth(/*notify_others=*/true);
  }
}

views::View* LoginHandlerViewsDialog::GetInitiallyFocusedView() {
  return login_view_->GetInitiallyFocusedView();
}

views::View* LoginHandlerViewsDialog::GetContentsView() {
  return login_view_;
}

views::Widget* LoginHandlerViewsDialog::GetWidget() {
  return login_view_ ? login_view_->GetWidget() : nullptr;
}

const views::Widget* LoginHandlerViewsDialog::GetWidget() const {
  return login_view_ ? login_view_->GetWidget() : nullptr;
}

LoginHandlerViewsDialog::~LoginHandlerViewsDialog() {
  if (handler_) {
    handler_->OnDialogDestroyed();
  }
}

// static
std::unique_ptr<LoginHandler> LoginHandler::Create(
    const net::AuthChallengeInfo& auth_info,
    content::WebContents* web_contents,
    content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback) {
  return std::make_unique<LoginHandlerViews>(auth_info, web_contents,
                                             std::move(auth_required_callback));
}
