{
  "name": "dark-mode",
  "type": "registry:component",
  "files": [
    {
      "name": "index.ts",
      "content": "export * from './dark-mode';\n"
    },
    {
      "name": "dark-mode.ts",
      "content": "import { MediaMatcher } from '@angular/cdk/layout';\nimport { isPlatformBrowser, DOCUMENT } from '@angular/common';\nimport { afterNextRender, DestroyRef, Injectable, PLATFORM_ID, computed, inject, signal, effect } from '@angular/core';\n\nexport enum EDarkModes {\n  LIGHT = 'light',\n  DARK = 'dark',\n  SYSTEM = 'system',\n}\nexport type DarkModeOptions = EDarkModes.LIGHT | EDarkModes.DARK | EDarkModes.SYSTEM;\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ZardDarkMode {\n  private readonly document = inject(DOCUMENT);\n  private readonly destroyRef = inject(DestroyRef);\n  private readonly mediaMatcher = inject(MediaMatcher);\n\n  private static readonly STORAGE_KEY = 'theme';\n  private handleThemeChange = (event: MediaQueryListEvent) => this.systemDark.set(event.matches);\n  private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n  private readonly themeSignal = signal<DarkModeOptions>(EDarkModes.SYSTEM);\n  private _query?: MediaQueryList;\n  private readonly initialized = signal(false);\n  private readonly systemDark = signal(false);\n  readonly themeMode = computed<EDarkModes.LIGHT | EDarkModes.DARK>(() => {\n    const activeTheme = this.themeSignal();\n    if (activeTheme === EDarkModes.SYSTEM) {\n      return this.systemDark() ? EDarkModes.DARK : EDarkModes.LIGHT;\n    }\n    return activeTheme;\n  });\n\n  constructor() {\n    if (this.isBrowser) {\n      effect(() => {\n        if (!this.initialized()) {\n          return;\n        }\n        const theme = this.themeSignal();\n        const isDarkMode = this.isDarkModeActive(theme);\n        this.updateThemeMode(isDarkMode, theme);\n      });\n\n      afterNextRender({\n        write: () => {\n          // Only initialize if init() wasn't already called synchronously (e.g., in tests)\n          if (!this.initialized()) {\n            this.ensureQueryInitialized();\n            this.initializeTheme();\n          }\n        },\n      });\n    }\n  }\n\n  init() {\n    if (!this.initialized() && this.isBrowser) {\n      this.ensureQueryInitialized();\n      this.initializeTheme();\n    }\n  }\n\n  toggleTheme(targetMode?: DarkModeOptions): void {\n    if (!this.isBrowser) {\n      return;\n    }\n\n    if (targetMode) {\n      this.applyTheme(targetMode);\n    } else {\n      const next = this.themeMode() === EDarkModes.DARK ? EDarkModes.LIGHT : EDarkModes.DARK;\n      this.applyTheme(next);\n    }\n  }\n\n  /**\n   * Returns a ReadonlySignal<\"light\" | \"dark\" | \"system\"> that cannot be mutated externally.\n   * Call currentTheme() to access the value or use it directly in templates where signals are supported.\n   * @example service.currentTheme() // returns \"light\", \"dark\", or \"system\"\n   */\n  get currentTheme() {\n    return this.themeSignal.asReadonly();\n  }\n\n  private ensureQueryInitialized(): void {\n    if (!this._query) {\n      this._query = this.mediaMatcher.matchMedia('(prefers-color-scheme: dark)');\n      this.systemDark.set(this._query.matches);\n      this.destroyRef.onDestroy(() => this.handleSystemChanges(false));\n    }\n  }\n\n  private get query(): MediaQueryList {\n    if (!this.isBrowser || !this._query) {\n      throw new Error('MediaQueryList not available: either running on server or not initialized');\n    }\n    return this._query;\n  }\n\n  private initializeTheme(): void {\n    const storedTheme = this.getStoredTheme();\n    if (storedTheme) {\n      this.themeSignal.set(storedTheme);\n    }\n\n    if (!storedTheme || storedTheme === EDarkModes.SYSTEM) {\n      this.handleSystemChanges();\n    }\n    this.initialized.set(true);\n\n    const theme = this.themeSignal();\n    this.updateThemeMode(this.isDarkModeActive(theme), theme);\n  }\n\n  private applyTheme(theme: DarkModeOptions): void {\n    if (!this.isBrowser) {\n      return;\n    }\n\n    try {\n      localStorage.setItem(ZardDarkMode.STORAGE_KEY, theme);\n    } catch (error) {\n      console.warn('Failed to save theme to localStorage:', error);\n    }\n    this.themeSignal.set(theme);\n\n    if (theme === EDarkModes.SYSTEM) {\n      if (this.query) {\n        this.systemDark.set(this.query.matches);\n      } else {\n        this.ensureQueryInitialized();\n      }\n      this.handleSystemChanges();\n    } else {\n      this.handleSystemChanges(false);\n    }\n  }\n\n  private getStoredTheme(): DarkModeOptions | undefined {\n    if (!this.isBrowser) {\n      return undefined;\n    }\n\n    try {\n      const value = localStorage.getItem(ZardDarkMode.STORAGE_KEY);\n      if (value === EDarkModes.LIGHT || value === EDarkModes.DARK || value === EDarkModes.SYSTEM) {\n        return value;\n      }\n    } catch (error) {\n      console.warn('Failed to read theme from localStorage:', error);\n    }\n    return undefined;\n  }\n\n  private updateThemeMode(isDarkMode: boolean, themeMode: EDarkModes): void {\n    const html = this.document.documentElement;\n    html.classList.toggle('dark', isDarkMode);\n    html.setAttribute('data-theme', themeMode);\n  }\n\n  private isDarkModeActive(currentTheme: DarkModeOptions): boolean {\n    if (!this.isBrowser) {\n      return false;\n    }\n\n    return currentTheme === EDarkModes.DARK || (currentTheme === EDarkModes.SYSTEM && this.systemDark());\n  }\n\n  private handleSystemChanges(addListener = true): void {\n    if (!this._query) {\n      return;\n    }\n\n    try {\n      if (addListener) {\n        this.query.addEventListener('change', this.handleThemeChange);\n      } else {\n        this.query.removeEventListener('change', this.handleThemeChange);\n      }\n    } catch (error) {\n      console.warn('Failed to manage media query event listener:', error);\n    }\n  }\n}\n"
    }
  ],
  "basePath": "services"
}
