import type { ThemePreference } from "@batch-cooking/shared";
import { createContext, type ReactNode, useCallback, useContext, useEffect, useState } from "react";
import { apiClient } from "../../api/client";
import { useAuth } from "../auth/AuthContext";

/** Shape of the theme state/actions exposed via {@link useTheme}. */
interface ThemeContextValue {
  /** The signed-in user's theme preference — `"SYSTEM"` (the default) for a logged-out visitor too, nothing to load a preference for. */
  theme: ThemePreference;
  /** Persists `theme` and applies it immediately. Throws `ApiError` on failure. */
  setTheme: (theme: ThemePreference) => Promise<void>;
}

/** React context carrying {@link ThemeContextValue} — always accessed through {@link useTheme}, never directly. */
const ThemeContext = createContext<ThemeContextValue | null>(null);

/**
 * Applies `theme` to the document. `SYSTEM` *removes* the override rather
 * than setting a literal `"system"` value `_theme.scss` wouldn't recognize
 * — with no `data-theme` attribute at all, its `prefers-color-scheme`
 * media query decides instead, exactly the "system" behavior.
 */
function applyTheme(theme: ThemePreference) {
  if (theme === "SYSTEM") {
    delete document.documentElement.dataset.theme;
  } else {
    document.documentElement.dataset.theme = theme.toLowerCase();
  }
}

/**
 * Provides the signed-in user's theme preference and keeps the document in
 * sync with it. Must be nested inside `AuthProvider` (reads `useAuth()`'s
 * `user` to know when a profile is available to load a preference for) —
 * see `main.tsx`.
 *
 * Keyed on `user?.id` rather than `user` itself: `AuthContext`'s `user`
 * object is a fresh reference every time it refreshes (e.g. after an
 * unrelated `refreshUser()` call elsewhere), which shouldn't re-trigger a
 * preferences fetch — only actually signing in/out or switching accounts
 * should.
 */
export function ThemeProvider({ children }: { children: ReactNode }) {
  const { user } = useAuth();
  const [theme, setThemeState] = useState<ThemePreference>("SYSTEM");

  // biome-ignore lint/correctness/useExhaustiveDependencies: keyed on the id on purpose, see the doc comment above.
  useEffect(() => {
    if (!user) {
      setThemeState("SYSTEM");
      applyTheme("SYSTEM");
      return;
    }

    let cancelled = false;
    apiClient
      .getPreferences()
      .then((preferences) => {
        if (cancelled) return;
        setThemeState(preferences.theme);
        applyTheme(preferences.theme);
      })
      // Failing to load a preference (e.g. a network hiccup) shouldn't break
      // the rest of the app — worst case the theme just stays at its
      // current/default value, same "not fatal" spirit as AuthContext's own
      // `me()` call on mount.
      .catch(() => {});
    return () => {
      cancelled = true;
    };
  }, [user?.id]);

  const setTheme = useCallback(async (newTheme: ThemePreference) => {
    try {
      await apiClient.updatePreferences(newTheme);
      setThemeState(newTheme);
      applyTheme(newTheme);
    } catch (err) {
      // Rethrown as-is — the caller is what actually handles this (see this
      // context value's doc comment, "Throws ApiError on failure"); this
      // callback just isn't allowed a bare `await` per the repo's
      // async/try-catch convention.
      throw err;
    }
  }, []);

  return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
}

/** Reads the current theme state/actions. Must be called from within a {@link ThemeProvider}. */
export function useTheme(): ThemeContextValue {
  const ctx = useContext(ThemeContext);
  if (!ctx) {
    throw new Error("useTheme must be used within a ThemeProvider");
  }
  return ctx;
}
