import { useRef, useEffect, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { motion, AnimatePresence } from 'motion/react'
import { useStore } from '../store'
import type { Stroke, StrokePoint } from '../recognition/types'
import { recognizeEnsemble, initEngines } from '../recognition/ensemble'

const BIG = 120
const SMALL = 60

interface Props {
  onDone: () => void
}

export default function ZoomStrip({ onDone }: Props) {
  const { t } = useTranslation()
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const puzzle = useStore((s) => s.puzzle)
  const entries = useStore((s) => s.entries)
  const selectedCell = useStore((s) => s.selectedCell)
  const direction = useStore((s) => s.direction)
  const wordSpans = useStore((s) => s.wordSpans)
  const activeClue = useStore((s) => s.activeClue)
  const settings = useStore((s) => s.stylusSettings)
  const acceptCandidate = useStore((s) => s.acceptCandidate)
  const setRecognitionResult = useStore((s) => s.setRecognitionResult)

  const strokes = useRef<Stroke[]>([])
  const currentStroke = useRef<StrokePoint[]>([])
  const debounceTimer = useRef<number | null>(null)
  const drawing = useRef(false)
  const enginesInitialized = useRef(false)

  const [canvasVersion, setCanvasVersion] = useState(0)
  const canvasRefCallback = useCallback((el: HTMLCanvasElement | null) => {
    const prev = canvasRef.current
    canvasRef.current = el
    if (el && el !== prev && prev !== null) {
      setCanvasVersion(v => v + 1)
    }
    if (el && el !== prev) {
      el.width = BIG; el.height = BIG
      const ctx = el.getContext('2d')
      if (ctx) {
        ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--cell-white').trim() || '#fff'
        ctx.fillRect(0, 0, BIG, BIG)
        ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--cell-selected').trim() || '#fce4d6'
        ctx.lineWidth = 3
        ctx.strokeRect(0, 0, BIG, BIG)
      }
    }
  }, [])

  // pre-initialize recognition engines when puzzle loads
  useEffect(() => {
    if (puzzle && !enginesInitialized.current) {
      enginesInitialized.current = true
      initEngines(settings)
    }
  }, [puzzle, settings])

  const [recLetter, setRecLetter] = useState('')
  const [showRec, setShowRec] = useState(false)
  const [retryMsg, setRetryMsg] = useState(false)

  const wordCells = (() => {
    if (!puzzle || !selectedCell) return []
    // try active clue in current direction first
    let span = wordSpans.find(
      (s) => s.number === activeClue && s.direction === direction,
    )
    // fall back to any span containing the selected cell in current direction
    if (!span) {
      span = wordSpans.find(
        (s) => s.direction === direction && s.cells.some(([r, c]) => r === selectedCell!.row && c === selectedCell!.col),
      )
    }
    // last resort: any span containing the selected cell
    if (!span) {
      span = wordSpans.find(
        (s) => s.cells.some(([r, c]) => r === selectedCell!.row && c === selectedCell!.col),
      )
    }
    return span?.cells ?? []
  })()

  const activeIndex = wordCells.findIndex(
    ([r, c]) => selectedCell && r === selectedCell.row && c === selectedCell.col,
  )

  // always 3 slots: prev, active, next. null for edges.
  const prevCell = activeIndex > 0 ? wordCells[activeIndex - 1] : null
  const activeCell = activeIndex >= 0 ? wordCells[activeIndex] : null
  const nextCell = activeIndex < wordCells.length - 1 ? wordCells[activeIndex + 1] : null

  type Slot = { cell: [number, number] | null; role: 'prev' | 'active' | 'next' }
  const slots: Slot[] = [
    { cell: prevCell, role: 'prev' },
    { cell: activeCell, role: 'active' },
    { cell: nextCell, role: 'next' },
  ]

  function getCellLetter(cell: [number, number] | null) {
    if (!cell) return ''
    return entries[cell[0]]?.[cell[1]] || ''
  }
  function getCellNum(cell: [number, number] | null) {
    if (!cell) return undefined
    return puzzle?.cell_numbers[`${cell[0]},${cell[1]}`]
  }

  const clearCanvas = useCallback(() => {
    const canvas = canvasRef.current
    if (!canvas) return
    canvas.width = BIG; canvas.height = BIG
    const ctx = canvas.getContext('2d')
    if (!ctx) return
    ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--cell-white').trim() || '#fff'
    ctx.fillRect(0, 0, BIG, BIG)
    ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--cell-selected').trim() || '#fce4d6'
    ctx.lineWidth = 3
    ctx.strokeRect(0, 0, BIG, BIG)
  }, [])

  useEffect(() => {
    clearCanvas()
    strokes.current = []
    queueMicrotask(() => { setShowRec(false); setRecLetter('') })
  }, [selectedCell, direction, activeClue, clearCanvas])

  const advanceToNextEmpty = useCallback(() => {
    const store = useStore.getState()
    const cell = store.selectedCell
    if (!puzzle || !cell) { onDone(); return }
    const dir = store.direction
    const ents = store.entries
    const dr = dir === 'down' ? 1 : 0
    const dc = dir === 'across' ? 1 : 0
    let nr = cell.row + dr, nc = cell.col + dc
    while (nr < puzzle.height && nc < puzzle.width) {
      if (puzzle.cells[nr]?.[nc] === 'black') break
      if (!ents[nr]?.[nc]) {
        const span = store.wordSpans.find((s) => s.direction === dir && s.cells.some(([r, c]) => r === nr && c === nc))
        useStore.setState({ selectedCell: { row: nr, col: nc }, activeClue: span?.number ?? store.activeClue })
        strokes.current = []
        return
      }
      nr += dr; nc += dc
    }
    onDone()
  }, [puzzle, onDone])

  const runRecognition = useCallback(async () => {
    const cell = useStore.getState().selectedCell
    if (strokes.current.length === 0 || !cell) return
    if (!enginesInitialized.current) {
      enginesInitialized.current = true
      await initEngines(settings)
    }
    await new Promise(r => setTimeout(r, 50))
    const result = await recognizeEnsemble(strokes.current, settings)
    strokes.current = []

    if (result.candidates.length === 0 || result.decision === 'redraw') {
      setRetryMsg(true)
      setTimeout(() => {
        setRetryMsg(false)
        clearCanvas()
      }, 1200)
      return
    }

    if (result.decision === 'auto-fill') {
      const letter = result.candidates[0].letter
      setRecLetter(letter)
      setShowRec(true)
      setTimeout(() => {
        acceptCandidate(letter)
        // typeLetter (called by acceptCandidate) already advances to next empty
        strokes.current = []
      }, 350)
    } else if (result.decision === 'disambiguate') {
      setRecognitionResult({ candidates: result.candidates, cell })
      useStore.setState({ showDisambiguation: true })
    }
  }, [settings, acceptCandidate, setRecognitionResult, advanceToNextEmpty, clearCanvas])

  // pointer events
  useEffect(() => {
    const canvas = canvasRef.current
    if (!canvas) return
    function onDown(e: PointerEvent) {
      e.preventDefault(); e.stopPropagation()
      canvas!.setPointerCapture(e.pointerId); drawing.current = true
      if (debounceTimer.current) { clearTimeout(debounceTimer.current); debounceTimer.current = null }
      const rect = canvas!.getBoundingClientRect()
      const sx = canvas!.width / rect.width, sy = canvas!.height / rect.height
      currentStroke.current = [{ x: (e.clientX - rect.left) * sx, y: (e.clientY - rect.top) * sy, pressure: e.pressure || 0.5, timestamp: Date.now() }]
    }
    function onMove(e: PointerEvent) {
      if (!drawing.current) return; e.preventDefault(); e.stopPropagation()
      const rect = canvas!.getBoundingClientRect()
      const sx = canvas!.width / rect.width, sy = canvas!.height / rect.height
      const pt: StrokePoint = { x: (e.clientX - rect.left) * sx, y: (e.clientY - rect.top) * sy, pressure: e.pressure || 0.5, timestamp: Date.now() }
      if (currentStroke.current.length > 0) {
        const prev = currentStroke.current[currentStroke.current.length - 1]
        const ctx = canvas!.getContext('2d')!
        ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--ink-color').trim() || '#3a2820'
        ctx.lineWidth = settings.strokeWidth * (1 + pt.pressure); ctx.lineCap = 'round'; ctx.lineJoin = 'round'
        ctx.beginPath(); ctx.moveTo(prev.x, prev.y); ctx.lineTo(pt.x, pt.y); ctx.stroke()
      }
      currentStroke.current.push(pt)
    }
    function onUp() {
      if (!drawing.current) return; drawing.current = false
      if (currentStroke.current.length > 1) strokes.current.push([...currentStroke.current])
      currentStroke.current = []
      if (strokes.current.length > 0) {
        if (debounceTimer.current) clearTimeout(debounceTimer.current)
        debounceTimer.current = window.setTimeout(() => runRecognition(), settings.debounceMs)
      }
    }
    canvas.addEventListener('pointerdown', onDown); canvas.addEventListener('pointermove', onMove)
    canvas.addEventListener('pointerup', onUp); canvas.addEventListener('pointercancel', onUp)
    return () => { canvas.removeEventListener('pointerdown', onDown); canvas.removeEventListener('pointermove', onMove)
      canvas.removeEventListener('pointerup', onUp); canvas.removeEventListener('pointercancel', onUp) }
  }, [canvasRef, settings, runRecognition, canvasVersion])

  if (!puzzle || !selectedCell || wordCells.length === 0) return null

  const springTransition = {
    layout: { type: 'spring' as const, visualDuration: 0.35, bounce: 0.12 },
    opacity: { duration: 0.2 },
  }

  return (
    <div style={{
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      gap: 2, padding: '1rem 0 0.5rem', position: 'relative',
      minHeight: BIG + 16,
    }}>
      <AnimatePresence mode="popLayout" initial={false}>
        {slots.map((slot) => {
          if (!slot.cell) {
            return (
              <div key={`empty-${slot.role}`} style={{ width: SMALL, height: SMALL, flexShrink: 0 }} />
            )
          }

          const cellKey = `${slot.cell[0]},${slot.cell[1]}`
          const isActive = slot.role === 'active'
          const isLeft = slot.role === 'prev'
          const isRight = slot.role === 'next'
          const size = isActive ? BIG : SMALL
          const letter = getCellLetter(slot.cell)
          const num = getCellNum(slot.cell)
          const maskDir = isLeft ? 'to right' : isRight ? 'to left' : undefined

          return (
            <motion.div
              key={cellKey}
              layout
              initial={{ opacity: 0, scale: 0.8 }}
              animate={{ opacity: 1, scale: 1 }}
              exit={{ opacity: 0, scale: 0.8 }}
              transition={springTransition}
              style={{
                width: size, height: size, borderRadius: 2,
                backgroundColor: 'var(--cell-white)',
                border: '1px solid var(--cell-border)',
                flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
                position: 'relative', overflow: 'hidden',
                zIndex: isActive ? 3 : 1,
                WebkitMaskImage: maskDir ? `linear-gradient(${maskDir}, transparent 0%, black 60%)` : undefined,
                maskImage: maskDir ? `linear-gradient(${maskDir}, transparent 0%, black 60%)` : undefined,
              }}
            >
              {num && (
                <span className="zoom-num-sm" style={{ position: 'absolute', top: 3, left: 4, fontSize: '0.5rem', zIndex: 5 }}>{num}</span>
              )}
              {!isActive && letter && (
                <span className="zoom-letter" style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '1.5rem' }}>{letter}</span>
              )}
              {isActive && showRec && (
                <motion.div initial={{ opacity: 0, scale: 0.7 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.2 }}
                  style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 3 }}>
                  <span className="zoom-letter" style={{ fontSize: '2.5rem' }}>{recLetter}</span>
                </motion.div>
              )}
              {isActive && retryMsg && (
                <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.15 }}
                  style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 4, background: 'var(--cell-white)' }}>
                  <span style={{ fontFamily: 'var(--font-display)', fontSize: '0.75rem', fontStyle: 'italic', color: 'var(--text-tertiary)' }}>{t('common.tryAgain')}</span>
                </motion.div>
              )}
            </motion.div>
          )
        })}
      </AnimatePresence>

      {/* canvas rendered OUTSIDE AnimatePresence so motion never touches it */}
      {activeCell && (
        <canvas
          ref={canvasRefCallback}
          style={{
            position: 'absolute',
            width: BIG, height: BIG,
            touchAction: 'none', cursor: 'crosshair', display: 'block',
            opacity: showRec || retryMsg ? 0 : 1,
            transition: 'opacity 200ms ease',
            zIndex: 10,
            pointerEvents: 'auto',
          }}
        />
      )}
    </div>
  )
}
