"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react"; import { getStoredTheme, getSystemTheme, setTheme as applyThemePreference, subscribeToThemeChanges, type Theme, } from "@/lib/theme"; import { ACTIVE_SESSION_EVENT, ACTIVE_SESSION_STORAGE_KEY, LANGUAGE_EVENT, LANGUAGE_STORAGE_KEY, SIDEBAR_COLLAPSED_EVENT, SIDEBAR_COLLAPSED_STORAGE_KEY, normalizeLanguage, readStoredActiveSessionId, readStoredLanguage, readStoredSidebarCollapsed, writeStoredActiveSessionId, writeStoredLanguage, writeStoredSidebarCollapsed, type AppLanguage, } from "@/context/app-shell-storage"; interface AppShellContextValue { theme: Theme; setTheme: (theme: Theme) => void; language: AppLanguage; setLanguage: (language: AppLanguage) => void; activeSessionId: string | null; setActiveSessionId: (sessionId: string | null) => void; sidebarCollapsed: boolean; setSidebarCollapsed: (collapsed: boolean) => void; } const AppShellContext = createContext(null); export function AppShellProvider({ children }: { children: React.ReactNode }) { const [theme, setThemeState] = useState(() => { return getStoredTheme() ?? getSystemTheme(); }); // Always start with "en" to match SSR; hydrate from localStorage after mount const [language, setLanguageState] = useState("en"); const [activeSessionId, setActiveSessionIdState] = useState( () => readStoredActiveSessionId(), ); // Always start expanded to match SSR; hydrate from localStorage after mount const [sidebarCollapsed, setSidebarCollapsedState] = useState(false); useEffect(() => { // Hydrate client-only preferences after SSR-safe first render. // eslint-disable-next-line react-hooks/set-state-in-effect setLanguageState(readStoredLanguage()); setSidebarCollapsedState(readStoredSidebarCollapsed()); }, []); useEffect(() => { return subscribeToThemeChanges((nextTheme) => { setThemeState(nextTheme); }); }, []); useEffect(() => { if (typeof window === "undefined") return; const onStorage = (event: StorageEvent) => { if (event.key === LANGUAGE_STORAGE_KEY) { setLanguageState(normalizeLanguage(event.newValue)); } if (event.key === ACTIVE_SESSION_STORAGE_KEY) { setActiveSessionIdState(event.newValue); } if (event.key === SIDEBAR_COLLAPSED_STORAGE_KEY) { setSidebarCollapsedState(event.newValue === "1"); } }; const onLanguage = (event: Event) => { const detail = (event as CustomEvent<{ language?: AppLanguage }>).detail; setLanguageState(normalizeLanguage(detail?.language)); }; const onActiveSession = (event: Event) => { const detail = (event as CustomEvent<{ sessionId?: string | null }>) .detail; setActiveSessionIdState(detail?.sessionId ?? null); }; const onSidebarCollapsed = (event: Event) => { const detail = (event as CustomEvent<{ collapsed?: boolean }>).detail; setSidebarCollapsedState(Boolean(detail?.collapsed)); }; window.addEventListener("storage", onStorage); window.addEventListener(LANGUAGE_EVENT, onLanguage); window.addEventListener(ACTIVE_SESSION_EVENT, onActiveSession); window.addEventListener(SIDEBAR_COLLAPSED_EVENT, onSidebarCollapsed); return () => { window.removeEventListener("storage", onStorage); window.removeEventListener(LANGUAGE_EVENT, onLanguage); window.removeEventListener(ACTIVE_SESSION_EVENT, onActiveSession); window.removeEventListener(SIDEBAR_COLLAPSED_EVENT, onSidebarCollapsed); }; }, []); const setTheme = useCallback((nextTheme: Theme) => { applyThemePreference(nextTheme); setThemeState(nextTheme); }, []); const setLanguage = useCallback((nextLanguage: AppLanguage) => { writeStoredLanguage(nextLanguage); setLanguageState(nextLanguage); }, []); const setActiveSessionId = useCallback((sessionId: string | null) => { writeStoredActiveSessionId(sessionId); setActiveSessionIdState(sessionId); }, []); const setSidebarCollapsed = useCallback((collapsed: boolean) => { writeStoredSidebarCollapsed(collapsed); setSidebarCollapsedState(collapsed); }, []); const value = useMemo( () => ({ theme, setTheme, language, setLanguage, activeSessionId, setActiveSessionId, sidebarCollapsed, setSidebarCollapsed, }), [ activeSessionId, language, setActiveSessionId, setLanguage, setSidebarCollapsed, setTheme, sidebarCollapsed, theme, ], ); return ( {children} ); } export function useAppShell() { const context = useContext(AppShellContext); if (!context) { throw new Error("useAppShell must be used inside AppShellProvider"); } return context; }