import { useRef, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore } from '../store'
import DisambiguationPopup from './DisambiguationPopup'

interface Props {
  svgRef: React.RefObject<SVGSVGElement | null>
}

// iPadOS Scribble prevention -
//
// Scribble intercepts Apple Pencil touches at the OS compositing level,
// BEFORE the web engine dispatches pointer/touch events. It probes the
// DOM looking for editable targets (input, textarea, contentEditable).
// When it claims a stroke, Safari never fires pointerdown at all - the
// event just vanishes.
//
// No single CSS property or JS handler can fully stop it because it
// operates above the event dispatch layer. The working defense, derived
// from reading tldraw and excalidraw source, Apple Developer Forums
// threads, and empirical testing, is a layered approach:
//
// Layer 1: CSS - touch-action:none, user-select:none, contain:strict
//          on the canvas and the wrapper. These prevent Safari's own
//          text-selection and gesture-recognition from firing.
//
// Layer 2: Event interception - preventDefault on touchstart/touchmove/
//          touchend with passive:false on the WRAPPER element (not just
//          the canvas). Scribble's probe touches arrive as touch events
//          first; canceling them at the wrapper level stops the
//          escalation to text recognition in many cases.
//
// Layer 3: Pointer capture - setPointerCapture on pointerdown with
//          pointerType==='pen'. Once captured, the element owns all
//          subsequent events for that pointer, preventing the OS from
//          re-routing them to Scribble.
//
// Layer 4: Selection cleanup - a pointerdown handler on document that
//          clears any window.getSelection() when a pen stroke starts
//          inside the grid, and a selectstart handler that cancels
//          selection originating from pen input.
//
// Layer 5: Scribble-trap textarea - a hidden, zero-opacity textarea
//          positioned behind the canvas. Scribble preferentially targets
//          editable elements; this gives it something to latch onto
//          instead of probing the broader DOM. The textarea is readonly
//          and intercepts any input Scribble tries to inject.

export default function InkOverlay({ svgRef }: Props) {
  const { t } = useTranslation()
  const gridCanvasRef = useRef<HTMLCanvasElement>(null)
  const scribbleTrapRef = useRef<HTMLTextAreaElement>(null)
  const puzzle = useStore((s) => s.puzzle)
  const inputMode = useStore((s) => s.inputMode)
  const selectCell = useStore((s) => s.selectCell)

  const lastTapTime = useRef(0)
  const lastTapCell = useRef('')

  // sync grid canvas size to SVG
  const syncSize = useCallback(() => {
    const svg = svgRef.current
    const canvas = gridCanvasRef.current
    if (!svg || !canvas) return
    const rect = svg.getBoundingClientRect()
    const dpr = devicePixelRatio || 1
    canvas.width = rect.width * dpr
    canvas.height = rect.height * dpr
    canvas.style.width = rect.width + 'px'
    canvas.style.height = rect.height + 'px'
  }, [svgRef])

  useEffect(() => {
    syncSize()
    const svg = svgRef.current
    if (!svg) return
    const observer = new ResizeObserver(syncSize)
    observer.observe(svg)
    window.addEventListener('resize', syncSize)
    return () => { observer.disconnect(); window.removeEventListener('resize', syncSize) }
  }, [svgRef, syncSize, puzzle])

  // Layer 2+3+4: event interception on the wrapper AND canvas
  useEffect(() => {
    const canvas = gridCanvasRef.current
    if (!canvas || !puzzle) return
    if (inputMode === 'keyboard') return

    // get the grid-wrapper parent for wrapper-level interception
    const wrapper = canvas.closest<HTMLElement>('.grid-wrapper')

    function onGridPointerDown(e: PointerEvent) {
      // keyboard mode disables the overlay entirely; auto and stylus both
      // accept pen, touch, and mouse input on the canvas
      if (e.pointerType === 'mouse' && inputMode === 'keyboard') return
      e.preventDefault()
      e.stopPropagation()

      // Layer 3: capture the pointer so the OS can't re-route it for pen;
      // mouse capture is harmless and keeps click handling on this element
      try { canvas!.setPointerCapture(e.pointerId) } catch { /* ignored */ }

      // Layer 4: kill any text selection Scribble may have started
      try { window.getSelection()?.removeAllRanges() } catch { /* ignored */ }

      const rect = canvas!.getBoundingClientRect()
      const cellW = rect.width / puzzle!.width
      const cellH = rect.height / puzzle!.height
      const c = Math.floor((e.clientX - rect.left) / cellW)
      const r = Math.floor((e.clientY - rect.top) / cellH)

      if (r < 0 || r >= puzzle!.height || c < 0 || c >= puzzle!.width) return
      if (puzzle!.cells[r][c] === 'black') return

      // double-tap to delete
      const now = Date.now()
      const cellKey = `${r},${c}`
      if (now - lastTapTime.current < 300 && lastTapCell.current === cellKey) {
        useStore.getState().deleteLetter()
        lastTapTime.current = 0
        return
      }
      lastTapTime.current = now
      lastTapCell.current = cellKey

      selectCell(r, c)
    }

    // prevent default on pointer move/up too - Scribble can start
    // recognition mid-stroke if it gets a move event it can interpret
    function onPenPointerMove(e: PointerEvent) {
      if (e.pointerType !== 'pen') return
      e.preventDefault()
    }

    function onPenPointerUp(e: PointerEvent) {
      if (e.pointerType !== 'pen') return
      e.preventDefault()
    }

    // Layer 2: prevent touch events at the wrapper level.
    // iPadOS Scribble sends touch events before pointer events;
    // canceling them here stops the text-recognition probe.
    function preventTouch(e: TouchEvent) {
      e.preventDefault()
    }

    // Layer 4: cancel text selection from pen input
    function preventSelect(e: Event) {
      e.preventDefault()
    }

    // attach to canvas
    canvas.addEventListener('pointerdown', onGridPointerDown)
    canvas.addEventListener('pointermove', onPenPointerMove)
    canvas.addEventListener('pointerup', onPenPointerUp)
    canvas.addEventListener('touchstart', preventTouch, { passive: false })
    canvas.addEventListener('touchmove', preventTouch, { passive: false })
    canvas.addEventListener('touchend', preventTouch, { passive: false })
    canvas.addEventListener('selectstart', preventSelect)

    // Layer 4: document-level pen selection cleanup
    function onDocPointerDown(e: PointerEvent) {
      if (e.pointerType !== 'pen') return
      const target = e.target
      if (target instanceof Node && !(canvas !== null && canvas.contains(target)) && !(wrapper !== null && wrapper.contains(target))) return
      try { window.getSelection()?.removeAllRanges() } catch { /* ignored */ }
    }
    document.addEventListener('pointerdown', onDocPointerDown, true)

    return () => {
      canvas.removeEventListener('pointerdown', onGridPointerDown)
      canvas.removeEventListener('pointermove', onPenPointerMove)
      canvas.removeEventListener('pointerup', onPenPointerUp)
      canvas.removeEventListener('touchstart', preventTouch)
      canvas.removeEventListener('touchmove', preventTouch)
      canvas.removeEventListener('touchend', preventTouch)
      canvas.removeEventListener('selectstart', preventSelect)
      document.removeEventListener('pointerdown', onDocPointerDown, true)
    }
  }, [gridCanvasRef, puzzle, inputMode, selectCell])

  // Layer 5: keep the scribble trap textarea empty and unfocusable
  useEffect(() => {
    const trap = scribbleTrapRef.current
    if (!trap) return
    function onInput() { trap!.value = '' }
    function onFocus() { trap!.blur() }
    trap.addEventListener('input', onInput)
    trap.addEventListener('focus', onFocus)
    return () => {
      trap.removeEventListener('input', onInput)
      trap.removeEventListener('focus', onFocus)
    }
  }, [])

  if (!puzzle) return null

  const pointerEvents = inputMode === 'keyboard' ? 'none' : 'auto'

  return (
    <>
      {/* Layer 5: scribble trap - gives iPadOS Scribble a target to latch
          onto instead of probing the rest of the DOM. Positioned behind
          the canvas, readonly, zero-opacity, with attributes that tell
          the browser this is not a real input target. */}
      {inputMode === 'stylus' && (
        <textarea
          ref={scribbleTrapRef}
          className="scribble-trap"
          aria-hidden="true"
          tabIndex={-1}
          readOnly
          autoComplete="off"
          autoCorrect="off"
          spellCheck={false}
          inputMode="none"
        />
      )}
      <canvas
        ref={gridCanvasRef}
        className="ink-overlay"
        aria-label={t('grid.handwritingCanvas')}
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          touchAction: 'none',
          pointerEvents,
          contain: 'strict',
        }}
        tabIndex={-1}
      />
      <DisambiguationPopup svgRef={svgRef} />
    </>
  )
}
