import { DOCUMENT } from '@angular/common'; import { DestroyRef, Inject, Injectable, effect, signal, computed } from '@angular/core'; export type ThemeName = 'light' | 'dark'; @Injectable({ providedIn: 'root' }) export class ThemeService { private static readonly STORAGE_KEY = 'obsiwatcher.theme'; private readonly document = this.doc; private readonly prefersDarkQuery = typeof window !== 'undefined' ? window.matchMedia('(prefers-color-scheme: dark)') : null; private readonly currentTheme = signal(this.detectSystemTheme()); readonly theme = computed(() => this.currentTheme()); readonly isDark = computed(() => this.currentTheme() === 'dark'); constructor( @Inject(DOCUMENT) private readonly doc: Document, private readonly destroyRef: DestroyRef ) { effect(() => { const theme = this.currentTheme(); this.applyTheme(theme); this.persist(theme); }); if (this.prefersDarkQuery) { const listener = (event: MediaQueryListEvent) => { if (!this.getStoredTheme()) { this.currentTheme.set(event.matches ? 'dark' : 'light'); } }; this.prefersDarkQuery.addEventListener('change', listener); this.destroyRef.onDestroy(() => { this.prefersDarkQuery?.removeEventListener('change', listener); }); } } initFromStorage(): void { const stored = this.getStoredTheme(); if (stored) { this.currentTheme.set(stored); } else { this.currentTheme.set(this.detectSystemTheme()); } } setTheme(theme: ThemeName): void { this.currentTheme.set(theme); } toggleTheme(): void { this.currentTheme.update(theme => (theme === 'light' ? 'dark' : 'light')); } private detectSystemTheme(): ThemeName { if (typeof window === 'undefined') { return 'light'; } return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } private applyTheme(theme: ThemeName): void { const root = this.document.documentElement; root.setAttribute('data-theme', theme); if (theme === 'dark') { root.classList.add('dark'); } else { root.classList.remove('dark'); } } private persist(theme: ThemeName): void { try { if (typeof window === 'undefined' || !window.localStorage) { return; } window.localStorage.setItem(ThemeService.STORAGE_KEY, theme); } catch { // Ignore storage failures (private browsing, etc.) } } private getStoredTheme(): ThemeName | null { try { if (typeof window === 'undefined' || !window.localStorage) { return null; } const stored = window.localStorage.getItem(ThemeService.STORAGE_KEY) as ThemeName | null; return stored === 'light' || stored === 'dark' ? stored : null; } catch { return null; } } }