// Game store: reducer, context, responsive mode, and localStorage-backed tweaks.
// Mirrors the prototype's app.jsx store, but the case comes from the server and
// suspicion/verdict are server-authoritative (the client only displays them).
import { createContext } from 'preact'
import type { ComponentChildren } from 'preact'
import { useCallback, useContext, useEffect, useReducer, useState } from 'preact/hooks'

import type { PublicCase } from './types'

export type Screen =
  | 'title' | 'story' | 'briefing' | 'board' | 'suspects' | 'interro' | 'evidence'
  | 'flashback' | 'timeline' | 'notes' | 'accuse' | 'verdict' | 'share' | 'boot'

export interface Line {
  role: 'det' | 'sus'
  text: string
  ev?: string
}

export interface AccuseState {
  suspect: string | null
  motive: string | null
  evidence: string[]
}

export interface GameState {
  screen: Screen
  payload: Record<string, unknown>
  suspicion: Record<string, number>
  interrogations: Record<string, Line[]>
  usedQ: Record<string, string[]>
  usedEv: Record<string, string[]>
  pinned: string[]
  /** Suspects whose lie cracked (cornered / contradiction exposed) at least once. */
  cracked: Record<string, boolean>
  /** evidenceId -> suspect ids it rattled or cracked when presented. */
  evHits: Record<string, string[]>
  accuse: AccuseState
  startedAt: number
}

/** A suspect at or above this suspicion is "cornered" — ready to accuse. */
export const HOT_SUSPICION = 75

type Action =
  | { type: 'NAV'; screen: Screen; payload?: Record<string, unknown> }
  | { type: 'SUSP_SET'; sid: string; value: number }
  | { type: 'ADD_LINE'; sid: string; line: Line }
  | { type: 'USEQ'; sid: string; qid: string }
  | { type: 'USEEV'; sid: string; ev: string }
  | { type: 'PIN'; ev: string }
  | { type: 'FLAG'; sid: string; evId?: string; kind: 'rattled' | 'cornered' | 'contradiction' }
  | { type: 'ACCUSE'; field: keyof AccuseState; value: unknown }

function reducer(s: GameState, a: Action): GameState {
  switch (a.type) {
    case 'NAV':
      return { ...s, screen: a.screen, payload: a.payload || {} }
    case 'SUSP_SET':
      return { ...s, suspicion: { ...s.suspicion, [a.sid]: Math.max(0, Math.min(100, Math.round(a.value))) } }
    case 'ADD_LINE':
      return { ...s, interrogations: { ...s.interrogations, [a.sid]: [...(s.interrogations[a.sid] || []), a.line] } }
    case 'USEQ':
      return { ...s, usedQ: { ...s.usedQ, [a.sid]: [...(s.usedQ[a.sid] || []), a.qid] } }
    case 'USEEV':
      return { ...s, usedEv: { ...s.usedEv, [a.sid]: [...(s.usedEv[a.sid] || []), a.ev] } }
    case 'PIN':
      return { ...s, pinned: s.pinned.includes(a.ev) ? s.pinned : [...s.pinned, a.ev] }
    case 'FLAG': {
      const cracked = a.kind !== 'rattled' ? { ...s.cracked, [a.sid]: true } : s.cracked
      let { evHits } = s
      if (a.evId) {
        const cur = s.evHits[a.evId] || []
        if (!cur.includes(a.sid)) evHits = { ...s.evHits, [a.evId]: [...cur, a.sid] }
      }
      return { ...s, cracked, evHits }
    }
    case 'ACCUSE':
      return { ...s, accuse: { ...s.accuse, [a.field]: a.value } }
    default:
      return s
  }
}

function initialState(c: PublicCase, screen: Screen = 'title'): GameState {
  const suspicion: Record<string, number> = {}
  c.suspects.forEach((s) => {
    suspicion[s.id] = s.baselineSuspicion
  })
  return {
    screen,
    payload: {},
    suspicion,
    interrogations: {},
    usedQ: {},
    usedEv: {},
    pinned: [],
    cracked: {},
    evHits: {},
    accuse: { suspect: null, motive: null, evidence: [] },
    startedAt: Date.now(),
  }
}

// ---- tweaks ----
export interface Tweaks {
  palette: 'sodium' | 'harbor' | 'violet'
  fonts: 'crisp' | 'terminal' | 'stamp'
  fx: 'low' | 'med' | 'high'
  mood: 'night' | 'day'
  pixelScale: number
  typeSpeed: number
  rain: boolean
}

export const TWEAK_DEFAULTS: Tweaks = {
  palette: 'sodium', fonts: 'crisp', fx: 'med', mood: 'night',
  pixelScale: 1, typeSpeed: 18, rain: true,
}

// In-memory only: the tweaks sheet UI is gone, so persisted values would be
// unrevertable. Defaults are deterministic; setTweak stays as a debug hook.
export function useTweaks(): [Tweaks, <K extends keyof Tweaks>(k: K, v: Tweaks[K]) => void] {
  const [t, setT] = useState<Tweaks>(TWEAK_DEFAULTS)
  const setTweak = useCallback(<K extends keyof Tweaks>(k: K, v: Tweaks[K]) => {
    setT((prev) => ({ ...prev, [k]: v }))
  }, [])
  return [t, setTweak]
}

// ---- responsive mode ----
export type Device = 'auto' | 'desktop' | 'mobile'
export function useMode(device: Device): 'desktop' | 'mobile' {
  const [w, setW] = useState(typeof window !== 'undefined' ? window.innerWidth : 1200)
  useEffect(() => {
    const f = () => setW(window.innerWidth)
    window.addEventListener('resize', f)
    return () => window.removeEventListener('resize', f)
  }, [])
  if (device === 'desktop') return 'desktop'
  if (device === 'mobile') return 'mobile'
  return w < 820 ? 'mobile' : 'desktop'
}

// ---- context ----
export interface Game {
  state: GameState & { tweaks: Tweaks }
  dispatch: (a: Action) => void
  nav: (screen: Screen, payload?: Record<string, unknown>) => void
  mode: 'desktop' | 'mobile'
  runStats: () => [string, string][]
  case: PublicCase
  runId: string
  setTweak: <K extends keyof Tweaks>(k: K, v: Tweaks[K]) => void
  newCase: () => void // fetch a fresh case from the server and start playing it
  loadCase: (id: string) => void // load a specific case by ID and jump straight into it
}

const GameCtx = createContext<Game | null>(null)
export const useGame = (): Game => useContext(GameCtx)!

interface ProviderProps {
  case: PublicCase
  runId: string
  mode: 'desktop' | 'mobile'
  tweaks: Tweaks
  setTweak: <K extends keyof Tweaks>(k: K, v: Tweaks[K]) => void
  initialScreen?: Screen
  newCase: () => void
  loadCase: (id: string) => void
  children: ComponentChildren
}

export function GameProvider({ case: c, runId, mode, tweaks, setTweak, initialScreen = 'title', newCase, loadCase, children }: ProviderProps) {
  const [state, dispatch] = useReducer(reducer, c, (cc) => initialState(cc, initialScreen))

  const nav = useCallback((screen: Screen, payload?: Record<string, unknown>) => {
    dispatch({ type: 'NAV', screen, payload })
    const stage = document.querySelector('.app__view')
    if (stage) stage.scrollTop = 0
  }, [])

  const runStats = useCallback((): [string, string][] => {
    const ms = Date.now() - state.startedAt
    const mm = String(Math.floor(ms / 60000)).padStart(2, '0')
    const ss = String(Math.floor((ms % 60000) / 1000)).padStart(2, '0')
    const grilled = Object.values(state.interrogations).filter((x) => x.length > 1).length
    return [
      ['TIME', `${mm}:${ss}`],
      ['GRILLED', `${grilled}/${c.suspects.length}`],
      ['EXHIBITS', `${state.pinned.length}/${c.evidence.length}`],
    ]
  }, [state, c])

  const game: Game = {
    state: { ...state, tweaks },
    dispatch,
    nav,
    mode,
    runStats,
    case: c,
    runId,
    setTweak,
    newCase,
    loadCase,
  }
  window.__game = game

  return <GameCtx.Provider value={game}>{children}</GameCtx.Provider>
}
