// Copyright 2014 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/ui/passwords/manage_passwords_ui_controller.h"

#include <algorithm>
#include <optional>
#include <string>
#include <utility>

#include "base/auto_reset.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/notimplemented.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/actor/actor_keyed_service.h"
#include "chrome/browser/feature_engagement/tracker_factory.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/password_manager/chrome_password_change_service.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "chrome/browser/password_manager/factories/account_password_store_factory.h"
#include "chrome/browser/password_manager/factories/profile_password_store_factory.h"
#include "chrome/browser/password_manager/password_change_service_factory.h"
#include "chrome/browser/signin/signin_promo_util.h"
#include "chrome/browser/sync/sync_ui_util.h"
#include "chrome/browser/ui/actions/chrome_action_id.h"
#include "chrome/browser/ui/autofill/bubble_manager.h"
#include "chrome/browser/ui/browser_actions.h"
#include "chrome/browser/ui/browser_command_controller.h"
#include "chrome/browser/ui/browser_commands.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/chrome_pages.h"
#include "chrome/browser/ui/desktop_to_mobile_promos/ios_promo_trigger_service.h"
#include "chrome/browser/ui/desktop_to_mobile_promos/ios_promo_trigger_service_factory.h"
#include "chrome/browser/ui/desktop_to_mobile_promos/ios_promos_utils.h"
#include "chrome/browser/ui/hats/trust_safety_sentiment_service_factory.h"
#include "chrome/browser/ui/location_bar/location_bar.h"
#include "chrome/browser/ui/navigator/browser_navigator_params.h"
#include "chrome/browser/ui/page_action/page_action_icon_type.h"
#include "chrome/browser/ui/passwords/credential_leak_dialog_controller_impl.h"
#include "chrome/browser/ui/passwords/credential_manager_dialog_controller_impl.h"
#include "chrome/browser/ui/passwords/manage_passwords_auto_signin_toast_delegate.h"
#include "chrome/browser/ui/passwords/password_dialog_prompts.h"
#include "chrome/browser/ui/passwords/passwords_leak_dialog_delegate.h"
#include "chrome/browser/ui/passwords/ui_utils.h"
#include "chrome/browser/ui/simple_message_box.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/browser/ui/tab_dialogs.h"
#include "chrome/browser/ui/tabs/public/tab_features.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/user_education/browser_user_education_interface.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/passwords/manage_passwords_page_action_controller.h"
#include "chrome/browser/ui/views/passwords/password_bubble_view_base.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/autofill/core/browser/logging/log_manager.h"
#include "components/autofill/core/common/save_password_progress_logger.h"
#include "components/browsing_data/content/browsing_data_helper.h"
#include "components/desktop_to_mobile_promos/features.h"
#include "components/desktop_to_mobile_promos/promos_types.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/password_manager/core/browser/browser_save_password_progress_logger.h"
#include "components/password_manager/core/browser/features/password_features.h"
#include "components/password_manager/core/browser/form_saver_impl.h"
#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
#include "components/password_manager/core/browser/move_password_to_account_store_helper.h"
#include "components/password_manager/core/browser/password_bubble_experiment.h"
#include "components/password_manager/core/browser/password_form_manager_for_ui.h"
#include "components/password_manager/core/browser/password_manager_constants.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_manager_util.h"
#include "components/password_manager/core/browser/password_store/interactions_stats.h"
#include "components/password_manager/core/browser/password_store/password_form_converters.h"
#include "components/password_manager/core/browser/password_ui_utils.h"
#include "components/password_manager/core/browser/ui/password_check_referrer.h"
#include "components/password_manager/core/browser/undo_password_change_controller.h"
#include "components/password_manager/core/common/credential_manager_types.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/password_manager/core/common/password_manager_ui.h"
#include "components/signin/public/base/signin_metrics.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/tabs/public/tab_interface.h"
#include "components/user_education/common/feature_promo/feature_promo_controller.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "ui/actions/actions.h"
#include "ui/base/l10n/l10n_util.h"

#if BUILDFLAG(IS_WIN)
#include "chrome/browser/password_manager/password_manager_util_win.h"
#elif BUILDFLAG(IS_MAC)
#include "base/time/time.h"
#include "chrome/browser/password_manager/password_manager_util_mac.h"
#endif

using password_manager::MovePasswordToAccountStoreHelper;
using password_manager::PasswordFormManagerForUI;

int ManagePasswordsUIController::save_fallback_timeout_in_seconds_ = 90;

namespace {

using Logger = autofill::SavePasswordProgressLogger;

#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
// Should be kept in sync with constant declared in
// bubble_controllers/relaunch_chrome_bubble_controller.cc.
constexpr int kMaxNumberOfTimesKeychainErrorBubbleIsShown = 3;
#endif

password_manager::PasswordStoreInterface* GetProfilePasswordStore(
    content::WebContents* web_contents) {
  return ProfilePasswordStoreFactory::GetForProfile(
             Profile::FromBrowserContext(web_contents->GetBrowserContext()),
             ServiceAccessType::EXPLICIT_ACCESS)
      .get();
}

password_manager::PasswordStoreInterface* GetAccountPasswordStore(
    content::WebContents* web_contents) {
  return AccountPasswordStoreFactory::GetForProfile(
             Profile::FromBrowserContext(web_contents->GetBrowserContext()),
             ServiceAccessType::EXPLICIT_ACCESS)
      .get();
}

ChromePasswordChangeService* GetPasswordChangeService(
    content::WebContents* web_contents) {
  return PasswordChangeServiceFactory::GetForProfile(
      Profile::FromBrowserContext(web_contents->GetBrowserContext()));
}

std::vector<std::unique_ptr<password_manager::PasswordForm>> CopyFormVector(
    const std::vector<std::unique_ptr<password_manager::PasswordForm>>& forms) {
  std::vector<std::unique_ptr<password_manager::PasswordForm>> result(
      forms.size());
  for (size_t i = 0; i < forms.size(); ++i) {
    result[i] = std::make_unique<password_manager::PasswordForm>(*forms[i]);
  }
  return result;
}

const password_manager::InteractionsStats* FindStatsByUsername(
    base::span<const password_manager::InteractionsStats> stats,
    const std::u16string& username) {
  auto it = std::ranges::find(
      stats, username, &password_manager::InteractionsStats::username_value);
  return it == stats.end() ? nullptr : &*it;
}

void MaybeShowPasswordManagerShortcutIPH(BrowserWindowInterface* browser) {
  // Don't show IPH if shortcut can't be created.
  if (!web_app::AreWebAppsEnabled(browser->GetProfile())) {
    return;
  }
  BrowserUserEducationInterface::From(browser)->MaybeShowFeaturePromo(
      feature_engagement::kIPHPasswordManagerShortcutFeature);
}

std::optional<password_manager::BrowserSavePasswordProgressLogger>
GetSaveProgressLogger(password_manager::PasswordManagerClient* client) {
  if (!client) {
    return std::nullopt;
  }

  autofill::LogManager* log_manager = client->GetCurrentLogManager();
  if (!log_manager || !log_manager->IsLoggingActive()) {
    return std::nullopt;
  }

  return std::make_optional<
      password_manager::BrowserSavePasswordProgressLogger>(log_manager);
}

}  // namespace

ManagePasswordsUIController::ManagePasswordsUIController(
    content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents),
      content::WebContentsUserData<ManagePasswordsUIController>(*web_contents) {
  passwords_data_.set_client(
      ChromePasswordManagerClient::FromWebContents(web_contents));
  password_manager::PasswordStoreInterface* profile_password_store =
      GetProfilePasswordStore(web_contents);
  if (profile_password_store) {
    profile_password_store->AddObserver(this);
  }
  password_manager::PasswordStoreInterface* account_password_store =
      GetAccountPasswordStore(web_contents);
  if (account_password_store) {
    account_password_store->AddObserver(this);
  }
}

ManagePasswordsUIController::~ManagePasswordsUIController() = default;

void ManagePasswordsUIController::OnPasswordSubmitted(
    std::unique_ptr<PasswordFormManagerForUI> form_manager) {
  save_password_after_trusted_vault_error_resolution_ = false;
  bool password_change_ongoing = IsPasswordChangeOngoing();

  if (!password_change_ongoing) {
    // Password change bubble shouldn't be destroyed if the flow is still
    // running.
    DestroyPopups();
  }

  save_fallback_timer_.Stop();

  passwords_data_.OnPendingPassword(std::move(form_manager));

  // All necessary events should be triggered in `passwords_data_`, so that the
  // state would be correct after password change is finished, but no other
  // bubbles should be displayed.
  if (password_change_ongoing) {
    return;
  }

  const auto saving_prompt_status = GetSavingPromptStatus();
  if (saving_prompt_status == SavingPromptStatus::kCanShow) {
    bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  }
  UpdateBubbleAndIconVisibility();
  const GURL url = web_contents()->GetLastCommittedURL();
  if (saving_prompt_status == SavingPromptStatus::kExplicitlyBlocklisted &&
      ChromePasswordManagerClient::CanShowBubbleOnURL(url)) {
    if (auto* const user_education =
            BrowserUserEducationInterface::MaybeGetForWebContentsInTab(
                web_contents())) {
      user_education->MaybeShowFeaturePromo(
          feature_engagement::kIPHPasswordsSaveRecoveryPromoFeature);
    }
  }
}

void ManagePasswordsUIController::OnUpdatePasswordSubmitted(
    std::unique_ptr<PasswordFormManagerForUI> form_manager) {
  save_password_after_trusted_vault_error_resolution_ = false;
  DestroyPopups();
  save_fallback_timer_.Stop();
  passwords_data_.OnUpdatePassword(std::move(form_manager));

  // Trigger `passwords_data_` updates so the state is correct after password
  // change, but do not display any bubbles.
  if (IsPasswordChangeOngoing()) {
    return;
  }

  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnShowManualFallbackForSaving(
    std::unique_ptr<PasswordFormManagerForUI> form_manager,
    bool has_generated_password,
    bool is_update) {
  DestroyPopups();
  if (has_generated_password) {
    passwords_data_.OnAutomaticPasswordSave(std::move(form_manager));
  } else if (is_update) {
    passwords_data_.OnUpdatePassword(std::move(form_manager));
  } else {
    passwords_data_.OnPendingPassword(std::move(form_manager));
  }
  UpdateBubbleAndIconVisibility();
  save_fallback_timer_.Start(
      FROM_HERE, GetTimeoutForSaveFallback(), this,
      &ManagePasswordsUIController::OnHideManualFallbackForSaving);
}

void ManagePasswordsUIController::OnHideManualFallbackForSaving() {
  if (passwords_data_.state() != password_manager::ui::PENDING_PASSWORD_STATE &&
      passwords_data_.state() !=
          password_manager::ui::PENDING_PASSWORD_UPDATE_STATE &&
      passwords_data_.state() !=
          password_manager::ui::SAVE_CONFIRMATION_STATE) {
    return;
  }
  // Don't hide the fallback if the bubble is open or in the bubble manager
  // queue.
  if (IsShowingBubble() || BubbleManagerHasPasswordBubbleInQueue()) {
    return;
  }

  save_fallback_timer_.Stop();

  ClearPopUpFlagForBubble();
  if (passwords_data_.GetCurrentForms().empty()) {
    passwords_data_.OnInactive();
  } else {
    passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
  }

  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnOpenPasswordDetailsBubble(
    const password_manager::PasswordForm& form) {
  std::u16string message;
#if BUILDFLAG(IS_MAC)
  message = l10n_util::GetStringUTF16(
      IDS_PASSWORDS_PAGE_AUTHENTICATION_PROMPT_BIOMETRIC_SUFFIX);
#elif BUILDFLAG(IS_WIN)
  message = l10n_util::GetStringUTF16(IDS_PASSWORDS_PAGE_AUTHENTICATION_PROMPT);
#endif

  AuthenticateUserWithMessage(
      message, base::BindOnce(
                   [](ManagePasswordsUIController* self,
                      password_manager::PasswordForm form, bool auth_success) {
                     if (auth_success) {
                       self->passwords_data_.OpenPasswordDetailsBubble(form);
                       self->bubble_status_ = BubbleStatus::SHOULD_POP_UP;
                       self->UpdateBubbleAndIconVisibility();
                     }
                   },
                   this, form));
}

bool ManagePasswordsUIController::OnChooseCredentials(
    std::vector<std::unique_ptr<password_manager::PasswordForm>>
        local_credentials,
    const url::Origin& origin,
    ManagePasswordsState::CredentialsCallback callback) {
  DCHECK(!local_credentials.empty());
  if (!HasBrowserWindow()) {
    return false;
  }
  // Delete any existing window from the screen.
  dialog_controller_.reset();
  // If |local_credentials| contains PSL matches they shouldn't be propagated to
  // the state (unless they are also web affiliations) because PSL matches
  // aren't saved for current page. This logic is implemented here because
  // Android uses ManagePasswordsState as a data source for account chooser.
  CredentialManagerDialogController::FormsVector locals;
  if (password_manager_util::GetMatchType(*local_credentials[0]) !=
      password_manager_util::GetLoginMatchType::kPSL) {
    locals = CopyFormVector(local_credentials);
  }
  passwords_data_.OnRequestCredentials(std::move(locals), origin);
  passwords_data_.set_credentials_callback(std::move(callback));
  auto* raw_controller = new CredentialManagerDialogControllerImpl(
      Profile::FromBrowserContext(web_contents()->GetBrowserContext()), this);
  dialog_controller_.reset(raw_controller);
  raw_controller->ShowAccountChooser(CreateAccountChooser(raw_controller),
                                     std::move(local_credentials));
  UpdateBubbleAndIconVisibility();
  return true;
}

void ManagePasswordsUIController::OnAutoSignin(
    std::vector<std::unique_ptr<password_manager::PasswordForm>> local_forms,
    const url::Origin& origin) {
  DCHECK(!local_forms.empty());
  DestroyPopups();
  passwords_data_.OnAutoSignin(std::move(local_forms), origin);
  if (base::FeatureList::IsEnabled(
          password_manager::features::kCredentialManagementUnifiedUi)) {
    ShowAutoSignInToast();
    bubble_status_ = BubbleStatus::NOT_SHOWN;
  } else {
    bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  }
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnAutoSignInToastClosed() {
  if (GetState() == password_manager::ui::AUTO_SIGNIN_STATE) {
    passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
    UpdateBubbleAndIconVisibility();
  }
}

void ManagePasswordsUIController::ShowAutoSignInToast() {
  const password_manager::PasswordForm& form = GetPendingPassword();
  if (form.username_value.empty()) {
    return;
  }
  if (!auto_signin_toast_delegate_) {
    auto_signin_toast_delegate_ =
        std::make_unique<ManagePasswordsAutoSigninToastDelegate>(
            web_contents());
  }
  auto_signin_toast_delegate_->SetOnToastClosedCallback(
      base::BindOnce(&ManagePasswordsUIController::OnAutoSignInToastClosed,
                     weak_ptr_factory_.GetWeakPtr()));
  auto_signin_toast_delegate_->OnAutoSignInToast(form.username_value);
}

void ManagePasswordsUIController::OnPromptEnableAutoSignin() {
  // Both the account chooser and the previous prompt shouldn't be closed.
  if (dialog_controller_) {
    return;
  }
  auto* raw_controller = new CredentialManagerDialogControllerImpl(
      Profile::FromBrowserContext(web_contents()->GetBrowserContext()), this);
  dialog_controller_.reset(raw_controller);
  raw_controller->ShowAutosigninPrompt(CreateAutoSigninPrompt(raw_controller));
}

void ManagePasswordsUIController::OnAutomaticPasswordSave(
    std::unique_ptr<PasswordFormManagerForUI> form_manager,
    bool is_update_confirmation) {
  save_password_after_trusted_vault_error_resolution_ = false;
  DestroyPopups();
  save_fallback_timer_.Stop();
  auto ui_state =
      is_update_confirmation ? password_manager::ui::UPDATE_CONFIRMATION_STATE
      : form_manager->GetPendingCredentials().username_value.empty()
          ? password_manager::ui::GENERATED_PASSWORD_CONFIRMATION_STATE
          : password_manager::ui::SAVE_CONFIRMATION_STATE;
  passwords_data_.OnSubmittedGeneratedPassword(ui_state,
                                               std::move(form_manager));

  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnPasswordAutofilled(
    base::span<const password_manager::StoredCredential> password_credentials,
    const url::Origin& origin,
    base::span<const password_manager::StoredCredential> federated_matches) {
  // To change to managed state only when the managed state is more important
  // for the user that the current state.
  if (passwords_data_.state() != password_manager::ui::INACTIVE_STATE &&
      passwords_data_.state() != password_manager::ui::MANAGE_STATE) {
    return;
  }
  ClearPopUpFlagForBubble();
  passwords_data_.OnPasswordAutofilled(password_credentials, origin,
                                       federated_matches);
  // Don't close the existing bubble. Update the icon later.
  if (bubble_status_ == BubbleStatus::SHOWN) {
    bubble_status_ = BubbleStatus::SHOWN_PENDING_ICON_UPDATE;
  }
  if (bubble_status_ != BubbleStatus::SHOWN_PENDING_ICON_UPDATE) {
    UpdateBubbleAndIconVisibility();
  }

  if (GetState() != password_manager::ui::MANAGE_STATE) {
    return;
  }
  // There nothing to do if this controller is not attached to currently active
  // tab.
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          web_contents());
  if (!browser) {
    return;
  }
  if (browser->GetTabStripModel()->GetActiveWebContents() != web_contents()) {
    return;
  }

  const bool has_unnotified_shared_credentials = std::ranges::any_of(
      GetCurrentForms(),
      [](const std::unique_ptr<password_manager::PasswordForm>& form) {
        return form->type ==
                   password_manager::PasswordForm::Type::kReceivedViaSharing &&
               !form->sharing_notification_displayed;
      });
  if (has_unnotified_shared_credentials) {
    passwords_data_.TransitionToState(
        password_manager::ui::NOTIFY_RECEIVED_SHARED_CREDENTIALS);
    bubble_status_ = BubbleStatus::SHOULD_POP_UP;
    UpdateBubbleAndIconVisibility();
    return;
  }

  const bool has_non_empty_note = !std::ranges::all_of(
      GetCurrentForms(), &std::u16string::empty,
      &password_manager::PasswordForm::GetNoteWithEmptyUniqueDisplayName);
  // Only one of these promos will be able to show. Try the more specific one
  // first.
  if (has_non_empty_note) {
    BrowserUserEducationInterface::From(browser)->MaybeShowFeaturePromo(
        feature_engagement::kIPHPasswordsManagementBubbleDuringSigninFeature);
  }
  MaybeShowPasswordManagerShortcutIPH(browser);
}

void ManagePasswordsUIController::OnCredentialLeak(
    password_manager::LeakedPasswordDetails details) {
  if (passwords_data_.client()->IsActorTaskActive()) {
    return;
  }
  // Existing dialog shouldn't be closed.
  if (dialog_controller_) {
    return;
  }

  // Hide the manage passwords bubble if currently shown.
  if (IsShowingBubble()) {
    HideBubble(/*initiated_by_bubble_manager=*/false);
  } else {
    ClearPopUpFlagForBubble();
  }

  if (password_manager::IsPasswordChangeSupported(details.leak_type) &&
      !password_manager::IsPasswordSavedAsBackup(details.leak_type)) {
    auto* password_change_service = GetPasswordChangeService(web_contents());
    CHECK(password_change_service);

    // Password change is already ongoing for this tab. Don't do anything.
    if (password_change_service->GetPasswordChangeDelegate(web_contents())) {
      return;
    }

    base::UmaHistogramBoolean(
        "PasswordManager.PasswordChange.UserHasPasswordSavedOnAPCLaunch",
        !GetCurrentForms().empty());

    password_change_service->OfferPasswordChangeUi(
        std::move(details.credentials), web_contents());
    UpdateBubbleAndIconVisibility();
    return;
  }

  auto metric_recorder = std::make_unique<
      password_manager::metrics_util::LeakDialogMetricsRecorder>(
      web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId(),
      password_manager::GetLeakDialogType(details.leak_type));

  auto* raw_controller = new CredentialLeakDialogControllerImpl(
      this, details.leak_type, std::move(metric_recorder));
  dialog_controller_.reset(raw_controller);
  raw_controller->ShowCredentialLeakPrompt(
      CreateCredentialLeakPrompt(raw_controller));
}

void ManagePasswordsUIController::OnShowMoveToAccountBubble(
    std::unique_ptr<password_manager::PasswordFormManagerForUI> form_to_move) {
  base::UmaHistogramEnumeration(
      "PasswordManager.AccountStorage.MoveToAccountStoreFlowOffered",
      password_manager::metrics_util::MoveToAccountStoreTrigger::
          kSuccessfulLoginWithProfileStorePassword);
  passwords_data_.OnPasswordMovable(std::move(form_to_move));
  // TODO(crbug.com/40138077): Add smartness like OnPasswordSubmitted?
  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnBiometricAuthenticationForFilling(
    PrefService* prefs) {
  // Existing dialog shouldn't be closed and if the state is inactive do not
  // transition or show bubble.
  if (dialog_controller_ ||
      GetState() == password_manager::ui::INACTIVE_STATE) {
    return;
  }
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
  const std::string promo_shown_counter =
      password_manager::prefs::kBiometricAuthBeforeFillingPromoShownCounter;
  // Checking GetIfBiometricAuthenticationPromoWasShown() prevents from
  // displaying multiple prompts on the same tab (eg. when there are multiple
  // password forms).
  if (was_biometric_authentication_for_filling_promo_shown_ ||
      prefs->GetBoolean(
          password_manager::prefs::kHasUserInteractedWithBiometricAuthPromo) ||
      prefs->GetInteger(promo_shown_counter) >=
          kMaxNumberOfTimesBiometricAuthForFillingPromoWillBeShown ||
      prefs->GetBoolean(
          password_manager::prefs::kBiometricAuthenticationBeforeFilling)) {
    return;
  }
  prefs->SetInteger(promo_shown_counter,
                    prefs->GetInteger(promo_shown_counter) + 1);

  passwords_data_.TransitionToState(
      password_manager::ui::BIOMETRIC_AUTHENTICATION_FOR_FILLING_STATE);
  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  was_biometric_authentication_for_filling_promo_shown_ = true;
  UpdateBubbleAndIconVisibility();
#else
  NOTIMPLEMENTED();
#endif
}

void ManagePasswordsUIController::ShowBiometricActivationConfirmation() {
  // Existing dialog shouldn't be closed.
  if (dialog_controller_) {
    return;
  }
  passwords_data_.TransitionToState(
      password_manager::ui::BIOMETRIC_AUTHENTICATION_CONFIRMATION_STATE);
  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::ShowMovePasswordBubble(
    const password_manager::PasswordForm& form) {
  CHECK(GetState() == password_manager::ui::MANAGE_STATE ||
        GetState() == password_manager::ui::SAVE_CONFIRMATION_STATE ||
        GetState() == password_manager::ui::UPDATE_CONFIRMATION_STATE);
  // Existing dialog shouldn't be closed.
  if (dialog_controller_) {
    return;
  }
  passwords_data_.set_selected_password(
      std::make_unique<password_manager::PasswordForm>(form));
  passwords_data_.TransitionToState(
      password_manager::ui::MOVE_CREDENTIAL_FROM_MANAGE_BUBBLE_STATE);
  bubble_status_ = BubbleStatus::SHOULD_POP_UP_WITH_FOCUS;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnBiometricAuthBeforeFillingDeclined() {
  CHECK(!dialog_controller_);
  passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnKeychainError() {
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
  CHECK(!dialog_controller_);
  PrefService* prefs =
      Profile::FromBrowserContext(web_contents()->GetBrowserContext())
          ->GetPrefs();
  if (base::FeatureList::IsEnabled(
          password_manager::features::kRestartToGainAccessToKeychain) &&
      prefs->GetInteger(
          password_manager::prefs::kRelaunchChromeBubbleDismissedCounter) <=
          kMaxNumberOfTimesKeychainErrorBubbleIsShown) {
    passwords_data_.OnKeychainError();
    bubble_status_ = BubbleStatus::SHOULD_POP_UP;
    UpdateBubbleAndIconVisibility();
  }
#endif
}

void ManagePasswordsUIController::OnPasskeySaved(bool gpm_pin_created,
                                                 std::string passkey_rp_id) {
  passwords_data_.OnPasskeySaved(gpm_pin_created, std::move(passkey_rp_id));
  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnPasskeyDeleted() {
  passwords_data_.OnPasskeyDeleted();
  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnPasskeyUpdated(std::string passkey_rp_id) {
  passwords_data_.OnPasskeyUpdated(std::move(passkey_rp_id));
  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnPasskeyNotAccepted(
    std::string passkey_rp_id) {
  passwords_data_.OnPasskeyNotAccepted(std::move(passkey_rp_id));
  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnPasskeyUpgrade(std::string passkey_rp_id) {
  passwords_data_.OnPasskeyUpgrade(std::move(passkey_rp_id));
  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnAddUsernameSaveClicked(
    const std::u16string& username,
    const password_manager::PasswordForm& form_to_update) {
  CHECK(!dialog_controller_);

  passwords_data_.form_manager()->OnUpdateUsernameFromPrompt(username);
  save_fallback_timer_.Stop();

  passwords_data_.form_manager()->Save();
  passwords_data_.OnSubmittedGeneratedPassword(
      password_manager::ui::SAVE_CONFIRMATION_STATE, nullptr, form_to_update);
  // After adding a new username, confirmation helium bubble should appear.
  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnLoginsChanged(
    password_manager::PasswordStoreInterface* /*store*/,
    const password_manager::PasswordStoreChangeList& changes) {
  password_manager::ui::State current_state = GetState();
  passwords_data_.ProcessLoginsChanged(changes);
  if (current_state != GetState()) {
    ClearPopUpFlagForBubble();
    UpdateBubbleAndIconVisibility();
  }
}

void ManagePasswordsUIController::OnLoginsRetained(
    password_manager::PasswordStoreInterface* /*store*/,
    const std::vector<
        password_manager::StoredCredential>& /*retained_passwords*/) {}

void ManagePasswordsUIController::OnPasswordChangeFinishedSuccessfully() {
  // If the password change finished successfully, don't show save/update
  // bubble.
  if (passwords_data_.state() ==
          password_manager::ui::PENDING_PASSWORD_UPDATE_STATE ||
      passwords_data_.state() == password_manager::ui::PENDING_PASSWORD_STATE) {
    passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
  }
}

base::WeakPtr<PasswordsModelDelegate>
ManagePasswordsUIController::GetModelDelegateProxy() {
  return weak_ptr_factory_.GetWeakPtr();
}

content::WebContents* ManagePasswordsUIController::GetWebContents() const {
  return web_contents();
}

url::Origin ManagePasswordsUIController::GetOrigin() const {
  return passwords_data_.origin();
}

password_manager::PasswordFormMetricsRecorder*
ManagePasswordsUIController::GetPasswordFormMetricsRecorder() {
  // The form manager may be null for example for auto sign-in toasts of the
  // credential manager API.
  password_manager::PasswordFormManagerForUI* form_manager =
      passwords_data_.form_manager();
  return form_manager ? form_manager->GetMetricsRecorder() : nullptr;
}

password_manager::PasswordFeatureManager*
ManagePasswordsUIController::GetPasswordFeatureManager() {
  password_manager::PasswordManagerClient* client = passwords_data_.client();
  return client->GetPasswordFeatureManager();
}

password_manager::ui::State ManagePasswordsUIController::GetState() const {
  PasswordChangeDelegate* delegate = GetPasswordChangeDelegate();
  if (delegate) {
    if (delegate->GetCurrentState() ==
        PasswordChangeDelegate::State::kPasswordSuccessfullyChanged) {
      // Prevent any UI being displayed instead of the password change bubble.
      return password_manager::ui::State::PASSWORD_CHANGE_STATE;
    }
    // Prevent any UI being displayed during ongoing password change flow.
    return password_manager::ui::State::INACTIVE_STATE;
  }
  return passwords_data_.state();
}

const password_manager::PasswordForm&
ManagePasswordsUIController::GetPendingPassword() const {
  if (GetState() == password_manager::ui::AUTO_SIGNIN_STATE) {
    return *GetCurrentForms()[0];
  }

  if (GetState() ==
      password_manager::ui::MOVE_CREDENTIAL_FROM_MANAGE_BUBBLE_STATE) {
    password_manager::PasswordForm* selected_password_form =
        passwords_data_.selected_password();
    return *selected_password_form;
  }

  CHECK(
      GetState() == password_manager::ui::PENDING_PASSWORD_STATE ||
      GetState() == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE ||
      GetState() == password_manager::ui::SAVE_CONFIRMATION_STATE ||
      GetState() == password_manager::ui::MOVE_CREDENTIAL_AFTER_LOG_IN_STATE ||
      GetState() == password_manager::ui::GENERATED_PASSWORD_CONFIRMATION_STATE)
      << GetState();
  password_manager::PasswordFormManagerForUI* form_manager =
      passwords_data_.form_manager();
  return form_manager->GetPendingCredentials();
}

password_manager::metrics_util::CredentialSourceType
ManagePasswordsUIController::GetCredentialSource() const {
  password_manager::PasswordFormManagerForUI* form_manager =
      passwords_data_.form_manager();
  return form_manager
             ? form_manager->GetCredentialSource()
             : password_manager::metrics_util::CredentialSourceType::kUnknown;
}

const std::vector<std::unique_ptr<password_manager::PasswordForm>>&
ManagePasswordsUIController::GetCurrentForms() const {
  return passwords_data_.GetCurrentForms();
}

const std::optional<password_manager::PasswordForm>&
ManagePasswordsUIController::
    GetManagePasswordsSingleCredentialDetailsModeCredential() const {
  return passwords_data_.single_credential_mode_credential();
}

const password_manager::InteractionsStats*
ManagePasswordsUIController::GetCurrentInteractionStats() const {
  CHECK_EQ(GetState(), password_manager::ui::PENDING_PASSWORD_STATE);
  password_manager::PasswordFormManagerForUI* form_manager =
      passwords_data_.form_manager();
  return FindStatsByUsername(
      form_manager->GetInteractionsStats(),
      form_manager->GetPendingCredentials().username_value);
}

size_t ManagePasswordsUIController::GetTotalNumberCompromisedPasswords() const {
  DCHECK(GetState() == password_manager::ui::PASSWORD_UPDATED_SAFE_STATE ||
         GetState() == password_manager::ui::PASSWORD_UPDATED_MORE_TO_FIX);
  return post_save_compromised_helper_->compromised_count();
}

bool ManagePasswordsUIController::BubbleIsManualFallbackForSaving() const {
  return save_fallback_timer_.IsRunning();
}

bool ManagePasswordsUIController::GpmPinCreatedDuringRecentPasskeyCreation()
    const {
  CHECK_EQ(GetState(), password_manager::ui::PASSKEY_SAVED_CONFIRMATION_STATE);
  return passwords_data_.gpm_pin_created_during_recent_passkey_creation();
}

const std::string& ManagePasswordsUIController::PasskeyRpId() const {
  return passwords_data_.passkey_rp_id();
}

const std::u16string& ManagePasswordsUIController::PasswordChangeUsername()
    const {
  return passwords_data_.password_change_username();
}

const std::u16string& ManagePasswordsUIController::PasswordChangeNewPassword()
    const {
  return passwords_data_.password_change_new_password();
}

void ManagePasswordsUIController::OnBubbleShown() {
  bubble_status_ = BubbleStatus::SHOWN;
}

void ManagePasswordsUIController::OnBubbleHidden() {
  passwords_data_.clear_selected_password();
  bool update_icon =
      (bubble_status_ == BubbleStatus::SHOWN_PENDING_ICON_UPDATE);
  bubble_status_ = BubbleStatus::NOT_SHOWN;
  if (GetState() == password_manager::ui::SAVE_CONFIRMATION_STATE ||
      GetState() == password_manager::ui::AUTO_SIGNIN_STATE ||
      GetState() ==
          password_manager::ui::BIOMETRIC_AUTHENTICATION_CONFIRMATION_STATE ||
      GetState() == password_manager::ui::PASSWORD_UPDATED_SAFE_STATE ||
      GetState() == password_manager::ui::PASSWORD_UPDATED_MORE_TO_FIX ||
      GetState() == password_manager::ui::NOTIFY_RECEIVED_SHARED_CREDENTIALS) {
    passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
    update_icon = true;
  } else if (GetState() ==
             password_manager::ui::MOVE_CREDENTIAL_FROM_MANAGE_BUBBLE_STATE) {
    passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
    passwords_data_.clear_selected_password();
    update_icon = true;
  } else if (GetState() == password_manager::ui::MANAGE_STATE &&
             passwords_data_.single_credential_mode_credential().has_value()) {
    if (passwords_data_.GetCurrentForms().empty()) {
      ClearPopUpFlagForBubble();
      passwords_data_.OnInactive();
    } else {
      passwords_data_.ClearSingleCredentialModeCredential();
    }
    update_icon = true;
  } else if (GetState() == password_manager::ui::PASSWORD_CHANGE_STATE) {
    ClearPopUpFlagForBubble();
    passwords_data_.OnInactive();
    update_icon = true;
  }
  if (update_icon) {
    UpdateBubbleAndIconVisibility();
  }

  if (auto* manager =
          autofill::BubbleManager::GetForWebContents(web_contents())) {
    manager->OnBubbleHiddenByController(*this, /*show_next_bubble=*/true);
  }
}

void ManagePasswordsUIController::OnNoInteraction() {
  if (GetState() != password_manager::ui::PENDING_PASSWORD_UPDATE_STATE &&
      GetState() != password_manager::ui::PENDING_PASSWORD_STATE) {
    // Do nothing if the state was changed. It can happen for example when the
    // bubble is active and a page navigation happens.
    return;
  }
  bool is_update =
      GetState() == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE;
  password_manager::PasswordFormManagerForUI* form_manager =
      passwords_data_.form_manager();
  DCHECK(form_manager);
  form_manager->OnNoInteraction(is_update);
}

void ManagePasswordsUIController::OnNopeUpdateClicked() {
  password_manager::PasswordFormManagerForUI* form_manager =
      passwords_data_.form_manager();
  DCHECK(form_manager);
  form_manager->OnNopeUpdateClicked();
}

void ManagePasswordsUIController::NeverSavePassword() {
  DCHECK_EQ(password_manager::ui::PENDING_PASSWORD_STATE, GetState());
  passwords_data_.form_manager()->OnNeverClicked();
  ClearPopUpFlagForBubble();
  if (passwords_data_.GetCurrentForms().empty()) {
    passwords_data_.OnInactive();
  } else {
    passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
  }
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnNotNowClicked() {
  DCHECK_EQ(password_manager::ui::PENDING_PASSWORD_STATE, GetState());
  // Treat this identically to dismissing via the X button.
  OnNoInteraction();
}

void ManagePasswordsUIController::OnPasswordsRevealed() {
  DCHECK(passwords_data_.form_manager());
  passwords_data_.form_manager()->OnPasswordsRevealed();
}

void ManagePasswordsUIController::HandlePasswordRecoveryFinished(
    const std::u16string& username,
    const std::u16string& password,
    const std::u16string& password_backup) const {
  auto pending_credentials = GetPendingPassword();
  if (pending_credentials.password_value != password ||
      pending_credentials.username_value != username) {
    return;
  }

  if (password_backup == password) {
    password_manager::metrics_util::LogPrimaryPasswordUpdatedWithBackup(
        web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId());
  }
}

void ManagePasswordsUIController::SavePassword(const std::u16string& username,
                                               const std::u16string& password) {
  if (const password_manager::StoredCredential*
          changed_password_form_with_backup =
              password_manager_util::FindChangedPasswordLoginWithBackup(
                  *passwords_data_.form_manager())) {
    HandlePasswordRecoveryFinished(
        username, password,
        changed_password_form_with_backup->GetPasswordBackup().value());
  }
  UpdatePasswordFormUsernameAndPassword(username, password,
                                        passwords_data_.form_manager());

  if (auto* sentiment_service =
          TrustSafetySentimentServiceFactory::GetForProfile(
              Profile::FromBrowserContext(
                  web_contents()->GetBrowserContext()))) {
    sentiment_service->SavedPassword();
    if (IsPendingPasswordPhished()) {
      sentiment_service->PhishedPasswordUpdateFinished();
    }
  }

  if (GetPasswordFormMetricsRecorder() && BubbleIsManualFallbackForSaving()) {
    GetPasswordFormMetricsRecorder()->RecordDetailedUserAction(
        password_manager::PasswordFormMetricsRecorder::DetailedUserAction::
            kTriggeredManualFallbackForSaving);
  }
  save_fallback_timer_.Stop();
  passwords_data_.form_manager()->Save();

  // If we just saved a password to the account store, notify the IPH tracker
  // about it (so it can decide not to show the IPH again).
  if (GetPasswordFeatureManager()->IsAccountStorageActive()) {
    feature_engagement::TrackerFactory::GetForBrowserContext(
        Profile::FromBrowserContext(web_contents()->GetBrowserContext()))
        ->NotifyEvent("passwords_account_storage_used");
  }

  post_save_compromised_helper_ =
      std::make_unique<password_manager::PostSaveCompromisedHelper>(
          passwords_data_.form_manager()->GetInsecureCredentials(), username);
  post_save_compromised_helper_->AnalyzeLeakedCredentials(
      passwords_data_.client()->GetProfilePasswordStore(),
      passwords_data_.client()->GetAccountPasswordStore(),
      Profile::FromBrowserContext(web_contents()->GetBrowserContext())
          ->GetPrefs(),
      base::BindOnce(
          &ManagePasswordsUIController::OnTriggerPostSaveCompromisedBubble,
          weak_ptr_factory_.GetWeakPtr()));

  passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
  // The icon is to be updated after the bubble (either "Save password" or "Sign
  // in to Chrome") is closed.
  bubble_status_ = BubbleStatus::SHOWN_PENDING_ICON_UPDATE;
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          web_contents());
  // Do not trigger the IPH if the sign in promo will be shown.
  if (browser &&
      !signin::ShouldShowPasswordSignInPromo(*browser->GetProfile())) {
    // Only one of these promos will be able to show. Try the more specific one
    // first.
    BrowserUserEducationInterface::From(browser)->MaybeShowFeaturePromo(
        feature_engagement::kIPHPasswordsManagementBubbleAfterSaveFeature);
    MaybeShowPasswordManagerShortcutIPH(browser);
  }
}

void ManagePasswordsUIController::MovePasswordToAccountStore() {
  CHECK(GetState() ==
            password_manager::ui::MOVE_CREDENTIAL_AFTER_LOG_IN_STATE ||
        GetState() ==
            password_manager::ui::MOVE_CREDENTIAL_FROM_MANAGE_BUBBLE_STATE);

  MovePendingPasswordToAccountStoreUsingHelper(
      GetPendingPassword(),
      GetState() == password_manager::ui::MOVE_CREDENTIAL_AFTER_LOG_IN_STATE
          ? password_manager::metrics_util::MoveToAccountStoreTrigger::
                kSuccessfulLoginWithProfileStorePassword
          : password_manager::metrics_util::MoveToAccountStoreTrigger::
                kExplicitlyTriggeredInPasswordsManagementBubble);

  ClearPopUpFlagForBubble();
  passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
  passwords_data_.clear_selected_password();
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::BlockMovingPasswordToAccountStore() {
  DCHECK_EQ(GetState(),
            password_manager::ui::MOVE_CREDENTIAL_AFTER_LOG_IN_STATE)
      << GetState();
  passwords_data_.form_manager()->BlockMovingCredentialsToAccountStore();
  ClearPopUpFlagForBubble();
  passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::ChooseCredential(
    const password_manager::PasswordForm& form,
    password_manager::CredentialType credential_type) {
  CHECK(dialog_controller_);
  CHECK_EQ(password_manager::CredentialType::CREDENTIAL_TYPE_PASSWORD,
           credential_type);
  // Copy the argument before destroying the controller. |form| is a member of
  // |dialog_controller_|.
  password_manager::PasswordForm copy_form = form;
  dialog_controller_.reset();
  passwords_data_.ChooseCredential(&copy_form);
  ClearPopUpFlagForBubble();
  passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::NavigateToPasswordManagerSettingsPage(
    password_manager::ManagePasswordsReferrer referrer) {
  NavigateToManagePasswordsPage(
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          web_contents()),
      referrer);
}

void ManagePasswordsUIController::
    NavigateToPasswordDetailsPageInPasswordManager(
        const std::string& password_domain_name,
        password_manager::ManagePasswordsReferrer referrer) {
  NavigateToPasswordDetailsPage(
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          web_contents()),
      password_domain_name, referrer);
}

void ManagePasswordsUIController::NavigateToPasswordCheckup(
    password_manager::PasswordCheckReferrer referrer) {
  chrome::ShowPasswordCheck(
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          web_contents()));
  password_manager::LogPasswordCheckReferrer(referrer);
}

void ManagePasswordsUIController::OnDialogHidden() {
  dialog_controller_.reset();
  if (GetState() == password_manager::ui::CREDENTIAL_REQUEST_STATE) {
    ClearPopUpFlagForBubble();
    passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
    UpdateBubbleAndIconVisibility();
  }
  passwords_data_.clear_selected_password();
}

void ManagePasswordsUIController::OnLeakDialogHidden() {
  dialog_controller_.reset();
  if (GetState() == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) {
    bubble_status_ = BubbleStatus::SHOULD_POP_UP;
    UpdateBubbleAndIconVisibility();
    return;
  }
  if (GetState() == password_manager::ui::PENDING_PASSWORD_STATE) {
    if (GetSavingPromptStatus() == SavingPromptStatus::kCanShow) {
      bubble_status_ = BubbleStatus::SHOULD_POP_UP;
    }
    UpdateBubbleAndIconVisibility();
  }
}

ManagePasswordsUIController::SavingPromptStatus
ManagePasswordsUIController::GetSavingPromptStatus() const {
  auto logger = GetSaveProgressLogger(passwords_data_.client());
  if (IsExplicitlyBlocklisted()) {
    if (logger.has_value()) {
      logger->LogMessage(Logger::STRING_SAVING_BLOCKLISTED_EXPLICITLY);
    }
    return SavingPromptStatus::kExplicitlyBlocklisted;
  }

  const password_manager::InteractionsStats* stats =
      GetCurrentInteractionStats();
  const int show_threshold =
      password_bubble_experiment::GetSmartBubbleDismissalThreshold();
  const bool is_implicitly_blocked =
      stats && show_threshold > 0 && stats->dismissal_count >= show_threshold;
  if (is_implicitly_blocked && logger.has_value()) {
    logger->LogMessage(Logger::STRING_SAVING_BLOCKLISTED_BY_SMART_BUBBLE);
  }
  return is_implicitly_blocked ? SavingPromptStatus::kImplicitlyBlocked
                               : SavingPromptStatus::kCanShow;
}

bool ManagePasswordsUIController::IsExplicitlyBlocklisted() const {
  if (PasswordFormManagerForUI* const form_manager =
          passwords_data_.form_manager()) {
    return form_manager->IsBlocklisted();
  }
  return false;
}

void ManagePasswordsUIController::AuthenticateUserWithMessage(
    const std::u16string& message,
    AvailabilityCallback callback) {
  if (bypass_user_auth_for_testing_) {
    std::move(callback).Run(true);
    return;
  }
#if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_CHROMEOS)
  std::move(callback).Run(true);
  return;
#else
  base::OnceClosure on_reauth_completed =
      base::BindOnce(&ManagePasswordsUIController::OnReauthCompleted,
                     weak_ptr_factory_.GetWeakPtr());

  CancelAnyOngoingBiometricAuth();
  biometric_authenticator_ = passwords_data_.client()->GetDeviceAuthenticator();
  biometric_authenticator_->AuthenticateWithMessage(
      message, std::move(callback).Then(std::move(on_reauth_completed)));
#endif  // !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_WIN)
}

void ManagePasswordsUIController::MaybeShowIOSPasswordPromo() {
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          web_contents());
  if (!browser) {
    return;
  }

  if (MobilePromoOnDesktopTypeEnabled(
          MobilePromoOnDesktopPromoType::kAutofillPromo)) {
    IOSPromoTriggerService* service =
        IOSPromoTriggerServiceFactory::GetForProfile(browser->GetProfile());
    if (service) {
      service->NotifyPromoShouldBeShown(
          desktop_to_mobile_promos::PromoType::kPassword);
    }
  } else {
    ios_promos_utils::VerifyIOSPromoEligibility(
        desktop_to_mobile_promos::PromoType::kPassword, browser);
  }
}

void ManagePasswordsUIController::RelaunchChrome() {
  chrome::AttemptRestart();
}

void ManagePasswordsUIController::NavigateToPasswordChangeSettings() {
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          web_contents());
  ShowSingletonTabOverwritingNTP(browser,
                                 GURL(chrome::kChromeUiPasswordChangeUrl),
                                 NavigateParams::IGNORE_AND_NAVIGATE);
}

[[nodiscard]] std::unique_ptr<base::AutoReset<bool>>
ManagePasswordsUIController::BypassUserAuthtForTesting() {
  return std::make_unique<base::AutoReset<bool>>(&bypass_user_auth_for_testing_,
                                                 true);
}

void ManagePasswordsUIController::ShowChangePasswordBubble(
    const std::u16string& username,
    const std::u16string& new_password) {
  passwords_data_.OpenPasswordChangedBubble(username, new_password);
  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::UpdateBubbleAndIconVisibility() {
  // If we're not on a "webby" URL (e.g. "chrome://sign-in"), we shouldn't
  // display either the bubble or the icon.
  if (!ChromePasswordManagerClient::CanShowBubbleOnURL(
          web_contents()->GetLastCommittedURL())) {
    ClearPopUpFlagForBubble();
    passwords_data_.OnInactive();
  }

  // If Actor is operating on the tab, suppress all popups.
  if (passwords_data_.client()->IsActorTaskActive()) {
    ClearPopUpFlagForBubble();
  }

  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          web_contents());
  if (!browser) {
    return;
  }

  tabs::TabInterface* const tab_interface =
      tabs::TabInterface::MaybeGetFromContents(web_contents());
  // The tab interface can be null if the web contents is not a tab.
  if (!tab_interface) {
    return;
  }
  tabs::TabFeatures* const tab_features = tab_interface->GetTabFeatures();
  // During tab teardown, TabFeatures is destroyed before WebContents.
  // Destroying other tab features (like ReadAnything) can trigger
  // visibility changes on the WebContents, which calls this method
  // when TabFeatures is already gone.
  if (!tab_features) {
    return;
  }
  // Retrieve the controller responsible for managing the page action's
  // visibility and state.
  ManagePasswordsPageActionController* const controller =
      tab_features->manage_passwords_page_action_controller();
  // Get the action item associated with the passwords UI.
  actions::ActionItem* passwords_action_item =
      actions::ActionManager::Get().FindAction(
          kActionShowPasswordsBubbleOrPage,
          BrowserActions::From(browser)->root_action_item());
  UpdatePasswordIconAndBubbleState(controller, passwords_action_item);
}

void ManagePasswordsUIController::UpdatePasswordIconAndBubbleState(
    ManagePasswordsPageActionController* controller,
    actions::ActionItem* passwords_action_item) {
  password_manager::ui::State state = GetState();
  // If the UI state has changed since the last update,
  // close the current bubble to ensure that the UI reflects the new state.
  if (state != last_page_action_state_) {
    PasswordBubbleViewBase::CloseCurrentBubble();
  }
  // Update the last known state.
  last_page_action_state_ = state;
  // Determine whether the bubble should be shown automatically based on
  // current conditions.
  const bool show_bubble =
      IsAutomaticallyOpeningBubble() ||
      bubble_status_ == BubbleStatus::SHOULD_POP_UP_WITH_FOCUS;
  // If the bubble is not to be shown, and there's a dialog controller active
  // and the state is Credential Request, force the state to inactive to
  // prevent icon from being active.
  if (!show_bubble && dialog_controller_ &&
      state == password_manager::ui::CREDENTIAL_REQUEST_STATE) {
    state = password_manager::ui::INACTIVE_STATE;
  }

  // If the auto sign-in toast is shown (Unified UI), hide the icon.
  if (state == password_manager::ui::AUTO_SIGNIN_STATE &&
      base::FeatureList::IsEnabled(
          password_manager::features::kCredentialManagementUnifiedUi)) {
    state = password_manager::ui::INACTIVE_STATE;
  }

  // Update the visibility of the page action based on the current state
  // and the passwords action item.
  controller->UpdateVisibility(state, *this, *passwords_action_item);

  if (show_bubble) {
    PasswordBubbleViewBase::CloseCurrentBubble();
    // This will detach any existing bubble so OnBubbleHidden() isn't called.
    weak_ptr_factory_.InvalidateWeakPtrs();
    QueueOrShowBubble(/*user_action=*/false);
    // If the bubble appeared then the status is updated in OnBubbleShown().
    ClearPopUpFlagForBubble();
  }
}

std::unique_ptr<AccountChooserPrompt>
ManagePasswordsUIController::CreateAccountChooser(
    CredentialManagerDialogController* controller) {
  return CreateAccountChooserPromptView(controller, web_contents());
}

std::unique_ptr<AutoSigninFirstRunPrompt>
ManagePasswordsUIController::CreateAutoSigninPrompt(
    CredentialManagerDialogController* controller) {
  return CreateAutoSigninPromptView(controller, web_contents());
}

std::unique_ptr<CredentialLeakPrompt>
ManagePasswordsUIController::CreateCredentialLeakPrompt(
    CredentialLeakDialogController* controller) {
  return CreateCredentialLeakPromptView(controller, web_contents());
}

bool ManagePasswordsUIController::HasBrowserWindow() const {
  return GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
             web_contents()) != nullptr;
}

std::unique_ptr<MovePasswordToAccountStoreHelper>
ManagePasswordsUIController::CreateMovePasswordToAccountStoreHelper(
    const password_manager::PasswordForm& form,
    password_manager::metrics_util::MoveToAccountStoreTrigger trigger,
    base::OnceCallback<void()> on_move_finished) {
  return std::make_unique<MovePasswordToAccountStoreHelper>(
      form, passwords_data_.client(), trigger, std::move(on_move_finished));
}

void ManagePasswordsUIController::MovePendingPasswordToAccountStoreUsingHelper(
    const password_manager::PasswordForm& form,
    password_manager::metrics_util::MoveToAccountStoreTrigger trigger) {
  // Insert nullptr first to obtain the iterator passed to the callback.
  auto helper_it = move_to_account_store_helpers_.insert(
      move_to_account_store_helpers_.begin(), nullptr);
  // This class owns and thus outlives the helper so base::Unretained is safe.
  auto on_move_finished = base::BindOnce(
      &ManagePasswordsUIController::OnMovePasswordToAccountStoreComplete,
      base::Unretained(this), helper_it);
  *helper_it = CreateMovePasswordToAccountStoreHelper(
      form, trigger, std::move(on_move_finished));
}

void ManagePasswordsUIController::PrimaryPageChanged(content::Page& page) {
  CancelAnyOngoingBiometricAuth();

  // Keep the state if the bubble is currently open or the fallback for saving
  // should be still available.
  if (IsShowingBubble() || save_fallback_timer_.IsRunning() ||
      BubbleManagerHasPasswordBubbleInQueue()) {
    return;
  }

  // Otherwise, reset the password manager.
  save_password_after_trusted_vault_error_resolution_ = false;
  DestroyPopups();
  ClearPopUpFlagForBubble();
  passwords_data_.OnInactive();
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnVisibilityChanged(
    content::Visibility visibility) {
  // The page action icon (and its pinned toolbar button) is shared at the
  // window level. When this tab becomes visible, we must push our tab-specific
  // state to the shared icon/action item to ensure it correctly reflects our
  // state (e.g. clearing any highlights from a previously active tab).
  if (visibility == content::Visibility::VISIBLE) {
    UpdateBubbleAndIconVisibility();
  }

  // BubbleManager will handle the effects of tab changes.
}

bool ManagePasswordsUIController::ShouldReshowOnTabVisible() const {
  return false;
}

PasswordChangeDelegate* ManagePasswordsUIController::GetPasswordChangeDelegate()
    const {
  ChromePasswordChangeService* password_change_service =
      PasswordChangeServiceFactory::GetForProfile(
          Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
  if (!password_change_service) {
    return nullptr;
  }
  return password_change_service->GetPasswordChangeDelegate(web_contents());
}

PasswordsLeakDialogDelegate*
ManagePasswordsUIController::GetPasswordsLeakDialogDelegate() {
  return this;
}

// static
base::TimeDelta ManagePasswordsUIController::GetTimeoutForSaveFallback() {
  return base::Seconds(
      ManagePasswordsUIController::save_fallback_timeout_in_seconds_);
}

void ManagePasswordsUIController::ShowBubbleWithoutUserInteraction() {
  CHECK(IsAutomaticallyOpeningBubble() ||
        bubble_status_ == BubbleStatus::SHOULD_POP_UP_WITH_FOCUS);
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          web_contents());
  // Can be zero in the tests.
  if (!browser) {
    return;
  }

  chrome::ExecuteCommand(browser, IDC_MANAGE_PASSWORDS_FOR_PAGE);
}

void ManagePasswordsUIController::ClearPopUpFlagForBubble() {
  if (IsAutomaticallyOpeningBubble() ||
      bubble_status_ == BubbleStatus::SHOULD_POP_UP_WITH_FOCUS) {
    bubble_status_ = BubbleStatus::NOT_SHOWN;
  }
}

void ManagePasswordsUIController::DestroyPopups() {
  HideBubble(/*initiated_by_bubble_manager=*/false);
  if (dialog_controller_ && dialog_controller_->IsShowingAccountChooser()) {
    dialog_controller_.reset();
    passwords_data_.TransitionToState(password_manager::ui::MANAGE_STATE);
  }
}

void ManagePasswordsUIController::WebContentsDestroyed() {
  password_manager::PasswordStoreInterface* profile_password_store =
      GetProfilePasswordStore(web_contents());
  if (profile_password_store) {
    profile_password_store->RemoveObserver(this);
  }
  password_manager::PasswordStoreInterface* account_password_store =
      GetAccountPasswordStore(web_contents());
  if (account_password_store) {
    account_password_store->RemoveObserver(this);
  }
  HideBubble(/*initiated_by_bubble_manager=*/false);
  web_contents()->RemoveUserData(UserDataKey());
  // `this` is now destroyed - do not add code here.
}

void ManagePasswordsUIController::OnTriggerPostSaveCompromisedBubble(
    password_manager::PostSaveCompromisedHelper::BubbleType type,
    size_t count_compromised_passwords_) {
  using password_manager::PostSaveCompromisedHelper;
  // If the controller changed the state in the mean time or the Sign-in promo
  // is still open, don't show anything.
  if (passwords_data_.state() != password_manager::ui::MANAGE_STATE ||
      bubble_status_ != BubbleStatus::NOT_SHOWN) {
    return;
  }
  password_manager::ui::State state;
  switch (type) {
    case PostSaveCompromisedHelper::BubbleType::kNoBubble:
      post_save_compromised_helper_.reset();
      return;
    case PostSaveCompromisedHelper::BubbleType::kPasswordUpdatedSafeState:
      state = password_manager::ui::PASSWORD_UPDATED_SAFE_STATE;
      break;
    case PostSaveCompromisedHelper::BubbleType::kPasswordUpdatedWithMoreToFix:
      state = password_manager::ui::PASSWORD_UPDATED_MORE_TO_FIX;
      break;
  }
  passwords_data_.TransitionToState(state);
  bubble_status_ = BubbleStatus::SHOULD_POP_UP;
  UpdateBubbleAndIconVisibility();
}

void ManagePasswordsUIController::OnMovePasswordToAccountStoreComplete(
    std::list<std::unique_ptr<MovePasswordToAccountStoreHelper>>::iterator
        done_helper_it) {
  move_to_account_store_helpers_.erase(done_helper_it);
}

void ManagePasswordsUIController::OnReauthCompleted() {
  biometric_authenticator_.reset();
}

void ManagePasswordsUIController::CancelAnyOngoingBiometricAuth() {
  if (!biometric_authenticator_) {
    return;
  }
  biometric_authenticator_->Cancel();
  biometric_authenticator_.reset();
}

bool ManagePasswordsUIController::IsPendingPasswordPhished() const {
  const password_manager::PasswordForm& pending_form = GetPendingPassword();
  return pending_form.password_issues.find(
             password_manager::InsecureType::kPhished) !=
         pending_form.password_issues.end();
}

bool ManagePasswordsUIController::IsPasswordChangeOngoing() const {
  return GetPasswordChangeDelegate();
}

bool ManagePasswordsUIController::BubbleManagerHasPasswordBubbleInQueue()
    const {
  if (auto* manager =
          autofill::BubbleManager::GetForWebContents(web_contents())) {
    return manager->HasPendingBubbleOfSameType(GetBubbleType());
  }

  return false;
}

void ManagePasswordsUIController::ShowBubble() {
  if (TabDialogs* tab_dialogs = TabDialogs::FromWebContents(web_contents())) {
    tab_dialogs->ShowManagePasswordsBubble(user_action_);
  }
}

void ManagePasswordsUIController::HideBubble(bool initiated_by_bubble_manager) {
  is_mouse_hovered_ = false;
  if (TabDialogs* tab_dialogs = TabDialogs::FromWebContents(web_contents())) {
    tab_dialogs->HideManagePasswordsBubble();
  }
}

autofill::BubbleType ManagePasswordsUIController::GetBubbleType() const {
  return autofill::BubbleType::kPassword;
}

bool ManagePasswordsUIController::IsShowingBubble() const {
  return bubble_status_ == BubbleStatus::SHOWN ||
         bubble_status_ == BubbleStatus::SHOWN_PENDING_ICON_UPDATE;
}

base::WeakPtr<autofill::BubbleControllerBase>
ManagePasswordsUIController::GetBubbleControllerBaseWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

bool ManagePasswordsUIController::CanBeReshown() const {
  return GetState() != password_manager::ui::AUTO_SIGNIN_STATE &&
         GetState() != password_manager::ui::INACTIVE_STATE;
}

void ManagePasswordsUIController::OnMouseEntered() {
  is_mouse_hovered_ = true;
}

void ManagePasswordsUIController::OnMouseExited() {
  is_mouse_hovered_ = false;
}

bool ManagePasswordsUIController::IsMouseHovered() const {
  return is_mouse_hovered_;
}

void ManagePasswordsUIController::StartTrustedVaultErrorResolutionFlow() {
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          web_contents());
  OpenTabForSyncKeyRetrieval(
      browser,
      trusted_vault::TrustedVaultUserActionTriggerForUMA::kPasswordSavePrompt);
}

void ManagePasswordsUIController::
    SavePasswordAfterTrustedVaultErrorResolution() {
  if (IsSavingBlockedByTrustedVaultError()) {
    // Defer saving the password until the password store error is fixed.
    save_password_after_trusted_vault_error_resolution_ = true;
    return;
  }
  // No trusted vault errors, so we can save the password immediately.
  save_password_after_trusted_vault_error_resolution_ = false;
  if (PasswordFormManagerForUI* form_manager = passwords_data_.form_manager()) {
    password_manager::PasswordForm pending_credentials =
        form_manager->GetPendingCredentials();
    SavePassword(pending_credentials.username_value,
                 pending_credentials.password_value);
    // SavePassword() sets bubble_status_ to SHOWN_PENDING_ICON_UPDATE.
    // However, since the bubble was already closed when we started the error
    // resolution flow, we must reset it to NOT_SHOWN.
    bubble_status_ = BubbleStatus::NOT_SHOWN;
    UpdateBubbleAndIconVisibility();
  }
}

bool ManagePasswordsUIController::IsSavingBlockedByTrustedVaultError() const {
  if (password_manager::PasswordFormManagerForUI* form_manager =
          passwords_data_.form_manager()) {
    return password_manager_util::IsSavingBlockedByTrustedVaultError(
        passwords_data_.client(), form_manager);
  }
  return false;
}
void ManagePasswordsUIController::OnErrorStateChanged(
    password_manager::PasswordStoreInterface* /*store*/,
    password_manager::ActionableError new_state) {
  if (base::FeatureList::IsEnabled(
          password_manager::features::kPasswordSaveInContextErrorResolution)) {
    if (save_password_after_trusted_vault_error_resolution_ &&
        new_state == password_manager::ActionableError::kNoError) {
      SavePasswordAfterTrustedVaultErrorResolution();
    }
    if (IsShowingBubble()) {
      // If the error state of the store changed, the safest option is to hide
      // the password save / update bubble to avoid showing stale UI.
      using password_manager::ui::State;
      const State state = GetState();
      if (state == State::PENDING_PASSWORD_STATE ||
          state == State::PENDING_PASSWORD_UPDATE_STATE) {
        HideBubble(/*initiated_by_bubble_manager=*/false);
      }
    }
  }
}

void ManagePasswordsUIController::QueueOrShowBubble(bool user_action) {
  if (PasswordBubbleViewBase::manage_password_bubble()) {
    PasswordBubbleViewBase::CloseCurrentBubble();
  }

  user_action_ = user_action;

  if (auto* manager =
          autofill::BubbleManager::GetForWebContents(web_contents())) {
    CHECK(!manager->HasConflictingPendingBubble(GetBubbleType()));
    manager->RequestShowController(*this, user_action);
  }
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(ManagePasswordsUIController);
