import { useCallback, useEffect, useSyncExternalStore } from "react"; type ThemePreference = "system" | "light" | "dark"; type ResolvedTheme = "light" | "dark"; const STORAGE_KEY = "initiative:theme"; const listeners = new Set<() => void>(); let currentPreference: ThemePreference = loadPreference(); function loadPreference(): ThemePreference { try { const raw = localStorage.getItem(STORAGE_KEY); if (raw === "light" || raw === "dark" || raw === "system") return raw; } catch { // storage unavailable } return "system"; } function savePreference(pref: ThemePreference): void { try { localStorage.setItem(STORAGE_KEY, pref); } catch { // quota exceeded or storage unavailable } } function getSystemTheme(): ResolvedTheme { if (typeof globalThis.matchMedia !== "function") return "dark"; return globalThis.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"; } function resolve(pref: ThemePreference): ResolvedTheme { return pref === "system" ? getSystemTheme() : pref; } function applyTheme(resolved: ResolvedTheme): void { document.documentElement.dataset.theme = resolved; } function notifyAll(): void { for (const listener of listeners) { listener(); } } // Apply on load applyTheme(resolve(currentPreference)); // Listen for OS preference changes if (typeof globalThis.matchMedia === "function") { globalThis .matchMedia("(prefers-color-scheme: light)") .addEventListener("change", () => { if (currentPreference === "system") { applyTheme(resolve("system")); notifyAll(); } }); } function subscribe(callback: () => void): () => void { listeners.add(callback); return () => listeners.delete(callback); } function getSnapshot(): ThemePreference { return currentPreference; } const CYCLE: ThemePreference[] = ["system", "light", "dark"]; export function useTheme() { const preference = useSyncExternalStore(subscribe, getSnapshot); const resolved = resolve(preference); useEffect(() => { applyTheme(resolved); }, [resolved]); const setPreference = useCallback((pref: ThemePreference) => { currentPreference = pref; savePreference(pref); applyTheme(resolve(pref)); notifyAll(); }, []); const cycleTheme = useCallback(() => { const idx = CYCLE.indexOf(currentPreference); const next = CYCLE[(idx + 1) % CYCLE.length]; setPreference(next); }, [setPreference]); return { preference, resolved, setPreference, cycleTheme } as const; }