import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from "react";
import { qk } from "../lib/queryKeys";
import { useQuery } from "@tanstack/react-query";
import { api } from "../lib/api";
import {
  applyTheme,
  DEFAULT_THEME,
  loadLocalTheme,
  saveLocalTheme,
  type ThemePrefs,
} from "../lib/theme";
import { configureNotifications, getNotifSettings, type NotifSettings } from "../lib/notifications";
import { hintsEnabled, setHintsEnabled } from "../lib/hints";
import { getAccountRaw, LS, setAccountRaw } from "../lib/storage";
import type { PrefsController } from "./SettingsPanel";

const PERF_KEY = LS.perfMode;

// The four Settings-page preferences. They apply locally for instant preview and auto-save (debounced)
// to the server — there is no draft or Save button. This provider owns them (lifted out of the App god
// component) plus the last-saved "baseline" used to suppress the echo when a change came from adopting
// server prefs on login. Consumers (SettingsPanel, GlassTuner) read the PrefsController via usePrefs().
type EditablePrefs = {
  theme: ThemePrefs;
  performanceMode: boolean;
  hints: boolean;
  notifications: NotifSettings;
};

const PrefsContext = createContext<PrefsController>({
  theme: DEFAULT_THEME,
  setTheme: () => {},
  perf: false,
  setPerf: () => {},
  hints: true,
  setHints: () => {},
  notif: getNotifSettings(),
  setNotif: () => {},
  saveState: "idle",
});

export function usePrefs(): PrefsController {
  return useContext(PrefsContext);
}

export function PrefsProvider({ children }: { children: ReactNode }) {
  const [theme, setThemeState] = useState<ThemePrefs>(() => loadLocalTheme());
  const [perf, setPerf] = useState(() => getAccountRaw(PERF_KEY) === "1");
  const [hints, setHints] = useState(() => hintsEnabled());
  const [notif, setNotif] = useState<NotifSettings>(() => getNotifSettings());
  // The last server-known prefs. Written on adopt + after each save so the auto-save effect can
  // compare against it and avoid echoing a server-adopted value straight back. A ref (drives no UI).
  const baselineRef = useRef<EditablePrefs>({
    theme: loadLocalTheme(),
    performanceMode: getAccountRaw(PERF_KEY) === "1",
    hints: hintsEnabled(),
    notifications: getNotifSettings(),
  });
  const [saveState, setSaveState] = useState<PrefsController["saveState"]>("idle");
  const saveMsgTimer = useRef<number | undefined>(undefined);
  const saveTimer = useRef<number | undefined>(undefined);
  // Passive observer of the account App fetches (enabled:false → reads cache, re-renders on update).
  const { data: me } = useQuery({ queryKey: qk.me(), queryFn: api.me, enabled: false });

  // Live-apply the prefs locally for instant preview (their localStorage mirrors update via the
  // stores too); persistence to the server is the auto-save effect below.
  useEffect(() => applyTheme(theme), [theme]);
  useEffect(() => {
    document.documentElement.dataset.perf = perf ? "1" : "";
    setAccountRaw(PERF_KEY, perf ? "1" : "0");
  }, [perf]);
  // The per-scheme background image (the "glass over image" look) is on when the user hasn't opted
  // out and we're not in perf mode. Drives `html[data-backdrop]` (see index.css), which paints the
  // theme-appropriate image on <body> and switches the glass to translucent.
  useEffect(() => {
    const on = theme.bgImage && !perf;
    document.documentElement.dataset.backdrop = on ? "on" : "off";
  }, [theme.bgImage, perf]);
  useEffect(() => setHintsEnabled(hints), [hints]);
  useEffect(() => configureNotifications(notif), [notif]);

  // Auto-save on change (debounced ~500ms so a slider drag coalesces into one PUT). Live-apply is the
  // effects above; this is the only persistence path. `baselineRef` suppresses the echo when the
  // change came from adopting server prefs on login (`/me` merges, so a full payload is fine).
  const editablePrefs = useMemo<EditablePrefs>(
    () => ({ theme, performanceMode: perf, hints, notifications: notif }),
    [theme, perf, hints, notif],
  );
  useEffect(() => {
    window.clearTimeout(saveTimer.current);
    if (JSON.stringify(editablePrefs) === JSON.stringify(baselineRef.current)) {
      // Back at the last-saved value (initial load, server adopt, or an edit undone) — nothing to
      // save; clear any "Saving…" left from a change that was reverted before it flushed.
      setSaveState((s) => (s === "saving" ? "idle" : s));
      return;
    }
    setSaveState("saving");
    window.clearTimeout(saveMsgTimer.current);
    const payload = editablePrefs;
    saveTimer.current = window.setTimeout(() => {
      api
        .savePrefs(payload)
        .then(() => {
          baselineRef.current = payload;
          setSaveState("saved");
          saveMsgTimer.current = window.setTimeout(() => setSaveState("idle"), 2500);
        })
        .catch(() => {
          // api.req() already surfaces the failure; flag the indicator so the user sees it didn't take.
          setSaveState("error");
          saveMsgTimer.current = window.setTimeout(() => setSaveState("idle"), 4000);
        });
    }, 500);
    return () => window.clearTimeout(saveTimer.current);
  }, [editablePrefs]);
  // Clear pending timers on unmount so a debounced PUT or a fade timer never fires setState on a dead
  // component.
  useEffect(
    () => () => {
      window.clearTimeout(saveTimer.current);
      window.clearTimeout(saveMsgTimer.current);
    },
    [],
  );

  // On login / account switch, adopt the server-stored prefs into the live states AND the auto-save
  // baseline (so adopting them doesn't echo a save back).
  useEffect(() => {
    const prefs = me?.preferences;
    if (!prefs) return;
    const adopted: EditablePrefs = {
      theme: prefs.theme ? { ...DEFAULT_THEME, ...prefs.theme } : loadLocalTheme(),
      performanceMode:
        typeof prefs.performanceMode === "boolean"
          ? prefs.performanceMode
          : getAccountRaw(PERF_KEY) === "1",
      hints: typeof prefs.hints === "boolean" ? prefs.hints : hintsEnabled(),
      notifications: prefs.notifications
        ? { ...getNotifSettings(), ...prefs.notifications }
        : getNotifSettings(),
    };
    setThemeState(adopted.theme);
    saveLocalTheme(adopted.theme);
    setPerf(adopted.performanceMode);
    setHints(adopted.hints);
    setNotif(adopted.notifications);
    baselineRef.current = adopted;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [me?.id]);

  // Theme applies live (and caches to localStorage); the auto-save effect persists it to the server.
  const setTheme = useCallback((next: ThemePrefs) => {
    setThemeState(next);
    saveLocalTheme(next);
  }, []);

  const value = useMemo<PrefsController>(
    () => ({ theme, setTheme, perf, setPerf, hints, setHints, notif, setNotif, saveState }),
    [theme, setTheme, perf, hints, notif, saveState],
  );

  return <PrefsContext.Provider value={value}>{children}</PrefsContext.Provider>;
}
