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

#ifndef CHROME_BROWSER_ASH_LOGIN_QUICKSTART_CONTROLLER_H_
#define CHROME_BROWSER_ASH_LOGIN_QUICKSTART_CONTROLLER_H_

#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/scoped_observation.h"
#include "chrome/browser/ash/login/oobe_quick_start/connectivity/fido_assertion_info.h"
#include "chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h"
#include "chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h"
#include "chrome/browser/ash/login/oobe_screen.h"
#include "chrome/browser/ui/webui/ash/login/oobe_ui.h"
#include "chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"

class PrefService;

namespace ash::quick_start {

class QuickStartMetrics;

// Main orchestrator of the QuickStart flow in OOBE
//
// QuickStartController holds all the logic for QuickStart and acts as the
// source of truth for what the UI (QuickStartScreen) should be showing. Unlike
// other OOBE screens, QuickStartScreen just acts as a delegate for this main
// controller.
class QuickStartController
    : public OobeUI::Observer,
      public TargetDeviceBootstrapController::Observer,
      public bluetooth_config::mojom::SystemPropertiesObserver {
 public:
  using AbortFlowReason = QuickStartMetrics::AbortFlowReason;
  using EntryPoint = QuickStartMetrics::EntryPoint;

  // Main state used by the controller.
  // TODO(b:283965994) - Finalize states.
  enum class ControllerState {
    NOT_ACTIVE,
    WAITING_FOR_BLUETOOTH_PERMISSION,
    WAITING_FOR_BLUETOOTH_ACTIVATION,
    WAITING_TO_RESUME_AFTER_UPDATE,
    INITIALIZING,
    ADVERTISING,
    CONNECTED,
    // TODO(b:283965994) - Replace with more appropriate state.
    CONTINUING_AFTER_ENROLLMENT_CHECKS,
    FALLBACK_URL_FLOW_ON_GAIA_SCREEN,
    SETUP_COMPLETE,
  };

  // Implemented by the QuickStartScreen
  class UiDelegate : public base::CheckedObserver {
   public:
    // UI State that is used for dictating what the QuickStartScreen should
    // show.
    enum class UiState {
      SHOWING_BLUETOOTH_DIALOG,
      CONNECTING_TO_PHONE,
      SHOWING_QR,
      SHOWING_PIN,
      CONNECTING_TO_WIFI,
      WIFI_CREDENTIALS_RECEIVED,
      CONFIRM_GOOGLE_ACCOUNT,
      SIGNING_IN,
      // Same state as 'SIGNING_IN' but without the 'Cancel' button.
      CREATING_ACCOUNT,
      // Triggers a screen exit into the Gaia screen for the fallback URL flow.
      FALLBACK_URL_FLOW,
      SETUP_COMPLETE,
      // Exits the screen.
      EXIT_SCREEN,
    };

    virtual void OnUiUpdateRequested(UiState desired_state) = 0;

   protected:
    ~UiDelegate() override = default;
  };

  // For showing the user information on the UI
  struct UserInfo {
    std::string email = "";
    std::string full_name = "";
    std::string avatar_url = "";
  };

  // EntryPointButtonVisibilityCallback is a RepeatingCallback since the
  // bluetooth adapter may not be present and powered the first time it's
  // invoked. The bluetooth adapter asynchronously affects the feature support
  // status and thus the entry point visibility.
  using EntryPointButtonVisibilityCallback =
      base::RepeatingCallback<void(bool)>;
  using UiState = UiDelegate::UiState;

  // `local_state` must be non-null and must outlive `this`.
  explicit QuickStartController(PrefService* local_state);

  QuickStartController(const QuickStartController&) = delete;
  QuickStartController& operator=(const QuickStartController&) = delete;

  ~QuickStartController() override;

  // Enable QuickStart even when the feature isn't enabled. This is only called
  // when enabling via the keyboard shortcut Ctrl+Alt+Q on the Welcome screen.
  void ForceEnableQuickStart();

  // Whether QuickStart is supported. Used for determining whether the entry
  // point buttons are shown.
  void DetermineEntryPointVisibility(
      EntryPointButtonVisibilityCallback callback);

  // Invoked by the frontend whenever the user cancels the flow or proceeds with
  // enterprise enrollment, the flow completes, or we encounter an error.
  void AbortFlow(AbortFlowReason reason);

  // Whether QuickStart is ongoing and orchestrating the flow.
  bool IsSetupOngoing() {
    return controller_state_ != ControllerState::NOT_ACTIVE;
  }

  // Whenever the QuickStartScreen is shown, it will attach itself and observe
  // the controller so that it knows when to update the UI.
  void AttachFrontend(UiDelegate* delegate);
  void DetachFrontend(UiDelegate* delegate);

  // Accessors methods to be used by the UI for retrieving data. It is an error
  // to retrieve these values when they do not exist.
  QRCode GetQrCode() { return qr_code_.value(); }
  std::string GetPin() { return pin_.value(); }
  UserInfo GetUserInfo() { return user_info_; }
  std::string GetWiFiName() { return wifi_name_.value(); }
  std::string GetFallbackUrl() { return fallback_url_.value(); }

  // If we're already connected to Wi-Fi at the start of the flow we won't
  // request Wi-Fi details from the source device. This lets us reflect that in
  // the UI.
  bool WillRequestWiFi();

  // Called by the Gaia screen during the 'CompleteAuthentication' call. This
  // notifies us that the flow succeeded and we use this signal to show the
  // 'setup complete' step of QuickStart.
  void OnFallbackUrlFlowSuccess();

  // Triggered when the user clicks on 'Turn on Bluetooth'
  void OnBluetoothPermissionGranted();

  // Exit point to be used when the flow is cancelled.
  EntryPoint GetExitPoint();

  // Exposes TargetDeviceBootstrapController::PrepareForUpdate() to the OOBE
  // UpdateScreen and ConsumerUpdateScreen.
  void PrepareForUpdate(bool is_forced);

  // Resumes current session if an update is aborted on
  // the OOBE UpdateScreen or ConsumerUpdateScreen.
  void ResumeSessionAfterCancelledUpdate();

  // Called after a user clicks "next" on final Setup Complete UI.
  void RecordFlowFinished();

  bool did_transfer_wifi() const {
    return bootstrap_controller_->did_transfer_wifi();
  }

 private:
  // Initializes the BootstrapController and starts to observe it.
  void InitTargetDeviceBootstrapController();

  // Initializes the Bluetooth and starts to observe it.
  void StartObservingBluetoothState();

  // Request advertising to start. Should only be called when bluetooth is
  // enabled.
  void StartAdvertising();

  // bluetooth_config::mojom::SystemPropertiesObserver
  void OnPropertiesUpdated(bluetooth_config::mojom::BluetoothSystemPropertiesPtr
                               properties) override;

  // Records ScreenOpened metric when UiState or OOBE screen changes.
  void MaybeRecordQuickStartScreenOpened(UiState new_ui);

  // Records ScreenClosed metric when UiState or OOBE screen changes.
  void MaybeRecordQuickStartScreenAdvanced(std::optional<UiState> closed_ui);

  // Updates the UI state and notifies the frontend.
  void UpdateUiState(UiState ui_state);

  // TargetDeviceBootstrapController::Observer
  void OnStatusChanged(
      const TargetDeviceBootstrapController::Status& status) final;

  void OnGetQuickStartFeatureSupportStatus(
      EntryPointButtonVisibilityCallback callback,
      TargetDeviceConnectionBroker::FeatureSupportStatus status);

  // OobeUI::Observer:
  void OnCurrentScreenChanged(OobeScreenId previous_screen,
                              OobeScreenId current_screen) override;
  void OnDestroyingOobeUI() override;

  // Activates the OobeUI::Observer
  void StartObservingScreenTransitions();

  // Invoked whenever OOBE transitions into the QuickStart screen.
  void HandleTransitionToQuickStartScreen();

  // Starts transferring the user account from the phone.
  void StartAccountTransfer();

  // Steps to take when all the account data has been exchanged with the phone.
  void OnOAuthTokenReceived(TargetDeviceBootstrapController::GaiaCredentials);

  // Steps to take when the connection with the phone is fully established.
  // Either transfers WiFi credentials if early in the OOBE flow, or starts
  // to transfer the user's credentials.
  void OnPhoneConnectionEstablished();

  // Updates the exit point to the default one: Welcome screen if OOBE is
  // incomplete, GAIA Info screen otherwise.
  void SetExitPointToDefault();

  // Called when the user returns to the QuickStart screen after it was exited
  // externally. This method updates the UI to reflect the state it was in
  // before the exit.
  void RestoreCachedUIState();

  void SavePhoneInstanceID();

  // Performs the final steps and triggers ChromeOS account creation flow.
  void FinishAccountCreation();

  bool IsBluetoothDisabled();

  // Resets all internal values. Invoked when the flow is interrupted.
  void ResetState();

  const raw_ref<PrefService> local_state_;

  // "Main" controller for interacting with the phone. Only valid when the
  // feature flag is enabled or if the feature was enabled via the keyboard
  // shortcut.
  base::WeakPtr<TargetDeviceBootstrapController> bootstrap_controller_;

  // Source of truth of OOBE's current state via OobeUI::Observer
  std::optional<OobeScreenId> current_screen_, previous_screen_;

  // Bookkeeping where the quick start flow started and ended.
  std::optional<EntryPoint> entry_point_, exit_point_;

  // QR Code to be shown on the UI when requested.
  std::optional<QRCode> qr_code_;

  // PIN to be shown on the UI when requested.
  std::optional<std::string> pin_;

  // Fallback URL to be used on the Gaia screen when needed.
  std::optional<std::string> fallback_url_;

  // User information that is shown while 'Signing in...'
  UserInfo user_info_;

  // WiFi name to be shown on the UI.
  std::optional<std::string> wifi_name_;

  // Main state that the controller can be in.
  ControllerState controller_state_ = ControllerState::NOT_ACTIVE;

  // UI state that should be displayed by the QuickStartScreen. Only exists when
  // there is an ongoing setup.
  std::optional<UiState> ui_state_;

  // QuickStartScreen implements the UiDelegate and registers itself whenever it
  // is shown. UI updates happen over this observation path.
  base::ObserverList<UiDelegate> ui_delegates_;

  std::unique_ptr<QuickStartMetrics> metrics_;

  // Gaia credentials used for account creation.
  TargetDeviceBootstrapController::GaiaCredentials gaia_creds_;

  mojo::Remote<bluetooth_config::mojom::CrosBluetoothConfig>
      cros_bluetooth_config_remote_;

  mojo::Receiver<bluetooth_config::mojom::SystemPropertiesObserver>
      cros_system_properties_observer_receiver_{this};

  bluetooth_config::mojom::BluetoothSystemState bluetooth_system_state_ =
      bluetooth_config::mojom::BluetoothSystemState::kUnavailable;

  // Whether OOBE is transitioning to the QuickStartScreen. Used for recording
  // UI metrics.
  bool is_transitioning_to_quick_start_screen_ = false;

  bool should_resume_quick_start_after_update_ = false;

  // Used for sanity checks in order to discard unrequested data from the phone.
  // Similar checks exist on the TargetDeviceBootstrapController level.
  bool did_request_wifi_credentials_ = false;
  bool did_request_account_info_ = false;
  bool did_request_account_transfer_ = false;

  base::ScopedObservation<OobeUI, OobeUI::Observer> observation_{this};
  base::WeakPtrFactory<QuickStartController> weak_ptr_factory_{this};
};

std::ostream& operator<<(std::ostream& stream,
                         const QuickStartController::UiState& ui_state);

std::ostream& operator<<(
    std::ostream& stream,
    const QuickStartController::AbortFlowReason& abort_flow_reason);

std::ostream& operator<<(
    std::ostream& stream,
    const QuickStartController::ControllerState& controller_state);

}  // namespace ash::quick_start

#endif  // CHROME_BROWSER_ASH_LOGIN_QUICKSTART_CONTROLLER_H_
