'use client'

import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'

import type { AppMode } from '@/lib/domain/provenance'

/**
 * Operating mode, appearance and motion, in one provider (§6, §2, §73).
 *
 * Mode is grouped with theme and motion because all three are properties of *how the
 * console is being used right now* rather than of any page — and because mode in
 * particular has to be impossible to lose track of. A simulation that looked like live
 * operations would be the most dangerous defect this product could ship, so the mode
 * lives in the shell, is written into the document element, and is announced.
 *
 * Everything persists to `localStorage` so a presenter who reloads mid-demo lands back
 * where they were, and everything falls back safely when storage is unavailable.
 */

export type Theme = 'dark' | 'light'
export type Contrast = 'normal' | 'high'
export type Motion = 'full' | 'reduced'

interface AppModeValue {
  mode: AppMode
  setMode: (mode: AppMode) => void
  theme: Theme
  setTheme: (theme: Theme) => void
  contrast: Contrast
  setContrast: (contrast: Contrast) => void
  motion: Motion
  setMotion: (motion: Motion) => void
  /** True whenever nothing on screen should be read as an operational reading. */
  isSimulated: boolean
}

const AppModeContext = createContext<AppModeValue | null>(null)

const STORAGE_KEY = 'nabdh.appearance'

interface StoredPreferences {
  mode?: AppMode
  theme?: Theme
  contrast?: Contrast
  motion?: Motion
}

function readStored(): StoredPreferences {
  if (typeof window === 'undefined') return {}
  try {
    const raw = window.localStorage.getItem(STORAGE_KEY)
    return raw ? (JSON.parse(raw) as StoredPreferences) : {}
  } catch {
    // A private window or a browser with site data blocked is a normal condition, not an
    // error: the console works fine, it just does not remember.
    return {}
  }
}

export function AppModeProvider({
  children,
  initialMode = 'live',
}: {
  children: ReactNode
  initialMode?: AppMode
}) {
  const [mode, setModeState] = useState<AppMode>(initialMode)
  const [theme, setThemeState] = useState<Theme>('dark')
  const [contrast, setContrastState] = useState<Contrast>('normal')
  const [motion, setMotionState] = useState<Motion>('full')

  // Preferences are read after mount rather than during render: the server has no
  // localStorage, and reading it during render would make the first paint disagree with
  // the markup React sent.
  useEffect(() => {
    const stored = readStored()
    if (stored.theme) setThemeState(stored.theme)
    if (stored.contrast) setContrastState(stored.contrast)
    if (stored.motion) setMotionState(stored.motion)
    // Mode is deliberately *not* restored. Coming back to a console that silently
    // remembered it was in simulation is exactly the confusion this provider exists to
    // prevent; a session starts live unless the page it lands on says otherwise.
  }, [])

  const persist = useCallback((next: StoredPreferences) => {
    if (typeof window === 'undefined') return
    try {
      window.localStorage.setItem(STORAGE_KEY, JSON.stringify({ ...readStored(), ...next }))
    } catch {
      // Nothing to do: the preference still applies for this session.
    }
  }, [])

  useEffect(() => {
    const root = document.documentElement
    root.dataset.theme = theme
    root.dataset.contrast = contrast
    root.dataset.motion = motion
    root.dataset.mode = mode
  }, [theme, contrast, motion, mode])

  const value = useMemo<AppModeValue>(
    () => ({
      mode,
      setMode: (next) => setModeState(next),
      theme,
      setTheme: (next) => {
        setThemeState(next)
        persist({ theme: next })
      },
      contrast,
      setContrast: (next) => {
        setContrastState(next)
        persist({ contrast: next })
      },
      motion,
      setMotion: (next) => {
        setMotionState(next)
        persist({ motion: next })
      },
      isSimulated: mode !== 'live',
    }),
    [mode, theme, contrast, motion, persist],
  )

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

export function useAppMode(): AppModeValue {
  const value = useContext(AppModeContext)
  if (!value) throw new Error('useAppMode must be used inside <AppModeProvider>')
  return value
}

/**
 * Declare the mode a page runs in.
 *
 * Pages that are inherently simulated — the twin sandbox, demo mode, the resilience lab —
 * call this on mount so the banner and every provenance label follow them automatically,
 * and so leaving the page restores whatever was in force before.
 */
export function useDeclaredMode(mode: AppMode) {
  const { setMode } = useAppMode()
  useEffect(() => {
    setMode(mode)
    return () => setMode('live')
  }, [mode, setMode])
}
