import { createContext, useCallback, useContext, useEffect, useRef, useState, type ReactNode, } from "react"; import type { HeadingFont, PresetId, ThemeConfig, ThemePreset } from "./types.ts"; import { DEFAULT_THEME_CONFIG } from "./types.ts"; import { getPreset } from "./presets.ts"; import { applyTheme } from "./theme-engine.ts"; const STORAGE_KEY = "ua-theme"; interface ThemeContextValue { config: ThemeConfig; preset: ThemePreset; setPreset: (presetId: PresetId) => void; setAccent: (accentId: string) => void; setHeadingFont: (font: HeadingFont) => void; } const ThemeContext = createContext(null); function loadFromLocalStorage(): ThemeConfig | null { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return null; const parsed = JSON.parse(raw); if (parsed && typeof parsed.presetId === "string" && typeof parsed.accentId === "string") { return parsed as ThemeConfig; } return null; } catch { return null; } } function saveToLocalStorage(config: ThemeConfig): void { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(config)); } catch { // Storage full or unavailable — ignore } } function resolveInitialTheme(metaTheme?: ThemeConfig | null): ThemeConfig { return loadFromLocalStorage() ?? metaTheme ?? DEFAULT_THEME_CONFIG; } interface ThemeProviderProps { metaTheme?: ThemeConfig | null; children: ReactNode; } export function ThemeProvider({ metaTheme, children }: ThemeProviderProps) { const [config, setConfig] = useState(() => resolveInitialTheme(metaTheme)); const initialized = useRef(false); // Apply theme on mount and config changes useEffect(() => { applyTheme(config); if (initialized.current) { saveToLocalStorage(config); } initialized.current = true; }, [config]); // Update if metaTheme arrives later (async fetch) and no localStorage preference exists useEffect(() => { if (metaTheme && !loadFromLocalStorage()) { setConfig(metaTheme); } }, [metaTheme]); const setPreset = useCallback((presetId: PresetId) => { setConfig((_prev) => { const newPreset = getPreset(presetId); return { presetId, accentId: newPreset.defaultAccentId }; }); }, []); const setAccent = useCallback((accentId: string) => { setConfig((prev) => ({ ...prev, accentId })); }, []); const setHeadingFont = useCallback((font: HeadingFont) => { setConfig((prev) => ({ ...prev, headingFont: font })); }, []); const preset = getPreset(config.presetId); return ( {children} ); } export function useTheme(): ThemeContextValue { const ctx = useContext(ThemeContext); if (!ctx) throw new Error("useTheme must be used within ThemeProvider"); return ctx; }