/* tdd-guard:skip — client-only pointer/canvas UI, verified via gstack-smoke + Vercel-Preview (KAR-344/662) */
'use client'

import * as React from 'react'
import { Undo2, Eraser, Pencil, Type } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
  type NoteCanvasData,
  type NotePoint,
  emptyCanvas,
  strokeToSvgPath,
  INK_COLORS,
  STROKE_SIZES,
  DEFAULT_INK,
  DEFAULT_SIZE,
  DEFAULT_TEXT_SIZE,
} from '@/lib/notes/canvas'
import { type NoteBackground } from '@/lib/notes/document'

interface NoteCanvasProps {
  value: NoteCanvasData
  onChange: (next: NoteCanvasData) => void
  disabled?: boolean
  /** Optional background image (photo annotation mode). */
  backgroundUrl?: string
  /** Fixed coordinate system (A4 px) → resolution-independent + exact PDF. */
  viewBox?: { width: number; height: number }
  /** Page background type + spacing (px) for grid/lines, behind the strokes. */
  background?: NoteBackground
  gridPx?: number
  /** Tailwind height class when no fixed viewBox is used (image annotation). */
  heightClass?: string
}

type Mode = 'draw' | 'text'
type TextDrag = { index: number; startX: number; startY: number; origX: number; origY: number; moved: boolean }

export function NoteCanvas({
  value,
  onChange,
  disabled = false,
  backgroundUrl,
  viewBox,
  background = 'blank',
  gridPx = 20,
  heightClass = 'h-[60vh] min-h-80',
}: NoteCanvasProps) {
  const svgRef = React.useRef<SVGSVGElement | null>(null)
  const patternId = React.useId()
  // Active stroke: the ref is the source of truth. Touch fires pointermove many
  // times per frame and React 19 batches updates, so a state-closure read drops
  // points and you only get a dot. `preview` mirrors the ref for live rendering.
  const currentRef = React.useRef<NotePoint[] | null>(null)
  const [preview, setPreview] = React.useState<NotePoint[] | null>(null)
  const [color, setColor] = React.useState<string>(DEFAULT_INK)
  const [size, setSize] = React.useState<number>(DEFAULT_SIZE)
  const [mode, setMode] = React.useState<Mode>('draw')
  const [editingIndex, setEditingIndex] = React.useState<number | null>(null)
  const [drag, setDrag] = React.useState<TextDrag | null>(null)

  const texts = value.texts ?? []
  const canText = Boolean(viewBox) // text editing only on fixed A4 pages

  // iOS Safari honours `touch-action: none` on SVG unreliably; a non-passive
  // touchmove preventDefault guarantees the drag stays a draw gesture (no scroll).
  React.useEffect(() => {
    const svg = svgRef.current
    if (!svg) return
    const prevent = (e: TouchEvent) => {
      if (!disabled) e.preventDefault()
    }
    svg.addEventListener('touchmove', prevent, { passive: false })
    return () => svg.removeEventListener('touchmove', prevent)
  }, [disabled])

  function pointFromEvent(e: React.PointerEvent<SVGSVGElement | SVGTextElement>): NotePoint {
    const rect = svgRef.current?.getBoundingClientRect()
    let x = e.clientX - (rect?.left ?? 0)
    let y = e.clientY - (rect?.top ?? 0)
    if (viewBox && rect && rect.width > 0 && rect.height > 0) {
      x = x * (viewBox.width / rect.width)
      y = y * (viewBox.height / rect.height)
    }
    const pressure = e.pressure > 0 ? e.pressure : 0.5
    return [x, y, pressure]
  }

  function handlePointerDown(e: React.PointerEvent<SVGSVGElement>) {
    if (disabled) return
    if (mode === 'text' && canText) {
      const [x, y] = pointFromEvent(e)
      const next = [...texts, { x, y, text: '', color, size: DEFAULT_TEXT_SIZE }]
      onChange({ ...value, texts: next })
      setEditingIndex(next.length - 1)
      return
    }
    e.currentTarget.setPointerCapture(e.pointerId)
    const p = pointFromEvent(e)
    currentRef.current = [p]
    setPreview([p])
  }

  function startTextDrag(e: React.PointerEvent<SVGTextElement>, index: number) {
    if (disabled || mode !== 'text') return
    e.stopPropagation()
    const [x, y] = pointFromEvent(e)
    const t = texts[index]
    svgRef.current?.setPointerCapture(e.pointerId)
    setDrag({ index, startX: x, startY: y, origX: t.x, origY: t.y, moved: false })
  }

  function handlePointerMove(e: React.PointerEvent<SVGSVGElement>) {
    if (disabled) return
    if (drag) {
      const [x, y] = pointFromEvent(e)
      const moved = drag.moved || Math.abs(x - drag.startX) + Math.abs(y - drag.startY) > 4
      const nx = drag.origX + (x - drag.startX)
      const ny = drag.origY + (y - drag.startY)
      setDrag({ ...drag, moved })
      onChange({ ...value, texts: texts.map((t, i) => (i === drag.index ? { ...t, x: nx, y: ny } : t)) })
      return
    }
    if (mode === 'text' || currentRef.current === null) return
    currentRef.current = [...currentRef.current, pointFromEvent(e)]
    setPreview(currentRef.current)
  }

  function handlePointerUp() {
    if (drag) {
      if (!drag.moved) setEditingIndex(drag.index) // tap (no move) = edit
      setDrag(null)
      return
    }
    commitStroke()
  }

  function abortPointer() {
    if (drag) {
      setDrag(null)
      return
    }
    commitStroke()
  }

  function commitStroke() {
    const pts = currentRef.current
    if (pts === null) return
    if (pts.length > 0) {
      onChange({ ...value, strokes: [...value.strokes, { points: pts, color, size }] })
    }
    currentRef.current = null
    setPreview(null)
  }

  function updateText(index: number, text: string) {
    onChange({ ...value, texts: texts.map((t, i) => (i === index ? { ...t, text } : t)) })
  }

  function commitText() {
    if (editingIndex === null) return
    const t = texts[editingIndex]
    if (!t || t.text.trim().length === 0) {
      onChange({ ...value, texts: texts.filter((_, i) => i !== editingIndex) })
    }
    setEditingIndex(null)
  }

  function undo() {
    if (disabled || value.strokes.length === 0) return
    onChange({ ...value, strokes: value.strokes.slice(0, -1) })
  }

  function clear() {
    if (disabled || (value.strokes.length === 0 && texts.length === 0)) return
    onChange(emptyCanvas())
    setEditingIndex(null)
  }

  const showBg = Boolean(viewBox) && background !== 'blank'
  const bgWidth = viewBox?.width ?? 0
  const bgHeight = viewBox?.height ?? 0
  const baseSvgClass = `note-draw-surface rounded-md border border-border bg-card text-primary ${
    mode === 'text' ? 'cursor-text' : ''
  }`

  const svgElement = (
    <svg
      ref={svgRef}
      role="img"
      aria-label="Notiz-Zeichenfläche"
      style={{ touchAction: 'none' }}
      onPointerDown={handlePointerDown}
      onPointerMove={handlePointerMove}
      onPointerUp={handlePointerUp}
      onPointerLeave={abortPointer}
      onPointerCancel={abortPointer}
      {...(viewBox
        ? {
            viewBox: `0 0 ${viewBox.width} ${viewBox.height}`,
            preserveAspectRatio: 'none',
            className: `${baseSvgClass} absolute inset-0 h-full w-full`,
          }
        : { className: `${baseSvgClass} w-full ${heightClass}` })}
    >
      {showBg && (
        <>
          <defs>
            {background === 'grid' ? (
              <pattern id={patternId} width={gridPx} height={gridPx} patternUnits="userSpaceOnUse">
                <path
                  d={`M ${gridPx} 0 L 0 0 0 ${gridPx}`}
                  fill="none"
                  stroke="var(--border)"
                  strokeWidth={1}
                />
              </pattern>
            ) : (
              <pattern id={patternId} width={bgWidth} height={gridPx} patternUnits="userSpaceOnUse">
                <line x1={0} y1={gridPx} x2={bgWidth} y2={gridPx} stroke="var(--border)" strokeWidth={1} />
              </pattern>
            )}
          </defs>
          <rect width={bgWidth} height={bgHeight} fill={`url(#${patternId})`} />
        </>
      )}
      {backgroundUrl && (
        <image
          href={backgroundUrl}
          x="0"
          y="0"
          width="100%"
          height="100%"
          preserveAspectRatio="xMidYMid meet"
        />
      )}
      {value.strokes.map((stroke, i) => (
        <path
          key={i}
          d={strokeToSvgPath(stroke.points, stroke.size ? { size: stroke.size } : {})}
          fill={stroke.color ?? 'currentColor'}
        />
      ))}
      {preview && preview.length > 0 && (
        <path d={strokeToSvgPath(preview, { size })} fill={color} />
      )}
      {texts.map((t, i) =>
        i === editingIndex ? null : (
          <text
            key={i}
            x={t.x}
            y={t.y}
            fill={t.color ?? 'currentColor'}
            fontSize={t.size ?? DEFAULT_TEXT_SIZE}
            dominantBaseline="hanging"
            style={{ cursor: mode === 'text' ? 'move' : 'default', touchAction: 'none' }}
            onPointerDown={(e) => startTextDrag(e, i)}
          >
            {t.text}
          </text>
        ),
      )}
    </svg>
  )

  return (
    <div className="space-y-2">
      <div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-2">
        <div className="flex flex-wrap items-center gap-3">
          {canText && (
            <div className="flex items-center gap-1" role="group" aria-label="Werkzeug">
              <Button
                type="button"
                variant={mode === 'draw' ? 'secondary' : 'outline'}
                size="lg"
                onClick={() => {
                  setMode('draw')
                  setEditingIndex(null)
                }}
                disabled={disabled}
              >
                <Pencil size={16} />
                Stift
              </Button>
              <Button
                type="button"
                variant={mode === 'text' ? 'secondary' : 'outline'}
                size="lg"
                onClick={() => setMode('text')}
                disabled={disabled}
              >
                <Type size={16} />
                Text
              </Button>
            </div>
          )}
          <div className="flex items-center gap-1.5" role="group" aria-label="Farbe">
            {INK_COLORS.map((c) => (
              <button
                key={c.value}
                type="button"
                aria-label={`Farbe ${c.label}`}
                aria-pressed={color === c.value}
                disabled={disabled}
                onClick={() => setColor(c.value)}
                style={{ backgroundColor: c.value }}
                className={`h-9 w-9 rounded-full border disabled:opacity-50 ${
                  color === c.value
                    ? 'ring-2 ring-ring ring-offset-1 border-foreground'
                    : 'border-border'
                }`}
              />
            ))}
          </div>
          {mode === 'draw' && (
            <div className="flex items-center gap-1" role="group" aria-label="Strichstärke">
              {STROKE_SIZES.map((s) => (
                <button
                  key={s.value}
                  type="button"
                  aria-pressed={size === s.value}
                  disabled={disabled}
                  onClick={() => setSize(s.value)}
                  className={`h-10 px-3 rounded-sm border text-xs font-semibold disabled:opacity-50 ${
                    size === s.value
                      ? 'border-primary text-primary'
                      : 'border-border text-muted-foreground'
                  }`}
                >
                  {s.label}
                </button>
              ))}
            </div>
          )}
        </div>
        <div className="flex gap-2">
          {mode === 'draw' && (
            <Button
              type="button"
              variant="outline"
              size="lg"
              onClick={undo}
              disabled={disabled || value.strokes.length === 0}
            >
              <Undo2 size={16} />
              Zurück
            </Button>
          )}
          <Button
            type="button"
            variant="outline"
            size="lg"
            onClick={clear}
            disabled={disabled || (value.strokes.length === 0 && texts.length === 0)}
          >
            <Eraser size={16} />
            Leeren
          </Button>
        </div>
      </div>

      {mode === 'text' && canText && (
        <p className="text-xs text-muted-foreground">
          Tippe auf die Seite, um Text zu platzieren. Text antippen zum Bearbeiten, ziehen zum
          Verschieben; leer lassen löscht ihn.
        </p>
      )}

      {viewBox ? (
        <div className="mx-auto w-full" style={{ maxWidth: `${viewBox.width}px` }}>
          <div className="relative" style={{ aspectRatio: `${viewBox.width} / ${viewBox.height}` }}>
            {svgElement}
            {editingIndex !== null && texts[editingIndex] && (
              <input
                autoFocus
                value={texts[editingIndex].text}
                onChange={(e) => updateText(editingIndex, e.target.value)}
                onBlur={commitText}
                onKeyDown={(e) => {
                  if (e.key === 'Enter') {
                    e.preventDefault()
                    commitText()
                  }
                }}
                style={{
                  position: 'absolute',
                  left: `${(texts[editingIndex].x / viewBox.width) * 100}%`,
                  top: `${(texts[editingIndex].y / viewBox.height) * 100}%`,
                  color: texts[editingIndex].color ?? DEFAULT_INK,
                }}
                className="min-w-32 rounded-sm border border-primary bg-card px-1 text-sm outline-none"
                placeholder="Text…"
              />
            )}
          </div>
        </div>
      ) : (
        svgElement
      )}
    </div>
  )
}
