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

#ifndef CHROME_BROWSER_THEMES_THEME_SERVICE_H_
#define CHROME_BROWSER_THEMES_THEME_SERVICE_H_

#include <memory>
#include <string>

#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "base/task/cancelable_task_tracker.h"
#include "chrome/browser/search/background/theme_delegate.h"
#include "chrome/browser/themes/browser_theme_provider_delegate.h"
#include "chrome/browser/themes/theme_helper.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/prefs/pref_change_registrar.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension_id.h"
#include "ui/base/mojom/themes.mojom.h"
#include "ui/base/theme_provider.h"
#include "ui/color/color_provider_key.h"
#include "ui/color/system_theme.h"

class BrowserThemePack;
class NtpCustomBackgroundService;
class Profile;
class ThemeServiceObserver;
class ThemeSyncableService;

namespace extensions {
class Extension;
}  // namespace extensions

namespace theme_service_internal {
class ThemeServiceTest;
}  // namespace theme_service_internal

namespace ui {
class ColorProvider;
struct ColorProviderKey;
}  // namespace ui

namespace user_prefs {
class PrefRegistrySyncable;
}  // namespace user_prefs

namespace waap {
class PrewarmHelper;
}  // namespace waap

// A theme consists of a set of colors and images, including the NTP background
// image. See CustomThemeSupplier for details. There are multiple sources for
// themes, including extensions, NTP, the system theme, and policy.
// TODO(https://crbug.com/341787825): When the NTP generates a custom background
// image, the color is stored in ThemeService but the background image is stored
// in NtpCustomBackgroundService. This divergence from other theme sources
// introduces complexity and fragility.
class ThemeService : public KeyedService,
                     public BrowserThemeProviderDelegate,
                     public ThemeDelegate {
 public:
  // This is stored as an integer in the profile prefs, so entries should not be
  // renumbered and numeric values should never be reused.
  //
  // LINT.IfChange(BrowserColorScheme)
  enum class BrowserColorScheme {
    kSystem = 0,
    kLight = 1,
    kDark = 2,
    kMaxValue = kDark,
  };
  // LINT.ThenChange(//tools/metrics/histograms/metadata/chrome/enums.xml:BrowserColorScheme)

  // This class keeps track of the number of existing |ThemeReinstaller|
  // objects. When that number reaches 0 then unused themes will be deleted.
  class ThemeReinstaller {
   public:
    ThemeReinstaller(Profile* profile, base::OnceClosure installer);
    ThemeReinstaller(const ThemeReinstaller&) = delete;
    ThemeReinstaller& operator=(const ThemeReinstaller&) = delete;
    ~ThemeReinstaller();

    void Reinstall();

   private:
    base::OnceClosure installer_;
    const raw_ptr<ThemeService> theme_service_;
  };

  // Constant ID to use for all autogenerated themes.
  static const char kAutogeneratedThemeID[];
  // Constant ID to use for all user color themes.
  static const char kUserColorThemeID[];

  // Creates a ThemeProvider with a custom theme supplier specified via
  // |delegate|. The return value must not outlive |profile|'s ThemeService.
  static std::unique_ptr<ui::ThemeProvider> CreateBoundThemeProvider(
      Profile* profile,
      BrowserThemeProviderDelegate* delegate);

  // Displays the theme installed infobar for `profile` on the last active tab
  // using the modern centralized infobar framework.
  static void ShowThemeInstalledInfoBar(
      Profile* profile,
      const std::string& theme_name,
      const std::string& theme_id,
      std::unique_ptr<ThemeReinstaller> prev_theme_reinstaller);

  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);

  ThemeService(Profile* profile, const ThemeHelper& theme_helper);
  ThemeService(const ThemeService&) = delete;
  ThemeService& operator=(const ThemeService&) = delete;
  ~ThemeService() override;

  void Init();

  // KeyedService:
  void Shutdown() override;

  // Overridden from BrowserThemeProviderDelegate:
  CustomThemeSupplier* GetThemeSupplier() const override;
  bool ShouldUseCustomFrame() const override;

  // Set the current theme to the theme defined in |extension|.
  // |extension| must already be added to this profile's
  // ExtensionService.
  void SetTheme(const extensions::Extension* extension);

  // Similar to SetTheme, but doesn't show an undo infobar.
  void RevertToExtensionTheme(const std::string& extension_id);

  // Sets the platform theme based on `system_theme`.
  virtual void UseTheme(ui::SystemTheme system_theme);

  // Reset the theme to default.
  virtual void UseDefaultTheme();

  // Toggle whether the browser follows its own theme or from the OS. For now,
  // this is distinct from the ui::SystemTheme.
  virtual void UseDeviceTheme(bool follow);

  // Returns true if the browser should follow the OS theme.
  virtual bool UsingDeviceTheme() const;

  // Set the current theme to the system theme. On some platforms, the system
  // theme is the default theme.
  virtual void UseSystemTheme();

  // Returns true if the default theme and system theme are not the same on
  // this platform.
  virtual bool IsSystemThemeDistinctFromDefaultTheme() const;

  // Forwards to ThemeProviderBase::IsDefaultTheme().
  // Virtual for testing.
  virtual bool UsingDefaultTheme() const;

  // Whether we are using the system theme. On Linux, the system theme is the
  // GTK or QT themes, not the "Classic" theme.
  virtual bool UsingSystemTheme() const;

  // ThemeDelegate:
  void OnBackgroundColorExtracted(SkColor color) override;
  void OnNtpCustomBackgroundServiceShuttingDown() override;
  bool UsingExtensionTheme() const override;

  // Forwards to ThemeProviderBase::IsAutogeneratedTheme().
  // Virtual for testing.
  virtual bool UsingAutogeneratedTheme() const;

  // Whether current theme colors are enforced through a policy.
  // Virtual for testing.
  virtual bool UsingPolicyTheme() const;

  // Gets the id of the last installed theme. (The theme may have been further
  // locally customized.)
  virtual std::string GetThemeID() const;

  // Uninstall theme extensions which are no longer in use.
  void RemoveUnusedThemes();

  // Returns the syncable service for syncing theme. The returned service is
  // owned by |this| object.
  virtual ThemeSyncableService* GetThemeSyncableService() const;

  // Gets the ThemeProvider for |profile|. This will be different for an
  // incognito or isolated profile and its original profile, even though all
  // profiles use the same ThemeService.
  //
  // Before using this function, consider if the caller is in a rooted UI tree.
  // If it is, strongly favor referring to the conceptual roots for a
  // ThemeProvider. For Views, this is the Widget. For Cocoa, this is the
  // AppControl.
  static const ui::ThemeProvider& GetThemeProviderForProfile(Profile* profile);
  static CustomThemeSupplier* GetThemeSupplierForProfile(Profile* profile);

  // Gets the ColorProvider for this ThemeService.
  //
  // Before using this function, consider if the caller is in a rooted UI tree.
  // If it is, strongly favor referring to the conceptual roots for a
  // ColorProvider. For Views, this is the Widget. For Cocoa, this is the
  // AppControl.
  ui::ColorProvider* GetColorProvider();

  // Builds an autogenerated theme from a given |color| and applies it.
  virtual void BuildAutogeneratedThemeFromColor(SkColor color);

  // Builds an autogenerated theme from a given |color| and applies it.
  virtual void BuildAutogeneratedThemeFromColor(SkColor color,
                                                bool store_in_prefs);

  // Returns the theme color for an autogenerated theme.
  virtual SkColor GetAutogeneratedThemeColor() const;

  // Builds an autogenerated theme from the applied policy color and applies it.
  virtual void BuildAutogeneratedPolicyTheme();

  // Returns the theme color for the current policy theme.
  virtual SkColor GetPolicyThemeColor() const;

  // Sets the browser color scheme preference.
  virtual void SetBrowserColorScheme(BrowserColorScheme color_scheme);

  // Gets the browser color scheme preference.
  virtual BrowserColorScheme GetBrowserColorScheme() const;

  // Returns true if either the browser color scheme is dark, or the browser
  // color scheme is "system" and the native theme is dark.
  bool BrowserUsesDarkColors() const;

  // Sets/gets the browser user color preference.
  virtual void SetUserColor(std::optional<SkColor> user_color);
  virtual std::optional<SkColor> GetUserColor() const;

  // Sets/gets the browser kSchemeVariant preference.
  void SetBrowserColorVariant(ui::mojom::BrowserColorVariant color_variant);
  virtual ui::mojom::BrowserColorVariant GetBrowserColorVariant() const;

  // Convenience method that allows setting both the kUserColor and
  // kSchemeVariant before propagating theme update notifications.
  virtual void SetUserColorAndBrowserColorVariant(
      SkColor user_color,
      ui::mojom::BrowserColorVariant color_variant);

  // Sets/gets the browser grayscale theme preference.
  virtual void SetIsGrayscale(bool is_grayscale);
  virtual bool GetIsGrayscale() const;

  // Returns true if the current theme is the "baseline" blue theme.
  virtual bool GetIsBaseline() const;

  // Returns |ThemeService::ThemeReinstaller| for the current theme.
  std::unique_ptr<ThemeService::ThemeReinstaller>
  BuildReinstallerForCurrentTheme();

  // Virtual for testing.
  virtual void AddObserver(ThemeServiceObserver* observer);

  void RemoveObserver(ThemeServiceObserver* observer);

  const ThemeHelper& theme_helper_for_testing() const { return *theme_helper_; }

  // Returns a baseline ThemeProvider where custom theme assets (such as
  // extension images) are suppressed.
  const ui::ThemeProvider& GetDefaultThemeProvider() const;

  // Don't create "Cached Theme.pak" in the extension directory, for testing.
  static void DisableThemePackForTesting();

  void ResetThemeSyncableServiceForTest();

 protected:
  // Set a custom default theme instead of the normal default theme.
  virtual void SetCustomDefaultTheme(
      scoped_refptr<CustomThemeSupplier> theme_supplier);

  // Returns the theme service type that should be used on startup.
  virtual ui::SystemTheme GetDefaultSystemTheme() const;

  // Clears override fields and saves the dictionary. When `reset_all_settings`
  // is true, also resets NTP background and follows system theme colors.
  virtual void ClearThemeData(bool reset_all_settings);

  // Initialize current theme state data from preferences.
  virtual void InitFromPrefs();

  // Let all the browser views know that themes have changed.
  virtual void NotifyThemeChanged();

  // If there is an inconsistency in preferences, change preferences to a
  // consistent state.
  virtual void FixInconsistentPreferencesIfNeeded();

  Profile* profile() const { return profile_; }

  void set_ready() { ready_ = true; }

  // True if the theme service is ready to be used.
  // TODO(pkotwicz): Add DCHECKS to the theme service's getters once
  // ThemeSource no longer uses the ThemeService when it is not ready.
  bool ready_ = false;

 private:
  // This class implements ui::ThemeProvider on behalf of ThemeHelper and
  // keeps track of the incognito state and CustemThemeSupplier for the calling
  // code.
  class BrowserThemeProvider : public ui::ThemeProvider {
   public:
    BrowserThemeProvider(const ThemeHelper& theme_helper,
                         bool incognito,
                         const BrowserThemeProviderDelegate* delegate);
    BrowserThemeProvider(const BrowserThemeProvider&) = delete;
    BrowserThemeProvider& operator=(const BrowserThemeProvider&) = delete;
    ~BrowserThemeProvider() override;

    // Overridden from ui::ThemeProvider:
    gfx::ImageSkia* GetImageSkiaNamed(int id) const override;
    color_utils::HSL GetTint(int original_id) const override;
    int GetDisplayProperty(int id) const override;
    bool ShouldUseNativeFrame() const override;
    bool HasCustomImage(int id) const override;
    scoped_refptr<base::RefCountedMemory> GetRawData(
        int id,
        ui::ResourceScaleFactor scale_factor) const override;

   private:
    CustomThemeSupplier* GetThemeSupplier() const;

    const raw_ref<const ThemeHelper> theme_helper_;
    bool incognito_;
    raw_ptr<const BrowserThemeProviderDelegate> delegate_;
  };
  friend class BrowserThemeProvider;
  friend class BrowserWidget;
  friend class ProfilePickerWidget;
  friend class InitialWebUIProfileService;
  friend class ThemeColorsSourceManager;
  friend class theme_service_internal::ThemeServiceTest;
  friend class ThemeServiceBrowserTest;
  friend class BrowserWidgetColorProviderTest;
  friend class waap::PrewarmHelper;

  // Returns a ColorProviderKey configured with Profile-scoped state. The
  // `profile` param is necessary as the service itself may be keyed to the
  // original profile.
  // Note: Do not use this directly - any UI using colors from this directly can
  // encounter consistency and contrast issues when inserted into its UI tree,
  // since theme context is determined at the granularity of a UI tree (e.g.
  // widget) and not a profile. Please instead fetch the ColorProvider from the
  // UI tree into which the element is inserted (i.e. the host widget, web
  // contents etc).
  ui::ColorProviderKey GetColorProviderKey(const ui::ColorProviderKey& base_key,
                                           const Profile* profile) const;

  // virtual for testing.
  virtual void DoSetTheme(const extensions::Extension* extension,
                          bool suppress_infobar);

  // Called when the extension service is ready.
  void OnExtensionServiceReady();

  // Migrate the theme to the new theme pack schema by recreating the data pack
  // from the extension.
  void MigrateTheme();

  // Replaces the current theme supplier with a new one and calls
  // StopUsingTheme() or StartUsingTheme() as appropriate.
  void SwapThemeSupplier(scoped_refptr<CustomThemeSupplier> theme_supplier);

  // Implementation of SetTheme() (and the fallback from InitFromPrefs() in
  // case we don't have a theme pack). |new_theme| indicates whether this is a
  // newly installed theme or a migration.
  void BuildFromExtension(const extensions::Extension* extension,
                          bool new_theme);

  // Callback when |pack| has finished or failed building.
  void OnThemeBuiltFromExtension(const extensions::ExtensionId& extension_id,
                                 scoped_refptr<BrowserThemePack> pack,
                                 bool new_theme);

  // Handles theme color policy pref updates. if policy value contains valid
  // color(s), sets browser theme accordingly.
  void HandlePolicyColorUpdate();

  // Functions that modify theme prefs.
  void ClearThemePrefs();
  void SetThemePrefsForExtension(const extensions::Extension* extension);
  void SetThemePrefsForColor(SkColor color);

  bool DisableExtension(const std::string& extension_id);

  raw_ptr<Profile> profile_;
  PrefChangeRegistrar pref_change_registrar_;

  const raw_ref<const ThemeHelper> theme_helper_;
  scoped_refptr<CustomThemeSupplier> theme_supplier_;

  // The id of the theme extension which has just been installed but has not
  // been loaded yet. The theme extension with |installed_pending_load_id_| may
  // never be loaded if the install is due to updating a disabled theme.
  // |pending_install_id_| should be set to |kDefaultThemeID| if there are no
  // recently installed theme extensions
  std::string installed_pending_load_id_ = ThemeHelper::kDefaultThemeID;

  // The number of infobars currently displayed.
  int number_of_reinstallers_ = 0;

  // Configuring ThemeService into a desired theme state may involve the
  // configuration of multiple theme prefs. However updating a single theme pref
  // may result in requests to propagate theme update notifications. This flag
  // is used to avoid propagating updates until all theme prefs have been set
  // to their desired values.
  bool should_suppress_theme_updates_ = false;

  // Declared before |theme_syncable_service_|, because ThemeSyncableService
  // removes itself from the |observers_| list on destruction.
  base::ObserverList<ThemeServiceObserver> observers_;

  std::unique_ptr<ThemeSyncableService> theme_syncable_service_;

#if BUILDFLAG(ENABLE_EXTENSIONS)
  class ThemeObserver;
  std::unique_ptr<ThemeObserver> theme_observer_;
#endif

  BrowserThemeProvider original_theme_provider_;
  BrowserThemeProvider incognito_theme_provider_;
  BrowserThemeProvider default_theme_provider_;

  // Allows us to cancel building a theme pack from an extension.
  base::CancelableTaskTracker build_extension_task_tracker_;

  // The ID of the theme that's currently being built on a different thread.
  // We hold onto this just to be sure not to uninstall the extension view
  // RemoveUnusedThemes while it's still being built.
  std::string building_extension_id_;

  raw_ptr<NtpCustomBackgroundService> ntp_custom_background_service_ = nullptr;

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

#endif  // CHROME_BROWSER_THEMES_THEME_SERVICE_H_
