import { createPersistentStore } from '$shared/lib'; type Theme = 'light' | 'dark'; type ThemeSource = 'system' | 'user'; class ThemeManager { // Private reactive state #theme = $state('light'); #source = $state('system'); #mediaQuery: MediaQueryList | null = null; #store = createPersistentStore('glyphdiff:theme', null); #systemChangeHandler = this.#onSystemChange.bind(this); constructor() { // Derive initial values from stored preference or OS const stored = this.#store.value; if (stored === 'dark' || stored === 'light') { this.#theme = stored; this.#source = 'user'; } else { this.#theme = this.#getSystemTheme(); this.#source = 'system'; } } get value(): Theme { return this.#theme; } get source(): ThemeSource { return this.#source; } get isDark(): boolean { return this.#theme === 'dark'; } get isUserControlled(): boolean { return this.#source === 'user'; } /** Call once in root onMount */ init(): void { this.#applyToDom(this.#theme); this.#mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); this.#mediaQuery.addEventListener('change', this.#systemChangeHandler); } /** Call in root onDestroy */ destroy(): void { this.#mediaQuery?.removeEventListener('change', this.#systemChangeHandler); this.#mediaQuery = null; } setTheme(theme: Theme): void { this.#source = 'user'; this.#theme = theme; this.#store.value = theme; this.#applyToDom(theme); } toggle(): void { this.setTheme(this.value === 'dark' ? 'light' : 'dark'); } /** Hand control back to OS */ resetToSystem(): void { this.#store.clear(); this.#theme = this.#getSystemTheme(); this.#source = 'system'; this.#applyToDom(this.#theme); } // ------------------------- // Private helpers // ------------------------- #getSystemTheme(): Theme { if (typeof window === 'undefined') { return 'light'; } return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } #applyToDom(theme: Theme): void { document.documentElement.classList.toggle('dark', theme === 'dark'); } #onSystemChange(e: MediaQueryListEvent): void { if (this.#source === 'system') { this.#theme = e.matches ? 'dark' : 'light'; this.#applyToDom(this.#theme); } } } // Export a singleton — one instance for the whole app export const themeManager = new ThemeManager();