// Copyright 2022 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_UI_STARTUP_FIRST_RUN_SERVICE_H_
#define CHROME_BROWSER_UI_STARTUP_FIRST_RUN_SERVICE_H_

#include <memory>

#include "base/functional/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/no_destructor.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile_keyed_service_factory.h"
#include "chrome/browser/ui/profiles/profile_picker.h"
#include "components/keyed_service/core/keyed_service.h"

class PrefRegistrySimple;
class Profile;
class ProfileNameResolver;

namespace version_info {
enum class Channel;
}

namespace signin {
class IdentityManager;
}

// Task to run after the FRE is exited, with `proceed` indicating whether it
// should be aborted or resumed.
using ResumeTaskCallback = base::OnceCallback<void(bool proceed)>;

// Service handling the First Run Experience for the primary profile on Dice
// platforms. It is not available on the other profiles.
class FirstRunService : public KeyedService {
 public:
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class FinishedReason {
    kExperimentCounterfactual = 0,
    kFinishedFlow = 1,
    kProfileAlreadySetUp = 2,
    kSkippedByPolicies = 3,
    kForceSignin = 4,

    kMaxValue = kForceSignin,
  };

  static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);

  FirstRunService(Profile& profile, signin::IdentityManager& identity_manager);
  ~FirstRunService() override;

  // Runs `::ShouldOpenFirstRun(Profile*)` with the profile associated with this
  // service instance.
  virtual bool ShouldOpenFirstRun() const;

  // This function takes the user through the browser FRE.
  // 1) First, it checks whether the FRE flow can be skipped in the first place.
  //    This is the case when sync consent is already given or when enterprise
  //    policies forbid the FRE. If so, the call directly 'finishes' the flow
  //    (see below).
  // 2) Then, it opens the FRE UI (in the profile picker window) and
  //    asynchronously 'finishes' the flow (sets a flag in the local prefs) once
  //    the user chooses any action on the sync consent screen. If the user
  //    exits the FRE UI via the generic 'Close window' affordances, it is
  //    interpreted as an intent to exit the app and `callback` will be called
  //    with `proceed` set to false. If they exit it via the dedicated options
  //    in the flow, it will be considered 'completed' and `callback` will be
  //    run with `proceed` set to true. If the FRE flow is exited before the
  //    sync consent screen, the flow is considered 'aborted', and can be shown
  //    again at the next startup.
  // When this method is called again while FRE is in progress, the previous
  // callback is aborted (called with false), and is replaced by `callback`.
  virtual void OpenFirstRunIfNeeded(ResumeTaskCallback callback);

  // Terminates the first run without re-opening a browser window.
  virtual void FinishFirstRunWithoutResumeTask();

 private:
  friend class FirstRunServiceFactory;
  FRIEND_TEST_ALL_PREFIXES(FirstRunServiceTest,
                           ShouldPopulateProfileNameFromPrimaryAccount);

  // Asynchronously attempts to complete the first run silently.
  // By the time `callback` is run (if non-null), either:
  // - the first run has been marked finished because it can't be run for this
  //   profile (e.g. policies) or because we want to enable Sync silently
  // - the first run is ready to be opened.
  // The finished state can be checked by calling `ShouldOpenFirstRun()`.
  void TryMarkFirstRunAlreadyFinished(base::OnceClosure callback);

  void OpenFirstRunInternal();

  // Processes the outcome from the FRE and resumes the user's interrupted task.
  void OnFirstRunHasExited(ProfilePicker::FirstRunExitStatus status);

  // Marks the first run as finished and updates the profile entry based on
  // the info obtained during the first run.
  // Noting that the latter part is done by calling `FinishProfileSetUp()`,
  // which will be done asynchronously in most cases.
  void FinishFirstRun(FinishedReason reason);

  void FinishProfileSetUp(std::u16string profile_name);

  // Owns of this instance via the KeyedService mechanism.
  const raw_ref<Profile> profile_;

  // KeyedService(s) this service depends on:
  const raw_ref<signin::IdentityManager> identity_manager_;

  std::unique_ptr<ProfileNameResolver> profile_name_resolver_;

  ResumeTaskCallback resume_task_callback_;

  base::WeakPtrFactory<FirstRunService> weak_ptr_factory_{this};
};

class FirstRunServiceFactory : public ProfileKeyedServiceFactory {
 public:
  FirstRunServiceFactory(const FirstRunServiceFactory&) = delete;
  FirstRunServiceFactory& operator=(const FirstRunServiceFactory&) = delete;

  static FirstRunService* GetForBrowserContext(
      content::BrowserContext* context);

  static FirstRunService* GetForBrowserContextIfExists(
      content::BrowserContext* context);

  static FirstRunServiceFactory* GetInstance();

 private:
  friend class base::NoDestructor<FirstRunServiceFactory>;
  friend class FirstRunServiceBrowserTestBase;

  FirstRunServiceFactory();
  ~FirstRunServiceFactory() override;

  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
      content::BrowserContext* context) const override;
};

// Returns whether the first run experience (including sync promo) might be
// opened for `profile`. It should be checked before
// `FirstRunService::OpenFirstRunIfNeeded()` is called.
//
// Even if this method returns `true`, the FRE can still be skipped if for
// example the feature is disabled, a policy suppresses it, etc.
bool ShouldOpenFirstRun(Profile* profile);

#endif  // CHROME_BROWSER_UI_STARTUP_FIRST_RUN_SERVICE_H_
