// 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.

#include "chrome/browser/ui/ash/user_education/chrome_user_education_delegate.h"

#include <optional>

#include "ash/ash_element_identifiers.h"
#include "ash/constants/web_app_id_constants.h"
#include "ash/user_education/user_education_types.h"
#include "ash/user_education/user_education_util.h"
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/values.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/views/user_education/browser_user_education_service.h"
#include "chrome/browser/user_education/user_education_service.h"
#include "chrome/browser/user_education/user_education_service_factory.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "chromeos/ash/components/file_manager/app_id.h"
#include "components/account_id/account_id.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/user_education/common/help_bubble/help_bubble.h"
#include "components/user_education/common/help_bubble/help_bubble_factory_registry.h"
#include "components/user_education/common/help_bubble/help_bubble_params.h"
#include "components/user_education/common/tutorial/tutorial_registry.h"
#include "components/user_education/common/tutorial/tutorial_service.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "components/user_manager/user_type.h"
#include "ui/base/interaction/element_tracker.h"

namespace {

// Helpers ---------------------------------------------------------------------

app_list::AppListSyncableService* GetAppListSyncableService(Profile* profile) {
  return app_list::AppListSyncableServiceFactory::GetForProfile(profile);
}

Profile* GetProfile(const AccountId& account_id) {
  return Profile::FromBrowserContext(
      ash::BrowserContextHelper::Get()->GetBrowserContextByAccountId(
          account_id));
}

bool IsPrimaryProfile(Profile* profile) {
  return user_manager::UserManager::Get()->IsPrimaryUser(
      ash::BrowserContextHelper::Get()->GetUserByBrowserContext(profile));
}

std::optional<std::string> ToString(
    std::optional<ash::TutorialId> tutorial_id) {
  return tutorial_id ? std::make_optional(ash::user_education_util::ToString(
                           tutorial_id.value()))
                     : std::nullopt;
}

}  // namespace

// ChromeUserEducationDelegate -------------------------------------------------

ChromeUserEducationDelegate::ChromeUserEducationDelegate() {
  user_manager::UserManager* user_manager = user_manager::UserManager::Get();
  user_manager_observation_.Observe(user_manager);
  for (user_manager::User* user : user_manager->GetLoggedInUsers()) {
    if (user->GetProfilePrefs()) {
      OnUserProfileCreated(*user);
    }
  }
}

ChromeUserEducationDelegate::~ChromeUserEducationDelegate() = default;

std::optional<ui::ElementIdentifier>
ChromeUserEducationDelegate::GetElementIdentifierForAppId(
    const std::string& app_id) const {
  if (app_id == file_manager::kFileManagerSwaAppId) {
    return ash::kFilesAppElementId;
  }
  if (app_id == ash::kHelpAppId) {
    return ash::kExploreAppElementId;
  }
  if (app_id == ash::kOsSettingsAppId) {
    return ash::kSettingsAppElementId;
  }
  return std::nullopt;
}

const std::optional<bool>& ChromeUserEducationDelegate::IsNewUser(
    const AccountId& account_id) const {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));
  return is_primary_profile_new_user_;
}

bool ChromeUserEducationDelegate::IsTutorialRegistered(
    const AccountId& account_id,
    ash::TutorialId tutorial_id) const {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));
  return UserEducationServiceFactory::GetForBrowserContext(profile)
      ->tutorial_registry()
      .IsTutorialRegistered(ash::user_education_util::ToString(tutorial_id));
}

void ChromeUserEducationDelegate::RegisterTutorial(
    const AccountId& account_id,
    ash::TutorialId tutorial_id,
    user_education::TutorialDescription tutorial_description) {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));
  UserEducationServiceFactory::GetForBrowserContext(profile)
      ->tutorial_registry()
      .AddTutorial(ash::user_education_util::ToString(tutorial_id),
                   std::move(tutorial_description));
}

void ChromeUserEducationDelegate::StartTutorial(
    const AccountId& account_id,
    ash::TutorialId tutorial_id,
    ui::ElementContext element_context,
    base::OnceClosure completed_callback,
    base::OnceClosure aborted_callback) {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));
  UserEducationServiceFactory::GetForBrowserContext(profile)
      ->tutorial_service()
      .StartTutorial(ash::user_education_util::ToString(tutorial_id),
                     std::move(element_context), std::move(completed_callback),
                     std::move(aborted_callback));
}

void ChromeUserEducationDelegate::AbortTutorial(
    const AccountId& account_id,
    std::optional<ash::TutorialId> tutorial_id) {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));

  auto& tutorial_service =
      UserEducationServiceFactory::GetForBrowserContext(profile)
          ->tutorial_service();
  tutorial_service.CancelTutorialIfRunning(ToString(tutorial_id));
}

void ChromeUserEducationDelegate::LaunchSystemWebAppAsync(
    const AccountId& account_id,
    ash::SystemWebAppType system_web_app_type,
    apps::LaunchSource launch_source,
    int64_t display_id) {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));

  ash::SystemAppLaunchParams launch_params;
  launch_params.launch_source = launch_source;
  ash::LaunchSystemWebAppAsync(profile, system_web_app_type, launch_params,
                               std::make_unique<apps::WindowInfo>(display_id));
}

bool ChromeUserEducationDelegate::IsRunningTutorial(
    const AccountId& account_id,
    std::optional<ash::TutorialId> tutorial_id) const {
  return UserEducationServiceFactory::GetForBrowserContext(
             GetProfile(account_id))
      ->tutorial_service()
      .IsRunningTutorial(ToString(tutorial_id));
}

void ChromeUserEducationDelegate::OnUserProfileCreated(
    const user_manager::User& user) {
  // NOTE: User eduction in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  if (!user_manager::UserManager::Get()->IsPrimaryUser(&user)) {
    return;
  }

  // User education is only supported for regular users.
  if (user.GetType() != user_manager::UserType::kRegular) {
    return;
  }

  // Since we only currently support the primary user profile, we can stop
  // observing the user manager once it has been added.
  user_manager_observation_.Reset();

  Profile* profile = GetProfile(user.GetAccountId());
  CHECK(!profile->IsOffTheRecord());

  // Register tutorial dependencies.
  RegisterChromeHelpBubbleFactories(
      UserEducationServiceFactory::GetForBrowserContext(profile)
          ->help_bubble_factory_registry());

  // Cache whether the user associated with the primary profile is considered
  // new, based on whether the first app list sync in the session was the first
  // sync ever across all ChromeOS devices and sessions for the given user.
  if (auto* app_list_syncable_service = GetAppListSyncableService(profile)) {
    app_list_syncable_service->OnFirstSync(base::BindOnce(
        [](const base::WeakPtr<ChromeUserEducationDelegate>& self,
           bool was_first_sync_ever) {
          if (self) {
            self->is_primary_profile_new_user_ = was_first_sync_ever;
          }
        },
        weak_ptr_factory_.GetWeakPtr()));
  }
}
