import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode, } from "react"; export type Theme = "catppuccin-mocha" | "tokyo-night" | "catppuccin-latte"; export const THEME_META: Record = { "catppuccin-mocha": { label: "Catppuccin Mocha", dark: true, bg: "#1e1e2e", primary: "#89b4fa" }, "tokyo-night": { label: "Tokyo Night", dark: true, bg: "#1a1b26", primary: "#7aa2f7" }, "catppuccin-latte": { label: "Catppuccin Latte", dark: false, bg: "#eff1f5", primary: "#1e66f5" }, }; const VALID_THEMES: Theme[] = ["catppuccin-mocha", "tokyo-night", "catppuccin-latte"]; const DEFAULT_THEME: Theme = "catppuccin-mocha"; interface ThemeContextValue { theme: Theme; setTheme: (theme: Theme) => void; toggleTheme: () => void; } const THEME_STORAGE_KEY = "paperclip.theme"; const ThemeContext = createContext(undefined); function isValidTheme(value: string | null): value is Theme { return value !== null && VALID_THEMES.includes(value as Theme); } function readStoredTheme(): Theme { if (typeof document === "undefined") return DEFAULT_THEME; try { const stored = localStorage.getItem(THEME_STORAGE_KEY); return isValidTheme(stored) ? stored : DEFAULT_THEME; } catch { return DEFAULT_THEME; } } function applyTheme(theme: Theme) { if (typeof document === "undefined") return; const meta = THEME_META[theme]; const root = document.documentElement; root.classList.toggle("dark", meta.dark); root.classList.toggle("theme-tokyo-night", theme === "tokyo-night"); root.style.colorScheme = meta.dark ? "dark" : "light"; const themeColorMeta = document.querySelector('meta[name="theme-color"]'); if (themeColorMeta instanceof HTMLMetaElement) { themeColorMeta.setAttribute("content", meta.bg); } } export function ThemeProvider({ children }: { children: ReactNode }) { const [theme, setThemeState] = useState(() => readStoredTheme()); const setTheme = useCallback((nextTheme: Theme) => { setThemeState(nextTheme); }, []); const toggleTheme = useCallback(() => { setThemeState((current) => { const idx = VALID_THEMES.indexOf(current); return VALID_THEMES[(idx + 1) % VALID_THEMES.length]; }); }, []); useEffect(() => { applyTheme(theme); try { localStorage.setItem(THEME_STORAGE_KEY, theme); } catch { // Ignore localStorage write failures in restricted environments. } }, [theme]); const value = useMemo( () => ({ theme, setTheme, toggleTheme, }), [theme, setTheme, toggleTheme], ); return ( {children} ); } export function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error("useTheme must be used within ThemeProvider"); } return context; }