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

#include "chrome/browser/ash/login/wizard_controller.h"

#include <signal.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/types.h>

#include <memory>
#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/constants/ash_switches.h"
#include "ash/public/cpp/login_types.h"
#include "ash/webui/help_app_ui/help_app_prefs.h"
#include "base/check.h"
#include "base/check_deref.h"
#include "base/check_is_test.h"
#include "base/command_line.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_string_value_serializer.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "base/syslog_logging.h"
#include "base/system/sys_info.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.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/arc/arc_util.h"
#include "chrome/browser/ash/customization/customization_document.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/ash/login/choobe_flow_controller.h"
#include "chrome/browser/ash/login/configuration_keys.h"
#include "chrome/browser/ash/login/demo_mode/demo_session.h"
#include "chrome/browser/ash/login/demo_mode/demo_setup_controller.h"
#include "chrome/browser/ash/login/enrollment/auto_enrollment_check_screen.h"
#include "chrome/browser/ash/login/enrollment/enrollment_screen.h"
#include "chrome/browser/ash/login/existing_user_controller.h"
#include "chrome/browser/ash/login/fjord_oobe/fjord_image_downloader.h"
#include "chrome/browser/ash/login/fjord_oobe/fjord_oobe_state_manager.h"
#include "chrome/browser/ash/login/fjord_oobe/fjord_oobe_util.h"
#include "chrome/browser/ash/login/fjord_oobe/proto/fjord_oobe_state.pb.h"
#include "chrome/browser/ash/login/hwid_checker.h"
#include "chrome/browser/ash/login/login_wizard.h"
#include "chrome/browser/ash/login/oobe_screen.h"
#include "chrome/browser/ash/login/quick_unlock/quick_unlock_utils.h"
#include "chrome/browser/ash/login/quickstart_controller.h"
// Make sure to include new screen to all relevant metric enums.
// LINT.IfChange(UsageMetrics)
#include "chrome/browser/ash/login/screens/account_selection_screen.h"
#include "chrome/browser/ash/login/screens/add_child_screen.h"
#include "chrome/browser/ash/login/screens/ai_intro_screen.h"
#include "chrome/browser/ash/login/screens/app_downloading_screen.h"
#include "chrome/browser/ash/login/screens/app_launch_splash_screen.h"
#include "chrome/browser/ash/login/screens/base_screen.h"
#include "chrome/browser/ash/login/screens/categories_selection_screen.h"
#include "chrome/browser/ash/login/screens/choobe_screen.h"
#include "chrome/browser/ash/login/screens/consolidated_consent_screen.h"
#include "chrome/browser/ash/login/screens/consumer_update_screen.h"
#include "chrome/browser/ash/login/screens/demo_preferences_screen.h"
#include "chrome/browser/ash/login/screens/demo_setup_screen.h"
#include "chrome/browser/ash/login/screens/device_disabled_screen.h"
#include "chrome/browser/ash/login/screens/display_size_screen.h"
#include "chrome/browser/ash/login/screens/drive_pinning_screen.h"
#include "chrome/browser/ash/login/screens/edu_coexistence_login_screen.h"
#include "chrome/browser/ash/login/screens/enable_adb_sideloading_screen.h"
#include "chrome/browser/ash/login/screens/enable_debugging_screen.h"
#include "chrome/browser/ash/login/screens/encryption_migration_screen.h"
#include "chrome/browser/ash/login/screens/error_screen.h"
#include "chrome/browser/ash/login/screens/family_link_notice_screen.h"
#include "chrome/browser/ash/login/screens/fingerprint_setup_screen.h"
#include "chrome/browser/ash/login/screens/fjord_fw_update_screen.h"
#include "chrome/browser/ash/login/screens/fjord_image_download_screen.h"
#include "chrome/browser/ash/login/screens/fjord_image_selection_screen.h"
#include "chrome/browser/ash/login/screens/fjord_station_setup_screen.h"
#include "chrome/browser/ash/login/screens/fjord_touch_controller_screen.h"
#include "chrome/browser/ash/login/screens/gaia_info_screen.h"
#include "chrome/browser/ash/login/screens/gaia_screen.h"
#include "chrome/browser/ash/login/screens/gemini_intro_screen.h"
#include "chrome/browser/ash/login/screens/gesture_navigation_screen.h"
#include "chrome/browser/ash/login/screens/hardware_data_collection_screen.h"
#include "chrome/browser/ash/login/screens/hid_detection_screen.h"
#include "chrome/browser/ash/login/screens/install_attributes_error_screen.h"
#include "chrome/browser/ash/login/screens/local_state_error_screen.h"
#include "chrome/browser/ash/login/screens/locale_switch_screen.h"
#include "chrome/browser/ash/login/screens/management_transition_screen.h"
#include "chrome/browser/ash/login/screens/marketing_opt_in_screen.h"
#include "chrome/browser/ash/login/screens/multidevice_setup_screen.h"
#include "chrome/browser/ash/login/screens/network_error.h"
#include "chrome/browser/ash/login/screens/network_screen.h"
#include "chrome/browser/ash/login/screens/offline_login_screen.h"
#include "chrome/browser/ash/login/screens/online_authentication_screen.h"
#include "chrome/browser/ash/login/screens/osauth/apply_online_password_screen.h"
#include "chrome/browser/ash/login/screens/osauth/cryptohome_recovery_screen.h"
#include "chrome/browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen.h"
#include "chrome/browser/ash/login/screens/osauth/enter_old_password_screen.h"
#include "chrome/browser/ash/login/screens/osauth/factor_setup_success_screen.h"
#include "chrome/browser/ash/login/screens/osauth/local_data_loss_warning_screen.h"
#include "chrome/browser/ash/login/screens/osauth/local_password_setup_screen.h"
#include "chrome/browser/ash/login/screens/osauth/osauth_error_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/osauth/remove_local_auth_factors_screen.h"
#include "chrome/browser/ash/login/screens/packaged_license_screen.h"
#include "chrome/browser/ash/login/screens/perks_discovery_screen.h"
#include "chrome/browser/ash/login/screens/personalized_recommend_apps_screen.h"
#include "chrome/browser/ash/login/screens/pin_setup_screen.h"
#include "chrome/browser/ash/login/screens/quick_start_screen.h"
#include "chrome/browser/ash/login/screens/recommend_apps_screen.h"
#include "chrome/browser/ash/login/screens/remote_activity_notification_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/screens/smart_privacy_protection_screen.h"
#include "chrome/browser/ash/login/screens/split_modifier_keyboard_info_screen.h"
#include "chrome/browser/ash/login/screens/sync_consent_screen.h"
#include "chrome/browser/ash/login/screens/theme_selection_screen.h"
#include "chrome/browser/ash/login/screens/touchpad_scroll_screen.h"
#include "chrome/browser/ash/login/screens/tpm_error_screen.h"
#include "chrome/browser/ash/login/screens/update_required_screen.h"
#include "chrome/browser/ash/login/screens/update_screen.h"
#include "chrome/browser/ash/login/screens/user_allowlist_check_screen.h"
#include "chrome/browser/ash/login/screens/user_creation_screen.h"
#include "chrome/browser/ash/login/screens/welcome_screen.h"
#include "chrome/browser/ash/login/screens/wrong_hwid_screen.h"
// Add new screens before this block. Add screens and exit reasons to
// OOBE histograms.
// LINT.ThenChange(//tools/metrics/histograms/metadata/oobe/histograms.xml)
#include "chrome/browser/ash/login/session/user_session_manager.h"
#include "chrome/browser/ash/login/startup_utils.h"
#include "chrome/browser/ash/login/wizard_context.h"
#include "chrome/browser/ash/multidevice_setup/multidevice_setup_client_factory.h"
#include "chrome/browser/ash/net/delay_network_call.h"
#include "chrome/browser/ash/net/rollback_network_config/rollback_network_config_service.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h"
#include "chrome/browser/ash/policy/enrollment/auto_enrollment_controller.h"
#include "chrome/browser/ash/policy/enrollment/enrollment_requisition_manager.h"
#include "chrome/browser/ash/settings/stats_reporting_controller.h"
#include "chrome/browser/ash/system/device_disabling_manager.h"
#include "chrome/browser/ash/system/timezone_resolver_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/enterprise/util/affiliation.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/metrics/cros_pre_choice_metrics_manager.h"
#include "chrome/browser/metrics/metrics_reporting_state.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/login/login_display_host.h"
#include "chrome/browser/ui/ash/system/system_tray_client_impl.h"
#include "chrome/browser/ui/webui/ash/login/account_selection_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/add_child_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/ai_intro_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/app_downloading_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/app_launch_splash_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/auto_enrollment_check_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/categories_selection_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/choobe_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/consolidated_consent_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/consumer_update_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/cryptohome_recovery_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/cryptohome_recovery_setup_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/demo_preferences_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/demo_setup_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/device_disabled_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/display_size_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/drive_pinning_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/enable_adb_sideloading_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/enable_debugging_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/encryption_migration_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/enrollment_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/enter_old_password_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/error_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/family_link_notice_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/fingerprint_setup_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/fjord_fw_update_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/fjord_image_download_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/fjord_image_selection_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/fjord_station_setup_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/fjord_touch_controller_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/gaia_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/gemini_intro_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/gesture_navigation_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/guest_tos_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/hardware_data_collection_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/hid_detection_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/install_attributes_error_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/local_password_setup_handler.h"
#include "chrome/browser/ui/webui/ash/login/local_state_error_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/marketing_opt_in_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/multidevice_setup_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/network_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/offline_login_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/online_authentication_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/oobe_ui.h"
#include "chrome/browser/ui/webui/ash/login/os_install_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/os_trial_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/osauth/apply_online_password_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/osauth/factor_setup_success_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/osauth/local_data_loss_warning_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/osauth/osauth_error_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/packaged_license_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/parental_handoff_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/password_selection_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/perks_discovery_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/personalized_recommend_apps_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/pin_setup_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/quick_start_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/recommend_apps_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/recovery_eligibility_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/remote_activity_notification_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/smart_privacy_protection_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/split_modifier_keyboard_info_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/sync_consent_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/terms_of_service_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/theme_selection_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/touchpad_scroll_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/tpm_error_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/update_required_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/update_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/browser/ui/webui/ash/login/welcome_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/wrong_hwid_screen_handler.h"
#include "chrome/browser/ui/webui/help/help_utils_chromeos.h"
#include "chrome/common/chrome_constants.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/ash/components/dbus/update_engine/update_engine_client.h"
#include "chromeos/ash/components/geolocation/system_location_provider.h"
#include "chromeos/ash/components/install_attributes/install_attributes.h"
#include "chromeos/ash/components/language_packs/language_pack_manager.h"
#include "chromeos/ash/components/login/auth/mount_performer.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/osauth/public/auth_session_storage.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ash/components/settings/cros_settings_provider.h"
#include "chromeos/ash/components/settings/timezone_settings.h"
#include "chromeos/ash/components/timezone/timezone_provider.h"
#include "chromeos/ash/components/timezone/timezone_request.h"
#include "chromeos/ash/components/timezone/timezone_util.h"
#include "chromeos/ash/experiences/arc/arc_features.h"
#include "chromeos/ash/experiences/arc/arc_prefs.h"
#include "chromeos/ash/experiences/arc/arc_util.h"
#include "chromeos/ash/experiences/arc/session/arc_bridge_service.h"
#include "chromeos/ash/services/cros_healthd/private/cpp/dlc_utils.h"
#include "chromeos/ash/services/rollback_network_config/public/mojom/rollback_network_config.mojom.h"
#include "components/application_locale_storage/application_locale_storage.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user_manager.h"
#include "components/user_manager/user_names.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "remoting/host/chromeos/features.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/service_manager/public/cpp/connector.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/base/accelerators/accelerator.h"

// Enable VLOG level 1.
#undef ENABLED_VLOG_LEVEL
#define ENABLED_VLOG_LEVEL 1

namespace ash {

namespace {

using ::content::BrowserThread;

bool g_using_zero_delays = false;

// Total timezone resolving process timeout.
const unsigned int kResolveTimeZoneTimeoutSeconds = 60;

constexpr const char kDefaultExitReason[] = "Next";
constexpr const char kResetScreenExitReason[] = "Cancel";

// TODO(crbug.com/40738890) Remove after stepping stone is set after M87.
constexpr char kLegacyUpdateScreenName[] = "update";

// Stores the list of all screens that should be shown when resuming OOBE.
const StaticOobeScreenId kResumableOobeScreens[] = {
    WelcomeView::kScreenId,
    NetworkScreenView::kScreenId,
    UpdateView::kScreenId,
    ConsumerUpdateScreenView::kScreenId,
};

const StaticOobeScreenId kResumablePostLoginScreens[] = {
    TermsOfServiceScreenView::kScreenId,
    SyncConsentScreenView::kScreenId,
    HWDataCollectionView::kScreenId,
    FingerprintSetupScreenView::kScreenId,
    GestureNavigationScreenView::kScreenId,
    RecommendAppsScreenView::kScreenId,
    AiIntroScreenView::kScreenId,
    GeminiIntroScreenView::kScreenId,
    PinSetupScreenView::kScreenId,
    MarketingOptInScreenView::kScreenId,
    MultiDeviceSetupScreenView::kScreenId,
    ConsolidatedConsentScreenView::kScreenId,
    ThemeSelectionScreenView::kScreenId,
    ChoobeScreenView::kScreenId,
    DisplaySizeScreenView::kScreenId,
    TouchpadScrollScreenView::kScreenId,
    CategoriesSelectionScreenView::kScreenId,
    PerksDiscoveryScreenView::kScreenId,
    SplitModifierKeyboardInfoScreenView::kScreenId,
};

const StaticOobeScreenId kScreensWithHiddenStatusArea[] = {
    AppLaunchSplashScreenView::kScreenId,
    EnableAdbSideloadingScreenView::kScreenId,
    EnableDebuggingScreenView::kScreenId,
    ManagementTransitionScreenView::kScreenId,
    TpmErrorView::kScreenId,
    InstallAttributesErrorView::kScreenId,
    WrongHWIDScreenView::kScreenId,
    LocalStateErrorScreenView::kScreenId,
};

bool IsResumableOobeScreen(OobeScreenId screen_id) {
  for (const auto& resumable_screen : kResumableOobeScreens) {
    if (screen_id == resumable_screen) {
      return true;
    }
  }
  return false;
}

bool ShouldHideStatusArea(OobeScreenId screen_id) {
  for (const auto& s : kScreensWithHiddenStatusArea) {
    if (screen_id == s) {
      return true;
    }
  }
  return false;
}

LoginDisplayHost* GetLoginDisplayHost() {
  return LoginDisplayHost::default_host();
}

OobeUI* GetOobeUI() {
  auto* host = LoginDisplayHost::default_host();
  return host ? host->GetOobeUI() : nullptr;
}

OobeScreenId PrefToScreenId(const std::string& pref_value) {
  if (pref_value == kLegacyUpdateScreenName) {
    return UpdateView::kScreenId;
  }
  return OobeScreenId(pref_value);
}

bool IsGaiaPageDefaultsToSAML() {
  int authentication_behavior = 0;
  bool authentication_behavior_set = CrosSettings::Get()->GetInteger(
      kLoginAuthenticationBehavior, &authentication_behavior);
  return authentication_behavior_set && authentication_behavior;
}

bool IsContextNeededForScreen(OobeScreenId screen_id) {
  return screen_id == SamlConfirmPasswordView::kScreenId ||
         screen_id == CryptohomeRecoveryScreenView::kScreenId ||
         screen_id == LocalDataLossWarningScreenView::kScreenId;
}

std::string SetupTypeToString(WizardContext::AuthChangeFlow flow_type) {
  switch (flow_type) {
    case WizardContext::AuthChangeFlow::kInitialSetup:
      return "Initial Setup";
    case WizardContext::AuthChangeFlow::kReauthentication:
      return "ReAuth";
    case WizardContext::AuthChangeFlow::kRecovery:
      return "Recovery";
  }
}
bool IsInitialSetup(const WizardContext& wizard_context) {
  return wizard_context.knowledge_factor_setup.auth_setup_flow ==
         WizardContext::AuthChangeFlow::kInitialSetup;
}

void InvalidateTokenAndRequestSignout(const WizardContext& wizard_context) {
  if (!wizard_context.extra_factors_token.has_value()) {
    // If there is no token, we can just sign out. This can happen when the user
    // is ephemeral.
    LOG(WARNING) << "No extra factors token to invalidate, directly requesting "
                    "sign out.";
    session_manager::SessionManager::Get()->RequestSignOut();
    return;
  }
  ash::AuthSessionStorage::Get()->Invalidate(
      wizard_context.extra_factors_token.value(),
      base::BindOnce(&session_manager::SessionManager::RequestSignOut,
                     base::Unretained(session_manager::SessionManager::Get())));
}

}  // namespace

// static
const int WizardController::kMinAudibleOutputVolumePercent = 10;

// static
bool WizardController::skip_enrollment_prompts_for_testing_ = false;

// static
WizardController* WizardController::default_controller() {
  auto* host = LoginDisplayHost::default_host();
  return (host && host->IsWizardControllerCreated())
             ? host->GetWizardController()
             : nullptr;
}

///////////////////////////////////////////////////////////////////////////////
// WizardController, public:

PrefService* WizardController::local_state_for_testing_ = nullptr;

WizardController::WizardController(
    PrefService* local_state,
    ::metrics::MetricsService* metrics_service,
    ApplicationLocaleStorage* application_locale_storage,
    scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
    policy::BrowserPolicyConnectorAsh* browser_policy_connector_ash,
    scoped_refptr<component_updater::ComponentManagerAsh> component_manager_ash,
    WizardContext* wizard_context)
    : local_state_(CHECK_DEREF(local_state)),
      metrics_service_(metrics_service),
      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)),
      component_manager_ash_(std::move(component_manager_ash)),
      quickstart_controller_(
          std::make_unique<quick_start::QuickStartController>(
              &local_state_.get())),
      screen_manager_(std::make_unique<ScreenManager>()),
      wizard_context_(wizard_context) {
  if (!metrics_service_) {
    CHECK_IS_TEST();
  }
  CHECK(component_manager_ash_);
  const auto has_been_skipped =
      wizard_context_->skip_post_login_screens_for_tests;
  wizard_context_->skip_post_login_screens_for_tests =
      has_been_skipped || switches::ShouldSkipOobePostLogin();
  AccessibilityManager* accessibility_manager = AccessibilityManager::Get();
  if (accessibility_manager) {
    // accessibility_manager could be null in Tests.
    accessibility_subscription_ = accessibility_manager->RegisterCallback(
        base::BindRepeating(&WizardController::OnAccessibilityStatusChanged,
                            weak_factory_.GetWeakPtr()));
  }
  if (GetOobeUI()) {
    // could be null in unit tests.
    screen_manager_->Init(CreateScreens());
    // If the frontend element is constructed and OobeScreensHandlerFactory is
    // created with a pending receiver, bind the receiver when the
    // WizardController is created.
    if (GetOobeUI()->GetOobeScreensHandlerFactory()) {
      GetOobeUI()->GetOobeScreensHandlerFactory()->BindScreensHandlerFactory();
    }
    // OOBE UI can be recreated in case of CrossOriginOpenerPolicyByDefault.
    // TODO(crbug.com/40138102): Remove this logic after WebUI split is done,
    // as screens should work with late binding/early unbinding in that case.
    oobe_ui_observation_.Observe(GetOobeUI());

    MaybeEnablePreChoiceMetrics();
  }
}

WizardController::~WizardController() {
  for (ScreenObserver& obs : screen_observers_) {
    obs.OnShutdown();
  }

  // Use `OobeUI` reference in `oobe_ui_observation_` because the destructor
  // could be called after `LoginDisplayHost` resets its reference to OobeUI.
  // As a result,`GetOobeUI()` would return nullptr but the actual OobeUI
  // instance might still be alive and could call us.
  // See http://crbug.com/384745864.
  OobeUI* oobe_ui = oobe_ui_observation_.GetSource();
  if (oobe_ui && oobe_ui->GetOobeScreensHandlerFactory()) {
    oobe_ui->GetOobeScreensHandlerFactory()->UnbindScreensHandlerFactory();
  }

  previous_screens_.clear();
  screen_manager_.reset();
}

void WizardController::Init(OobeScreenId first_screen) {
  DCHECK(!is_initialized());
  is_initialized_ = true;

  prescribed_enrollment_config_ =
      policy::EnrollmentConfig::GetPrescribedEnrollmentConfig(
          local_state_.get());

  VLOG(1) << "Starting OOBE wizard with screen: " << first_screen;

  bool oobe_complete = StartupUtils::IsOobeCompleted(local_state_.get());
  if (!oobe_complete) {
    UpdateOobeConfiguration();
    is_out_of_box_ = true;
  }

  wizard_context_->is_add_person_flow =
      oobe_complete && StartupUtils::IsDeviceOwned();

  MaybeNotifyFjordOobeStateManager(
      fjord_oobe_state::proto::FjordOobeStateInfo::FJORD_OOBE_STATE_START);
  if (fjord_util::ShouldShowFjordOobe() &&
      fjord_util::ShouldShowFjordOobeImageSwitch()) {
    fjord_image_downloader_ = std::make_unique<FjordImageDownloader>();
  }

  // This is a hacky way to check for local state corruption, because
  // it depends on the fact that the local state is loaded
  // synchronously and at the first demand.
  // ash::InstallAttributes::IsEnterpriseManaged() check is required
  // because currently powerwash is disabled for enterprise-enrolled devices.
  //
  // TODO (ygorshenin@): implement handling of the local state
  // corruption in the case of asynchronous loading.
  if (!ash::InstallAttributes::Get()->IsEnterpriseManaged()) {
    const PrefService::PrefInitializationStatus status =
        GetLocalState()->GetInitializationStatus();
    if (status == PrefService::INITIALIZATION_STATUS_ERROR) {
      OnLocalStateInitialized(false);
      return;
    }
    if (status == PrefService::INITIALIZATION_STATUS_WAITING) {
      GetLocalState()->AddPrefInitObserver(
          base::BindOnce(&WizardController::OnLocalStateInitialized,
                         weak_factory_.GetWeakPtr()));
    }
  }

  if (!oobe_complete) {
    bool updated =
        GetLocalState()->GetBoolean(prefs::kOobeConsumerUpdateCompleted) ||
        GetLocalState()->GetBoolean(prefs::kOobeCriticalUpdateCompleted);
    if (!updated) {
      GetLoginDisplayHost()->GetOobeMetricsHelper()->RecordChromeVersion();
    }
  }

  // Do not show the HID Detection screen if device is owned.
  if (!StartupUtils::IsDeviceOwned() &&
      HIDDetectionScreen::CanShowScreen(local_state_.get()) &&
      first_screen == ash::OOBE_SCREEN_UNKNOWN) {
    // TODO(https://crbug.com/1275960): Move logic into
    // HIDDetectionScreen::MaybeSkip.
    GetScreen<HIDDetectionScreen>()->CheckIsScreenRequired(
        base::BindOnce(&WizardController::OnHIDScreenNecessityCheck,
                       weak_factory_.GetWeakPtr()));
    return;
  }

  AdvanceToScreenAfterHIDDetection(first_screen);
}

void WizardController::OnDestroyingOobeUI() {
  previous_screens_.clear();

  // Reset `current_screen_` to prevent its usage after OobeUI is gone.
  ResetCurrentScreen();

  // Reset screens, they should not access handlers anymore.
  // TODO(https://crbug.com/1309022): This should probably be removed when all
  // the screen/handlers migrated to the new patterns.
  screen_manager_->Shutdown();
  oobe_ui_observation_.Reset();
}

void WizardController::HideCurrentScreen() {
  SetCurrentScreen(nullptr);
}

void WizardController::ContinueOobeFlow() {
  // Use the saved screen preference from Local State if exist.
  const std::string screen_pref =
      GetLocalState()->GetString(prefs::kOobeScreenPending);
  const OobeScreenId screen_id = PrefToScreenId(screen_pref);
  GetLoginDisplayHost()->GetOobeMetricsHelper()->RecordPreLoginOobeResume(
      screen_id);

  if (!screen_pref.empty() && HasScreen(screen_id)) {
    AdvanceToScreen(screen_id);
  } else {
    ShowPackagedLicenseScreen();
  }
}

void WizardController::AdvanceToScreenAfterHIDDetection(
    OobeScreenId first_screen) {
  OobeScreenId actual_first_screen = first_screen;
  if (actual_first_screen == ash::OOBE_SCREEN_UNKNOWN) {
    if (!is_out_of_box_) {
      DeviceSettingsService::Get()->GetOwnershipStatusAsync(
          base::BindOnce(&WizardController::OnOwnershipStatusCheckDone,
                         weak_factory_.GetWeakPtr()));
      return;
    }

    // Use the saved screen preference from Local State.
    const std::string screen_pref =
        GetLocalState()->GetString(prefs::kOobeScreenPending);
    if (!screen_pref.empty() && HasScreen(PrefToScreenId(screen_pref))) {
      actual_first_screen = PrefToScreenId(screen_pref);
    } else {
      actual_first_screen = WelcomeView::kScreenId;
    }
  }

  first_screen_for_testing_ = actual_first_screen;

  if (!IsMachineHWIDCorrect() &&
      !StartupUtils::IsDeviceRegistered(local_state_.get()) &&
      first_screen == ash::OOBE_SCREEN_UNKNOWN) {
    ShowWrongHWIDScreen();
  } else {
    AdvanceToScreen(actual_first_screen);
  }

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kOobeSkipToLogin)) {
    SkipToLoginForTesting();
  }
}

ErrorScreen* WizardController::GetErrorScreen() {
  return GetOobeUI()->GetErrorScreen();
}

bool WizardController::HasScreen(OobeScreenId screen_id) const {
  return screen_manager_->HasScreen(screen_id);
}

// static
bool WizardController::IsErrorScreen(OobeScreenId screen_id) {
  return screen_id == ErrorScreenView::kScreenId;
}

BaseScreen* WizardController::GetScreen(OobeScreenId screen_id) {
  if (WizardController::IsErrorScreen(screen_id)) {
    return GetErrorScreen();
  }
  return screen_manager_->GetScreen(screen_id);
}

OobeScreenId WizardController::GetScreenByName(const std::string& screen_name) {
  if (screen_name == ErrorScreenView::kScreenId.name) {
    return ErrorScreenView::kScreenId;
  }
  return screen_manager_->GetScreenByName(screen_name);
}

void WizardController::SetCurrentScreenForTesting(BaseScreen* screen) {
  current_screen_ = screen;
}

void WizardController::SetSharedURLLoaderFactoryForTesting(
    scoped_refptr<network::SharedURLLoaderFactory> factory) {
  shared_url_loader_factory_ = factory;
}

std::vector<std::pair<OobeScreenId, std::unique_ptr<BaseScreen>>>
WizardController::CreateScreens() {
  OobeUI* oobe_ui = GetOobeUI();

  std::vector<std::pair<OobeScreenId, std::unique_ptr<BaseScreen>>> result;

  auto append = [&](std::unique_ptr<BaseScreen> screen) {
    result.emplace_back(screen->screen_id(), std::move(screen));
  };

  if (oobe_ui->display_type() == OobeUI::kOobeDisplay) {
    append(std::make_unique<WelcomeScreen>(
        &local_state_.get(), &application_locale_storage_.get(),
        oobe_ui->GetView<WelcomeScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnWelcomeScreenExit,
                            weak_factory_.GetWeakPtr())));

    append(std::make_unique<DemoPreferencesScreen>(
        &local_state_.get(),
        oobe_ui->GetView<DemoPreferencesScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnDemoPreferencesScreenExit,
                            weak_factory_.GetWeakPtr())));
  }

  append(std::make_unique<QuickStartScreen>(
      oobe_ui->GetView<QuickStartScreenHandler>()->AsWeakPtr(),
      quick_start_controller(),
      base::BindRepeating(&WizardController::OnQuickStartScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<NetworkScreen>(
      oobe_ui->GetView<NetworkScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnNetworkScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<UpdateScreen>(
      &local_state_.get(), oobe_ui->GetView<UpdateScreenHandler>()->AsWeakPtr(),
      oobe_ui->GetErrorScreen(),
      base::BindRepeating(&WizardController::OnUpdateScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<EnrollmentScreen>(
      &local_state_.get(), shared_url_loader_factory_,
      &browser_policy_connector_ash_.get(),
      oobe_ui->GetView<EnrollmentScreenHandler>()->AsWeakPtr(),
      oobe_ui->GetErrorScreen(),
      base::BindRepeating(&WizardController::OnEnrollmentScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<ResetScreen>(
      &local_state_.get(), oobe_ui->GetView<ResetScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnResetScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<DemoSetupScreen>(
      oobe_ui->GetView<DemoSetupScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnDemoSetupScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<EnableAdbSideloadingScreen>(
      &local_state_.get(),
      oobe_ui->GetView<EnableAdbSideloadingScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnEnableAdbSideloadingScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<EnableDebuggingScreen>(
      &local_state_.get(),
      oobe_ui->GetView<EnableDebuggingScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnEnableDebuggingScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<LocaleSwitchScreen>(
      &local_state_.get(), &application_locale_storage_.get(),
      oobe_ui->GetView<LocaleSwitchScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnLocaleSwitchScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<RecoveryEligibilityScreen>(
      base::BindRepeating(&WizardController::OnRecoveryEligibilityScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<CryptohomeRecoverySetupScreen>(
      &local_state_.get(),
      oobe_ui->GetView<CryptohomeRecoverySetupScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(
          &WizardController::OnCryptohomeRecoverySetupScreenExit,
          weak_factory_.GetWeakPtr())));
  append(std::make_unique<TermsOfServiceScreen>(
      shared_url_loader_factory_, &browser_policy_connector_ash_.get(),
      oobe_ui->GetView<TermsOfServiceScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnTermsOfServiceScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<SyncConsentScreen>(
      oobe_ui->GetView<SyncConsentScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnSyncConsentScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<RecommendAppsScreen>(
      oobe_ui->GetView<RecommendAppsScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnRecommendAppsScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<AppDownloadingScreen>(
      oobe_ui->GetView<AppDownloadingScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnAppDownloadingScreenExit,
                          weak_factory_.GetWeakPtr())));

  if (features::IsOobeAiIntroEnabled()) {
    append(std::make_unique<AiIntroScreen>(
        oobe_ui->GetView<AiIntroScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnAiIntroScreenExit,
                            weak_factory_.GetWeakPtr())));
  }

  append(std::make_unique<GeminiIntroScreen>(
      oobe_ui->GetView<GeminiIntroScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnGeminiIntroScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<WrongHWIDScreen>(
      oobe_ui->GetView<WrongHWIDScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnWrongHWIDScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<LocalStateErrorScreen>(
      oobe_ui->GetView<LocalStateErrorScreenHandler>()->AsWeakPtr()));

  if (HIDDetectionScreen::CanShowScreen(local_state_.get())) {
    append(std::make_unique<HIDDetectionScreen>(
        &local_state_.get(),
        oobe_ui->GetView<HIDDetectionScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnHidDetectionScreenExit,
                            weak_factory_.GetWeakPtr())));
  }

  append(std::make_unique<AutoEnrollmentCheckScreen>(
      &local_state_.get(),
      oobe_ui->GetView<AutoEnrollmentCheckScreenHandler>()->AsWeakPtr(),
      oobe_ui->GetErrorScreen(),
      base::BindRepeating(&WizardController::OnAutoEnrollmentCheckScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<DeviceDisabledScreen>(
      oobe_ui->GetView<DeviceDisabledScreenHandler>()->AsWeakPtr()));
  append(std::make_unique<EncryptionMigrationScreen>(
      oobe_ui->GetView<EncryptionMigrationScreenHandler>()->AsWeakPtr()));
  append(std::make_unique<ManagementTransitionScreen>(
      oobe_ui->GetView<ManagementTransitionScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnManagementTransitionScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<UpdateRequiredScreen>(
      &browser_policy_connector_ash_.get(),
      oobe_ui->GetView<UpdateRequiredScreenHandler>()->AsWeakPtr(),
      oobe_ui->GetErrorScreen(),
      base::BindRepeating(&WizardController::OnUpdateRequiredScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<MultiDeviceSetupScreen>(
      oobe_ui->GetView<MultiDeviceSetupScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnMultiDeviceSetupScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<PinSetupScreen>(
      &local_state_.get(),
      oobe_ui->GetView<PinSetupScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnPinSetupScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<FingerprintSetupScreen>(
      oobe_ui->GetView<FingerprintSetupScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnFingerprintSetupScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<GestureNavigationScreen>(
      oobe_ui->GetView<GestureNavigationScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnGestureNavigationScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<MarketingOptInScreen>(
      &local_state_.get(),
      oobe_ui->GetView<MarketingOptInScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnMarketingOptInScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<PackagedLicenseScreen>(
      &local_state_.get(),
      oobe_ui->GetView<PackagedLicenseScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnPackagedLicenseScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<GaiaInfoScreen>(
      oobe_ui->GetView<GaiaInfoScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnGaiaInfoScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<GaiaScreen>(
      &local_state_.get(), shared_url_loader_factory_,
      browser_policy_connector_ash_->device_management_service(),
      oobe_ui->GetView<GaiaScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnGaiaScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<OnlineAuthenticationScreen>(
      oobe_ui->GetView<OnlineAuthenticationScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnOnlineAuthenticationScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<UserAllowlistCheckScreen>(
      oobe_ui->GetView<UserAllowlistCheckScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnUserAllowlistCheckScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<SamlConfirmPasswordScreen>(
      oobe_ui->GetView<SamlConfirmPasswordHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnSamlConfirmPasswordScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<OfflineLoginScreen>(
      &local_state_.get(), &browser_policy_connector_ash_.get(),
      oobe_ui->GetView<OfflineLoginScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnOfflineLoginScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<TpmErrorScreen>(
      &local_state_.get(),
      oobe_ui->GetView<TpmErrorScreenHandler>()->AsWeakPtr()));

  append(std::make_unique<InstallAttributesErrorScreen>(
      oobe_ui->GetView<InstallAttributesErrorScreenHandler>()->AsWeakPtr()));

  append(std::make_unique<FamilyLinkNoticeScreen>(
      oobe_ui->GetView<FamilyLinkNoticeScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnFamilyLinkNoticeScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<UserCreationScreen>(
      oobe_ui->GetView<UserCreationScreenHandler>()->AsWeakPtr(),
      oobe_ui->GetErrorScreen(),
      base::BindRepeating(&WizardController::OnUserCreationScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<AddChildScreen>(
      oobe_ui->GetView<AddChildScreenHandler>()->AsWeakPtr(),
      oobe_ui->GetErrorScreen(),
      base::BindRepeating(&WizardController::OnAddChildScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<EduCoexistenceLoginScreen>(
      base::BindRepeating(&WizardController::OnEduCoexistenceLoginScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<SignInFatalErrorScreen>(
      oobe_ui->GetView<SignInFatalErrorScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnSignInFatalErrorScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<ParentalHandoffScreen>(
      oobe_ui->GetView<ParentalHandoffScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnParentalHandoffScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<ConsolidatedConsentScreen>(
      &local_state_.get(), &application_locale_storage_.get(),
      metrics_service_.get(),
      oobe_ui->GetView<ConsolidatedConsentScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnConsolidatedConsentScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<GuestTosScreen>(
      &local_state_.get(), &application_locale_storage_.get(),
      oobe_ui->GetView<GuestTosScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnGuestTosScreenExit,
                          weak_factory_.GetWeakPtr())));

  if (switches::IsOsInstallAllowed()) {
    append(std::make_unique<OsInstallScreen>(
        oobe_ui->GetView<OsInstallScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnOsInstallScreenExit,
                            weak_factory_.GetWeakPtr())));
    append(std::make_unique<OsTrialScreen>(
        oobe_ui->GetView<OsTrialScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnOsTrialScreenExit,
                            weak_factory_.GetWeakPtr())));
  }

  if (switches::IsRevenBranding()) {
    append(std::make_unique<HWDataCollectionScreen>(
        oobe_ui->GetView<HWDataCollectionScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnHWDataCollectionScreenExit,
                            weak_factory_.GetWeakPtr())));
  }

  append(std::make_unique<SmartPrivacyProtectionScreen>(
      oobe_ui->GetView<SmartPrivacyProtectionScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnSmartPrivacyProtectionScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<ThemeSelectionScreen>(
      oobe_ui->GetView<ThemeSelectionScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnThemeSelectionScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<RemoteActivityNotificationScreen>(
      oobe_ui->GetView<RemoteActivityNotificationScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(
          &WizardController::OnRemoteActivityNotificationScreenExit,
          weak_factory_.GetWeakPtr())));

  append(std::make_unique<CryptohomeRecoveryScreen>(
      local_state_.get(), shared_url_loader_factory_,
      oobe_ui->GetView<CryptohomeRecoveryScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnCryptohomeRecoveryScreenExit,
                          weak_factory_.GetWeakPtr())));

  if (features::IsOobeChoobeEnabled()) {
    append(std::make_unique<ChoobeScreen>(
        oobe_ui->GetView<ChoobeScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnChoobeScreenExit,
                            weak_factory_.GetWeakPtr())));
  }

  if (features::IsOobeTouchpadScrollEnabled()) {
    append(std::make_unique<TouchpadScrollScreen>(
        oobe_ui->GetView<TouchpadScrollScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnTouchpadScreenExit,
                            weak_factory_.GetWeakPtr())));
  }
  if (features::IsOobeDisplaySizeEnabled()) {
    append(std::make_unique<DisplaySizeScreen>(
        oobe_ui->GetView<DisplaySizeScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnDisplaySizeScreenExit,
                            weak_factory_.GetWeakPtr())));
  }

  if (drive::util::IsOobeDrivePinningScreenEnabled()) {
    append(std::make_unique<DrivePinningScreen>(
        oobe_ui->GetView<DrivePinningScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnDrivePinningScreenExit,
                            weak_factory_.GetWeakPtr())));
  }

  if (features::IsOobeSoftwareUpdateEnabled()) {
    append(std::make_unique<ConsumerUpdateScreen>(
        &local_state_.get(),
        oobe_ui->GetView<ConsumerUpdateScreenHandler>()->AsWeakPtr(),
        oobe_ui->GetErrorScreen(),
        base::BindRepeating(&WizardController::OnConsumerUpdateScreenExit,
                            weak_factory_.GetWeakPtr())));
  }

  // We don't guard creation of those screens via feature flag to prevent cases
  // when we didn't apply Finch config, but user restarted their device
  // and our screen being marked as resumable is expected to be shown, but Finch
  // decided that feature should be turned off.
  // Instead, we will check for feature flag inside each screen's `MaybeSkip()`.
  append(std::make_unique<CategoriesSelectionScreen>(
      oobe_ui->GetView<CategoriesSelectionScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnCategoriesSelectionScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<PersonalizedRecommendAppsScreen>(
      oobe_ui->GetView<PersonalizedRecommendAppsScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(
          &WizardController::OnPersonalizedRecomendAppsScreenExit,
          weak_factory_.GetWeakPtr())));

  append(std::make_unique<SplitModifierKeyboardInfoScreen>(
      oobe_ui->GetView<SplitModifierKeyboardInfoScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(
          &WizardController::OnSplitModifierKeyboardInfoScreenExit,
          weak_factory_.GetWeakPtr())));

  append(std::make_unique<PasswordSelectionScreen>(
      oobe_ui->GetView<PasswordSelectionScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnPasswordSelectionScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<ApplyOnlinePasswordScreen>(
      &local_state_.get(),
      oobe_ui->GetView<ApplyOnlinePasswordScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnApplyOnlinePasswordScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<LocalPasswordSetupScreen>(
      &local_state_.get(),
      oobe_ui->GetView<LocalPasswordSetupHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnLocalPasswordSetupScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<LocalDataLossWarningScreen>(
      local_state_.get(),
      oobe_ui->GetView<LocalDataLossWarningScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnLocalDataLossWarningScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<EnterOldPasswordScreen>(
      local_state_.get(),
      oobe_ui->GetView<EnterOldPasswordScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnEnterOldPasswordScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<PerksDiscoveryScreen>(
      oobe_ui->GetView<PerksDiscoveryScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnPerksDiscoveryScreenExit,
                          weak_factory_.GetWeakPtr())));

  append(std::make_unique<OSAuthErrorScreen>(
      oobe_ui->GetView<OSAuthErrorScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnOSAuthErrorScreenExit,
                          weak_factory_.GetWeakPtr())));
  append(std::make_unique<FactorSetupSuccessScreen>(
      oobe_ui->GetView<FactorSetupSuccessScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnFactorSetupSuccessScreenExit,
                          weak_factory_.GetWeakPtr())));

  if (features::IsOobeAddUserDuringEnrollmentEnabled()) {
    append(std::make_unique<AccountSelectionScreen>(
        oobe_ui->GetView<AccountSelectionScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnAccountSelectionScreenExit,
                            weak_factory_.GetWeakPtr())));
  }

  append(std::make_unique<AppLaunchSplashScreen>(
      oobe_ui->GetView<AppLaunchSplashScreenHandler>()->AsWeakPtr(),
      oobe_ui->GetErrorScreen(),
      base::BindRepeating(&WizardController::OnAppLaunchSplashScreenExit,
                          weak_factory_.GetWeakPtr())));

  if (fjord_util::ShouldShowFjordOobe()) {
    append(std::make_unique<FjordTouchControllerScreen>(
        oobe_ui->GetView<FjordTouchControllerScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnFjordTouchControllerScreenExit,
                            weak_factory_.GetWeakPtr())));
    append(std::make_unique<FjordStationSetupScreen>(
        oobe_ui->GetView<FjordStationSetupScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnFjordStationSetupScreenExit,
                            weak_factory_.GetWeakPtr())));
    append(std::make_unique<FjordFwUpdateScreen>(
        oobe_ui->GetView<FjordFwUpdateScreenHandler>()->AsWeakPtr(),
        base::BindRepeating(&WizardController::OnFjordFwUpdateScreenExit,
                            weak_factory_.GetWeakPtr())));
    if (fjord_util::ShouldShowFjordOobeImageSwitch()) {
      append(std::make_unique<FjordImageSelectionScreen>(
          oobe_ui->GetView<FjordImageSelectionScreenHandler>()->AsWeakPtr(),
          base::BindRepeating(
              &WizardController::OnFjordImageSelectionScreenExit,
              weak_factory_.GetWeakPtr())));
      append(std::make_unique<FjordImageDownloadScreen>(
          oobe_ui->GetView<FjordImageDownloadScreenHandler>()->AsWeakPtr(),
          base::BindRepeating(&WizardController::OnFjordImageDownloadScreenExit,
                              weak_factory_.GetWeakPtr())));
    }
  }
  append(std::make_unique<RemoveLocalAuthFactorsScreen>(
      &local_state_.get(),
      oobe_ui->GetView<RemoveLocalAuthFactorsScreenHandler>()->AsWeakPtr(),
      base::BindRepeating(&WizardController::OnRemoveLocalAuthFactorsScreenExit,
                          weak_factory_.GetWeakPtr())));

  return result;
}

void WizardController::ShowWelcomeScreen() {
  SetCurrentScreen(GetScreen(WelcomeView::kScreenId));
}

void WizardController::ShowQuickStartScreen() {
  CHECK(wizard_context_->quick_start_enabled);
  SetCurrentScreen(GetScreen(QuickStartView::kScreenId));
}

void WizardController::ShowNetworkScreen() {
  if (switches::IsOOBENetworkSetupSkippedForTesting()) {
    OnNetworkScreenExit(NetworkScreen::Result::NOT_APPLICABLE);
    return;
  }

  SetCurrentScreen(GetScreen(NetworkScreenView::kScreenId));
}

void WizardController::OnOwnershipStatusCheckDone(
    DeviceSettingsService::OwnershipStatus status) {
  if (status == DeviceSettingsService::OwnershipStatus::kOwnershipNone) {
    ContinueOobeFlow();
  } else {
    ShowLoginScreen();
  }
}

void WizardController::ShowSignInFatalErrorScreen(
    SignInFatalErrorScreen::Error error,
    base::DictValue params) {
  GetScreen<SignInFatalErrorScreen>()->SetErrorState(error, std::move(params));
  AdvanceToScreen(SignInFatalErrorView::kScreenId);
}

void WizardController::OnSignInFatalErrorScreenExit() {
  OnScreenExit(SignInFatalErrorView::kScreenId, kDefaultExitReason);

  const bool is_scraped_password_verification_failure =
      wizard_context_->is_scraped_password_verification_failure.value_or(false);
  wizard_context_->is_scraped_password_verification_failure.reset();

  // Saml confirm password screen's `kScrapedPasswordVerificationFailure` in the
  // initial setup happens after cryptohome mount, so to properly handle errors
  // we have to make sure we invalidate the token and sign out.
  if (features::IsManagedLocalPinAndPasswordEnabled() &&
      IsInitialSetup(*wizard_context_) &&
      is_scraped_password_verification_failure) {
    InvalidateTokenAndRequestSignout(*wizard_context_);
    return;
  }

  if (previous_screens_.contains(current_screen_) &&
      IsContextNeededForScreen(
          previous_screens_[current_screen_]->screen_id())) {
    // If the last screen user have visited before reaching SignInFatalError
    // screen was a screen that needs user context, we should not go back there
    // because the context is lost at this point. We should go to the Gaia
    // screen instead.
    previous_screens_[current_screen_] = GetScreen(GaiaView::kScreenId);
  }

  // It's possible to get on the SignInFatalError screen both from the user pods
  // and from the Gaia sign-in screen. The screen exits when user presses
  // "try again". Go to the previous screen if it is set. Otherwise go to the
  // login screen with pods.
  if (MaybeSetToPreviousScreen()) {
    return;
  }
  ShowLoginScreen();
}

void WizardController::ShowLoginScreen() {
  VLOG(1) << "Showing login screen.";
  UpdateStatusAreaVisibilityForScreen(GaiaView::kScreenId);
  GetLoginDisplayHost()->StartSignInScreen();
}

void WizardController::ShowGaiaInfoScreen() {
  SetCurrentScreen(GetScreen(GaiaInfoScreenView::kScreenId));
}

void WizardController::ShowAddChildScreen() {
  SetCurrentScreen(GetScreen(AddChildScreenView::kScreenId));
}

void WizardController::ShowConsumerUpdateScreen() {
  SetCurrentScreen(GetScreen(ConsumerUpdateScreenView::kScreenId));
}

void WizardController::ShowLocalPasswordSetupScreen() {
  SetCurrentScreen(GetScreen(LocalPasswordSetupView::kScreenId));
}

void WizardController::ShowApplyOnlinePasswordScreen() {
  SetCurrentScreen(GetScreen(ApplyOnlinePasswordScreenView::kScreenId));
}

void WizardController::ShowLocalDataLossWarningScreen() {
  SetCurrentScreen(GetScreen(LocalDataLossWarningScreenView::kScreenId));
}

void WizardController::ShowEnterOldPasswordScreen() {
  SetCurrentScreen(GetScreen(EnterOldPasswordScreenView::kScreenId));
}

void WizardController::ShowEnrollmentScreen() {
  MaybeAbortQuickStartFlow(quick_start::QuickStartController::AbortFlowReason::
                               ENTERPRISE_ENROLLMENT);

  // Update the enrollment configuration and start the screen.
  GetLoginDisplayHost()->GetOobeMetricsHelper()->RecordEnrollingUserType();
  prescribed_enrollment_config_ =
      policy::EnrollmentConfig::GetPrescribedEnrollmentConfig(
          local_state_.get());
  StartEnrollmentScreen();
}

void WizardController::ShowDemoModePreferencesScreen() {
  SetCurrentScreen(GetScreen(DemoPreferencesScreenView::kScreenId));
}

void WizardController::ShowDemoModeSetupScreen() {
  SetCurrentScreen(GetScreen(DemoSetupScreenView::kScreenId));
}

void WizardController::ShowDrivePinningScreen() {
  if (drive::util::IsOobeDrivePinningAvailable()) {
    SetCurrentScreen(GetScreen(DrivePinningScreenView::kScreenId));
  } else {
    OnDrivePinningScreenExit(DrivePinningScreen::Result::NOT_APPLICABLE);
  }
}

void WizardController::ShowResetScreen() {
  SetCurrentScreen(GetScreen(ResetView::kScreenId));
}

void WizardController::ShowEnableAdbSideloadingScreen() {
  SetCurrentScreen(GetScreen(EnableAdbSideloadingScreenView::kScreenId));
}

void WizardController::ShowEnableDebuggingScreen() {
  SetCurrentScreen(GetScreen(EnableDebuggingScreenView::kScreenId));
}

void WizardController::ShowTermsOfServiceScreen() {
  SetCurrentScreen(GetScreen(TermsOfServiceScreenView::kScreenId));
}

void WizardController::ShowFamilyLinkNoticeScreen() {
  AdvanceToScreen(FamilyLinkNoticeView::kScreenId);
}

void WizardController::ShowSyncConsentScreen() {
  // First screen after login. Perform a timezone request so that any screens
  // relying on geolocation can tailor their contents according to the user's
  // region. Currently used on the MarketingOptInScreen.
  StartNetworkTimezoneResolve();

  SetCurrentScreen(GetScreen(SyncConsentScreenView::kScreenId));
}

void WizardController::StartAuthFactorsSetup() {
  ShowCryptohomeRecoverySetupScreen();
}

void WizardController::ShowCryptohomeRecoverySetupScreen() {
  SetCurrentScreen(GetScreen(CryptohomeRecoverySetupScreenView::kScreenId));
}

void WizardController::ShowPasswordSelectionScreen() {
  SetCurrentScreen(GetScreen(PasswordSelectionScreenView::kScreenId));
}

void WizardController::ShowOSAuthErrorScreen() {
  CHECK(wizard_context_->osauth_error.has_value());
  SetCurrentScreen(GetScreen(OSAuthErrorScreenView::kScreenId));
}

void WizardController::ShowFactorSetupSuccessScreen() {
  SetCurrentScreen(GetScreen(FactorSetupSuccessScreenView::kScreenId));
}

void WizardController::ShowFingerprintSetupScreen() {
  SetCurrentScreen(GetScreen(FingerprintSetupScreenView::kScreenId));
}

void WizardController::ShowPinSetupScreenAsSecondaryFactor() {
  wizard_context_->knowledge_factor_setup.pin_setup_mode =
      WizardContext::PinSetupMode::kSetupAsSecondaryFactor;
  SetCurrentScreen(GetScreen(PinSetupScreenView::kScreenId));
}

void WizardController::ShowPinSetupScreenAsMainFactor() {
  wizard_context_->knowledge_factor_setup.pin_setup_mode =
      WizardContext::PinSetupMode::kSetupAsPrimaryFactor;
  SetCurrentScreen(GetScreen(PinSetupScreenView::kScreenId));
}

void WizardController::ShowPinSetupScreenForRecovery() {
  wizard_context_->knowledge_factor_setup.pin_setup_mode =
      WizardContext::PinSetupMode::kRecovery;
  SetCurrentScreen(GetScreen(PinSetupScreenView::kScreenId));
}

void WizardController::ShowThemeSelectionScreen() {
  SetCurrentScreen(GetScreen(ThemeSelectionScreenView::kScreenId));
}

void WizardController::ShowMarketingOptInScreen() {
  SetCurrentScreen(GetScreen(MarketingOptInScreenView::kScreenId));
}

void WizardController::ShowRecommendAppsScreen() {
  SetCurrentScreen(GetScreen(RecommendAppsScreenView::kScreenId));
}

void WizardController::ShowRemoteActivityNotificationScreen() {
  SetCurrentScreen(GetScreen(RemoteActivityNotificationView::kScreenId));
}

void WizardController::ShowAppDownloadingScreen() {
  SetCurrentScreen(GetScreen(AppDownloadingScreenView::kScreenId));
}

void WizardController::ShowAiIntroScreen() {
  SetCurrentScreen(GetScreen(AiIntroScreenView::kScreenId));
}

void WizardController::ShowGeminiIntroScreen() {
  SetCurrentScreen(GetScreen(GeminiIntroScreenView::kScreenId));
}

void WizardController::ShowWrongHWIDScreen() {
  SetCurrentScreen(GetScreen(WrongHWIDScreenView::kScreenId));
}

void WizardController::ShowAutoEnrollmentCheckScreen() {
  if (features::IsOobeAutoEnrollmentCheckForcedEnabled() &&
      wizard_context_->skip_auto_enrollment_check_for_tests) {
    OnAutoEnrollmentCheckScreenExit(
        AutoEnrollmentCheckScreen::Result::NOT_APPLICABLE);
    return;
  }

  AutoEnrollmentCheckScreen* screen = GetScreen<AutoEnrollmentCheckScreen>();
  screen->set_auto_enrollment_controller(GetAutoEnrollmentController());
  SetCurrentScreen(screen);
}

void WizardController::ShowHIDDetectionScreen() {
  SetCurrentScreen(GetScreen(HIDDetectionView::kScreenId));
}

void WizardController::ShowDeviceDisabledScreen() {
  SetCurrentScreen(GetScreen(DeviceDisabledScreenView::kScreenId));
}

void WizardController::ShowEncryptionMigrationScreen() {
  SetCurrentScreen(GetScreen(EncryptionMigrationScreenView::kScreenId));
}

void WizardController::ShowManagementTransitionScreen() {
  SetCurrentScreen(GetScreen(ManagementTransitionScreenView::kScreenId));
}

void WizardController::ShowUpdateRequiredScreen() {
  SetCurrentScreen(GetScreen(UpdateRequiredView::kScreenId));
}

void WizardController::ShowMultiDeviceSetupScreen() {
  SetCurrentScreen(GetScreen(MultiDeviceSetupScreenView::kScreenId));
}

void WizardController::ShowGestureNavigationScreen() {
  SetCurrentScreen(GetScreen(GestureNavigationScreenView::kScreenId));
}

void WizardController::ShowPackagedLicenseScreen() {
  SetCurrentScreen(GetScreen(PackagedLicenseView::kScreenId));
}

void WizardController::ShowEduCoexistenceLoginScreen() {
  SetCurrentScreen(GetScreen(EduCoexistenceLoginScreen::kScreenId));
}

void WizardController::ShowParentalHandoffScreen() {
  SetCurrentScreen(GetScreen(ParentalHandoffScreenView::kScreenId));
}

void WizardController::ShowOsInstallScreen() {
  SetCurrentScreen(GetScreen(OsInstallScreenView::kScreenId));
}

void WizardController::ShowOsTrialScreen() {
  SetCurrentScreen(GetScreen(OsTrialScreenView::kScreenId));
}

void WizardController::ShowConsolidatedConsentScreen() {
  // Disable PreChoice metrics if the user is affiliated or managed.
  Profile* profile = ProfileManager::GetActiveUserProfile();
  CHECK(profile);
  if (enterprise_util::IsBrowserManaged(profile)) {
    if (metrics::CrOSPreChoiceMetricsManager::Get()) {
      metrics::CrOSPreChoiceMetricsManager::Get()->Disable();
    }
  }

  SetCurrentScreen(GetScreen(ConsolidatedConsentScreenView::kScreenId));
}

void WizardController::ShowChoobeScreen() {
  DCHECK(features::IsOobeChoobeEnabled());
  SetCurrentScreen(GetScreen(ChoobeScreenView::kScreenId));
}

void WizardController::ShowCategoriesSelectionScreen() {
  SetCurrentScreen(GetScreen(CategoriesSelectionScreenView::kScreenId));
}

void WizardController::ShowPersonalizedRecomendAppsScreen() {
  SetCurrentScreen(GetScreen(PersonalizedRecommendAppsScreenView::kScreenId));
}

void WizardController::ShowPerksDiscoveryScreen() {
  SetCurrentScreen(GetScreen(PerksDiscoveryScreenView::kScreenId));
}

void WizardController::ShowSplitModifierKeyboardInfoScreen() {
  SetCurrentScreen(GetScreen(SplitModifierKeyboardInfoScreenView::kScreenId));
}

void WizardController::ShowTouchpadScrollScreen() {
  // If the `OobeChoobe` or `OobeDisplaySize` feature is disabled, the
  // DisplaySizeScreen object will not be created. In this case,
  // `OnTouchpadScreenExit()` function is called with the exit result
  // `kNotApplicable` to proceed to the next screen.
  if (features::IsOobeTouchpadScrollEnabled()) {
    SetCurrentScreen(GetScreen(TouchpadScrollScreenView::kScreenId));
  } else {
    OnTouchpadScreenExit(TouchpadScrollScreen::Result::kNotApplicable);
  }
}

void WizardController::ShowDisplaySizeScreen() {
  if (features::IsOobeDisplaySizeEnabled()) {
    SetCurrentScreen(GetScreen(DisplaySizeScreenView::kScreenId));
  } else {
    OnDisplaySizeScreenExit(DisplaySizeScreen::Result::kNotApplicable);
  }
}

void WizardController::ShowGuestTosScreen() {
  SetCurrentScreen(GetScreen(GuestTosScreenView::kScreenId));
}

void WizardController::ShowCryptohomeRecoveryScreen(
    std::unique_ptr<UserContext> user_context) {
  wizard_context_->user_context = std::move(user_context);
  SetCurrentScreen(GetScreen(CryptohomeRecoveryScreenView::kScreenId));
}

void WizardController::ShowAccountSelectionScreen() {
  SetCurrentScreen(GetScreen(AccountSelectionScreenView::kScreenId));
}

void WizardController::ShowAppLaunchSplashScreen() {
  SetCurrentScreen(GetScreen(AppLaunchSplashScreenView::kScreenId));
  MaybeNotifyFjordOobeStateManager(
      fjord_oobe_state::proto::FjordOobeStateInfo::FJORD_OOBE_STATE_COMPLETE);
}

void WizardController::ShowFjordTouchControllerScreen() {
  SetCurrentScreen(GetScreen(FjordTouchControllerScreenView::kScreenId));
}

void WizardController::ShowFjordStationSetupScreen() {
  SetCurrentScreen(GetScreen(FjordStationSetupScreenView::kScreenId));
}

void WizardController::ShowFjordFwUpdateScreen() {
  SetCurrentScreen(GetScreen(FjordFwUpdateScreenView::kScreenId));
}

void WizardController::ShowFjordImageSelectionScreen() {
  SetCurrentScreen(GetScreen(FjordImageSelectionScreenView::kScreenId));
}

void WizardController::ShowFjordImageDownloadScreen() {
  SetCurrentScreen(GetScreen(FjordImageDownloadScreenView::kScreenId));
}

void WizardController::OnUserCreationScreenExit(
    UserCreationScreen::Result result) {
  OnScreenExit(UserCreationView::kScreenId,
               UserCreationScreen::GetResultString(result));
  switch (result) {
    case UserCreationScreen::Result::SIGNIN_SCHOOL:
      MaybeAbortQuickStartFlow(
          quick_start::QuickStartController::AbortFlowReason::SIGNIN_SCHOOL);
      [[fallthrough]];
    case UserCreationScreen::Result::SIGNIN_TRIAGE:
      GetLocalState()->SetBoolean(prefs::kOobeIsConsumerSegment, true);
      StartupUtils::SaveScreenAfterConsumerUpdate(local_state_.get(),
                                                  GaiaView::kScreenId.name);
      ShowConsumerUpdateScreen();
      break;
    case UserCreationScreen::Result::SIGNIN:
      if (features::IsOobeSoftwareUpdateEnabled()) {
        GetLocalState()->SetBoolean(prefs::kOobeIsConsumerSegment, true);
        StartupUtils::SaveScreenAfterConsumerUpdate(
            local_state_.get(), GaiaInfoScreenView::kScreenId.name);
        ShowConsumerUpdateScreen();
      } else {
        ShowGaiaInfoScreen();
      }
      break;
    case UserCreationScreen::Result::SKIPPED:
      if (features::IsOobeAddUserDuringEnrollmentEnabled() &&
          wizard_context_->timebound_user_context_holder) {
        ShowAccountSelectionScreen();
      } else {
        AdvanceToScreen(GaiaView::kScreenId);
      }
      break;
    case UserCreationScreen::Result::ADD_CHILD:
      MaybeAbortQuickStartFlow(
          quick_start::QuickStartController::AbortFlowReason::ADD_CHILD);
      if (features::IsOobeSoftwareUpdateEnabled()) {
        StartupUtils::SaveScreenAfterConsumerUpdate(
            local_state_.get(), AddChildScreenView::kScreenId.name);
        ShowConsumerUpdateScreen();
      } else {
        ShowAddChildScreen();
      }
      break;
    case UserCreationScreen::Result::ENTERPRISE_ENROLL_TRIAGE:
    case UserCreationScreen::Result::ENTERPRISE_ENROLL_SHORTCUT:
      wizard_context_->enrollment_preference_ =
          WizardContext::EnrollmentPreference::kEnterprise;
      ShowEnrollmentScreenIfEligible();
      break;
    case UserCreationScreen::Result::KIOSK_ENTERPRISE_ENROLL:
      wizard_context_->enrollment_preference_ =
          WizardContext::EnrollmentPreference::kKiosk;
      ShowEnrollmentScreenIfEligible();
      break;
    case UserCreationScreen::Result::CANCEL:
      LoginDisplayHost::default_host()->HideOobeDialog();
      break;
  }
}

void WizardController::OnConsumerUpdateScreenExit(
    ConsumerUpdateScreen::Result result) {
  OnScreenExit(ConsumerUpdateScreenView::kScreenId,
               ConsumerUpdateScreen::GetResultString(result));

  if (result == ConsumerUpdateScreen::Result::BACK) {
    AdvanceToScreen(UserCreationView::kScreenId);
    return;
  }

  const std::string screen_name =
      GetLocalState()->GetString(prefs::kOobeScreenAfterConsumerUpdate);
  if (screen_name == GaiaInfoScreenView::kScreenId.name) {
    if (HasScreen(PrefToScreenId(screen_name))) {
      AdvanceToScreen(PrefToScreenId(screen_name));
    } else {
      AdvanceToScreen(GaiaView::kScreenId);
    }
  } else if (HasScreen(PrefToScreenId(screen_name))) {
    AdvanceToScreen(PrefToScreenId(screen_name));
  } else {
    // Fallback for resuming consumer update screen from local state. This
    // handles cases where screen names/structure changed between versions.
    // 'OnUserCreationScreenExit' would update the state for compatibility.
    AdvanceToScreen(UserCreationView::kScreenId);
  }
}

void WizardController::OnGaiaScreenExit(GaiaScreen::Result result) {
  OnScreenExit(GaiaView::kScreenId, GaiaScreen::GetResultString(result));
  switch (result) {
    case GaiaScreen::Result::BACK_CHILD:
      ShowAddChildScreen();
      break;
    case GaiaScreen::Result::BACK:
    case GaiaScreen::Result::CANCEL: {
      if (features::IsOobeSoftwareUpdateEnabled()) {
        // When `OobeSoftwareUpdate` is enabled, clicking the back button should
        // return the user to the user creation screen if it is enabled or the
        // user is still in the oobe flow.
        if ((wizard_context_->is_user_creation_enabled ||
             !wizard_context_->is_add_person_flow) &&
            result == GaiaScreen::Result::BACK) {
          AdvanceToScreen(UserCreationView::kScreenId);
          break;
        }
      }
      if (wizard_context_->is_user_creation_enabled) {
        // `Result::BACK` and `Result::BACK_CHILD` are only triggered when
        // pressing back button. It goes back to GaiaInfoScreenView if user
        // creation is enabled; otherwise, it behaves the same as
        // `Result::CANCEL` which is triggered by pressing ESC key.
        if (result == GaiaScreen::Result::BACK) {
          if (wizard_context_->is_add_person_flow) {
            AdvanceToScreen(UserCreationView::kScreenId);
          } else {
            AdvanceToScreen(GaiaInfoScreenView::kScreenId);
          }
          break;
        }
      }

      GetScreen<GaiaScreen>()->Reset();
      // If a default redirection to third party IdP is set we can hide the
      // dialog.
      const bool gaia_page_defaults_to_saml = IsGaiaPageDefaultsToSAML();
      const bool no_previous_screen =
          LoginDisplayHost::default_host()->HasUserPods() &&
          !wizard_context_->is_user_creation_enabled;
      const bool new_user_saml_redirect =
          !LoginDisplayHost::default_host()->HasUserPods() &&
          gaia_page_defaults_to_saml;
      if (no_previous_screen || new_user_saml_redirect) {
        LoginDisplayHost::default_host()->HideOobeDialog(
            gaia_page_defaults_to_saml);
      } else {
        GetScreen<GaiaScreen>()->LoadOnlineGaia();
      }
      break;
    }
    case GaiaScreen::Result::ENTERPRISE_ENROLL:
      ShowEnrollmentScreenIfEligible();
      break;
    case GaiaScreen::Result::ENTER_QUICK_START:
      [[fallthrough]];
    case GaiaScreen::Result::QUICK_START_ONGOING:
      ShowQuickStartScreen();
      break;
    case GaiaScreen::Result::ERROR_OOBE_NOT_COMPLETED:
      GetLoginDisplayHost()
          ->GetOobeMetricsHelper()
          ->RecordOobeNotCompletedErrorTrigger(
              OobeMetricsHelper::OobeNotCompletedTrigger::kGaiaScreen);
      ShowSignInFatalErrorScreen(
          SignInFatalErrorScreen::Error::kOobeCompletionSkipped,
          base::DictValue());
      break;
  }
}

void WizardController::OnUserAllowlistCheckScreenExit(
    UserAllowlistCheckScreen::Result result) {
  CHECK(result == UserAllowlistCheckScreen::Result::RETRY);
  OnScreenExit(UserAllowlistCheckScreenView::kScreenId,
               UserAllowlistCheckScreen::GetResultString(result));
  GetScreen<GaiaScreen>()->Reset();
  AdvanceToScreen(GaiaView::kScreenId);
}

void WizardController::OnOnlineAuthenticationScreenExit(
    OnlineAuthenticationScreen::Result result) {}

void WizardController::OnGaiaInfoScreenExit(GaiaInfoScreen::Result result) {
  OnScreenExit(GaiaInfoScreenView::kScreenId,
               GaiaInfoScreen::GetResultString(result));
  switch (result) {
    case GaiaInfoScreen::Result::kBack:
      AdvanceToScreen(UserCreationView::kScreenId);
      break;
    case GaiaInfoScreen::Result::kManual:
      [[fallthrough]];
    case GaiaInfoScreen::Result::kNotApplicable:
      AdvanceToScreen(GaiaView::kScreenId);
      break;
    case GaiaInfoScreen::Result::kEnterQuickStart:
      [[fallthrough]];
    case GaiaInfoScreen::Result::kQuickStartOngoing:
      ShowQuickStartScreen();
      break;
  }
}

void WizardController::OnAddChildScreenExit(AddChildScreen::Result result) {
  OnScreenExit(AddChildScreenView::kScreenId,
               AddChildScreen::GetResultString(result));
  switch (result) {
    case AddChildScreen::Result::CHILD_SIGNIN:
      wizard_context_->gaia_config.gaia_path =
          WizardContext::GaiaPath::kChildSignin;
      AdvanceToScreen(GaiaView::kScreenId);
      break;
    case AddChildScreen::Result::CHILD_ACCOUNT_CREATE:
      wizard_context_->gaia_config.gaia_path =
          WizardContext::GaiaPath::kChildSignup;
      AdvanceToScreen(GaiaView::kScreenId);
      break;
    case AddChildScreen::Result::ENTERPRISE_ENROLL:
      ShowEnrollmentScreenIfEligible();
      break;
    case AddChildScreen::Result::KIOSK_ENTERPRISE_ENROLL:
      wizard_context_->enrollment_preference_ =
          WizardContext::EnrollmentPreference::kKiosk;
      ShowEnrollmentScreenIfEligible();
      break;
    case AddChildScreen::Result::SKIPPED:
      AdvanceToScreen(GaiaView::kScreenId);
      break;
    case AddChildScreen::Result::BACK:
      AdvanceToScreen(UserCreationView::kScreenId);
      if (features::IsOobeSoftwareUpdateEnabled()) {
        GetScreen<UserCreationScreen>()->SetChildSetupStep();
      }
      break;
  }
}

void WizardController::OnSamlConfirmPasswordScreenExit(
    SamlConfirmPasswordScreen::Result result) {
  OnScreenExit(SamlConfirmPasswordView::kScreenId,
               SamlConfirmPasswordScreen::GetResultString(result));
  switch (result) {
    case SamlConfirmPasswordScreen::Result::kSuccess:
      switch (wizard_context_->knowledge_factor_setup.auth_setup_flow) {
        case WizardContext::AuthChangeFlow::kInitialSetup:
          // `user_context` is only set on the wizard context for pre-cryptohome
          // SAML confirm password screen exit. This can happen when the user is
          // ephemeral as we always show the confirm password screen for
          // ephemeral users before cryptohome mount.
          if (wizard_context_->user_context != nullptr) {
            CompleteLogin();
          } else {
            // Continue initial setup by showing other auth factors flows.
            ShowPinSetupScreenAsMainFactor();
          }
          break;
        case WizardContext::AuthChangeFlow::kRecovery:
        case WizardContext::AuthChangeFlow::kReauthentication:
          // During reauthentication/recovery the SAML confirm password screen
          // is only shown before Cryptohome is mounted.
          CompleteLogin();
          break;
      }
      break;
    case SamlConfirmPasswordScreen::Result::kCancel:
      if (features::IsManagedLocalPinAndPasswordEnabled() &&
          IsInitialSetup(*wizard_context_)) {
        // In case of an InitialSetup and user clicking cancel we should
        // invalidate the token and logout out to the main screen, to make sure
        // that there is no left over user state.
        InvalidateTokenAndRequestSignout(*wizard_context_);
      } else {
        LoginDisplayHost::default_host()->StartSignInScreen();
      }
      return;
    case SamlConfirmPasswordScreen::Result::kTooManyAttempts:
      wizard_context_->is_scraped_password_verification_failure = true;
      ShowSignInFatalErrorScreen(
          SignInFatalErrorScreen::Error::kScrapedPasswordVerificationFailure,
          base::DictValue());
      break;
    case ash::SamlConfirmPasswordScreen::Result::kNotApplicable:
      // Continue initial setup by showing other auth factors flows.
      ShowPinSetupScreenAsMainFactor();
      break;
  }
}

void WizardController::OnEduCoexistenceLoginScreenExit(
    EduCoexistenceLoginScreen::Result result) {
  OnScreenExit(EduCoexistenceLoginScreen::kScreenId,
               EduCoexistenceLoginScreen::GetResultString(result));
  ShowConsolidatedConsentScreen();
}

void WizardController::OnParentalHandoffScreenExit(
    ParentalHandoffScreen::Result result) {
  OnScreenExit(ParentalHandoffScreenView::kScreenId,
               ParentalHandoffScreen::GetResultString(result));
  ShowMultiDeviceSetupScreen();
}

void WizardController::OnConsolidatedConsentScreenExit(
    ConsolidatedConsentScreen::Result result) {
  OnScreenExit(ConsolidatedConsentScreenView::kScreenId,
               ConsolidatedConsentScreen::GetResultString(result));

  if (drive::util::IsOobeDrivePinningAvailable()) {
    GetScreen<DrivePinningScreen>()->StartCalculatingRequiredSpace();
  }

  if (wizard_context_->is_cloud_ready_update_flow) {
    AdvanceToScreen(HWDataCollectionView::kScreenId);
    return;
  }
  switch (result) {
    case ConsolidatedConsentScreen::Result::ACCEPTED:
    case ConsolidatedConsentScreen::Result::NOT_APPLICABLE:
      ShowSyncConsentScreen();
      break;
    case ConsolidatedConsentScreen::Result::ACCEPTED_DEMO_ONLINE:
      DCHECK(demo_setup_controller_);
      ShowAutoEnrollmentCheckScreen();
      break;
    case ConsolidatedConsentScreen::Result::BACK_DEMO:
      DCHECK(demo_setup_controller_);
      ShowDemoModePreferencesScreen();
      break;
  }
}

void WizardController::OnOfflineLoginScreenExit(
    OfflineLoginScreen::Result result) {
  OnScreenExit(OfflineLoginView::kScreenId,
               OfflineLoginScreen::GetResultString(result));
  switch (result) {
    case OfflineLoginScreen::Result::BACK:
      // Go back to online login, if still no connection it will trigger
      // ErrorScreen with fix options. If UserCreationScreen isn't available
      // it will exit with Result::SKIPPED and open GaiaScreen instead.
      AdvanceToScreen(UserCreationView::kScreenId);
      break;
    case OfflineLoginScreen::Result::RELOAD_ONLINE_LOGIN:
      AdvanceToScreen(GaiaView::kScreenId);
      break;
  }
}

void WizardController::OnOsInstallScreenExit() {
  OnScreenExit(OsInstallScreenView::kScreenId, kDefaultExitReason);
  // The screen exits when user goes back. There could be a previous_screen_ or
  // we could get to OsInstallScreen directly from the login screen.
  // (When installation is finished or error occurs - user can only shut down)
  if (LoginDisplayHost::default_host()->HasUserPods()) {
    LoginDisplayHost::default_host()->HideOobeDialog();
    return;
  }
  const bool did_advance = MaybeSetToPreviousScreen();
  DCHECK(did_advance);
}

void WizardController::OnOsTrialScreenExit(OsTrialScreen::Result result) {
  OnScreenExit(OsTrialScreenView::kScreenId,
               OsTrialScreen::GetResultString(result));
  switch (result) {
    case OsTrialScreen::Result::kBack:
      // The OS Trial screen is only shown when OS Installation is started from
      // the welcome screen, so if the back button was clicked we go back to
      // the welcome screen.
      ShowWelcomeScreen();
      break;
    case OsTrialScreen::Result::kNextTry:
      ShowNetworkScreen();
      break;
    case OsTrialScreen::Result::kNextInstall:
      ShowOsInstallScreen();
      break;
  }
}

void WizardController::OnHWDataCollectionScreenExit(
    HWDataCollectionScreen::Result result) {
  OnScreenExit(HWDataCollectionView::kScreenId,
               HWDataCollectionScreen::GetResultString(result));
  if (wizard_context_->is_cloud_ready_update_flow) {
    OnOobeFlowFinished();
    return;
  }
  StartAuthFactorsSetup();
}

void WizardController::OnSmartPrivacyProtectionScreenExit(
    SmartPrivacyProtectionScreen::Result result) {
  OnScreenExit(SmartPrivacyProtectionView::kScreenId,
               SmartPrivacyProtectionScreen::GetResultString(result));
  ShowParentalHandoffScreen();
}

void WizardController::OnGuestTosScreenExit(GuestTosScreen::Result result) {
  OnScreenExit(GuestTosScreenView::kScreenId,
               GuestTosScreen::GetResultString(result));
  switch (result) {
    case GuestTosScreen::Result::ACCEPT:
      ash::LoginDisplayHost::default_host()->GetExistingUserController()->Login(
          UserContext(user_manager::UserType::kGuest,
                      user_manager::GuestAccountId()),
          SigninSpecifics());
      break;
    case GuestTosScreen::Result::BACK:
      if (MaybeSetToPreviousScreen()) {
        break;
      }
      if (LoginDisplayHost::default_host()->HasUserPods()) {
        LoginDisplayHost::default_host()->HideOobeDialog();
      }
      break;
    case GuestTosScreen::Result::CANCEL:
      if (LoginDisplayHost::default_host()->HasUserPods()) {
        LoginDisplayHost::default_host()->HideOobeDialog();
      }
  }
}

void WizardController::OnThemeSelectionScreenExit(
    ThemeSelectionScreen::Result result) {
  OnScreenExit(ThemeSelectionScreenView::kScreenId,
               ThemeSelectionScreen::GetResultString(result));

  switch (result) {
    case ThemeSelectionScreen::Result::kProceed:
    case ThemeSelectionScreen::Result::kNotApplicable:
      if (wizard_context_->return_to_choobe_screen) {
        wizard_context_->return_to_choobe_screen = false;
        ShowChoobeScreen();
      } else {
        if (choobe_flow_controller_) {
          choobe_flow_controller_->OnChoobeFlowExit();
          choobe_flow_controller_.reset();
        }
        ShowMarketingOptInScreen();
      }
      break;
  }
}

void WizardController::OnCryptohomeRecoveryScreenExit(
    CryptohomeRecoveryScreen::Result result) {
  OnScreenExit(CryptohomeRecoveryScreenView::kScreenId,
               CryptohomeRecoveryScreen::GetResultString(result));
  switch (result) {
    case CryptohomeRecoveryScreen::Result::kAuthenticated: {
      switch (wizard_context_->knowledge_factor_setup.auth_setup_flow) {
        case WizardContext::AuthChangeFlow::kInitialSetup:
          NOTREACHED() << "Recovery can not be used during initial setup.";
        case WizardContext::AuthChangeFlow::kRecovery:
          if (ash::features::IsRecoveryFlowReorderEnabled()) {
            wizard_context_->allow_factor_change_during_recovery = true;
            ObtainContextAndLoginAuthenticated();
          } else {
            ShowPasswordSelectionScreen();
          }
          return;
        case WizardContext::AuthChangeFlow::kReauthentication:
          // Proceed with login
          ObtainContextAndLoginAuthenticated();
          return;
      }
    }
    case CryptohomeRecoveryScreen::Result::kGaiaLogin:
      // TODO(b/257073746): We probably want to differentiate between retry with
      // or without login.
      wizard_context_->gaia_config.prefilled_account =
          wizard_context_->user_context->GetAccountId();
      AdvanceToScreen(GaiaView::kScreenId);
      break;
    case CryptohomeRecoveryScreen::Result::kFallbackOnline:
      ShowEnterOldPasswordScreen();
      break;
    case CryptohomeRecoveryScreen::Result::kFallbackLocal: {
      switch (wizard_context_->knowledge_factor_setup.auth_setup_flow) {
        case WizardContext::AuthChangeFlow::kInitialSetup:
          NOTREACHED() << "Recovery is not used during initial setup";
        case WizardContext::AuthChangeFlow::kReauthentication:
          AttemptLocalAuthenticationWithContext(
              std::move(wizard_context_->user_context));
          return;
        case WizardContext::AuthChangeFlow::kRecovery:
          // Recovery flow indicates that user does not remember
          // their local password, so there is no step to retry.
          wizard_context_->knowledge_factor_setup.data_loss_back_option =
              WizardContext::DataLossBackOptions::kNone;
          ShowLocalDataLossWarningScreen();
          return;
      }
    }
    case CryptohomeRecoveryScreen::Result::kError:
      ShowOSAuthErrorScreen();
      break;
  }
}

void WizardController::OnLocalDataLossWarningScreenExit(
    LocalDataLossWarningScreen::Result result) {
  OnScreenExit(LocalDataLossWarningScreenView::kScreenId,
               LocalDataLossWarningScreen::GetResultString(result));
  switch (result) {
    case LocalDataLossWarningScreen::Result::kRemoveUser:
    case LocalDataLossWarningScreen::Result::kAutoWipe: {
      std::unique_ptr<UserContext> context =
          std::move(wizard_context_->user_context);
      ash::LoginDisplayHost::default_host()->CompleteLogin(*context);
      break;
    }
    case LocalDataLossWarningScreen::Result::kCryptohomeError:
      ShowOSAuthErrorScreen();
      break;
    case LocalDataLossWarningScreen::Result::kCancel:
      LoginDisplayHost::default_host()->CancelPasswordChangedFlow();
      break;
    case LocalDataLossWarningScreen::Result::kBackToOnlineAuth:
      ShowEnterOldPasswordScreen();
      break;
    case LocalDataLossWarningScreen::Result::kBackToLocalAuth:
      AttemptLocalAuthenticationWithContext(
          std::move(wizard_context_->user_context));
      break;
  }
}

void WizardController::OnEnterOldPasswordScreenExit(
    EnterOldPasswordScreen::Result result) {
  OnScreenExit(EnterOldPasswordScreenView::kScreenId,
               EnterOldPasswordScreen::GetResultString(result));
  switch (result) {
    case EnterOldPasswordScreen::Result::kForgotOldPassword:
      wizard_context_->knowledge_factor_setup.data_loss_back_option =
          WizardContext::DataLossBackOptions::kBackToOnlineAuth;
      ShowLocalDataLossWarningScreen();
      break;
    case EnterOldPasswordScreen::Result::kCryptohomeError:
      ShowOSAuthErrorScreen();
      break;
    case EnterOldPasswordScreen::Result::kAuthenticated: {
      switch (wizard_context_->knowledge_factor_setup.auth_setup_flow) {
        case WizardContext::AuthChangeFlow::kInitialSetup:
          NOTREACHED() << "Old password is not used during initial setup";
        case WizardContext::AuthChangeFlow::kRecovery:
        case WizardContext::AuthChangeFlow::kReauthentication:
          ShowApplyOnlinePasswordScreen();
          return;
      }
      break;
    }
  }
}

void WizardController::OnChoobeScreenExit(ChoobeScreen::Result result) {
  OnScreenExit(ChoobeScreenView::kScreenId,
               ChoobeScreen::GetResultString(result));

  switch (result) {
    case ChoobeScreen::Result::SELECTED:
    case ChoobeScreen::Result::NOT_APPLICABLE:
      ShowTouchpadScrollScreen();
      break;
    case ChoobeScreen::Result::SKIPPED:
      choobe_flow_controller_->OnChoobeFlowExit();
      choobe_flow_controller_.reset();
      ShowMarketingOptInScreen();
      break;
  }
}

void WizardController::OnTouchpadScreenExit(
    TouchpadScrollScreen::Result result) {
  OnScreenExit(TouchpadScrollScreenView::kScreenId,
               TouchpadScrollScreen::GetResultString(result));

  ShowDrivePinningScreen();
}

void WizardController::OnCategoriesSelectionScreenExit(
    CategoriesSelectionScreen::Result result) {
  OnScreenExit(CategoriesSelectionScreenView::kScreenId,
               CategoriesSelectionScreen::GetResultString(result));
  switch (result) {
    case CategoriesSelectionScreen::Result::kError:
    case CategoriesSelectionScreen::Result::kNotApplicable:
    case CategoriesSelectionScreen::Result::kDataMalformed:
    case CategoriesSelectionScreen::Result::kTimeout:
      ShowRecommendAppsScreen();
      break;
    case CategoriesSelectionScreen::Result::kNext:
    case CategoriesSelectionScreen::Result::kSkip:
      ShowPersonalizedRecomendAppsScreen();
      break;
  }
}

void WizardController::OnPerksDiscoveryScreenExit(
    PerksDiscoveryScreen::Result result) {
  OnScreenExit(PerksDiscoveryScreenView::kScreenId,
               PerksDiscoveryScreen::GetResultString(result));
  if (features::IsOobeAiIntroEnabled()) {
    ShowAiIntroScreen();
  } else {
    ShowGeminiIntroScreen();
  }
}

void WizardController::OnPersonalizedRecomendAppsScreenExit(
    PersonalizedRecommendAppsScreen::Result result) {
  OnScreenExit(PersonalizedRecommendAppsScreenView::kScreenId,
               PersonalizedRecommendAppsScreen::GetResultString(result));

  switch (result) {
    case PersonalizedRecommendAppsScreen::Result::kBack:
      ShowCategoriesSelectionScreen();
      break;
    case PersonalizedRecommendAppsScreen::Result::kNext:
    case PersonalizedRecommendAppsScreen::Result::kNotApplicable:
    case PersonalizedRecommendAppsScreen::Result::kSkip:
    case PersonalizedRecommendAppsScreen::Result::kDataMalformed:
    case PersonalizedRecommendAppsScreen::Result::kError:
    case PersonalizedRecommendAppsScreen::Result::kTimeout:
      ShowPerksDiscoveryScreen();
      break;
  }
}

void WizardController::OnSplitModifierKeyboardInfoScreenExit(
    SplitModifierKeyboardInfoScreen::Result result) {
  OnScreenExit(SplitModifierKeyboardInfoScreenView::kScreenId,
               SplitModifierKeyboardInfoScreen::GetResultString(result));

  ShowGestureNavigationScreen();
}

void WizardController::OnDrivePinningScreenExit(
    DrivePinningScreen::Result result) {
  OnScreenExit(DrivePinningScreenView::kScreenId,
               DrivePinningScreen::GetResultString(result));
  if (features::IsOobeDisplaySizeEnabled()) {
    ShowDisplaySizeScreen();
  } else {
    OnDisplaySizeScreenExit(DisplaySizeScreen::Result::kNotApplicable);
  }
}

void WizardController::OnDisplaySizeScreenExit(
    DisplaySizeScreen::Result result) {
  OnScreenExit(DisplaySizeScreenView::kScreenId,
               DisplaySizeScreen::GetResultString(result));

  switch (result) {
    case DisplaySizeScreen::Result::kNotApplicable:
    case DisplaySizeScreen::Result::kNext:
      ShowThemeSelectionScreen();
  }
}

void WizardController::OnAccountSelectionScreenExit(
    AccountSelectionScreen::Result result) {
  OnScreenExit(AccountSelectionScreenView::kScreenId,
               AccountSelectionScreen::GetResultString(result));
  switch (result) {
    case AccountSelectionScreen::Result::kNotApplicable:
    case AccountSelectionScreen::Result::kGaiaFallback:
      AdvanceToScreen(GaiaView::kScreenId);
  }
}

void WizardController::SkipToLoginForTesting() {
  VLOG(1) << "WizardController::SkipToLoginForTesting()";

  // This method should only be used on test images.
  base::SysInfo::CrashIfChromeOSNonTestImage();

  if (current_screen_ && current_screen_->screen_id() == GaiaView::kScreenId) {
    return;
  }
  wizard_context_->skip_to_login_for_tests = true;

  StartNetworkTimezoneResolve();
  DelayNetworkCall(ServicesCustomizationDocument::GetInstance()
                       ->EnsureCustomizationAppliedClosure());
  if (features::IsOobeAutoEnrollmentCheckForcedEnabled()) {
    StartupUtils::MarkOobeCompleted(local_state_.get());
  }
  OnDeviceDisabledChecked(/*device_disabled=*/false);
}

void WizardController::OnScreenExit(OobeScreenId screen,
                                    const std::string& exit_reason) {
  SYSLOG(INFO) << "(LOGIN) Wizard screen " << screen
               << " exited with reason: " << exit_reason
               << " during setup type: "
               << SetupTypeToString(
                      wizard_context_->knowledge_factor_setup.auth_setup_flow);
  // Do not perform checks and record stats for the skipped screen.
  if (exit_reason == BaseScreen::kNotApplicable) {
    return;
  }
  DCHECK(current_screen_->screen_id() == screen);

  GetLoginDisplayHost()->GetOobeMetricsHelper()->RecordScreenExit(screen,
                                                                  exit_reason);
}

///////////////////////////////////////////////////////////////////////////////
// WizardController, ExitHandlers:
void WizardController::OnWrongHWIDScreenExit() {
  OnScreenExit(WrongHWIDScreenView::kScreenId, kDefaultExitReason);
  OnDeviceModificationCanceled();
}

void WizardController::OnHidDetectionScreenExit(
    HIDDetectionScreen::Result result) {
  OnScreenExit(HIDDetectionView::kScreenId,
               HIDDetectionScreen::GetResultString(result));

  if (result == HIDDetectionScreen::Result::SKIPPED_FOR_TESTS &&
      current_screen_) {
    return;
  }

  AdvanceToScreenAfterHIDDetection(ash::OOBE_SCREEN_UNKNOWN);
}

void WizardController::OnWelcomeScreenExit(WelcomeScreen::Result result) {
  OnScreenExit(WelcomeView::kScreenId, WelcomeScreen::GetResultString(result));

  switch (result) {
    case WelcomeScreen::Result::kSetupDemo:
      StartDemoModeSetup();
      return;
    case WelcomeScreen::Result::kEnableDebugging:
      ShowEnableDebuggingScreen();
      return;
    case WelcomeScreen::Result::kNextOSInstall:
      ShowOsTrialScreen();
      return;
    case WelcomeScreen::Result::kNext:
      ShowNetworkScreen();
      return;
    case WelcomeScreen::Result::kQuickStart:
      ShowQuickStartScreen();
      return;
  }
}

void WizardController::OnQuickStartScreenExit(QuickStartScreen::Result result) {
  OnScreenExit(QuickStartView::kScreenId,
               QuickStartScreen::GetResultString(result));
  switch (result) {
    case QuickStartScreen::Result::CANCEL_AND_RETURN_TO_WELCOME:
      ShowWelcomeScreen();
      return;
    case QuickStartScreen::Result::WIFI_CREDENTIALS_RECEIVED:
    case QuickStartScreen::Result::CANCEL_AND_RETURN_TO_NETWORK:
      ShowNetworkScreen();
      return;
    case QuickStartScreen::Result::CANCEL_AND_RETURN_TO_GAIA_INFO:
      CHECK(StartupUtils::IsOobeCompleted(local_state_.get()));
      AdvanceToScreen(GaiaInfoScreenView::kScreenId);
      return;
    case ash::QuickStartScreen::Result::FALLBACK_URL_ON_GAIA:
      CHECK(StartupUtils::IsOobeCompleted(local_state_.get()));
      wizard_context_->gaia_config.gaia_path =
          WizardContext::GaiaPath::kQuickStartFallback;
      wizard_context_->gaia_config.quick_start_fallback_path_contents =
          quickstart_controller_->GetFallbackUrl();
      AdvanceToScreen(GaiaView::kScreenId);
      return;
    case QuickStartScreen::Result::CANCEL_AND_RETURN_TO_SIGNIN:
      CHECK(StartupUtils::IsOobeCompleted(local_state_.get()));
      AdvanceToScreen(GaiaView::kScreenId);
      return;
    // Last step of the QuickStart flow. This is triggered immediately
    // after the 'RecoveryEligibility' screen and continues OOBE into
    // the TermsOfServiceScreen
    case QuickStartScreen::Result::SETUP_COMPLETE_NEXT_BUTTON:
      CHECK(StartupUtils::IsOobeCompleted(local_state_.get()));
      quickstart_controller_->RecordFlowFinished();
      AdvanceToScreen(TermsOfServiceScreenView::kScreenId);
  }
}

// The network screen is part of 3 different flows:
// * Regular Flow: Welcome Screen -> Network Screen -> Update Screen.
// * Demo Flow: Welcome Screen -> Network Screen -> Demo Preferences Screen.
// * OS Install Flow: OS Trial Screen -> Network Screen -> Update Screen.
void WizardController::OnNetworkScreenExit(NetworkScreen::Result result) {
  OnScreenExit(NetworkScreenView::kScreenId,
               NetworkScreen::GetResultString(result));

  // Demo mode flow.
  if (DemoSetupController::IsOobeDemoSetupFlowInProgress()) {
    switch (result) {
      case NetworkScreen::Result::CONNECTED:
      case NetworkScreen::Result::NOT_APPLICABLE:
        demo_setup_controller_->set_demo_config(
            DemoSession::DemoModeConfig::kOnline);
        ShowDemoModePreferencesScreen();
        break;
      case NetworkScreen::Result::BACK:
        demo_setup_controller_.reset();
        ShowWelcomeScreen();
        break;
      case NetworkScreen::Result::QUICK_START:
        NOTREACHED();
    }
    return;
  }

  // OS Install flow.
  if (switches::IsOsInstallAllowed()) {
    switch (result) {
      case NetworkScreen::Result::CONNECTED:
      case NetworkScreen::Result::NOT_APPLICABLE:
        MaybeTakeTPMOwnership();
        PerformPostNetworkScreenActions();
        InitiateOOBEUpdate();
        break;
      case NetworkScreen::Result::BACK:
        ShowOsTrialScreen();
        break;
      case NetworkScreen::Result::QUICK_START:
        NOTREACHED();
    }
    return;
  }

  // Regular flow.
  switch (result) {
    case NetworkScreen::Result::CONNECTED:
    case NetworkScreen::Result::NOT_APPLICABLE:
      MaybeTakeTPMOwnership();
      PerformPostNetworkScreenActions();
      InitiateOOBEUpdate();
      break;
    case NetworkScreen::Result::BACK:
      ShowWelcomeScreen();
      break;
    case NetworkScreen::Result::QUICK_START:
      ShowQuickStartScreen();
      break;
  }
}

void WizardController::OnUpdateScreenExit(UpdateScreen::Result result) {
  OnScreenExit(UpdateView::kScreenId, UpdateScreen::GetResultString(result));

  switch (result) {
    case UpdateScreen::Result::UPDATE_NOT_REQUIRED:
    case UpdateScreen::Result::UPDATE_CHECK_TIMEOUT:
    case UpdateScreen::Result::UPDATE_SKIPPED:
    case UpdateScreen::Result::UPDATE_OPT_OUT_INFO_SHOWN:
      OnUpdateCompleted();
      break;
    case UpdateScreen::Result::UPDATE_ERROR:
      // Ignore update errors if the OOBE flow has already completed - this
      // prevents the user getting blocked from getting to the login screen.
      if (is_out_of_box_) {
        ShowNetworkScreen();
      } else {
        OnUpdateCompleted();
      }
      break;
  }
}

void WizardController::OnUpdateCompleted() {
  // Install language packs based on the user selected language.
  const std::string locale = application_locale_storage_->Get();
  language_packs::LanguagePackManager::UpdatePacksForOobe(locale,
                                                          base::DoNothing());

  if (fjord_util::ShouldShowFjordOobeImageSwitch()) {
    ShowFjordImageSelectionScreen();
    return;
  }

  if (demo_setup_controller_) {
    ShowConsolidatedConsentScreen();
    return;
  }
  ShowAutoEnrollmentCheckScreen();
}

void WizardController::OnAutoEnrollmentCheckScreenExit(
    AutoEnrollmentCheckScreen::Result result) {
  OnScreenExit(AutoEnrollmentCheckScreenView::kScreenId,
               AutoEnrollmentCheckScreen::GetResultString(result));
  VLOG(1) << "WizardController::OnAutoEnrollmentCheckScreenExit()";
  // Check whether the device is disabled. OnDeviceDisabledChecked() will be
  // invoked when the result of this check is known. Until then, the current
  // screen will remain visible and will continue showing a spinner.
  auto* device_disabling_manager =
      g_browser_process->platform_part()->device_disabling_manager();
  CHECK(device_disabling_manager);
  device_disabling_manager->CheckWhetherDeviceDisabledDuringOOBE(
      base::BindRepeating(&WizardController::OnDeviceDisabledChecked,
                          weak_factory_.GetWeakPtr()));
}

void WizardController::OnEnrollmentScreenExit(EnrollmentScreen::Result result) {
  OnScreenExit(EnrollmentScreenView::kScreenId,
               EnrollmentScreen::GetResultString(result));
  VLOG(1) << "WizardController::OnEnrollmentScreenExit(result= "
          << EnrollmentScreen::GetResultString(result) << ").";
  switch (result) {
    case EnrollmentScreen::Result::COMPLETED:
      OnEnrollmentDone();
      break;
    case EnrollmentScreen::Result::BACK:
    case EnrollmentScreen::Result::SKIPPED_FOR_TESTS:
      // The following `PerformOOBECompletedAction()` call will occur in both
      // manual and auto enrollment. However, in the manual enrollment case,
      // `PerformOOBECompletedAction()` method would be already called before
      // with `CompletedPreLoginOobeFlowType::kRegular` argument.
      // OOBECompletedActions are only performed in the first call.
      PerformOOBECompletedActions(
          OobeMetricsHelper::CompletedPreLoginOobeFlowType::kAutoEnrollment);
      DCHECK(!prescribed_enrollment_config_.is_forced());
      // set  the userCreationScreen with the default step creation and
      // pre-select 'For personal use'.
      GetScreen<UserCreationScreen>()->SetDefaultStep();
      ShowLoginScreen();
      break;
    case EnrollmentScreen::Result::TPM_ERROR:
      DCHECK(switches::IsTpmDynamic());
      wizard_context_->tpm_owned_error = true;
      AdvanceToScreen(TpmErrorView::kScreenId);
      break;
    case EnrollmentScreen::Result::TPM_DBUS_ERROR:
      DCHECK(switches::IsTpmDynamic());
      wizard_context_->tpm_dbus_error = true;
      AdvanceToScreen(TpmErrorView::kScreenId);
      break;
    case EnrollmentScreen::Result::BACK_TO_AUTO_ENROLLMENT_CHECK:
      ShowAutoEnrollmentCheckScreen();
      break;
  }
}

void WizardController::OnEnrollmentDone() {
  // The following `PerformOOBECompletedAction()` call will occur in both
  // manual and auto enrollment. However, in the manual enrollment case,
  // `PerformOOBECompletedAction()` method would be already called before
  // with `CompletedPreLoginOobeFlowType::kRegular` argument.
  // OOBECompletedActions are only performed in the first call.
  PerformOOBECompletedActions(
      OobeMetricsHelper::CompletedPreLoginOobeFlowType::kAutoEnrollment);

  // Restart to make the login page pick up the policy changes resulting from
  // enrollment recovery.  (Not pretty, but this codepath is rarely
  // exercised.)
  if (prescribed_enrollment_config_.mode ==
      policy::EnrollmentConfig::MODE_RECOVERY) {
    LOG(WARNING) << "Restart Chrome to pick up the policy changes";
    EnrollmentScreen* screen = EnrollmentScreen::Get(screen_manager());
    screen->OnBrowserRestart();
    session_manager::SessionManager::Get()->RequestRestart();
    return;
  }

  // We need a log to understand when the device finished enrollment.
  VLOG(1) << "Enrollment done";

  if (fjord_util::ShouldShowFjordOobe()) {
    ShowFjordTouchControllerScreen();
  } else if (auto app = KioskController::Get().GetAutoLaunchApp();
             app.has_value()) {
    AutoLaunchKioskApp(app.value());
  } else if (ash::InstallAttributes::Get()->IsEnterpriseManaged()) {
    // Could be not managed in tests.
    DCHECK_EQ(LoginDisplayHost::default_host()->GetOobeUI()->display_type(),
              OobeUI::kOobeDisplay);
    SwitchWebUItoMojo();
  } else {
    ShowLoginScreen();
  }
}

void WizardController::OnEnableAdbSideloadingScreenExit() {
  OnScreenExit(EnableAdbSideloadingScreenView::kScreenId, kDefaultExitReason);

  OnDeviceModificationCanceled();
}

void WizardController::OnEnableDebuggingScreenExit() {
  OnScreenExit(EnableDebuggingScreenView::kScreenId, kDefaultExitReason);

  OnDeviceModificationCanceled();
}

void WizardController::OnDemoPreferencesScreenExit(
    DemoPreferencesScreen::Result result) {
  OnScreenExit(DemoPreferencesScreenView::kScreenId,
               DemoPreferencesScreen::GetResultString(result));

  DCHECK(demo_setup_controller_);

  switch (result) {
    case DemoPreferencesScreen::Result::COMPLETED:
      demo_setup_controller_->set_demo_config(
          DemoSession::DemoModeConfig::kOnline);
      MaybeTakeTPMOwnership();
      PerformPostNetworkScreenActions();
      InitiateOOBEUpdate();
      break;
    case DemoPreferencesScreen::Result::CANCELED:
      ShowNetworkScreen();
      break;
  }
}

void WizardController::OnDemoSetupScreenExit(DemoSetupScreen::Result result) {
  OnScreenExit(DemoSetupScreenView::kScreenId,
               DemoSetupScreen::GetResultString(result));

  DCHECK(demo_setup_controller_);
  demo_setup_controller_.reset();

  switch (result) {
    case DemoSetupScreen::Result::kCompleted:
      PerformOOBECompletedActions(
          OobeMetricsHelper::CompletedPreLoginOobeFlowType::kDemo);
      SwitchWebUItoMojo();
      break;
    case DemoSetupScreen::Result::kCanceled:
      ShowWelcomeScreen();
      break;
  }
}

void WizardController::OnLocaleSwitchScreenExit(
    LocaleSwitchScreen::Result result) {
  OnScreenExit(LocaleSwitchView::kScreenId,
               LocaleSwitchScreen::GetResultString(result));
  AdvanceToScreen(RecoveryEligibilityView::kScreenId);
}

void WizardController::OnRecoveryEligibilityScreenExit(
    RecoveryEligibilityScreen::Result result) {
  OnScreenExit(RecoveryEligibilityView::kScreenId,
               RecoveryEligibilityScreen::GetResultString(result));
  // QuickStart's 'Setup Complete' screen step is the first screen
  // that a user sees after logging in. It just shows a 'Next' button
  // which exits the screen into the TermsOfServiceScreen
  if (wizard_context_->quick_start_enabled &&
      wizard_context_->quick_start_setup_ongoing) {
    AdvanceToScreen(QuickStartView::kScreenId);
  } else {
    AdvanceToScreen(TermsOfServiceScreenView::kScreenId);
  }
}

void WizardController::OnTermsOfServiceScreenExit(
    TermsOfServiceScreen::Result result) {
  OnScreenExit(TermsOfServiceScreenView::kScreenId,
               TermsOfServiceScreen::GetResultString(result));

  switch (result) {
    case TermsOfServiceScreen::Result::ACCEPTED:
    case TermsOfServiceScreen::Result::NOT_APPLICABLE:
      if (wizard_context_->screen_after_managed_tos ==
          ash::OOBE_SCREEN_UNKNOWN) {
        OnOobeFlowFinished();
        return;
      }
      AdvanceToScreen(wizard_context_->screen_after_managed_tos);
      break;
    case TermsOfServiceScreen::Result::DECLINED:
      // End the session and return to the login screen.
      SessionManagerClient::Get()->StopSession(
          login_manager::SessionStopReason::TERMS_DECLINED);
      break;
  }
}

void WizardController::OnFamilyLinkNoticeScreenExit(
    FamilyLinkNoticeScreen::Result result) {
  OnScreenExit(FamilyLinkNoticeView::kScreenId,
               FamilyLinkNoticeScreen::GetResultString(result));

  ShowEduCoexistenceLoginScreen();
}

void WizardController::OnSyncConsentScreenExit(
    SyncConsentScreen::Result result) {
  OnScreenExit(SyncConsentScreenView::kScreenId,
               SyncConsentScreen::GetResultString(result));
  if (switches::IsRevenBranding()) {
    AdvanceToScreen(HWDataCollectionView::kScreenId);
    return;
  }

  StartAuthFactorsSetup();
}

// Start of local authentication setup screen exit handlers.

void WizardController::OnCryptohomeRecoverySetupScreenExit(
    CryptohomeRecoverySetupScreen::Result result) {
  OnScreenExit(CryptohomeRecoverySetupScreenView::kScreenId,
               CryptohomeRecoverySetupScreen::GetResultString(result));
  bool might_require_saml_password_confirmation =
      AuthSessionStorage::Get() &&
      wizard_context_->extra_factors_token.has_value() &&
      AuthSessionStorage::Get()
          ->Peek(wizard_context_->extra_factors_token.value())
          ->GetRequiresPasswordConfirmation();

  if (features::IsManagedLocalPinAndPasswordEnabled() &&
      might_require_saml_password_confirmation) {
    // Show the SamlConfirmPassword screen if the requirements for
    // SamlConfirmPasswordScreen are met (no scraped password, more than 2
    // scrapped password).
    // Note: When the AllowedLocalAuthFactors policy is set,
    // the SamlConfirmPassword screen will be skipped due to being NotApplicable
    // and we will continue with the `ShowPinSetupScreenAsMainFactor` inside the
    // `OnSamlConfirmPasswordScreenExit`.
    ShowSamlConfirmPasswordScreen();
  } else {
    // First step of the AuthFactor setup flow. Offer PIN as a main factor. If
    // there isn't hardware support, the screen exits gracefully.
    ShowPinSetupScreenAsMainFactor();
  }
}

void WizardController::OnPasswordSelectionScreenExit(
    PasswordSelectionScreen::Result result) {
  OnScreenExit(PasswordSelectionScreenView::kScreenId,
               PasswordSelectionScreen::GetResultString(result));

  switch (result) {
    // TODO(b/291808449): add an edge case for Enterprise users
    // without GAIA/SAML password.
    case PasswordSelectionScreen::Result::NOT_APPLICABLE:
      ShowFingerprintSetupScreen();
      return;
    case PasswordSelectionScreen::Result::BACK: {
      // The back button is only shown for going back to main PIN setup.
      CHECK_EQ(wizard_context_->knowledge_factor_setup.pin_setup_mode,
               WizardContext::PinSetupMode::kUserChosePasswordInstead);
      ShowPinSetupScreenAsMainFactor();
      return;
    }
    case PasswordSelectionScreen::Result::PIN_RESET: {
      // User has PIN-only. This can only happen during the recovery path.
      CHECK_EQ(wizard_context_->knowledge_factor_setup.auth_setup_flow,
               WizardContext::AuthChangeFlow::kRecovery)
          << "PasswordSelection exited with PIN_RESET result outside recovery.";
      ShowPinSetupScreenForRecovery();
      return;
    }
    case PasswordSelectionScreen::Result::LOCAL_PASSWORD_CHOICE:
    case PasswordSelectionScreen::Result::LOCAL_PASSWORD_FORCED:
      ShowLocalPasswordSetupScreen();
      return;
    case PasswordSelectionScreen::Result::GAIA_PASSWORD_CHOICE:
    case PasswordSelectionScreen::Result::GAIA_PASSWORD_FALLBACK:
    case PasswordSelectionScreen::Result::GAIA_PASSWORD_ENTERPRISE:
      ShowApplyOnlinePasswordScreen();
      return;
  }
}

void WizardController::OnLocalPasswordSetupScreenExit(
    LocalPasswordSetupScreen::Result result) {
  OnScreenExit(LocalPasswordSetupView::kScreenId,
               LocalPasswordSetupScreen::GetResultString(result));
  switch (result) {
    case LocalPasswordSetupScreen::Result::kBack:
      // Go back to the PasswordSelectionScreen if the user had a choice between
      // local vs. online password.
      if (!wizard_context_->knowledge_factor_setup.local_password_forced) {
        ShowPasswordSelectionScreen();
        return;
      }

      // The user could not choose between an online vs. local password because
      // they went through a passwordless signin. In that case, going back is
      // only possible for returning to the PinSetupScreen as a main factor.
      CHECK_EQ(wizard_context_->knowledge_factor_setup.pin_setup_mode,
               WizardContext::PinSetupMode::kUserChosePasswordInstead);
      ShowPinSetupScreenAsMainFactor();
      return;
    case LocalPasswordSetupScreen::Result::kDone:
    case LocalPasswordSetupScreen::Result::kNotApplicable:
      ShowFactorSetupSuccessScreen();
      return;
  }
}

void WizardController::OnApplyOnlinePasswordScreenExit(
    ApplyOnlinePasswordScreen::Result result) {
  OnScreenExit(ApplyOnlinePasswordScreenView::kScreenId,
               ApplyOnlinePasswordScreen::GetResultString(result));
  switch (result) {
    case ApplyOnlinePasswordScreen::Result::kError:
      ShowOSAuthErrorScreen();
      return;
    case ApplyOnlinePasswordScreen::Result::kSuccess:
    case ApplyOnlinePasswordScreen::Result::kNotApplicable: {
      switch (wizard_context_->knowledge_factor_setup.auth_setup_flow) {
        case WizardContext::AuthChangeFlow::kInitialSetup:
          ShowFingerprintSetupScreen();
          return;
        case WizardContext::AuthChangeFlow::kRecovery:
          ShowFactorSetupSuccessScreen();
          return;
        case WizardContext::AuthChangeFlow::kReauthentication:
          NOTREACHED() << "Reauthentication should have been switched to "
                          "Recovery if there was password update";
      }
    }
      return;
  }
}

void WizardController::OnOSAuthErrorScreenExit(
    OSAuthErrorScreen::Result result) {
  OnScreenExit(OSAuthErrorScreenView::kScreenId,
               OSAuthErrorScreen::GetResultString(result));
  switch (result) {
    case OSAuthErrorScreen::Result::kFallbackOnline:
      ShowEnterOldPasswordScreen();
      break;
    case OSAuthErrorScreen::Result::kFallbackLocal: {
      switch (wizard_context_->knowledge_factor_setup.auth_setup_flow) {
        case WizardContext::AuthChangeFlow::kInitialSetup:
          NOTREACHED() << "Recovery is not used during initial setup";
        case WizardContext::AuthChangeFlow::kReauthentication:
          AttemptLocalAuthenticationWithContext(
              std::move(wizard_context_->user_context));
          return;
        case WizardContext::AuthChangeFlow::kRecovery:
          // Recovery flow means that the user forgot their
          // local password. It does not make sense to ask for it.
          ShowLocalDataLossWarningScreen();
          return;
      }
    }

    case OSAuthErrorScreen::Result::kProceedAuthenticated: {
      switch (wizard_context_->knowledge_factor_setup.auth_setup_flow) {
        case WizardContext::AuthChangeFlow::kInitialSetup:
          ShowPasswordSelectionScreen();
          return;

        case WizardContext::AuthChangeFlow::kRecovery:
          if (ash::features::IsRecoveryFlowReorderEnabled()) {
            wizard_context_->allow_factor_change_during_recovery = true;
            ObtainContextAndLoginAuthenticated();
            return;
          }
          ShowPasswordSelectionScreen();
          return;
        case WizardContext::AuthChangeFlow::kReauthentication:
          // Proceed with login
          ObtainContextAndLoginAuthenticated();
          return;
      }
    }
      return;
    case OSAuthErrorScreen::Result::kAbortSignin:
      ShowLoginScreen();
      return;
  }
}

void WizardController::OnFactorSetupSuccessScreenExit(
    FactorSetupSuccessScreen::Result result) {
  OnScreenExit(FactorSetupSuccessScreenView::kScreenId,
               FactorSetupSuccessScreen::GetResultString(result));
  switch (result) {
    case FactorSetupSuccessScreen::Result::kNotApplicable:
    case FactorSetupSuccessScreen::Result::kProceed: {
      switch (wizard_context_->knowledge_factor_setup.auth_setup_flow) {
        case WizardContext::AuthChangeFlow::kInitialSetup:
          ShowFingerprintSetupScreen();
          return;
        case WizardContext::AuthChangeFlow::kReauthentication:
          // Proceed with login
          ObtainContextAndLoginAuthenticated();
          return;
        case WizardContext::AuthChangeFlow::kRecovery:
          if (ash::features::IsRecoveryFlowReorderEnabled()) {
            ObtainContextAndFinalizeAuth();
            return;
          }
          ObtainContextAndLoginAuthenticated();
          return;
      }
    }
    case FactorSetupSuccessScreen::Result::kTimedOut:
      // After the recovery re-order changes there is a strong invariant that
      // auth factors are only reset in the recovery flow after a cryptohome
      // mount. In that case the safest way to logout is to restart the chrome
      // process.
      if (ash::features::IsRecoveryFlowReorderEnabled() &&
          wizard_context_->knowledge_factor_setup.auth_setup_flow ==
              WizardContext::AuthChangeFlow::kRecovery) {
        session_manager::SessionManager::Get()->RequestSignOut();
        return;
      }

      ShowLoginScreen();
      return;
  }
}

// After FingerprintSetup, PIN is offered as an additional factor, unless:
// - The user has already set a PIN as their main factor.
// - The user chose 'Use Password instead' when offered a PIN.
void WizardController::OnFingerprintSetupScreenExit(
    FingerprintSetupScreen::Result result) {
  OnScreenExit(FingerprintSetupScreenView::kScreenId,
               FingerprintSetupScreen::GetResultString(result));
  const bool pin_was_already_set =
      wizard_context_->knowledge_factor_setup.pin_setup_mode ==
      WizardContext::PinSetupMode::kAlreadyPerformed;
  const bool user_chose_password_instead =
      wizard_context_->knowledge_factor_setup.pin_setup_mode ==
      WizardContext::PinSetupMode::kUserChosePasswordInstead;

  if (pin_was_already_set || user_chose_password_instead) {
    FinishAuthFactorsSetup();
  } else {
    ShowPinSetupScreenAsSecondaryFactor();
  }
}

void WizardController::OnPinSetupScreenExit(PinSetupScreen::Result result) {
  OnScreenExit(PinSetupScreenView::kScreenId,
               PinSetupScreen::GetResultString(result));
  switch (result) {
    // PIN as a main factor is not supported or not wanted. In both cases,
    // proceed to the PasswordSelectionScreen.
    case PinSetupScreen::Result::kUserChosePassword:
      // The user does not wish to have a PIN as their main authentication
      // factor. Setting this setup mode ensures that the screen is not
      // resurfaced later in the flow a second time, and that a back button
      // will be shown on PasswordSelectionScreen for the user to go back.
      wizard_context_->knowledge_factor_setup.pin_setup_mode =
          WizardContext::PinSetupMode::kUserChosePasswordInstead;
      [[fallthrough]];
    case PinSetupScreen::Result::kNotApplicableAsPrimaryFactor:
      ShowPasswordSelectionScreen();
      return;
    case PinSetupScreen::Result::kDoneAsMainFactor:
      wizard_context_->knowledge_factor_setup.pin_setup_mode =
          WizardContext::PinSetupMode::kAlreadyPerformed;
      ShowFingerprintSetupScreen();
      return;
    // These are emitted when the screen is surfaced at the end of the flow,
    // offering PIN as an additional factor.
    case PinSetupScreen::Result::kDoneAsSecondaryFactor:
    case PinSetupScreen::Result::kUserSkip:
    case PinSetupScreen::Result::kNotApplicable:
    case PinSetupScreen::Result::kTimedOut:
      FinishAuthFactorsSetup();
      return;
    // Proceed into session when PIN is reset via recovery.
    case PinSetupScreen::Result::kDoneRecoveryReset:
      CHECK_EQ(wizard_context_->knowledge_factor_setup.auth_setup_flow,
               WizardContext::AuthChangeFlow::kRecovery);

      if (features::IsRecoveryFlowReorderEnabled()) {
        ObtainContextAndFinalizeAuth();
        return;
      }
      ObtainContextAndLoginAuthenticated();
      return;
  }
}

void WizardController::OnRemoveLocalAuthFactorsScreenExit(
    RemoveLocalAuthFactorsScreen::Result result) {
  OnScreenExit(RemoveLocalAuthFactorsScreenView::kScreenId,
               RemoveLocalAuthFactorsScreen::GetResultString(result));

  switch (result) {
    case RemoveLocalAuthFactorsScreen::Result::kSuccess:
      ObtainContextAndFinalizeAuth();
      break;
    case RemoveLocalAuthFactorsScreen::Result::kError:
      ShowOSAuthErrorScreen();
      return;
  }
}

void WizardController::ObtainContextAndFinalizeAuth() {
  CHECK(wizard_context_->extra_factors_token);
  auto token = std::move(wizard_context_->extra_factors_token);
  wizard_context_->extra_factors_token = std::nullopt;

  ash::AuthSessionStorage::Get()->Withdraw(
      *token, base::BindOnce(&WizardController::FinalizeAuthWithContext,
                             weak_factory_.GetWeakPtr()));
}

void WizardController::FinalizeAuthWithContext(
    std::unique_ptr<UserContext> context) {
  if (!context) {
    // Session has expired.
    LOG(ERROR) << "Session expired before login could proceed.";
    // Also dump to see in what scenarios would this happen.
    base::debug::DumpWithoutCrashing(FROM_HERE);
    session_manager::SessionManager::Get()->RequestSignOut();
    return;
  }

  auto mount_state = context->GetMountState();
  if (!mount_state.has_value()) {
    // In certain edge cases, such as a Reauthentication that transitions into
    // recovery with an automatic online password update, we can reach this
    // point before the cryptohome is mounted. In these cases, we need to
    // perform a login instead of just finalizing the auth.
    LoginAuthenticatedWithContext(std::move(context));
    return;
  }
  ash::LoginDisplayHost::default_host()
      ->GetExistingUserController()
      ->FinalizeAuthAndStartSession(*context);
}

void WizardController::ObtainContextAndLoginAuthenticated() {
  CHECK(wizard_context_->extra_factors_token);
  auto token = std::move(wizard_context_->extra_factors_token);
  wizard_context_->extra_factors_token = std::nullopt;

  ash::AuthSessionStorage::Get()->Withdraw(
      *token, base::BindOnce(&WizardController::LoginAuthenticatedWithContext,
                             weak_factory_.GetWeakPtr()));
}

void WizardController::ObtainContextAndAttemptLocalAuthentication() {
  CHECK(wizard_context_->extra_factors_token);
  auto token = std::move(wizard_context_->extra_factors_token);
  wizard_context_->extra_factors_token = std::nullopt;

  ash::AuthSessionStorage::Get()->Withdraw(
      *token,
      base::BindOnce(&WizardController::AttemptLocalAuthenticationWithContext,
                     weak_factory_.GetWeakPtr()));
}

void WizardController::CompleteLogin() {
  CHECK(wizard_context_->user_context);
  auto user_context = std::move(wizard_context_->user_context);
  LoginDisplayHost::default_host()->CompleteLogin(*user_context);
  wizard_context_->user_context = nullptr;
}

void WizardController::LoginAuthenticatedWithContext(
    std::unique_ptr<UserContext> context) {
  if (!context) {
    // Session has expired.
    LOG(ERROR) << "Session expired before login could proceed.";
    wizard_context_->osauth_error = WizardContext::OSAuthErrorKind::kFatal;
    ShowOSAuthErrorScreen();
    return;
  }
  ash::LoginDisplayHost::default_host()
      ->GetExistingUserController()
      ->LoginAuthenticated(std::move(context));
}

void WizardController::AttemptLocalAuthenticationWithContext(
    std::unique_ptr<UserContext> context) {
  ash::LoginDisplayHost::default_host()->GetSigninUI()->RunLocalAuthentication(
      std::move(context));
}

void WizardController::FinishAuthFactorsSetup() {
  // TODO(crbug.com/372213353): Ensure that AuthSession is terminated after this
  // step.
  if (wizard_context_->extra_factors_token.has_value()) {
    ash::AuthSessionStorage::Get()->Invalidate(
        wizard_context_->extra_factors_token.value(), base::DoNothing());
    wizard_context_->extra_factors_token = std::nullopt;
  }

  if (features::IsOobePersonalizedOnboardingEnabled()) {
    ShowCategoriesSelectionScreen();
  } else {
    ShowRecommendAppsScreen();
  }
}

// End of local authentication setup screen exit handlers.

void WizardController::OnRecommendAppsScreenExit(
    RecommendAppsScreen::Result result) {
  OnScreenExit(RecommendAppsScreenView::kScreenId,
               RecommendAppsScreen::GetResultString(result));

  switch (result) {
    case RecommendAppsScreen::Result::kSelected:
      ShowAppDownloadingScreen();
      break;
    case RecommendAppsScreen::Result::kSkipped:
    case RecommendAppsScreen::Result::kNotApplicable:
    case RecommendAppsScreen::Result::kLoadError:
      ShowPerksDiscoveryScreen();
      break;
  }
}

void WizardController::OnRemoteActivityNotificationScreenExit() {
  // Remember the user acknowledged the message.
  GetLocalState()->SetBoolean(ash::prefs::kRemoteAdminWasPresent, false);

  // Check if there are any local accounts present for the lock screen which
  // suggest that the OOBE flow was completed and the dialog should be hidden.
  if (LoginDisplayHost::default_host()->HasUserPods()) {
    LoginDisplayHost::default_host()->HideOobeDialog();
    return;
  }

  // When, there is no local accounts present and the OOBE flow must be
  // continued. Hence, we go back to the previous screen present in the flow.
  bool switched_screen = MaybeSetToPreviousScreen();
  CHECK(switched_screen);
}

void WizardController::OnAppDownloadingScreenExit() {
  OnScreenExit(AppDownloadingScreenView::kScreenId, kDefaultExitReason);
  ShowPerksDiscoveryScreen();
}

void WizardController::OnAiIntroScreenExit(AiIntroScreen::Result result) {
  OnScreenExit(AiIntroScreenView::kScreenId,
               AiIntroScreen::GetResultString(result));
  ShowGeminiIntroScreen();
}

void WizardController::OnGeminiIntroScreenExit(
    GeminiIntroScreen::Result result) {
  OnScreenExit(GeminiIntroScreenView::kScreenId,
               GeminiIntroScreen::GetResultString(result));

  if (result == GeminiIntroScreen::Result::kBack) {
    CHECK(!AiIntroScreen::ShouldBeSkipped());
    ShowAiIntroScreen();
    return;
  }

  AdvanceToScreen(SmartPrivacyProtectionView::kScreenId);
}

void WizardController::OnMultiDeviceSetupScreenExit(
    MultiDeviceSetupScreen::Result result) {
  OnScreenExit(MultiDeviceSetupScreenView::kScreenId,
               MultiDeviceSetupScreen::GetResultString(result));

  ShowSplitModifierKeyboardInfoScreen();
}

void WizardController::OnGestureNavigationScreenExit(
    GestureNavigationScreen::Result result) {
  OnScreenExit(GestureNavigationScreenView::kScreenId,
               GestureNavigationScreen::GetResultString(result));
  if (features::IsOobeChoobeEnabled()) {
    ShowChoobeScreen();
  } else {
    ShowThemeSelectionScreen();
  }
}

void WizardController::OnMarketingOptInScreenExit(
    MarketingOptInScreen::Result result) {
  OnScreenExit(MarketingOptInScreenView::kScreenId,
               MarketingOptInScreen::GetResultString(result));

  OnOobeFlowFinished();
}

void WizardController::OnResetScreenExit() {
  OnScreenExit(ResetView::kScreenId, kResetScreenExitReason);
  OnDeviceModificationCanceled();
}

void WizardController::OnDeviceModificationCanceled() {
  BaseScreen* previous_screen = nullptr;
  if (previous_screens_.contains(current_screen_)) {
    previous_screen = previous_screens_[current_screen_];
  }

  ResetCurrentScreen();

  if (previous_screen) {
    if (IsSigninScreen(previous_screen->screen_id())) {
      ShowLoginScreen();
    } else {
      SetCurrentScreen(previous_screen);
    }
    return;
  }

  // No previous screen found. Most likely the device is owned,
  // in which case the login screen is the default.
  if (ash::InstallAttributes::Get()->IsDeviceLocked()) {
    ShowLoginScreen();
    return;
  }

  LOG(WARNING) << "No previous screen on unowned device";
  // The WelcomeView is not available when we enter OOBE from the login screen.
  // TODO(crbug.com/393041544) Determine if safeguard is really required.
  if (HasScreen(WelcomeView::kScreenId)) {
    ShowWelcomeScreen();
    return;
  }
  ShowLoginScreen();
}

void WizardController::OnManagementTransitionScreenExit() {
  OnScreenExit(ManagementTransitionScreenView::kScreenId, kDefaultExitReason);

  OnOobeFlowFinished();
}

void WizardController::OnUpdateRequiredScreenExit() {
  ResetCurrentScreen();
  ShowLoginScreen();
}

void WizardController::OnPackagedLicenseScreenExit(
    PackagedLicenseScreen::Result result) {
  OnScreenExit(PackagedLicenseView::kScreenId,
               PackagedLicenseScreen::GetResultString(result));
  switch (result) {
    case PackagedLicenseScreen::Result::DONT_ENROLL:
    case PackagedLicenseScreen::Result::NOT_APPLICABLE:
      ShowLoginScreen();
      break;
    case PackagedLicenseScreen::Result::ENROLL:
    case PackagedLicenseScreen::Result::NOT_APPLICABLE_SKIP_TO_ENROLL:
      ShowEnrollmentScreen();
      break;
  }
}

void WizardController::OnAppLaunchSplashScreenExit() {
  // TODO(b/343483938): Exit AppLaunchSplashScreen before launching the app.
  NOTIMPLEMENTED();
}

void WizardController::OnFjordTouchControllerScreenExit() {
  OnScreenExit(FjordTouchControllerScreenView::kScreenId, kDefaultExitReason);
  MaybeNotifyFjordOobeStateManager(fjord_oobe_state::proto::FjordOobeStateInfo::
                                       FJORD_OOBE_STATE_ENROLLMENT_DONE);
  ShowFjordFwUpdateScreen();
}

void WizardController::OnFjordStationSetupScreenExit() {
  OnScreenExit(FjordStationSetupScreenView::kScreenId, kDefaultExitReason);
  auto app = KioskController::Get().GetAutoLaunchApp();
  if (app.has_value()) {
    AutoLaunchKioskApp(app.value());
    return;
  }

  NOTREACHED() << "Expected a kiosk app to be available";
}

void WizardController::OnFjordFwUpdateScreenExit() {
  MaybeNotifyFjordOobeStateManager(fjord_oobe_state::proto::FjordOobeStateInfo::
                                       FJORD_OOBE_STATE_READY_FOR_CALIBRATION);
  OnScreenExit(FjordFwUpdateScreenView::kScreenId, kDefaultExitReason);
  ShowFjordStationSetupScreen();
}

void WizardController::OnFjordImageSelectionScreenExit(
    FjordImageSelectionScreen::Result result) {
  OnScreenExit(FjordImageSelectionScreenView::kScreenId,
               FjordImageSelectionScreen::GetResultString(result));
  // Continue to enrollment if the chosen image type matches the current image
  // type.
  if ((result == FjordImageSelectionScreen::Result::kCuttlefish &&
       policy::EnrollmentRequisitionManager::IsCuttlefishDevice()) ||
      (result == FjordImageSelectionScreen::Result::kSquid &&
       policy::EnrollmentRequisitionManager::IsSquidDevice())) {
    ShowAutoEnrollmentCheckScreen();
  } else {
    ShowFjordImageDownloadScreen();
    // Map the selection result to the dissidia image type and start download.
    FjordImageDownloader::ImageType image_type =
        FjordImageDownloader::ImageType::kUnknown;
    switch (result) {
      case FjordImageSelectionScreen::Result::kCuttlefish:
        image_type = FjordImageDownloader::ImageType::kNoctis;
        break;
      case FjordImageSelectionScreen::Result::kSquid:
        image_type = FjordImageDownloader::ImageType::kSelphie;
        break;
      default:
        NOTREACHED();
    }
    fjord_image_downloader_->RunDissidia(
        image_type,
        base::BindOnce(&WizardController::OnFjordImageDownloadCompleted,
                       weak_factory_.GetWeakPtr()));
  }
}

void WizardController::OnFjordImageDownloadScreenExit() {
  // Image download screen is a terminal state because the image install will
  // reboot the device.
  OnScreenExit(FjordImageDownloadScreenView::kScreenId, kDefaultExitReason);
}

void WizardController::OnFjordImageDownloadCompleted(
    bool success,
    const std::string& error_message) {
  // TODO(b:501556529): Add a histogram for image download success/failure.
  if (success) {
    VLOG(1) << "Fjord image download (dissidia) completed successfully.";
  } else {
    LOG(ERROR) << "Fjord image download (dissidia) failed: " << error_message;
    GetScreen<FjordImageSelectionScreen>()->SetErrorMessage(error_message);
    ShowFjordImageSelectionScreen();
  }
}

bool WizardController::ExitFjordTouchControllerScreen() {
  // Return true if Station setup screen or FW update screen is showing because
  // this means the TC setup screen was shown before this. This ensures that if
  // the TC goes offline and online again, it can know if the TC setup screen
  // was exited.
  // TODO(b/477337635): Clean this up when TC10 moves to calling
  // GetFjordOobeState().
  if (current_screen()->screen_id() == FjordStationSetupScreenView::kScreenId ||
      current_screen()->screen_id() == FjordFwUpdateScreenView::kScreenId) {
    return true;
  }

  return GetScreen<FjordTouchControllerScreen>()->ExitScreen();
}

bool WizardController::ShowNextFjordOobeScreen(
    fjord_oobe_state::proto::FjordOobeStateInfo::FjordOobeState new_state) {
  if (new_state != fjord_oobe_state::proto::FjordOobeStateInfo::
                       FJORD_OOBE_STATE_READY_FOR_CALIBRATION) {
    return false;
  }
  return GetScreen<FjordFwUpdateScreen>()->ExitScreen();
}

void WizardController::OnOobeFlowFinished() {
  if (GetLoginDisplayHost()
          ->GetWizardContext()
          ->defer_oobe_flow_finished_for_tests) {
    return;
  }
  SetCurrentScreen(nullptr);

  user_manager::KnownUser known_user(GetLocalState());
  const AccountId account_id =
      user_manager::UserManager::Get()->GetActiveUser()->GetAccountId();
  known_user.SetOnboardingCompletedVersion(account_id,
                                           version_info::GetVersion());
  known_user.RemovePendingOnboardingScreen(account_id);

  PrefService* active_user_prefs =
      ProfileManager::GetActiveUserProfile()->GetPrefs();
  if (features::IsOobeChoobeEnabled()) {
    // Additional cleanup of CHOOBE prefs in case it was not already cleared.
    active_user_prefs->ClearPref(prefs::kChoobeSelectedScreens);
    active_user_prefs->ClearPref(prefs::kChoobeCompletedScreens);
  }

  GetLoginDisplayHost()->GetOobeMetricsHelper()->RecordOnboadingComplete(
      GetLocalState()->GetTime(prefs::kOobeStartTime),
      active_user_prefs->GetTime(prefs::kOobeOnboardingTime));

  GetLocalState()->ClearPref(prefs::kOobeStartTime);

  GetLocalState()->ClearPref(prefs::kOobeMetricsClientIdAtOobeStart);

  // Check if pre-choice metrics is still enabled.
  if (metrics::CrOSPreChoiceMetricsManager::Get()) {
    LOG(ERROR) << "OOBE flow is finished and Pre-choice metrics is still "
               << "enabled. Disabling pre-choice metrics.";
    metrics::CrOSPreChoiceMetricsManager::Get()->Disable();
  }

  // Launch browser and delete login host controller.
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          &UserSessionManager::DoBrowserLaunch,
          UserSessionManager::GetInstance()->GetUserSessionManagerAsWeakPtr(),
          ProfileManager::GetActiveUserProfile()));
}

void WizardController::OnDeviceDisabledChecked(bool device_disabled) {
  prescribed_enrollment_config_ =
      policy::EnrollmentConfig::GetPrescribedEnrollmentConfig(
          local_state_.get());

  if (device_disabled) {
    demo_setup_controller_.reset();
    ShowDeviceDisabledScreen();
  } else if (demo_setup_controller_) {
    ShowDemoModeSetupScreen();
  } else if (wizard_context_->enrollment_triggered_early ||
             prescribed_enrollment_config_.should_enroll()) {
    VLOG(1) << "StartEnrollment from OnDeviceDisabledChecked("
            << "device_disabled=" << device_disabled << ") "
            << "skip_update_enroll_after_eula_="
            << wizard_context_->enrollment_triggered_early
            << ", prescribed_enrollment_config_.should_enroll()="
            << prescribed_enrollment_config_.should_enroll();
    StartEnrollmentScreen();
  } else {
    PerformOOBECompletedActions(
        OobeMetricsHelper::CompletedPreLoginOobeFlowType::kRegular);

    // Show the `PackagedLicenseScreen` if Auto-Enrollment check isn't forced
    // or if OOBE is already completed. Otherwise, `SignInFatalErrorScreen` is
    // expected to be shown if the forced check hasn't led to OOBE completion.
    if (!features::IsOobeAutoEnrollmentCheckForcedEnabled() ||
        StartupUtils::IsOobeCompleted(local_state_.get())) {
      ShowPackagedLicenseScreen();
    }
  }
}

void WizardController::InitiateOOBEUpdate() {
  // If this is a Cellular First device, instruct UpdateEngine to allow
  // updates over cellular data connections.
  if (switches::IsCellularFirstDevice()) {
    UpdateEngineClient::Get()->SetUpdateOverCellularPermission(
        true, base::BindOnce(&WizardController::StartOOBEUpdate,
                             weak_factory_.GetWeakPtr()));
  } else {
    StartOOBEUpdate();
  }
}

void WizardController::StartOOBEUpdate() {
  SetCurrentScreen(GetScreen(UpdateView::kScreenId));
}

void WizardController::StartNetworkTimezoneResolve() {
  // Bypass the network requests for the geolocation and the timezone if the
  // timezone is being overridden through the command line.
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kOobeTimezoneOverrideForTests)) {
    auto timezone = std::make_unique<TimeZoneResponseData>();
    timezone->status = TimeZoneResponseData::OK;
    timezone->timeZoneId =
        base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
            switches::kOobeTimezoneOverrideForTests);
    VLOG(1) << "Timezone is being overridden with : " << timezone->timeZoneId;
    OnTimezoneResolved(std::move(timezone), /*server_error*/ false);
    return;
  }

  DelayNetworkCall(base::BindOnce(&WizardController::StartTimezoneResolve,
                                  weak_factory_.GetWeakPtr()));
}

// Resolving the timezone consists of first determining the location,
// and then determining the timezone.
void WizardController::StartTimezoneResolve() {
  if (!g_browser_process->platform_part()
           ->GetTimezoneResolverManager()
           ->TimeZoneResolverShouldBeRunning()) {
    return;
  }

  SystemLocationProvider::GetInstance()->RequestGeolocation(
      base::Seconds(kResolveTimeZoneTimeoutSeconds),
      false /* send_wifi_geolocation_data */,
      false /* send_cellular_geolocation_data */,
      base::BindOnce(&WizardController::OnLocationResolved,
                     weak_factory_.GetWeakPtr()),
      SystemLocationProvider::ClientId::kWizardController);
}

void WizardController::PerformPostNetworkScreenActions() {
  StartNetworkTimezoneResolve();
  DelayNetworkCall(ServicesCustomizationDocument::GetInstance()
                       ->EnsureCustomizationAppliedClosure());
  GetAutoEnrollmentController()->Start();
}

void WizardController::PerformOOBECompletedActions(
    OobeMetricsHelper::CompletedPreLoginOobeFlowType flow_type) {
  // Avoid marking OOBE as completed multiple times if going from login screen
  // to enrollment screen (and back).
  if (StartupUtils::IsOobeCompleted(local_state_.get())) {
    return;
  }

  if (features::IsOobeAutoEnrollmentCheckForcedEnabled()) {
    // To prevent auto-enrollment bypass, check if `kAutoEnrollmentCheckExited`
    // has been set. If it's false, the auto-enrollment check was not properly
    // completed.
    if (!GetLocalState()->GetBoolean(prefs::kAutoEnrollmentCheckExited)) {
      GetLoginDisplayHost()
          ->GetOobeMetricsHelper()
          ->RecordOobeNotCompletedErrorTrigger(
              OobeMetricsHelper::OobeNotCompletedTrigger::
                  kPerformOobeCompletedAction);

      // Show a fatal error and do not mark OOBE as completed.
      ShowSignInFatalErrorScreen(
          SignInFatalErrorScreen::Error::kOobeCompletionSkipped,
          base::DictValue());
      return;
    }
  }

  StartupUtils::MarkOobeCompleted(local_state_.get());
  GetLoginDisplayHost()->GetOobeMetricsHelper()->RecordPreLoginOobeComplete(
      flow_type);

  // Triggers DLC installation once OOBE is complete.
  cros_healthd::internal::TriggerDlcInstall();
}

void WizardController::SetCurrentScreen(BaseScreen* new_current) {
  VLOG(1) << "SetCurrentScreen: "
          << (new_current ? new_current->screen_id().name : "null");

  if (new_current && new_current->MaybeSkip(*wizard_context_)) {
    // choobe_flow_controller_ lives only while CHOOBE flow is active, metrics
    // regarding screens shown while CHOOBE is active is handled by
    // choobe_flow_controller_.
    if (features::IsOobeChoobeEnabled()) {
      if (ChoobeFlowController::IsOptionalScreen(new_current->screen_id()) &&
          choobe_flow_controller_) {
        return;
      }
    }

    GetLoginDisplayHost()->GetOobeMetricsHelper()->RecordScreenShownStatus(
        new_current->screen_id(),
        OobeMetricsHelper::ScreenShownStatus::kSkipped);
    return;
  }

  if (current_screen_ == new_current || GetOobeUI() == nullptr) {
    return;
  }

  // Check if we didn't come here via the previous screen logic.
  if (current_screen_ && new_current &&
      (!previous_screens_.contains(current_screen_) ||
       previous_screens_[current_screen_] != new_current)) {
    previous_screens_[new_current] = current_screen_;
  }

  ResetCurrentScreen();

  current_screen_ = new_current;

  if (!current_screen_) {
    NotifyScreenChanged();
    return;
  }

  // WizardController should not save the pending screen during demo mode, nor
  // should it save it while exercising the recovery path.
  const bool isEligibleForSavingPendingScreen =
      !demo_setup_controller_ &&
      wizard_context_->knowledge_factor_setup.auth_setup_flow !=
          WizardContext::AuthChangeFlow::kRecovery;

  // First remember how far have we reached so that we can resume if needed.
  if (isEligibleForSavingPendingScreen) {
    if (!wizard_context_->is_add_person_flow &&
        IsResumableOobeScreen(current_screen_->screen_id())) {
      StartupUtils::SaveOobePendingScreen(local_state_.get(),
                                          current_screen_->screen_id().name);
    } else if (IsResumablePostLoginScreen(current_screen_->screen_id()) &&
               !wizard_context_->is_cloud_ready_update_flow &&
               wizard_context_->screen_after_managed_tos !=
                   ash::OOBE_SCREEN_UNKNOWN) {
      // If screen_after_managed_tos == SCREEN_UNKNOWN means that the
      // onboarding has already been finished by the user and we don't need to
      // save the state here.
      user_manager::KnownUser(GetLocalState())
          .SetPendingOnboardingScreen(
              user_manager::UserManager::Get()->GetActiveUser()->GetAccountId(),
              current_screen_->screen_id().name);
    }
  }

  UpdateStatusAreaVisibilityForScreen(current_screen_->screen_id());
  GetLoginDisplayHost()->GetOobeMetricsHelper()->RecordScreenShownStatus(
      current_screen_->screen_id(),
      OobeMetricsHelper::ScreenShownStatus::kShown);
  current_screen_->Show(wizard_context_);
  NotifyScreenChanged();
}

void WizardController::UpdateStatusAreaVisibilityForScreen(
    OobeScreenId screen_id) {
  // SystemTrayClientImpl::Get() can be nullptr in unit tests.
  if (SystemTrayClientImpl::Get()) {
    SystemTrayClientImpl::Get()->SetPrimaryTrayVisible(
        !ShouldHideStatusArea(screen_id));
  }
}

void WizardController::OnHIDScreenNecessityCheck(bool screen_needed) {
  if (!GetOobeUI()) {
    return;
  }

  // Check for tests configurations.
  if (wizard_context_->skip_to_update_for_tests ||
      wizard_context_->skip_to_login_for_tests || current_screen_) {
    return;
  }

  if (screen_needed) {
    ShowHIDDetectionScreen();
  } else {
    AdvanceToScreenAfterHIDDetection(ash::OOBE_SCREEN_UNKNOWN);
  }
}

void WizardController::UpdateOobeConfiguration() {
  wizard_context_->configuration = configuration::FilterConfiguration(
      OobeConfiguration::Get()->configuration(),
      configuration::ConfigurationHandlerSide::HANDLER_CPP);
  auto* requisition_value = wizard_context_->configuration.FindString(
      configuration::kDeviceRequisition);

  if (requisition_value) {
    VLOG(1) << "Using Device Requisition from configuration"
            << *requisition_value;
    policy::EnrollmentRequisitionManager::SetDeviceRequisition(
        local_state_.get(), *requisition_value);
  } else if (policy::EnrollmentRequisitionManager::IsCuttlefishDevice()) {
    VLOG(1) << "Using default Device Requisition value for Cuttlefish build "
               "configuration"
            << policy::EnrollmentRequisitionManager::kCuttlefishRequisition;
    policy::EnrollmentRequisitionManager::SetDeviceRequisition(
        local_state_.get(),
        policy::EnrollmentRequisitionManager::kCuttlefishRequisition);
  } else if (policy::EnrollmentRequisitionManager::IsSquidDevice()) {
    VLOG(1) << "Using default Device Requisition value for Squid build "
               "configuration"
            << policy::EnrollmentRequisitionManager::kSquidRequisition;
    policy::EnrollmentRequisitionManager::SetDeviceRequisition(
        local_state_.get(),
        policy::EnrollmentRequisitionManager::kSquidRequisition);
  } else if (policy::EnrollmentRequisitionManager::IsMeetDevice(
                 local_state_.get())) {
    VLOG(1) << "Using default Device Requisition value for CFM build "
               "configuration"
            << policy::EnrollmentRequisitionManager::kRemoraRequisition;
    policy::EnrollmentRequisitionManager::SetDeviceRequisition(
        local_state_.get(),
        policy::EnrollmentRequisitionManager::kRemoraRequisition);
  }

  auto* network_config =
      wizard_context_->configuration.FindString(configuration::kNetworkConfig);
  if (network_config) {
    auto rollback_network_config = std::make_unique<
        mojo::Remote<rollback_network_config::mojom::RollbackNetworkConfig>>();
    rollback_network_config::BindToInProcessInstance(
        rollback_network_config->BindNewPipeAndPassReceiver());
    rollback_network_config->get()->RollbackConfigImport(*network_config,
                                                         base::DoNothing());
  }
}

bool WizardController::CanNavigateTo(OobeScreenId screen_id) {
  if (!current_screen_) {
    return true;
  }

  if (wizard_context_->skip_to_login_for_tests) {
    VLOG(1) << "CanNavigateTo to screen " << screen_id
            << " returns true because skip_to_login_for_tests is set to true";
    return true;
  }

  BaseScreen* next_screen = GetScreen(screen_id);
  return next_screen->screen_priority() <= current_screen_->screen_priority();
}

void WizardController::AdvanceToScreen(OobeScreenId screen_id) {
  VLOG(1) << "AdvanceToScreen " << screen_id;
  if (!CanNavigateTo(screen_id)) {
    LOG(WARNING) << "Cannot advance to screen : " << screen_id
                 << " as it's priority is less than the current screen : "
                 << current_screen_->screen_id();
    return;
  }

  if (screen_id == WelcomeView::kScreenId) {
    ShowWelcomeScreen();
  } else if (screen_id == NetworkScreenView::kScreenId) {
    ShowNetworkScreen();
  } else if (screen_id == PackagedLicenseView::kScreenId) {
    ShowPackagedLicenseScreen();
  } else if (screen_id == UpdateView::kScreenId) {
    InitiateOOBEUpdate();
  } else if (screen_id == ResetView::kScreenId) {
    ShowResetScreen();
  } else if (screen_id == EnableAdbSideloadingScreenView::kScreenId) {
    ShowEnableAdbSideloadingScreen();
  } else if (screen_id == EnableDebuggingScreenView::kScreenId) {
    ShowEnableDebuggingScreen();
  } else if (screen_id == EnrollmentScreenView::kScreenId) {
    ShowEnrollmentScreen();
  } else if (screen_id == DemoSetupScreenView::kScreenId) {
    ShowDemoModeSetupScreen();
  } else if (screen_id == DemoPreferencesScreenView::kScreenId) {
    ShowDemoModePreferencesScreen();
  } else if (screen_id == TermsOfServiceScreenView::kScreenId) {
    ShowTermsOfServiceScreen();
  } else if (screen_id == SyncConsentScreenView::kScreenId) {
    ShowSyncConsentScreen();
  } else if (screen_id == RecommendAppsScreenView::kScreenId) {
    ShowRecommendAppsScreen();
  } else if (screen_id == RemoteActivityNotificationView::kScreenId) {
    ShowRemoteActivityNotificationScreen();
  } else if (screen_id == AppDownloadingScreenView::kScreenId) {
    ShowAppDownloadingScreen();
  } else if (screen_id == AiIntroScreenView::kScreenId) {
    ShowAiIntroScreen();
  } else if (screen_id == GeminiIntroScreenView::kScreenId) {
    ShowGeminiIntroScreen();
  } else if (screen_id == WrongHWIDScreenView::kScreenId) {
    ShowWrongHWIDScreen();
  } else if (screen_id == AutoEnrollmentCheckScreenView::kScreenId) {
    ShowAutoEnrollmentCheckScreen();
  } else if (screen_id == AppLaunchSplashScreenView::kScreenId) {
    ShowAppLaunchSplashScreen();
  } else if (screen_id == HIDDetectionView::kScreenId) {
    ShowHIDDetectionScreen();
  } else if (screen_id == DeviceDisabledScreenView::kScreenId) {
    ShowDeviceDisabledScreen();
  } else if (screen_id == EncryptionMigrationScreenView::kScreenId) {
    ShowEncryptionMigrationScreen();
  } else if (screen_id == UpdateRequiredView::kScreenId) {
    ShowUpdateRequiredScreen();
  } else if (screen_id == MultiDeviceSetupScreenView::kScreenId) {
    ShowMultiDeviceSetupScreen();
  } else if (screen_id == GestureNavigationScreenView::kScreenId) {
    ShowGestureNavigationScreen();
  } else if (screen_id == PinSetupScreenView::kScreenId) {
    ShowPinSetupScreenAsSecondaryFactor();
  } else if (screen_id == FingerprintSetupScreenView::kScreenId) {
    ShowFingerprintSetupScreen();
  } else if (screen_id == MarketingOptInScreenView::kScreenId) {
    ShowMarketingOptInScreen();
  } else if (screen_id == ManagementTransitionScreenView::kScreenId) {
    ShowManagementTransitionScreen();
  } else if (screen_id == GuestTosScreenView::kScreenId) {
    ShowGuestTosScreen();
  } else if (screen_id == ConsolidatedConsentScreenView::kScreenId) {
    ShowConsolidatedConsentScreen();
  } else if (screen_id == CryptohomeRecoverySetupScreenView::kScreenId) {
    ShowCryptohomeRecoverySetupScreen();
  } else if (screen_id == TouchpadScrollScreenView::kScreenId) {
    ShowTouchpadScrollScreen();
  } else if (screen_id == GaiaInfoScreenView::kScreenId) {
    ShowGaiaInfoScreen();
  } else if (screen_id == DrivePinningScreenView::kScreenId) {
    ShowDrivePinningScreen();
  } else if (screen_id == DisplaySizeScreenView::kScreenId) {
    ShowDisplaySizeScreen();
  } else if (screen_id == ChoobeScreenView::kScreenId) {
    ShowChoobeScreen();
  } else if (screen_id == AddChildScreenView::kScreenId) {
    ShowAddChildScreen();
  } else if (screen_id == ConsumerUpdateScreenView::kScreenId) {
    ShowConsumerUpdateScreen();
  } else if (screen_id == PasswordSelectionScreenView::kScreenId) {
    ShowPasswordSelectionScreen();
  } else if (screen_id == ApplyOnlinePasswordScreenView::kScreenId) {
    ShowApplyOnlinePasswordScreen();
  } else if (screen_id == OSAuthErrorScreenView::kScreenId) {
    ShowOSAuthErrorScreen();
  } else if (screen_id == FactorSetupSuccessScreenView::kScreenId) {
    ShowFactorSetupSuccessScreen();
  } else if (screen_id == LocalPasswordSetupView::kScreenId) {
    ShowLocalPasswordSetupScreen();
  } else if (screen_id == CategoriesSelectionScreenView::kScreenId) {
    ShowCategoriesSelectionScreen();
  } else if (screen_id == PersonalizedRecommendAppsScreenView::kScreenId) {
    ShowPersonalizedRecomendAppsScreen();
  } else if (screen_id == PerksDiscoveryScreenView::kScreenId) {
    ShowPerksDiscoveryScreen();
  } else if (screen_id == SplitModifierKeyboardInfoScreenView::kScreenId) {
    ShowSplitModifierKeyboardInfoScreen();
  } else if (screen_id == TpmErrorView::kScreenId ||
             screen_id == InstallAttributesErrorView::kScreenId ||
             screen_id == FamilyLinkNoticeView::kScreenId ||
             screen_id == GaiaView::kScreenId ||
             screen_id == UserCreationView::kScreenId ||
             screen_id == SignInFatalErrorView::kScreenId ||
             screen_id == UserAllowlistCheckScreenView::kScreenId ||
             screen_id == LocaleSwitchView::kScreenId ||
             screen_id == RecoveryEligibilityView::kScreenId ||
             screen_id == OfflineLoginView::kScreenId ||
             screen_id == OsInstallScreenView::kScreenId ||
             screen_id == OsTrialScreenView::kScreenId ||
             screen_id == ParentalHandoffScreenView::kScreenId ||
             screen_id == HWDataCollectionView::kScreenId ||
             screen_id == SmartPrivacyProtectionView::kScreenId ||
             screen_id == ThemeSelectionScreenView::kScreenId ||
             screen_id == SamlConfirmPasswordView::kScreenId ||
             screen_id == LocalStateErrorScreenView::kScreenId ||
             screen_id == QuickStartView::kScreenId ||
             screen_id == FjordStationSetupScreenView::kScreenId ||
             screen_id == FjordTouchControllerScreenView::kScreenId ||
             screen_id == FjordImageSelectionScreenView::kScreenId ||
             screen_id == FjordImageDownloadScreenView::kScreenId ||
             screen_id == FjordFwUpdateScreenView::kScreenId ||
             screen_id == RemoveLocalAuthFactorsScreenView::kScreenId) {
    SetCurrentScreen(GetScreen(screen_id));
  } else {
    NOTREACHED();
  }
}

bool WizardController::HandleAccelerator(LoginAcceleratorAction action) {
  return current_screen_ && current_screen_->HandleAccelerator(action);
}

void WizardController::StartDemoModeSetup() {
  // Start Demo Mode by initiate demo set up controller and showing the first
  // network screen in demo mode setup flow.
  demo_setup_controller_ = std::make_unique<DemoSetupController>(
      &local_state_.get(), shared_url_loader_factory_,
      &browser_policy_connector_ash_.get(), component_manager_ash_);
  ShowNetworkScreen();
}

void WizardController::CreateChoobeFlowController() {
  choobe_flow_controller_ = std::make_unique<ChoobeFlowController>();
}

void WizardController::SimulateDemoModeSetupForTesting(
    std::optional<DemoSession::DemoModeConfig> demo_config) {
  if (!demo_setup_controller_) {
    demo_setup_controller_ = std::make_unique<DemoSetupController>(
        &local_state_.get(), shared_url_loader_factory_,
        &browser_policy_connector_ash_.get(), component_manager_ash_);
  }
  if (demo_config.has_value()) {
    demo_setup_controller_->set_demo_config(*demo_config);
  }
}

void WizardController::OnAccessibilityStatusChanged(
    const AccessibilityStatusEventDetails& details) {
  enum AccessibilityNotificationType type = details.notification_type;
  if (type == AccessibilityNotificationType::kManagerShutdown) {
    accessibility_subscription_ = {};
    return;
  } else if (type != AccessibilityNotificationType::kToggleSpokenFeedback ||
             !details.enabled) {
    return;
  }

  CrasAudioHandler* cras = CrasAudioHandler::Get();
  if (cras->IsOutputMuted()) {
    cras->SetOutputMute(false);
    cras->SetOutputVolumePercent(kMinAudibleOutputVolumePercent);
  } else if (cras->GetOutputVolumePercent() < kMinAudibleOutputVolumePercent) {
    cras->SetOutputVolumePercent(kMinAudibleOutputVolumePercent);
  }
}

void WizardController::AutoLaunchKioskApp(const KioskApp& app) {
  // Wait until `CrosSettings` is either trusted or permanently untrusted.
  const CrosSettingsProvider::TrustedStatus status =
      CrosSettings::Get()->PrepareTrustedValues(
          base::BindOnce(&WizardController::AutoLaunchKioskApp,
                         weak_factory_.GetWeakPtr(), app));
  if (status == CrosSettingsProvider::TEMPORARILY_UNTRUSTED) {
    return;
  }

  if (status == CrosSettingsProvider::PERMANENTLY_UNTRUSTED) {
    // If the `cros_settings_` are permanently untrusted, show an error
    // message and refuse to auto-launch the kiosk app.
    AdvanceToScreen(LocalStateErrorScreenView::kScreenId);
    return;
  }

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

  constexpr bool auto_launch = true;
  GetLoginDisplayHost()->StartKiosk(app.id(), auto_launch);
}

// static
void WizardController::SetZeroDelays() {
  g_using_zero_delays = true;
}

// static
bool WizardController::IsZeroDelayEnabled() {
  return g_using_zero_delays;
}

void WizardController::SkipPostLoginScreensForTesting() {
  wizard_context_->skip_post_login_screens_for_tests = true;
  auto* current_screen = default_controller()->current_screen();
  if (current_screen && !current_screen->MaybeSkip(*wizard_context_)) {
    LOG(WARNING) << __func__ << ": Ignore screen "
                 << current_screen->screen_id().name;
  }
}

// statis
bool WizardController::IsResumablePostLoginScreen(OobeScreenId screen_id) {
  for (const auto& resumable_screen : kResumablePostLoginScreens) {
    if (screen_id == resumable_screen) {
      return true;
    }
  }
  return false;
}

// static
void WizardController::SkipEnrollmentPromptsForTesting() {
  skip_enrollment_prompts_for_testing_ = true;
}

// static
bool WizardController::IsSigninScreen(OobeScreenId screen_id) {
  return screen_id == UserCreationView::kScreenId ||
         screen_id == GaiaView::kScreenId ||
         screen_id == SignInFatalErrorView::kScreenId;
}

void WizardController::AddObserver(ScreenObserver* obs) {
  screen_observers_.AddObserver(obs);
}

void WizardController::RemoveObserver(ScreenObserver* obs) {
  screen_observers_.RemoveObserver(obs);
}

void WizardController::OnLocalStateInitialized(bool /* succeeded */) {
  if (GetLocalState()->GetInitializationStatus() !=
      PrefService::INITIALIZATION_STATUS_ERROR) {
    return;
  }
  AdvanceToScreen(LocalStateErrorScreenView::kScreenId);
}

void WizardController::PrepareFirstRunPrefs() {
  // Showoff starts in parallel to OOBE onboarding. We need to store the prefs
  // early to make sure showoff has the correct data when launched.
  Profile* profile = ProfileManager::GetActiveUserProfile();
  bool shouldShowParentalControl =
      wizard_context_->sign_in_as_child && !profile->IsChild() &&
      !profile->GetProfilePolicyConnector()->IsManaged();
  profile->GetPrefs()->SetBoolean(
      ash::help_app::prefs::kHelpAppShouldShowParentalControl,
      shouldShowParentalControl);
}

PrefService* WizardController::GetLocalState() {
  // TODO(crbug.com/487538533): Fix WizardControllerBrokenLocalStateTest and
  // remove local_state_for_testing_.
  if (local_state_for_testing_) {
    return local_state_for_testing_;
  }
  return &local_state_.get();
}

void WizardController::OnTimezoneResolved(
    std::unique_ptr<TimeZoneResponseData> timezone,
    bool server_error) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(timezone);

  timezone_resolved_ = true;
  base::ScopedClosureRunner inform_test(
      std::move(on_timezone_resolved_for_testing_));

  VLOG(1) << "Resolved local timezone={" << timezone->ToStringForDebug()
          << "}.";

  if (timezone->status != TimeZoneResponseData::OK) {
    LOG(WARNING) << "Resolve TimeZone: failed to resolve timezone.";
    return;
  }

  if (ash::InstallAttributes::Get()->IsEnterpriseManaged()) {
    std::string policy_timezone;
    if (CrosSettings::Get()->GetString(kSystemTimezonePolicy,
                                       &policy_timezone) &&
        !policy_timezone.empty()) {
      VLOG(1) << "Resolve TimeZone: TimeZone settings are overridden"
              << " by DevicePolicy.";
      return;
    }
  }

  if (!timezone->timeZoneId.empty()) {
    VLOG(1) << "Resolve TimeZone: setting timezone to '" << timezone->timeZoneId
            << "'";
    system::SetSystemAndSigninScreenTimezone(local_state_.get(),
                                             timezone->timeZoneId);
  }
}

TimeZoneProvider* WizardController::GetTimezoneProvider() {
  if (!timezone_provider_) {
    timezone_provider_ = std::make_unique<TimeZoneProvider>(
        shared_url_loader_factory_, DefaultTimezoneProviderURL());
  }
  return timezone_provider_.get();
}

void WizardController::OnLocationResolved(const Geoposition& position,
                                          bool server_error,
                                          const base::TimeDelta elapsed) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  const base::TimeDelta timeout = base::Seconds(kResolveTimeZoneTimeoutSeconds);
  // Ignore invalid position.
  if (!position.Valid()) {
    return;
  }

  if (elapsed >= timeout) {
    LOG(WARNING) << "Resolve TimeZone: got location after timeout ("
                 << elapsed.InSecondsF() << " seconds elapsed). Ignored.";
    return;
  }

  if (!g_browser_process->platform_part()
           ->GetTimezoneResolverManager()
           ->TimeZoneResolverShouldBeRunning()) {
    return;
  }

  // WizardController owns TimezoneProvider, so timezone request is silently
  // cancelled on destruction.
  GetTimezoneProvider()->RequestTimezone(
      position, timeout - elapsed,
      base::BindOnce(&WizardController::OnTimezoneResolved,
                     weak_factory_.GetWeakPtr()));
}

bool WizardController::SetOnTimeZoneResolvedForTesting(
    base::OnceClosure callback) {
  if (timezone_resolved_) {
    return false;
  }

  on_timezone_resolved_for_testing_ = std::move(callback);
  return true;
}

void WizardController::StartEnrollmentScreen() {
  VLOG(1) << "Showing enrollment screen.";

  // Determine the effective enrollment configuration. If OOBE Configuration
  // exits, it might also affect enrollment configuration.
  policy::EnrollmentConfig effective_config =
      prescribed_enrollment_config_.GetEffectiveConfig();
  effective_config.enrollment_nudge_email =
      GetScreen<GaiaScreen>()->EnrollmentNudgeEmail();

  EnrollmentScreen* screen = EnrollmentScreen::Get(screen_manager());
  screen->SetEnrollmentConfig(std::move(effective_config));
  UpdateStatusAreaVisibilityForScreen(EnrollmentScreenView::kScreenId);
  SetCurrentScreen(screen);
}

void WizardController::ShowEnrollmentScreenIfEligible() {
  const bool enterprise_managed =
      ash::InstallAttributes::Get()->IsEnterpriseManaged();
  const bool has_users =
      !user_manager::UserManager::Get()->GetPersistedUsers().empty();
  if (!has_users && !enterprise_managed) {
    AdvanceToScreen(EnrollmentScreenView::kScreenId);
  }
}

bool WizardController::MaybeSetToPreviousScreen() {
  DCHECK(current_screen_);
  if (!previous_screens_.contains(current_screen_)) {
    return false;
  }
  auto* old_current_screen = current_screen_.get();
  auto* previous_screen = previous_screens_[current_screen_].get();

  if (previous_screen->screen_id() == GaiaView::kScreenId) {
    wizard_context_->gaia_config.gaia_path =
        wizard_context_->gaia_config.last_gaia_path_shown;
  }

  SetCurrentScreen(previous_screen);
  return old_current_screen != current_screen_;
}

void WizardController::NotifyScreenChanged() {
  for (ScreenObserver& obs : screen_observers_) {
    obs.OnCurrentScreenChanged(current_screen_);
  }
}

policy::AutoEnrollmentController*
WizardController::GetAutoEnrollmentController() {
  if (!auto_enrollment_controller_) {
    auto_enrollment_controller_ =
        std::make_unique<policy::AutoEnrollmentController>(
            &local_state_.get(), shared_url_loader_factory_,
            browser_policy_connector_ash_->device_management_service(),
            browser_policy_connector_ash_->GetStateKeysBroker());
  }
  return auto_enrollment_controller_.get();
}

void WizardController::MaybeTakeTPMOwnership() {
  if (wizard_context_->is_branded_build || switches::IsTpmDynamic()) {
    return;
  }

  chromeos::TpmManagerClient::Get()->TakeOwnership(
      ::tpm_manager::TakeOwnershipRequest(), base::DoNothing());
}

void WizardController::ResetCurrentScreen() {
  if (current_screen_) {
    current_screen_->Hide();
    current_screen_ = nullptr;
  }
}

void WizardController::MaybeAbortQuickStartFlow(
    quick_start::QuickStartController::AbortFlowReason reason) {
  if (wizard_context_->quick_start_setup_ongoing) {
    quickstart_controller_->AbortFlow(reason);
  }
}

void WizardController::MaybeEnablePreChoiceMetrics() {
  if (switches::ShouldDisablePreConsentMetricsForTesting()) {
    return;
  }
  // Enable pre-choice metrics if this device is in oobe and is the first
  // user.
  Profile* profile = ProfileManager::GetActiveUserProfile();
  CHECK(profile);
  if (enterprise_util::IsBrowserManaged(profile)) {
    VLOG(1) << "Device enrolled. Do not enable pre choice metrics.";
    return;
  }

  if (!wizard_context_->is_add_person_flow &&
      metrics::CrOSPreChoiceMetricsManager::Get()) {
    // Update stats reporter that the current metrics choice is enabled. This
    // will make sure that any changes in the future are properly propagated
    // when using the API.
    StatsReportingController::Get()->SetEnabled(
        ProfileManager::GetActiveUserProfile(), true);
    metrics::CrOSPreChoiceMetricsManager::Get()->Enable();
  }
}

void WizardController::MaybeNotifyFjordOobeStateManager(
    fjord_oobe_state::proto::FjordOobeStateInfo::FjordOobeState state) {
  if (!fjord_util::ShouldShowFjordOobe()) {
    return;
  }
  FjordOobeStateManager* state_manager = FjordOobeStateManager::Get();
  if (!state_manager) {
    LOG(ERROR) << "FjordOobeStateManager does not exist";
    return;
  }

  state_manager->SetFjordOobeState(state);
}

void WizardController::ShowSamlConfirmPasswordScreen() {
  CHECK(features::IsManagedLocalPinAndPasswordEnabled());
  SetCurrentScreen(GetScreen<SamlConfirmPasswordScreen>());
}

}  // namespace ash
