import { createContext, useContext, useEffect, useRef, useState, useCallback, useMemo } from 'react'
import { api } from '../api/client.js'
import { applyThemeTokens } from '../lib/themeVars.js'

const SiteContext = createContext(null)

// Public site settings + mode (always reachable, even during maintenance).
export function SiteProvider({ children }) {
  const [settings, setSettings] = useState({})
  const [loading, setLoading] = useState(true)
  // Whether a fetch has actually SUCCEEDED, as distinct from `loading` — which
  // also goes false when the request failed and we fell back to {}. The boot
  // theme handoff below turns on this distinction.
  const [settled, setSettled] = useState(false)

  const refresh = useCallback(async () => {
    try {
      const data = await api.publicSettings()
      setSettings(data || {})
      setSettled(true)
    } catch {
      setSettings({})
    } finally {
      setLoading(false)
    }
  }, [])

  useEffect(() => {
    refresh()
  }, [refresh])

  const brand = useMemo(() => settings.brand || {}, [settings])

  // Apply the admin's theme. The whole effective token set is resolved
  // server-side, so this only writes it and takes back what it wrote before —
  // see lib/themeVars.js for why the removal half matters. No theme block means
  // the admin never themed this instance, and the shipped :root stands.
  const appliedTokens = useRef([])
  useEffect(() => {
    appliedTokens.current = applyThemeTokens(document.documentElement.style, settings.theme, appliedTokens.current)
    // Take over from the shell's boot block. The server injects the same tokens
    // into <head> so a themed instance does not paint the shipped palette for a
    // frame first (utils/htmlShell.js); from here on this effect is the
    // authority, and leaving the block behind would mean a later reset removed
    // the inline properties only to reveal the stale block underneath.
    //
    // Gated on a SUCCESSFUL fetch, not merely a finished one: a failed request
    // leaves us with no theme at all, and dropping the block then would strip a
    // themed instance back to the shipped palette for no reason.
    if (settled) document.getElementById('theme-boot')?.remove()
  }, [settings.theme, settled])

  // Apply the instance accent color to the CSS variable the theme is built on,
  // so branding flows to every `var(--accent)` at runtime (no rebuild). This is
  // the *effective* accent — the admin theme overrides BRAND_ACCENT_COLOR
  // server-side (docs/website/THEMING_AND_NAV.md §4.5) — so it agrees with the
  // theme block rather than fighting it.
  //
  // Deliberately ordered after the theme effect and re-run on any theme change:
  // resetting a theme removes --accent from the token map, and this has to be
  // the write that lands last or an instance with a custom BRAND_ACCENT_COLOR
  // would drop to the stylesheet's default accent until the next reload.
  useEffect(() => {
    if (brand.accent) document.documentElement.style.setProperty('--accent', brand.accent)
  }, [brand.accent, settings.theme])

  // Memoized so consumers don't re-render on every provider render (brand is a
  // fresh object each render, which would otherwise churn the context value).
  const value = useMemo(
    () => ({
      settings,
      loading,
      refresh,
      brand,
      mode: settings.site_mode || 'live',
      siteTitle: brand.name || settings.site_title || 'Runic Gateway',
      siteShortName: brand.shortName || brand.name || settings.site_title || 'Runic Gateway',
      contactEmail: brand.contactEmail || settings.contact_email || '',
      heroImage: brand.hero || '/assets/img/runic-emblem.png',
    }),
    [settings, loading, refresh, brand],
  )

  return <SiteContext.Provider value={value}>{children}</SiteContext.Provider>
}

export function useSite() {
  const ctx = useContext(SiteContext)
  if (!ctx) throw new Error('useSite must be used within SiteProvider')
  return ctx
}
