import { useEffect, useReducer, useRef, useState } from 'react';
import type { Annotation, AnnotationPoint, AnnotationTool } from './types';

interface AnnotationOverlayProps {
  view: 'axial' | 'coronal' | 'sagittal';
  /** Current slice index of this pane — annotations are tagged + filtered by it. */
  sliceIndex: number;
  tool: AnnotationTool;
  annotations: Annotation[];
  onCommit: (annotation: Annotation) => void;
  color?: string;
  /** Double-click (when not finishing a polygon) → expand/restore this pane. */
  onRequestExpand?: () => void;
  /** Forward wheel to the underlying viewport so slice-scroll / zoom keep working. */
  onForwardWheel?: (e: { deltaY: number; ctrlKey: boolean; metaKey: boolean }) => void;
}

const PEN_WIDTH = 2;
const BRUSH_WIDTH = 16;
const clamp01 = (v: number) => Math.max(0, Math.min(1, v));
const newId = () =>
  typeof crypto !== 'undefined' && crypto.randomUUID
    ? crypto.randomUUID()
    : `anno_${Math.round(performance.now())}_${Math.floor(Math.random() * 1e6)}`;

/**
 * Per-pane drawing surface for the ImageHub annotation tools. Sits on top of one
 * 2D viewport (absolute, inset-0). Pointer-transparent while the tool is "none"
 * (so VTK keeps slice-scroll / zoom / rotate); when a tool is active it captures
 * events with NATIVE listeners that `stopPropagation()`, so a draw drag never
 * reaches the VTK interactor bound on the parent canvas (which would otherwise
 * rotate / window-level the 2D views). Geometry is normalized [0..1] to the pane.
 */
export function AnnotationOverlay({
  view,
  sliceIndex,
  tool,
  annotations,
  onCommit,
  color = '#22d3ee',
  onRequestExpand,
  onForwardWheel,
}: AnnotationOverlayProps) {
  const ref = useRef<HTMLDivElement>(null);
  const [size, setSize] = useState({ w: 0, h: 0 });
  const draftRef = useRef<AnnotationPoint[] | null>(null);
  const drawingRef = useRef(false);
  const [, bump] = useReducer((x: number) => x + 1, 0);

  // Latest tool + callbacks in refs so the native listeners (added once per
  // view/slice) always see current values without re-binding mid-drag.
  const toolRef = useRef(tool);
  toolRef.current = tool;
  const cbRef = useRef({ onCommit, onRequestExpand, onForwardWheel, color });
  cbRef.current = { onCommit, onRequestExpand, onForwardWheel, color };

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const measure = () => {
      const r = el.getBoundingClientRect();
      setSize({ w: r.width, h: r.height });
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  // Cancel any in-progress draft when the tool changes or the slice scrolls away.
  useEffect(() => {
    draftRef.current = null;
    drawingRef.current = false;
    bump();
  }, [tool, sliceIndex, view]);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const toNorm = (e: PointerEvent): AnnotationPoint => {
      const r = el.getBoundingClientRect();
      return { x: clamp01((e.clientX - r.left) / r.width), y: clamp01((e.clientY - r.top) / r.height) };
    };
    const commit = (t: Exclude<AnnotationTool, 'none'>, points: AnnotationPoint[], strokeWidth?: number) =>
      cbRef.current.onCommit({ id: newId(), view, sliceIndex, tool: t, points, color: cbRef.current.color, strokeWidth });

    const onDown = (e: PointerEvent) => {
      const t = toolRef.current;
      if (t === 'none') return;
      e.stopPropagation();
      e.preventDefault();
      try {
        el.setPointerCapture(e.pointerId);
      } catch {
        /* best-effort */
      }
      const p = toNorm(e);
      if (t === 'points') {
        commit('points', [p]);
        return;
      }
      if (t === 'polygon') {
        draftRef.current = draftRef.current ? [...draftRef.current, p] : [p];
        bump();
        return;
      }
      drawingRef.current = true;
      draftRef.current = t === 'bbox' ? [p, p] : [p];
      bump();
    };
    const onMove = (e: PointerEvent) => {
      if (!drawingRef.current || !draftRef.current) return;
      e.stopPropagation();
      const t = toolRef.current;
      const p = toNorm(e);
      const d = draftRef.current;
      draftRef.current = t === 'bbox' ? [d[0], p] : [...d, p];
      bump();
    };
    const onUp = (e: PointerEvent) => {
      if (!drawingRef.current) return;
      e.stopPropagation();
      drawingRef.current = false;
      const t = toolRef.current;
      const d = draftRef.current;
      if (d) {
        if (t === 'bbox') {
          const [a, b] = d;
          if (Math.abs(a.x - b.x) > 0.005 && Math.abs(a.y - b.y) > 0.005) {
            commit('bbox', [
              { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y) },
              { x: Math.max(a.x, b.x), y: Math.max(a.y, b.y) },
            ]);
          }
        } else if (t === 'pen' && d.length > 1) commit('pen', d, PEN_WIDTH);
        else if (t === 'brush' && d.length > 1) commit('brush', d, BRUSH_WIDTH);
      }
      draftRef.current = null;
      bump();
    };
    const onDbl = (e: MouseEvent) => {
      const t = toolRef.current;
      if (t === 'none') return;
      e.stopPropagation();
      const d = draftRef.current;
      if (t === 'polygon' && d && d.length >= 3) {
        commit('polygon', d);
        draftRef.current = null;
        bump();
      } else {
        cbRef.current.onRequestExpand?.();
      }
    };
    const onWheel = (e: WheelEvent) => {
      if (toolRef.current === 'none') return;
      e.stopPropagation();
      cbRef.current.onForwardWheel?.({ deltaY: e.deltaY, ctrlKey: e.ctrlKey, metaKey: e.metaKey });
    };

    el.addEventListener('pointerdown', onDown);
    el.addEventListener('pointermove', onMove);
    el.addEventListener('pointerup', onUp);
    el.addEventListener('pointerleave', onUp);
    el.addEventListener('dblclick', onDbl);
    el.addEventListener('wheel', onWheel, { passive: false });
    return () => {
      el.removeEventListener('pointerdown', onDown);
      el.removeEventListener('pointermove', onMove);
      el.removeEventListener('pointerup', onUp);
      el.removeEventListener('pointerleave', onUp);
      el.removeEventListener('dblclick', onDbl);
      el.removeEventListener('wheel', onWheel);
    };
  }, [view, sliceIndex]);

  const { w, h } = size;
  const draft = draftRef.current;
  const px = (p: AnnotationPoint) => `${p.x * w},${p.y * h}`;
  const onThisSlice = annotations.filter((a) => a.view === view && a.sliceIndex === sliceIndex);

  const renderShape = (a: Annotation, key: string, live = false) => {
    const stroke = a.color || color;
    const dash = live ? '4 3' : undefined;
    if (a.tool === 'bbox' && a.points.length === 2) {
      const [tl, br] = a.points;
      return (
        <rect
          key={key}
          x={Math.min(tl.x, br.x) * w}
          y={Math.min(tl.y, br.y) * h}
          width={Math.abs(br.x - tl.x) * w}
          height={Math.abs(br.y - tl.y) * h}
          fill={`${stroke}22`}
          stroke={stroke}
          strokeWidth={2}
          strokeDasharray={dash}
        />
      );
    }
    if (a.tool === 'points') {
      return (
        <g key={key}>
          {a.points.map((p, i) => (
            <circle key={i} cx={p.x * w} cy={p.y * h} r={5} fill={stroke} stroke="#000" strokeWidth={1} />
          ))}
        </g>
      );
    }
    if (a.tool === 'pen' || a.tool === 'brush') {
      return (
        <polyline
          key={key}
          points={a.points.map(px).join(' ')}
          fill="none"
          stroke={stroke}
          strokeWidth={a.strokeWidth ?? PEN_WIDTH}
          strokeLinecap="round"
          strokeLinejoin="round"
          strokeDasharray={dash}
          opacity={a.tool === 'brush' ? 0.55 : 1}
        />
      );
    }
    if (a.tool === 'polygon') {
      const Tag = live ? 'polyline' : 'polygon';
      return (
        <Tag key={key} points={a.points.map(px).join(' ')} fill={live ? 'none' : `${stroke}22`} stroke={stroke} strokeWidth={2} strokeDasharray={dash} />
      );
    }
    return null;
  };

  const draftAnno: Annotation | null =
    draft && tool !== 'none' && tool !== 'points'
      ? { id: 'draft', view, sliceIndex, tool, points: draft, color, strokeWidth: tool === 'brush' ? BRUSH_WIDTH : PEN_WIDTH }
      : null;

  return (
    <div
      ref={ref}
      className="absolute inset-0"
      style={{
        pointerEvents: tool === 'none' ? 'none' : 'auto',
        cursor: tool === 'none' ? 'default' : 'crosshair',
        touchAction: 'none',
      }}
    >
      <svg width={w} height={h} className="absolute inset-0" style={{ pointerEvents: 'none' }}>
        {onThisSlice.map((a) => renderShape(a, a.id))}
        {draftAnno && renderShape(draftAnno, 'draft', true)}
      </svg>
    </div>
  );
}
