import { useEffect } from 'react'
import { useStore } from '../store'
import type { Direction } from '../types'
import i18n from '../i18n'
import * as narrator from '../narrator'

function readCurrentCell() {
  const t = i18n.t
  const { puzzle, selectedCell, entries, direction, wordSpans, activeClue } = useStore.getState()
  if (!puzzle || !selectedCell) {
    narrator.speak(t('a11y.noCellSelected'))
    return
  }
  const { row, col } = selectedCell
  const letter = entries[row]?.[col] || ''
  const num = puzzle.cell_numbers[`${row},${col}`]
  const span = wordSpans.find(s => s.number === activeClue && s.direction === direction)
  const pos = span ? span.cells.findIndex(([r, c]) => r === row && c === col) + 1 : 0
  const len = span?.cells.length || 0

  let msg = t('grid.cellLabel', { row: row + 1, col: col + 1 })
  if (num) msg += t('grid.cellNumber', { num })
  msg += letter ? t('grid.cellLetter', { letter }) : t('grid.cellEmpty')
  msg += `, ${t(direction === 'across' ? 'game.across' : 'game.down')}`
  if (pos && len) msg += t('a11y.position', { pos, len })
  narrator.speak(msg)
}

function readWord(dir: Direction) {
  const t = i18n.t
  const { puzzle, selectedCell, entries, wordSpans } = useStore.getState()
  if (!puzzle || !selectedCell) {
    narrator.speak(t('a11y.noCellSelected'))
    return
  }
  const span = wordSpans.find(s =>
    s.direction === dir && s.cells.some(([r, c]) => r === selectedCell.row && c === selectedCell.col)
  )
  if (!span) {
    narrator.speak(t(dir === 'across' ? 'a11y.noAcrossWord' : 'a11y.noDownWord'), false)
    return
  }
  const letters = span.cells.map(([r, c]) => entries[r]?.[c] || t('a11y.empty')).join(', ')
  const dirLabel = t(dir === 'across' ? 'game.across' : 'game.down')
  narrator.speak(t('a11y.wordReading', { dir: dirLabel, number: span.number, length: span.cells.length, letters }), false)
}

function readCurrentClue() {
  const t = i18n.t
  const { puzzle, activeClue, direction, wordSpans, entries, narratorSettings } = useStore.getState()
  if (!puzzle || activeClue === null) {
    narrator.speak(t('a11y.noClueSelected'))
    return
  }
  const clues = direction === 'across' ? puzzle.clues_across : puzzle.clues_down
  const clue = clues.find(c => c.number === activeClue)
  if (!clue) return

  const span = wordSpans.find(s => s.number === activeClue && s.direction === direction)
  const len = span?.cells.length || 0
  const filled = span ? span.cells.filter(([r, c]) => entries[r]?.[c]).length : 0
  const v = narratorSettings.verbosity
  const dirLabel = t(direction === 'across' ? 'game.across' : 'game.down')

  if (v === 'brief') {
    narrator.speak(t('a11y.clueBrief', { number: activeClue, dir: dirLabel, text: clue.text }))
  } else if (v === 'normal') {
    narrator.speak(t('a11y.clueNormal', { number: activeClue, dir: dirLabel, length: len, text: clue.text }))
  } else {
    narrator.speak(t('a11y.clueVerbose', { number: activeClue, dir: dirLabel, length: len, filled, text: clue.text }))
  }
}

function readPuzzleOverview() {
  const t = i18n.t
  const { puzzle, entries, wordSpans } = useStore.getState()
  if (!puzzle) {
    narrator.speak(t('a11y.noPuzzleLoaded'))
    return
  }
  let totalCells = 0
  let filledCells = 0
  for (let r = 0; r < puzzle.height; r++) {
    for (let c = 0; c < puzzle.width; c++) {
      if (puzzle.cells[r][c] !== 'black') {
        totalCells++
        if (entries[r]?.[c]) filledCells++
      }
    }
  }
  const totalClues = puzzle.clues_across.length + puzzle.clues_down.length
  const completedWords = wordSpans.filter(s =>
    s.cells.every(([r, c]) => entries[r]?.[c])
  ).length
  const remaining = totalClues - completedWords

  narrator.speak(
    t('a11y.overview', {
      width: puzzle.width, height: puzzle.height,
      filled: filledCells, total: totalCells, remaining,
    })
  )
}

function getGridEl(): HTMLElement | null {
  return document.querySelector('.grid-wrapper')
}

// tracks whether focus (or the last pointer interaction) is inside the grid,
// so typing after clicking a header button does not fill grid cells
let gridHasFocus = false

function updateGridFocus(target: EventTarget | null) {
  gridHasFocus = !!getGridEl()?.contains(document.activeElement) || (target instanceof Element && !!getGridEl()?.contains(target))
}

export function useKeyboard() {
  const puzzle = useStore((s) => s.puzzle)
  const typeLetter = useStore((s) => s.typeLetter)
  const deleteLetter = useStore((s) => s.deleteLetter)
  const moveSelection = useStore((s) => s.moveSelection)
  const toggleDirection = useStore((s) => s.toggleDirection)
  const tabClue = useStore((s) => s.tabClue)
  const narratorEnabled = useStore((s) => s.narratorEnabled)
  const undo = useStore((s) => s.undo)
  const redo = useStore((s) => s.redo)

  useEffect(() => {
    if (!puzzle) return

    gridHasFocus = !!getGridEl()?.contains(document.activeElement)

    const onPointerDown = (e: PointerEvent) => updateGridFocus(e.target)
    const onFocusIn = (e: FocusEvent) => updateGridFocus(e.target)
    document.addEventListener('pointerdown', onPointerDown, true)
    document.addEventListener('focusin', onFocusIn)

    // give the grid initial keyboard focus when nothing else is focused
    if (document.activeElement === document.body || document.activeElement === null) {
      const firstCell = getGridEl()?.querySelector('button.grid-sr-cell')
      if (firstCell instanceof HTMLButtonElement) firstCell.focus()
    }

    function onKeyDown(e: KeyboardEvent) {
      if (
        e.target instanceof HTMLInputElement ||
        e.target instanceof HTMLTextAreaElement ||
        e.target instanceof HTMLSelectElement
      )
        return

      // only handle letter input when the grid area has focus
      const gridFocused = gridHasFocus || !!getGridEl()?.contains(document.activeElement)

      const key = e.key
      const narr = useStore.getState().narratorEnabled

      // narrator-only shortcuts - only when grid is focused
      if (narr && gridFocused) {
        if (key === ' ') {
          e.preventDefault()
          readCurrentCell()
          return
        }
        if (key === '?' || key === '/' || (e.altKey && key.toLowerCase() === 'p')) {
          e.preventDefault()
          readCurrentClue()
          return
        }
        if ((e.ctrlKey && key.toLowerCase() === 'h') || key === 'F1') {
          e.preventDefault()
          readPuzzleOverview()
          return
        }
        if (e.altKey && key.toLowerCase() === 'a') {
          e.preventDefault()
          readWord('across')
          return
        }
        if (e.altKey && key.toLowerCase() === 'd') {
          e.preventDefault()
          readWord('down')
          return
        }
        if (key === 'Tab') {
          e.preventDefault()
          tabClue(!e.shiftKey)
          // clue announcement handled by useNarrator hook
          return
        }
      }

      if (gridFocused && e.ctrlKey && key.toLowerCase() === 'z' && !e.shiftKey) {
        e.preventDefault()
        undo()
        return
      }
      if (gridFocused && ((e.ctrlKey && key.toLowerCase() === 'y') || (e.ctrlKey && e.shiftKey && key.toLowerCase() === 'z'))) {
        e.preventDefault()
        redo()
        return
      }

      if (gridFocused && key.length === 1 && key >= 'a' && key <= 'z') {
        e.preventDefault()
        typeLetter(key)
        return
      }
      if (gridFocused && key.length === 1 && key >= 'A' && key <= 'Z') {
        e.preventDefault()
        typeLetter(key)
        return
      }

      if (!gridFocused) return

      switch (key) {
        case 'Backspace':
          e.preventDefault()
          deleteLetter()
          break
        case 'Escape':
          e.preventDefault()
          useStore.getState().deselectCell()
          break
        case 'ArrowUp':
          e.preventDefault()
          moveSelection(-1, 0)
          break
        case 'ArrowDown':
          e.preventDefault()
          moveSelection(1, 0)
          break
        case 'ArrowLeft':
          e.preventDefault()
          moveSelection(0, -1)
          break
        case 'ArrowRight':
          e.preventDefault()
          moveSelection(0, 1)
          break
        case ' ':
          e.preventDefault()
          toggleDirection()
          break
        case 'Tab':
          e.preventDefault()
          tabClue(!e.shiftKey)
          break
      }
    }

    document.addEventListener('keydown', onKeyDown)
    return () => {
      document.removeEventListener('keydown', onKeyDown)
      document.removeEventListener('pointerdown', onPointerDown, true)
      document.removeEventListener('focusin', onFocusIn)
    }
  }, [puzzle, typeLetter, deleteLetter, moveSelection, toggleDirection, tabClue, narratorEnabled, undo, redo])
}
