import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import nspell, { type NSpell } from 'nspell'

// useSpellChecker loads the vendored en-US Hunspell dictionary (served from
// /dictionaries/en, embedded in the Go binary via web/dist) and builds an
// in-browser nspell instance — zero backend round-trips, per spec. The
// dictionary is ~550KB, so it's fetched as a static asset (kept out of the JS
// bundle) once per app session, not per document. A personal word list lives in
// localStorage and is replayed into nspell on load; adding a word bumps a
// `version` so consumers re-run their decorations and the word stops flagging.

// SpellChecker is the minimal surface the editor decoration layer consumes.
export interface SpellChecker {
  correct(word: string): boolean
  suggest(word: string): string[]
}

const PERSONAL_KEY = 'petal.spell.personal'

function loadPersonal(): string[] {
  try {
    const raw = localStorage.getItem(PERSONAL_KEY)
    const parsed = raw ? JSON.parse(raw) : []
    return Array.isArray(parsed) ? parsed.filter((w): w is string => typeof w === 'string') : []
  } catch {
    return []
  }
}

function savePersonal(words: string[]) {
  try {
    localStorage.setItem(PERSONAL_KEY, JSON.stringify(words))
  } catch {
    /* storage unavailable — personal words just won't persist this session */
  }
}

export function useSpellChecker() {
  const spellRef = useRef<NSpell | null>(null)
  const [ready, setReady] = useState(false)
  // Bumped whenever the personal dictionary changes, to force re-decoration.
  const [version, setVersion] = useState(0)

  useEffect(() => {
    let cancelled = false
    ;(async () => {
      try {
        // Served from web/dist root (and embedded in the Go binary), same as /api.
        const [aff, dic] = await Promise.all([
          fetch('/dictionaries/en/en.aff').then((r) => r.text()),
          fetch('/dictionaries/en/en.dic').then((r) => r.text()),
        ])
        if (cancelled) return
        const sp = nspell(aff, dic)
        for (const w of loadPersonal()) sp.add(w)
        spellRef.current = sp
        setReady(true)
      } catch (err) {
        console.error('spell checker failed to load', err)
      }
    })()
    return () => {
      cancelled = true
    }
  }, [])

  // Recreate the checker's identity on load and on every personal-dict change so
  // the editor's effect re-pushes it and rebuilds decorations.
  const checker = useMemo<SpellChecker | null>(() => {
    if (!ready) return null
    return {
      correct: (w) => spellRef.current?.correct(w) ?? true,
      suggest: (w) => spellRef.current?.suggest(w) ?? [],
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ready, version])

  const addWord = useCallback((word: string) => {
    const sp = spellRef.current
    if (!sp) return
    sp.add(word)
    const next = Array.from(new Set([...loadPersonal(), word]))
    savePersonal(next)
    setVersion((v) => v + 1)
  }, [])

  return { checker, ready, addWord }
}
