// Copyright 2018 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/ash/login/login_display_host_common.h"

#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_login_pref_names.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/login/resources/grit/ash_login_strings.h"
#include "ash/public/cpp/login_accelerators.h"
#include "ash/public/cpp/wallpaper/wallpaper_controller.h"
#include "base/check_deref.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "base/types/pass_key.h"
#include "base/values.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/app_mode/kiosk_app_types.h"
#include "chrome/browser/ash/app_mode/kiosk_controller.h"
#include "chrome/browser/ash/app_mode/kiosk_launch_state.h"
#include "chrome/browser/ash/attestation/attestation_ca_client.h"
#include "chrome/browser/ash/auth/cryptohome_pin_engine.h"
#include "chrome/browser/ash/login/choobe_flow_controller.h"
#include "chrome/browser/ash/login/existing_user_controller.h"
#include "chrome/browser/ash/login/lock_screen_utils.h"
#include "chrome/browser/ash/login/oobe_cros_events_metrics.h"
#include "chrome/browser/ash/login/oobe_metrics_helper.h"
#include "chrome/browser/ash/login/oobe_quick_start/second_device_auth_broker.h"
#include "chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h"
#include "chrome/browser/ash/login/screens/app_launch_splash_screen.h"
#include "chrome/browser/ash/login/screens/encryption_migration_screen.h"
#include "chrome/browser/ash/login/screens/gaia_screen.h"
#include "chrome/browser/ash/login/screens/osauth/password_selection_screen.h"
#include "chrome/browser/ash/login/screens/osauth/recovery_eligibility_screen.h"
#include "chrome/browser/ash/login/screens/pin_setup_screen.h"
#include "chrome/browser/ash/login/screens/reset_screen.h"
#include "chrome/browser/ash/login/screens/saml_confirm_password_screen.h"
#include "chrome/browser/ash/login/screens/signin_fatal_error_screen.h"
#include "chrome/browser/ash/login/startup_utils.h"
#include "chrome/browser/ash/login/wizard_controller.h"
#include "chrome/browser/ash/nearby/quick_start_connectivity_service_factory.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/profiles/signin_profile_handler.h"
#include "chrome/browser/ash/system/device_disabling_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/chrome_device_id_helper.h"
#include "chrome/browser/ui/ash/login/login_feedback.h"
#include "chrome/browser/ui/ash/login/signin_ui.h"
#include "chrome/browser/ui/ash/system/system_tray_client_impl.h"
#include "chrome/browser/ui/webui/ash/diagnostics_dialog/diagnostics_dialog.h"
#include "chrome/browser/ui/webui/ash/login/family_link_notice_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/gaia_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/locale_switch_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/management_transition_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/offline_login_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/password_selection_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/remove_local_auth_factors_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/reset_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/saml_confirm_password_handler.h"
#include "chrome/browser/ui/webui/ash/login/signin_fatal_error_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/terms_of_service_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/user_allowlist_check_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/user_creation_screen_handler.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/ash/components/attestation/attestation_flow_adaptive.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "chromeos/ash/components/install_attributes/install_attributes.h"
#include "chromeos/ash/components/language_preferences/language_preferences.h"
#include "chromeos/ash/components/login/auth/auth_performer.h"
#include "chromeos/ash/components/osauth/public/auth_session_storage.h"
#include "components/application_locale_storage/application_locale_storage.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/strings/grit/components_strings.h"
#include "extensions/common/features/feature_session_type.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ime/ash/input_method_util.h"
#include "ui/base/l10n/l10n_util.h"

namespace ash {

namespace {

// The delay of triggering initialization of the device policy subsystem
// after the login screen is initialized. This makes sure that device policy
// network requests are made while the system is idle waiting for user input.
constexpr int64_t kPolicyServiceInitializationDelayMilliseconds = 100;

void PushFrontImIfNotExists(const std::string& input_method_id,
                            std::vector<std::string>* input_method_ids) {
  if (input_method_id.empty()) {
    return;
  }

  if (!std::ranges::contains(*input_method_ids, input_method_id)) {
    input_method_ids->insert(input_method_ids->begin(), input_method_id);
  }
}

void SetGaiaInputMethods(PrefService& local_state,
                         const std::string& application_locale,
                         const AccountId& account_id) {
  input_method::InputMethodManager* imm =
      input_method::InputMethodManager::Get();

  scoped_refptr<input_method::InputMethodManager::State> gaia_ime_state =
      imm->GetActiveIMEState()->Clone();
  imm->SetState(gaia_ime_state);
  gaia_ime_state->SetUIStyle(input_method::InputMethodManager::UIStyle::kLogin);

  // Set Least Recently Used input method for the user.
  if (account_id.is_valid()) {
    lock_screen_utils::SetUserInputMethod(local_state, account_id,
                                          gaia_ime_state.get(),
                                          true /*honor_device_policy*/);
  } else {
    lock_screen_utils::EnforceDevicePolicyInputMethods(std::string());
    std::vector<std::string> input_method_ids;
    if (gaia_ime_state->GetAllowedInputMethodIds().empty()) {
      input_method_ids = imm->GetInputMethodUtil()->GetHardwareInputMethodIds();
    } else {
      input_method_ids = gaia_ime_state->GetAllowedInputMethodIds();
    }
    const std::string owner_input_method_id =
        lock_screen_utils::GetUserLastInputMethodId(
            local_state, user_manager::UserManager::Get()->GetOwnerAccountId());
    const std::string system_input_method_id =
        local_state.GetString(language_prefs::kPreferredKeyboardLayout);

    PushFrontImIfNotExists(owner_input_method_id, &input_method_ids);
    PushFrontImIfNotExists(system_input_method_id, &input_method_ids);

    gaia_ime_state->EnableOobeInputMethods(application_locale,
                                           input_method_ids);

    if (!system_input_method_id.empty()) {
      gaia_ime_state->ChangeInputMethod(system_input_method_id,
                                        false /* show_message */);
    } else if (!owner_input_method_id.empty()) {
      gaia_ime_state->ChangeInputMethod(owner_input_method_id,
                                        false /* show_message */);
    }
  }
}

int ErrorToMessageId(SigninError error) {
  switch (error) {
    case SigninError::kCaptivePortalError:
      NOTREACHED();
    case SigninError::kGoogleAccountNotAllowed:
      return IDS_LOGIN_ERROR_GOOGLE_ACCOUNT_NOT_ALLOWED;
    case SigninError::kOwnerRequired:
      return IDS_LOGIN_ERROR_OWNER_REQUIRED;
    case SigninError::kTpmUpdateRequired:
      return IDS_LOGIN_ERROR_TPM_UPDATE_REQUIRED;
    case SigninError::kKnownUserFailedNetworkNotConnected:
      return IDS_LOGIN_ERROR_AUTHENTICATING;
    case SigninError::kNewUserFailedNetworkNotConnected:
      return IDS_LOGIN_ERROR_OFFLINE_FAILED_NETWORK_NOT_CONNECTED;
    case SigninError::kNewUserFailedNetworkConnected:
      return IDS_LOGIN_ERROR_AUTHENTICATING_NEW;
    case SigninError::kKnownUserFailedNetworkConnected:
      return IDS_LOGIN_ERROR_AUTHENTICATING;
    case SigninError::kOwnerKeyLost:
      return IDS_LOGIN_ERROR_OWNER_KEY_LOST;
    case SigninError::kChallengeResponseAuthMultipleClientCerts:
      return IDS_CHALLENGE_RESPONSE_AUTH_MULTIPLE_CLIENT_CERTS_ERROR;
    case SigninError::kChallengeResponseAuthInvalidClientCert:
      return IDS_CHALLENGE_RESPONSE_AUTH_INVALID_CLIENT_CERT_ERROR;
    case SigninError::kCookieWaitTimeout:
      return IDS_LOGIN_FATAL_ERROR_NO_AUTH_TOKEN;
    case SigninError::kFailedToFetchSamlRedirect:
      return IDS_FAILED_TO_FETCH_SAML_REDIRECT;
  }
}

bool IsAuthError(SigninError error) {
  return error == SigninError::kCaptivePortalError ||
         error == SigninError::kKnownUserFailedNetworkNotConnected ||
         error == SigninError::kNewUserFailedNetworkNotConnected ||
         error == SigninError::kNewUserFailedNetworkConnected ||
         error == SigninError::kKnownUserFailedNetworkConnected;
}

class AccessibilityManagerWrapper
    : public quick_start::TargetDeviceBootstrapController::
          AccessibilityManagerWrapper {
 public:
  AccessibilityManagerWrapper() = default;
  AccessibilityManagerWrapper(AccessibilityManagerWrapper&) = delete;
  AccessibilityManagerWrapper& operator=(AccessibilityManagerWrapper&) = delete;
  ~AccessibilityManagerWrapper() override = default;

  bool AllowQRCodeUX() const override {
    return ash::AccessibilityManager::Get()->AllowQRCodeUX();
  }
};

std::unique_ptr<quick_start::SecondDeviceAuthBroker>
CreateSecondDeviceAuthBroker() {
  std::unique_ptr<attestation::ServerProxy> server_proxy(
      new attestation::AttestationCAClient());
  std::unique_ptr<attestation::AttestationFlow> attestation_flow =
      std::make_unique<attestation::AttestationFlowAdaptive>(
          std::move(server_proxy));

  // TODO(b:286850431) - Fix device id generation.
  const std::string device_id =
      GenerateSigninScopedDeviceId(/*for_ephemeral=*/false);
  auto* signin_profile = ProfileHelper::GetSigninProfile();
  return std::make_unique<quick_start::SecondDeviceAuthBroker>(
      device_id, signin_profile->GetURLLoaderFactory(),
      std::move(attestation_flow));
}

}  // namespace

LoginDisplayHostCommon::LoginDisplayHostCommon(
    PrefService* local_state,
    ApplicationLocaleStorage* application_locale_storage,
    scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
    policy::BrowserPolicyConnectorAsh* browser_policy_connector_ash,
    bool update_geolocation_usage_allowed)
    : local_state_(CHECK_DEREF(local_state)),
      application_locale_storage_(CHECK_DEREF(application_locale_storage)),
      shared_url_loader_factory_(std::move(shared_url_loader_factory)),
      browser_policy_connector_ash_(CHECK_DEREF(browser_policy_connector_ash)),
      keep_alive_(KeepAliveOrigin::LOGIN_DISPLAY_HOST_WEBUI,
                  KeepAliveRestartOption::DISABLED),
      kiosk_app_menu_controller_(local_state),
      login_ui_pref_controller_(std::make_unique<LoginUIPrefController>(
          local_state,
          update_geolocation_usage_allowed)),
      wizard_context_(std::make_unique<WizardContext>()),
      // TODO(crbug.com/446058312): OobeMetricsHelper ctor should take a
      // `PrefService` pointer as parameter (g_browser_process->local_state()).
      // However, given the lifecycle of LoginDisplayHostCommon (that schedules
      // its deletion or self-deletes it), it is not trivial to ensure that
      // this PrefService reference won't become dangling.
      // Pass a getter callback for safity until the ownership described is
      // reworked.
      oobe_metrics_helper_(std::make_unique<OobeMetricsHelper>(
          base::PassKey<LoginDisplayHostCommon>(),
          base::BindRepeating(
              []() { return g_browser_process->local_state(); }),
          base::BindRepeating(
              []() { return g_browser_process->metrics_service(); }))) {
  CHECK(shared_url_loader_factory_);

  if (features::IsOobeCrosEventsEnabled()) {
    oobe_cros_events_metrics_ = std::make_unique<OobeCrosEventsMetrics>(
        &local_state_.get(), oobe_metrics_helper_.get());
  }

  browser_controller_observation_.Observe(BrowserController::GetInstance());

  // Close the login screen on app termination (for the case where shutdown
  // occurs before login completes).
  session_termination_observation_.Observe(
      ash::SessionTerminationManager::Get());
}

LoginDisplayHostCommon::~LoginDisplayHostCommon() = default;

void LoginDisplayHostCommon::BeforeSessionStart() {
  session_starting_ = true;
}

bool LoginDisplayHostCommon::LoginDisplayHostCommon::IsFinalizing() {
  return is_finalizing_;
}

void LoginDisplayHostCommon::Finalize(base::OnceClosure completion_callback) {
  LOG(WARNING) << "Finalize";
  LoginDisplayHost::Finalize(std::move(completion_callback));

  // If finalize is called twice the LoginDisplayHost instance will be deleted
  // multiple times.
  CHECK(!is_finalizing_);
  is_finalizing_ = true;

  OnFinalize();
}

void LoginDisplayHostCommon::FinalizeImmediately() {
  CHECK(!is_finalizing_);
  CHECK(!shutting_down_);
  is_finalizing_ = true;
  shutting_down_ = true;
  OnFinalize();
  Cleanup();
  delete this;
}

void LoginDisplayHostCommon::StartUserAdding(
    base::OnceClosure completion_callback) {
  LoginDisplayHost::StartUserAdding(std::move(completion_callback));
  OnStartUserAdding();
}

void LoginDisplayHostCommon::StartSignInScreen() {
  const user_manager::UserList& users =
      user_manager::UserManager::Get()->GetPersistedUsers();

  // Fix for users who updated device and thus never passed register screen.
  // If we already have users, we assume that it is not a second part of
  // OOBE. See http://crosbug.com/6289
  if (!StartupUtils::IsDeviceRegistered(local_state_.get()) && !users.empty()) {
    VLOG(1) << "Mark device registered because there are remembered users: "
            << users.size();
    StartupUtils::MarkDeviceRegistered(local_state_.get(), base::OnceClosure());
  }

  // Initiate device policy fetching.
  browser_policy_connector_ash_->ScheduleServiceInitialization(
      kPolicyServiceInitializationDelayMilliseconds);

  // Run UI-specific logic.
  OnStartSignInScreen();

  // Enable status area after starting sign-in screen, as it may depend on the
  // UI being visible.
  SystemTrayClientImpl::Get()->SetPrimaryTrayVisible(/*visible=*/true);
}

void LoginDisplayHostCommon::StartKiosk(const KioskAppId& kiosk_app_id,
                                        bool is_auto_launch) {
  VLOG(1) << "Login >> start kiosk of type "
          << std::to_underlying(kiosk_app_id.type);

  SetKioskLaunchStateCrashKey(KioskLaunchState::kAttemptToLaunch);

  SystemTrayClientImpl::Get()->SetPrimaryTrayVisible(/*visible=*/false);

  // Wait for the `CrosSettings` to become either trusted or permanently
  // untrusted.
  const CrosSettingsProvider::TrustedStatus status =
      CrosSettings::Get()->PrepareTrustedValues(base::BindOnce(
          &LoginDisplayHostCommon::StartKiosk, weak_factory_.GetWeakPtr(),
          kiosk_app_id, is_auto_launch));
  if (status == CrosSettingsProvider::TEMPORARILY_UNTRUSTED) {
    return;
  }

  if (status == CrosSettingsProvider::PERMANENTLY_UNTRUSTED) {
    // If the `CrosSettings` are permanently untrusted, refuse to launch a
    // single-app kiosk mode session.
    LOG(ERROR) << "Login >> Refusing to launch single-app kiosk mode.";
    SystemTrayClientImpl::Get()->SetPrimaryTrayVisible(/*visible=*/true);
    return;
  }

  if (system::DeviceDisablingManager::IsDeviceDisabledDuringNormalOperation()) {
    // If the device is disabled, bail out. A device disabled screen will be
    // shown by the DeviceDisablingManager.
    return;
  }

  // Prevent a race condition when user launches a kiosk app from the apps
  // menu while another login is in progress. E.g. UI shelf is not disabled on
  // slower devices.
  // A race can happen between manual launch kiosk and one of guest session,
  // MGS (manual or autolaunched) or autolaunched kiosk.
  // Currently needs to use both ExistingUserController and UserManager because
  // these sessions aren't consistent with setting various login states in time.
  // TODO(b/291293540): Check why ExistingUserController is not updated by
  // autolaunch kiosk.
  const auto& existing_user_controller =
      CHECK_DEREF(GetExistingUserController());
  const bool is_login_detected_existing_user_controller =
      existing_user_controller.IsSigninInProgress() ||
      existing_user_controller.IsUserSigninCompleted();
  const bool is_login_detected_user_manager =
      user_manager::UserManager::IsInitialized() &&
      user_manager::UserManager::Get()->IsUserLoggedIn();
  if (is_login_detected_existing_user_controller ||
      is_login_detected_user_manager) {
    LOG(ERROR) << "Cancel kiosk launch. Another user login is completed or in "
                  "progress.";
    return;
  }

  SetKioskLaunchStateCrashKey(KioskLaunchState::kStartLaunch);

  OnStartAppLaunch();

  int auto_launch_delay = -1;
  if (is_auto_launch) {
    if (!CrosSettings::Get()->GetInteger(
            kAccountsPrefDeviceLocalAccountAutoLoginDelay,
            &auto_launch_delay)) {
      auto_launch_delay = 0;
    }
    DCHECK_EQ(0, auto_launch_delay)
        << "Kiosks do not support non-zero auto-login delays";
  }

  extensions::SetCurrentFeatureSessionType(
      is_auto_launch && auto_launch_delay == 0
          ? extensions::mojom::FeatureSessionType::kAutolaunchedKiosk
          : extensions::mojom::FeatureSessionType::kKiosk);

  KioskController::Get().StartSession(
      kiosk_app_id, is_auto_launch, this,
      GetWizardController()->GetScreen<AppLaunchSplashScreen>());
}

void LoginDisplayHostCommon::CompleteLogin(const UserContext& user_context) {
  if (GetExistingUserController()) {
    GetExistingUserController()->CompleteLogin(user_context);
  } else {
    LOG(WARNING) << "LoginDisplayHostCommon::CompleteLogin - Failure : "
                 << "ExistingUserController not available.";
  }
}

void LoginDisplayHostCommon::OnGaiaScreenReady() {
  if (GetExistingUserController()) {
    GetExistingUserController()->OnGaiaScreenReady();
  } else {
    // Used to debug crbug.com/41424664. Feel free to remove after that is
    // fixed.
    LOG(ERROR) << "OnGaiaScreenReady: there is no existing user controller";
  }
}

void LoginDisplayHostCommon::SetDisplayEmail(const std::string& email) {
  if (GetExistingUserController()) {
    GetExistingUserController()->SetDisplayEmail(email);
  }
}

void LoginDisplayHostCommon::ShowAllowlistCheckFailedError() {
  StartWizard(UserAllowlistCheckScreenView::kScreenId);
}

void LoginDisplayHostCommon::UpdateWallpaper(
    const AccountId& prefilled_account) {
  auto* wallpaper_controller = ash::WallpaperController::Get();
  if (prefilled_account.is_valid()) {
    wallpaper_controller->ShowUserWallpaper(prefilled_account);
    return;
  }
  wallpaper_controller->ShowSigninWallpaper();
}

bool LoginDisplayHostCommon::IsUserAllowlisted(
    const AccountId& account_id,
    const std::optional<user_manager::UserType>& user_type) {
  if (!GetExistingUserController()) {
    return true;
  }
  return GetExistingUserController()->IsUserAllowlisted(account_id, user_type);
}

void LoginDisplayHostCommon::CancelPasswordChangedFlow() {
  if (GetExistingUserController()) {
    GetExistingUserController()->CancelPasswordChangedFlow();
  }

  OnCancelPasswordChangedFlow();
}

bool LoginDisplayHostCommon::HandleAccelerator(LoginAcceleratorAction action) {
  if (KioskController::Get().IsSessionStarting()) {
    KioskController::Get().HandleAccelerator(action);
    // Mark all accelerators as handled during kiosk launch so they are not
    // forwarded to the next accelerator target, meaning all accelerators are
    // suppressed during the kiosk launch.
    return true;
  }

  if (action == LoginAcceleratorAction::kShowFeedback) {
    login_feedback_ = std::make_unique<LoginFeedback>(
        ProfileHelper::Get()->GetSigninProfile());
    login_feedback_->Request(std::string());
    return true;
  }

  if (action == LoginAcceleratorAction::kLaunchDiagnostics) {
    // Don't handle this action if device is disabled.
    if (system::DeviceDisablingManager::
            IsDeviceDisabledDuringNormalOperation()) {
      return false;
    }
    DiagnosticsDialog::ShowDialog();
    return true;
  }

  // This path should only handle screen-specific accelerators, so we do not
  // need to create WebUI here.
  if (IsWizardControllerCreated() &&
      GetWizardController()->HandleAccelerator(action)) {
    return true;
  }

  // We check the reset screen accelerator after checking with the
  // WizardController because this accelerator is also handled by the
  // reset screen itself - triggering the rollback option. In such case we
  // return in the previous statement.
  if (action == LoginAcceleratorAction::kShowResetScreen) {
    ResetScreen::CheckIfPowerwashAllowed(
        base::BindOnce(&LoginDisplayHostCommon::OnPowerwashAllowedCallback,
                       weak_factory_.GetWeakPtr()));
    return true;
  }

  if (action == LoginAcceleratorAction::kCancelScreenAction) {
    if (!GetOobeUI() || !GetLoginWindowWidget() ||
        !GetLoginWindowWidget()->IsVisible()) {
      return false;
    }
    GetOobeUI()->GetCoreOobe()->ForwardCancel();
    return true;
  }

  return false;
}

void LoginDisplayHostCommon::SkipPostLoginScreensForDemoMode() {
  wizard_context_->skip_post_login_screens_for_tests = true;
}

void LoginDisplayHostCommon::SetScreenAfterManagedTos(OobeScreenId screen_id) {
  // If user stopped onboarding flow on TermsOfServiceScreen make sure that
  // next screen will be FamilyLinkNoticeView::kScreenId.
  if (screen_id == TermsOfServiceScreenView::kScreenId) {
    screen_id = FamilyLinkNoticeView::kScreenId;
  }
  wizard_context_->screen_after_managed_tos = screen_id;
}

void LoginDisplayHostCommon::OnPowerwashAllowedCallback(
    bool is_reset_allowed,
    std::optional<tpm_firmware_update::Mode> tpm_firmware_update_mode) {
  if (!is_reset_allowed) {
    return;
  }
  if (tpm_firmware_update_mode.has_value()) {
    // Force the TPM firmware update option to be enabled.
    local_state_->SetInteger(
        ash::prefs::kFactoryResetTPMFirmwareUpdateMode,
        std::to_underlying(tpm_firmware_update_mode.value()));
  }
  StartWizard(ResetView::kScreenId);
}

void LoginDisplayHostCommon::StartUserOnboarding() {
  oobe_metrics_helper_->RecordOnboardingStart(
      local_state_->GetTime(prefs::kOobeStartTime));
  StartWizard(LocaleSwitchView::kScreenId);
}

void LoginDisplayHostCommon::ResumeUserOnboarding(const PrefService& prefs,
                                                  OobeScreenId screen_id) {
  oobe_metrics_helper_->RecordOnboardingResume(screen_id);
  SetScreenAfterManagedTos(screen_id);

  if (features::IsOobeChoobeEnabled()) {
    if (ChoobeFlowController::ShouldResumeChoobe(prefs)) {
      oobe_metrics_helper_->RecordChoobeResume();
      GetWizardController()->CreateChoobeFlowController();
      GetWizardController()->choobe_flow_controller()->ResumeChoobe(prefs);
    }
  }

  // Try to show TermsOfServiceScreen first
  StartWizard(TermsOfServiceScreenView::kScreenId);
}

void LoginDisplayHostCommon::StartManagementTransition() {
  StartWizard(ManagementTransitionScreenView::kScreenId);
}

void LoginDisplayHostCommon::ShowTosForExistingUser() {
  SetScreenAfterManagedTos(ash::OOBE_SCREEN_UNKNOWN);
  StartUserOnboarding();
}

void LoginDisplayHostCommon::ShowNewTermsForFlexUsers() {
  SetScreenAfterManagedTos(ConsolidatedConsentScreenView::kScreenId);

  wizard_context_->is_cloud_ready_update_flow = true;
  StartWizard(TermsOfServiceScreenView::kScreenId);
}

void LoginDisplayHostCommon::ShowPasswordSelectionScreen() {
  StartWizard(PasswordSelectionScreenView::kScreenId);
}

void LoginDisplayHostCommon::ShowRemoveLocalAuthFactorsScreen() {
  StartWizard(RemoveLocalAuthFactorsScreenView::kScreenId);
}

void LoginDisplayHostCommon::SetAuthSessionForOnboarding(
    const UserContext& user_context) {
  wizard_context_->extra_factors_token = AuthSessionStorage::Get()->Store(
      std::make_unique<UserContext>(user_context));
}

void LoginDisplayHostCommon::ClearOnboardingAuthSession() {
  if (wizard_context_->extra_factors_token.has_value()) {
    AuthSessionStorage::Get()->Invalidate(
        wizard_context_->extra_factors_token.value(), base::DoNothing());
    wizard_context_->extra_factors_token = std::nullopt;
  }
}

void LoginDisplayHostCommon::StartEncryptionMigration(
    std::unique_ptr<UserContext> user_context,
    EncryptionMigrationMode migration_mode,
    base::OnceCallback<void(std::unique_ptr<UserContext>)> on_skip_migration) {
  StartWizard(EncryptionMigrationScreenView::kScreenId);

  EncryptionMigrationScreen* migration_screen =
      GetWizardController()->GetScreen<EncryptionMigrationScreen>();

  DCHECK(migration_screen);
  migration_screen->SetUserContext(std::move(user_context));
  migration_screen->SetMode(migration_mode);
  migration_screen->SetSkipMigrationCallback(std::move(on_skip_migration));
  migration_screen->SetupInitialView();
}

void LoginDisplayHostCommon::ShowSigninError(SigninError error,
                                             const std::string& details) {
  VLOG(1) << "Show error, error_id: " << std::to_underlying(error);

  if (error == SigninError::kKnownUserFailedNetworkNotConnected ||
      error == SigninError::kKnownUserFailedNetworkConnected) {
    if (!IsOobeUIDialogVisible()) {
      // Handled by Views UI.
      return;
    }
    OfflineLoginScreen* offline_login_screen =
        GetWizardController()->GetScreen<OfflineLoginScreen>();
    if (GetWizardController()->current_screen() == offline_login_screen) {
      offline_login_screen->ShowPasswordMismatchMessage();
      return;
    }
  }

  std::string error_text;
  switch (error) {
    case SigninError::kCaptivePortalError:
      error_text = l10n_util::GetStringFUTF8(
          IDS_LOGIN_ERROR_CAPTIVE_PORTAL,
          GetExistingUserController()->GetConnectedNetworkName());
      break;
    default:
      error_text = l10n_util::GetStringUTF8(ErrorToMessageId(error));
      break;
  }

  std::string keyboard_hint;

  // Only display hints about keyboard layout if the error is authentication-
  // related.
  if (IsAuthError(error)) {
    input_method::InputMethodManager* ime_manager =
        input_method::InputMethodManager::Get();
    // Display a hint to switch keyboards if there are other enabled input
    // methods.
    if (ime_manager->GetActiveIMEState()->GetNumEnabledInputMethods() > 1) {
      keyboard_hint =
          l10n_util::GetStringUTF8(IDS_LOGIN_ERROR_KEYBOARD_SWITCH_HINT);
    }
  }

  std::string help_link_text = l10n_util::GetStringUTF8(IDS_LEARN_MORE);

  GetWizardController()->GetScreen<SignInFatalErrorScreen>()->SetCustomError(
      error_text, keyboard_hint, details, help_link_text);
  StartWizard(SignInFatalErrorView::kScreenId);
}

void LoginDisplayHostCommon::ShowOobeNotCompletedError() {
  GetWizardController()->GetScreen<SignInFatalErrorScreen>()->SetErrorState(
      SignInFatalErrorScreen::Error::kOobeCompletionSkipped, base::DictValue());
  StartWizard(SignInFatalErrorView::kScreenId);
}

void LoginDisplayHostCommon::SAMLConfirmPassword(
    ::login::StringList scraped_passwords,
    std::unique_ptr<UserContext> user_context) {
  GetWizardController()
      ->GetScreen<SamlConfirmPasswordScreen>()
      ->SetContextAndPasswords(std::move(user_context),
                               std::move(scraped_passwords));
  StartWizard(SamlConfirmPasswordView::kScreenId);
}

void LoginDisplayHostCommon::ShowSamlConfirmPassword(
    std::unique_ptr<UserContext> user_context) {
  CHECK(features::IsManagedLocalPinAndPasswordEnabled());
  wizard_context_->user_context = std::move(user_context);
  StartWizard(SamlConfirmPasswordView::kScreenId);
}

WizardContext* LoginDisplayHostCommon::GetWizardContextForTesting() {
  return GetWizardContext();
}

void LoginDisplayHostCommon::OnBrowserCreated(BrowserDelegate* browser) {
  VLOG(4) << "OnBrowserCreated " << session_starting_;
  // Browsers created before session start (windows opened by extensions, for
  // example) are ignored.
  if (session_starting_) {
    // OnBrowserCreated is called when the browser is created, but not shown
    // yet. Lock window has to be closed at this point so that a browser window
    // exists and the window can acquire input focus.
    OnBrowserCreated();
    session_termination_observation_.Reset();
    browser_controller_observation_.Reset();
  }
}

WizardContext* LoginDisplayHostCommon::GetWizardContext() {
  return wizard_context_.get();
}

OobeMetricsHelper* LoginDisplayHostCommon::GetOobeMetricsHelper() {
  return oobe_metrics_helper_.get();
}

void LoginDisplayHostCommon::OnCancelPasswordChangedFlow() {
  LoginDisplayHost::default_host()->StartSignInScreen();
}

void LoginDisplayHostCommon::ShutdownDisplayHost() {
  LOG(WARNING) << "ShutdownDisplayHost";
  if (shutting_down_) {
    return;
  }
  shutting_down_ = true;

  Cleanup();
  base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
                                                                this);
}

void LoginDisplayHostCommon::OnStartSignInScreenCommon() {
  kiosk_app_menu_controller_.ConfigureKioskCallbacks();
  kiosk_app_menu_controller_.SendKioskApps();
}

void LoginDisplayHostCommon::ShowGaiaDialogCommon(
    const AccountId& prefilled_account) {
  if (GetExistingUserController()->IsSigninInProgress()) {
    return;
  }
  SetGaiaInputMethods(local_state_.get(), application_locale_storage_->Get(),
                      prefilled_account);

  if (!prefilled_account.is_valid()) {
    StartWizard(UserCreationView::kScreenId);
  } else {
    wizard_context_->gaia_config.prefilled_account = prefilled_account;
    StartWizard(GaiaView::kScreenId);
  }
}

void LoginDisplayHostCommon::AddWizardCreatedObserverForTests(
    base::RepeatingClosure on_created) {
  DCHECK(!on_wizard_controller_created_for_tests_);
  on_wizard_controller_created_for_tests_ = std::move(on_created);
}

base::WeakPtr<quick_start::TargetDeviceBootstrapController>
LoginDisplayHostCommon::GetQuickStartBootstrapController() {
  CHECK(wizard_context_->quick_start_enabled);
  if (!bootstrap_controller_) {
    Profile* profile = ProfileManager::GetActiveUserProfile();
    CHECK(profile);

    quick_start::QuickStartConnectivityService* service =
        quick_start::QuickStartConnectivityServiceFactory::GetForProfile(
            profile);
    CHECK(service);

    bootstrap_controller_ =
        std::make_unique<ash::quick_start::TargetDeviceBootstrapController>(
            &local_state_.get(), CreateSecondDeviceAuthBroker(),
            std::make_unique<AccessibilityManagerWrapper>(), service);
  }
  return bootstrap_controller_->GetAsWeakPtrForClient();
}

void LoginDisplayHostCommon::NotifyWizardCreated() {
  if (on_wizard_controller_created_for_tests_) {
    on_wizard_controller_created_for_tests_.Run();
  }
}

void LoginDisplayHostCommon::Cleanup() {
  if (wizard_context_->quick_start_enabled) {
    bootstrap_controller_.reset();
  }

  SigninProfileHandler::Get()->ClearSigninProfile(base::DoNothing());
  session_termination_observation_.Reset();
  browser_controller_observation_.Reset();
  login_ui_pref_controller_.reset();

  // Cancel kiosk session start since kiosk holds a pointer to `this` during
  // the start procedure.
  KioskController::Get().CancelSessionStart();
}

void LoginDisplayHostCommon::OnAppTerminating() {
  ShutdownDisplayHost();
}

}  // namespace ash
