/* ReviewCanvas: viewer met optionele SVG-correctiemodus */

/* Rotatie is VIEW-ONLY: opgeslagen quads blijven in originele beeldoriëntatie. Punten zijn
   genormaliseerd [0..1]. roteerPunt = origineel→weergave, ontroteerPunt = weergave→origineel. */
function roteerPunt([x, y], rot) {
  if (rot === 90)  return [1 - y, x];
  if (rot === 180) return [1 - x, 1 - y];
  if (rot === 270) return [y, 1 - x];
  return [x, y];
}
function ontroteerPunt(p, rot) {
  return roteerPunt(p, (360 - rot) % 360);
}

/* Douglas-Peucker op een genormaliseerde, open ring. tolNorm = tol_m / breedte_m,
   spiegelt _simplificeer_ring in de pipeline zodat slider ≈ pipeline-default geeft. */
function _perpDist(p, a, b) {
  const dx = b[0] - a[0], dy = b[1] - a[1];
  const len2 = dx * dx + dy * dy;
  if (len2 === 0) { const ex = p[0] - a[0], ey = p[1] - a[1]; return Math.sqrt(ex * ex + ey * ey); }
  let t = ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2;
  t = Math.max(0, Math.min(1, t));
  const px = a[0] + t * dx, py = a[1] + t * dy;
  const ex = p[0] - px, ey = p[1] - py;
  return Math.sqrt(ex * ex + ey * ey);
}
function _dpRange(pts, first, last, tol, keep) {
  let maxD = 0, idx = -1;
  for (let i = first + 1; i < last; i++) {
    const d = _perpDist(pts[i], pts[first], pts[last]);
    if (d > maxD) { maxD = d; idx = i; }
  }
  if (idx !== -1 && maxD > tol) {
    _dpRange(pts, first, idx, tol, keep);
    keep[idx] = true;
    _dpRange(pts, idx, last, tol, keep);
  }
}
function simplifyRing(ring, tolNorm) {
  if (!Array.isArray(ring) || ring.length <= 3 || tolNorm <= 0) return ring;
  const keep = new Array(ring.length).fill(false);
  keep[0] = true; keep[ring.length - 1] = true;
  _dpRange(ring, 0, ring.length - 1, tolNorm, keep);
  const out = ring.filter((_, i) => keep[i]);
  return out.length >= 3 ? out : ring;
}

/* ── Teken-hulp: afstand, projectie, ortho-constraint, regularisatie ── */
const _round5 = v => Math.round(v * 1e5) / 1e5;
function _afstand(a, b) { return Math.hypot(a[0] - b[0], a[1] - b[1]); }
function _projecteerOpSegment(p, a, b) {
  const dx = b[0] - a[0], dy = b[1] - a[1];
  const len2 = dx * dx + dy * dy;
  if (len2 === 0) return [a[0], a[1]];
  let t = ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2;
  t = Math.max(0, Math.min(1, t));
  return [a[0] + t * dx, a[1] + t * dy];
}
// Dwing een punt op de dichtstbijzijnde as 0/45/90° t.o.v. een referentiepunt.
function _ortho(p, ref) {
  const dx = p[0] - ref[0], dy = p[1] - ref[1];
  const len = Math.hypot(dx, dy);
  if (len < 1e-9) return p;
  const stap = Math.PI / 4;
  const hoek = Math.round(Math.atan2(dy, dx) / stap) * stap;
  return [ref[0] + Math.cos(hoek) * len, ref[1] + Math.sin(hoek) * len];
}
function _lijnSnijpunt(L1, L2) {
  const [x1, y1] = L1.p, [dx1, dy1] = L1.d, [x2, y2] = L2.p, [dx2, dy2] = L2.d;
  const den = dx1 * dy2 - dy1 * dx2;
  if (Math.abs(den) < 1e-9) return null;
  const t = ((x2 - x1) * dy2 - (y2 - y1) * dx2) / den;
  return [x1 + dx1 * t, y1 + dy1 * t];
}
// Orthogonaliseer een ring: elke rand naar haakse richting t.o.v. de langste rand,
// nieuwe hoekpunten = snijpunten van opeenvolgende rand-lijnen. Faalt → origineel.
function orthogonalizeRing(ring) {
  if (!Array.isArray(ring) || ring.length < 4) return ring;
  const n = ring.length;
  let theta = 0, maxLen = -1;
  for (let i = 0; i < n; i++) {
    const a = ring[i], b = ring[(i + 1) % n];
    const l = Math.hypot(b[0] - a[0], b[1] - a[1]);
    if (l > maxLen) { maxLen = l; theta = Math.atan2(b[1] - a[1], b[0] - a[0]); }
  }
  const lijnen = [];
  for (let i = 0; i < n; i++) {
    const a = ring[i], b = ring[(i + 1) % n];
    let rel = Math.atan2(b[1] - a[1], b[0] - a[0]) - theta;
    rel = Math.round(rel / (Math.PI / 2)) * (Math.PI / 2);
    const sa = theta + rel;
    lijnen.push({ p: [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2], d: [Math.cos(sa), Math.sin(sa)] });
  }
  const out = [];
  for (let i = 0; i < n; i++) {
    const ip = _lijnSnijpunt(lijnen[(i + n - 1) % n], lijnen[i]);
    if (!ip) return ring;
    out.push([_round5(ip[0]), _round5(ip[1])]);
  }
  return out;
}

function ReviewCanvas({ resultaatId, fotoUrl, detectieType = null, editable = true, onSave }) {
  const [clusters, setClusters] = useState([]);
  const [drag, setDrag] = useState(null);
  const [tekenModus, setTekenModus] = useState(null);
  const [natuurlijkeMaat, setNatuurlijkeMaat] = useState(null);
  const [stageMaat, setStageMaat] = useState(null);
  const [zoom, setZoom] = useState({ scale: 1, x: 0, y: 0 });
  const [pan, setPan] = useState(null);
  const [rotatie, setRotatie] = useState(0);
  const [simplifyTol, setSimplifyTol] = useState(0.4);
  // AHN-achtergrondtoggle (alleen contour): schone luchtfoto <-> AHN-hoogtekaart.
  const [ahnAan, setAhnAan] = useState(false);
  const [ahnBeschikbaar, setAhnBeschikbaar] = useState(false);
  // Detectie-overlay-toggle (alleen contour): toon het beeld MÉT gevonden contouren + nummers.
  const [contourAan, setContourAan] = useState(false);
  // Welk cluster onder de muis hangt (vanuit vlak óf lijstregel) → highlight beide kanten op.
  const [hoverId, setHoverId] = useState(null);
  // Snap-guides (BAG geel / perceel cyaan, genormaliseerd) + tekenhulp-state.
  const [bagRingen, setBagRingen] = useState([]);
  const [perceelRingen, setPerceelRingen] = useState([]);
  const [snapAan, setSnapAan] = useState(true);
  const [lijnenAan, setLijnenAan] = useState(true);   // BAG/perceel-guides tonen (snap werkt ook zonder)
  const [snapHint, setSnapHint] = useState(null);     // {punt} → snap gevonden (magenta)
  const [cursorPunt, setCursorPunt] = useState(null); // huidige (gesnapte) cursorpositie tijdens tekenen
  const [meet, setMeet] = useState(null);             // {m, ang} live lengte/hoek
  const [geselPunt, setGeselPunt] = useState(null);   // {clusterIdx, puntIdx} voor pijltjes-nudge
  const wrapRef = useRef(null);
  const svgRef = useRef(null);
  const rijRefs = useRef({});

  // Globale default-tolerantie ophalen als startwaarde van de slider.
  useEffect(() => {
    fetch('/api/config/contour')
      .then(r => r.json())
      .then(d => { if (typeof d.contour_simplificatie_m === 'number') setSimplifyTol(d.contour_simplificatie_m); })
      .catch(() => {});
  }, []);

  // Per resultaat de AHN-toggle resetten en checken of de dsm_clean-variant bestaat.
  useEffect(() => {
    setAhnAan(false);
    setAhnBeschikbaar(false);
    setContourAan(false);
    if (!resultaatId || detectieType !== 'contour') return;
    fetch(`${API}/afbeelding/${resultaatId}?variant=dsm_clean`, { method: 'HEAD' })
      .then(resp => setAhnBeschikbaar(resp.ok))
      .catch(() => setAhnBeschikbaar(false));
  }, [resultaatId, detectieType]);

  // Achtergrond-matrix (alleen contour):
  //   contourAan → beeld MÉT gevonden contouren + nummers ('default', of 'dsm' bij AHN)
  //   anders     → AHN-blend zonder nummers ('dsm_clean') bij AHN-toggle, anders de schone foto
  let bgUrl = fotoUrl;
  if (detectieType === 'contour') {
    if (contourAan) {
      bgUrl = `${API}/afbeelding/${resultaatId}?variant=${ahnAan && ahnBeschikbaar ? 'dsm' : 'default'}`;
    } else if (ahnAan && ahnBeschikbaar) {
      bgUrl = `${API}/afbeelding/${resultaatId}?variant=dsm_clean`;
    }
  }

  useEffect(() => {
    if (!resultaatId) return;
    setTekenModus(null);
    setDrag(null);
    setZoom({ scale: 1, x: 0, y: 0 });
    setPan(null);
    setRotatie(0);
    setSnapHint(null); setMeet(null); setGeselPunt(null);
    fetch(`/api/review/cluster?resultaat_id=${resultaatId}`)
      .then(r => r.json())
      .then(d => {
        setClusters(Array.isArray(d.clusters) ? d.clusters : []);
        setBagRingen(Array.isArray(d.bag_ringen) ? d.bag_ringen : []);
        setPerceelRingen(Array.isArray(d.perceel_ringen) ? d.perceel_ringen : []);
      })
      .catch(() => { setClusters([]); setBagRingen([]); setPerceelRingen([]); });
  }, [resultaatId]);

  useEffect(() => {
    if (!editable) {
      setTekenModus(null);
      setDrag(null);
    }
  }, [editable]);

  // Cursor-markering wissen zodra je niet meer tekent.
  useEffect(() => { if (!tekenModus) setCursorPunt(null); }, [tekenModus]);

  // Toetsenbord: Backspace = laatste tekenpunt terug; pijltjes = geselecteerd hoekpunt nudgen.
  useEffect(() => {
    if (!editable) return;
    const onKey = (e) => {
      if (e.key === 'Backspace' && tekenModus && tekenModus.punten.length) {
        e.preventDefault();
        setTekenModus({ ...tekenModus, punten: tekenModus.punten.slice(0, -1) });
      } else if (geselPunt && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
        e.preventDefault();
        const stap = pxNaarNorm(1) || 0.001;
        const dx = e.key === 'ArrowLeft' ? -stap : e.key === 'ArrowRight' ? stap : 0;
        const dy = e.key === 'ArrowUp' ? -stap : e.key === 'ArrowDown' ? stap : 0;
        setClusters(prev => prev.map((c, ci) => ci === geselPunt.clusterIdx
          ? schrijfPunten(c, puntenVan(c).map((p, pi) => pi === geselPunt.puntIdx ? [p[0] + dx, p[1] + dy] : p))
          : c));
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [editable, tekenModus, geselPunt, stageMaat, zoom]);

  useEffect(() => {
    if (!natuurlijkeMaat || !wrapRef.current) return;

    function berekenStageMaat() {
      const el = wrapRef.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const style = window.getComputedStyle(el);
      const padX = parseFloat(style.paddingLeft || 0) + parseFloat(style.paddingRight || 0);
      const padY = parseFloat(style.paddingTop || 0) + parseFloat(style.paddingBottom || 0);
      const maxW = Math.max(1, rect.width - padX);
      const maxH = Math.max(1, rect.height - padY);
      // Bij 90°/270° draait het beeld → fit op de geswapte (liggende) maat.
      const gedraaid = rotatie === 90 || rotatie === 270;
      const effW = gedraaid ? natuurlijkeMaat.height : natuurlijkeMaat.width;
      const effH = gedraaid ? natuurlijkeMaat.width : natuurlijkeMaat.height;
      const schaal = Math.min(maxW / effW, maxH / effH);
      setStageMaat({
        width: Math.max(1, Math.floor(effW * schaal)),
        height: Math.max(1, Math.floor(effH * schaal)),
      });
    }

    berekenStageMaat();
    if (window.ResizeObserver) {
      const ro = new ResizeObserver(berekenStageMaat);
      ro.observe(wrapRef.current);
      return () => ro.disconnect();
    }
    window.addEventListener('resize', berekenStageMaat);
    return () => window.removeEventListener('resize', berekenStageMaat);
  }, [natuurlijkeMaat, editable, rotatie]);

  const naarSvgCoords = (e) => {
    const rect = svgRef.current.getBoundingClientRect();
    const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
    const y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height));
    // svg toont de geroteerde weergave → invoer terug naar originele beeldcoördinaten.
    return ontroteerPunt([x, y], rotatie);
  };

  const clampZoom = (next) => {
    if (!stageMaat || !wrapRef.current) return next;
    const rect = wrapRef.current.getBoundingClientRect();
    const style = window.getComputedStyle(wrapRef.current);
    const padX = parseFloat(style.paddingLeft || 0) + parseFloat(style.paddingRight || 0);
    const padY = parseFloat(style.paddingTop || 0) + parseFloat(style.paddingBottom || 0);
    const padLeft = parseFloat(style.paddingLeft || 0);
    const padTop = parseFloat(style.paddingTop || 0);
    const maxW = Math.max(1, rect.width - padX);
    const maxH = Math.max(1, rect.height - padY);
    const scaledW = stageMaat.width * next.scale;
    const scaledH = stageMaat.height * next.scale;
    const baseX = padLeft + Math.max(0, (maxW - stageMaat.width) / 2);
    const baseY = padTop + Math.max(0, (maxH - stageMaat.height) / 2);
    const minX = scaledW > maxW ? padLeft + maxW - scaledW - baseX : 0;
    const maxX = scaledW > maxW ? padLeft - baseX : 0;
    const minY = scaledH > maxH ? padTop + maxH - scaledH - baseY : 0;
    const maxY = scaledH > maxH ? padTop - baseY : 0;
    return {
      scale: next.scale,
      x: Math.max(minX, Math.min(maxX, next.x)),
      y: Math.max(minY, Math.min(maxY, next.y)),
    };
  };

  const zoomNaar = (targetScale, clientX, clientY) => {
    if (!stageMaat || !wrapRef.current) return;
    const wrapRect = wrapRef.current.getBoundingClientRect();
    const style = window.getComputedStyle(wrapRef.current);
    const padX = parseFloat(style.paddingLeft || 0) + parseFloat(style.paddingRight || 0);
    const padY = parseFloat(style.paddingTop || 0) + parseFloat(style.paddingBottom || 0);
    const padLeft = parseFloat(style.paddingLeft || 0);
    const padTop = parseFloat(style.paddingTop || 0);
    const maxW = Math.max(1, wrapRect.width - padX);
    const maxH = Math.max(1, wrapRect.height - padY);
    const baseX = padLeft + Math.max(0, (maxW - stageMaat.width) / 2);
    const baseY = padTop + Math.max(0, (maxH - stageMaat.height) / 2);
    const relX = clientX - wrapRect.left;
    const relY = clientY - wrapRect.top;
    setZoom(prev => {
      const scale = Math.max(1, Math.min(5, targetScale));
      const imageX = (relX - baseX - prev.x) / prev.scale;
      const imageY = (relY - baseY - prev.y) / prev.scale;
      return clampZoom({
        scale,
        x: relX - baseX - imageX * scale,
        y: relY - baseY - imageY * scale,
      });
    });
  };

  const zoomStap = (factor) => {
    if (!wrapRef.current) return;
    const rect = wrapRef.current.getBoundingClientRect();
    zoomNaar(zoom.scale * factor, rect.left + rect.width / 2, rect.top + rect.height / 2);
  };

  const resetZoom = () => {
    setZoom({ scale: 1, x: 0, y: 0 });
    setPan(null);
  };

  const onStageDoubleClick = (e) => {
    e.preventDefault();
    zoomNaar(zoom.scale >= 2.8 ? 1 : Math.max(2, zoom.scale * 1.7), e.clientX, e.clientY);
  };

  const onPanStart = (e) => {
    if (zoom.scale <= 1 || tekenModus) return;
    e.preventDefault();
    setPan({ startX: e.clientX, startY: e.clientY, zoomX: zoom.x, zoomY: zoom.y });
  };

  const onPanMove = (e) => {
    if (!pan) return;
    setZoom(prev => clampZoom({
      scale: prev.scale,
      x: pan.zoomX + e.clientX - pan.startX,
      y: pan.zoomY + e.clientY - pan.startY,
    }));
  };

  const onPanEnd = () => setPan(null);

  // Een cluster bewerkt zijn `contour` (N punten) als die bestaat, anders de `quad`.
  const isContour = (c) => Array.isArray(c.contour);
  const puntenVan = (c) => (isContour(c) ? c.contour : c.quad);
  const schrijfPunten = (c, punten) => (isContour(c) ? { ...c, contour: punten } : { ...c, quad: punten });

  const verwijderPunt = (ci, pi) => setClusters(prev => prev.map((c, idx) => {
    if (idx !== ci || !isContour(c) || c.contour.length <= 3) return c;
    return { ...c, contour: c.contour.filter((_, k) => k !== pi) };
  }));

  const voegPuntToe = (ci, pi) => setClusters(prev => prev.map((c, idx) => {
    if (idx !== ci || !isContour(c)) return c;
    const pts = c.contour;
    const a = pts[pi], b = pts[(pi + 1) % pts.length];
    const mid = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
    return { ...c, contour: [...pts.slice(0, pi + 1), mid, ...pts.slice(pi + 1)] };
  }));

  // Slider: her-simplificeer elke contour-cluster vanuit de ruwe bron-ring.
  const herSimplify = (tolM) => {
    setSimplifyTol(tolM);
    setClusters(prev => prev.map(c => {
      if (!Array.isArray(c.contour_bron) || !Array.isArray(c.foto_bbox)) return c;
      const breedteM = c.foto_bbox[2] - c.foto_bbox[0];
      if (!(breedteM > 0)) return c;
      return { ...c, contour: simplifyRing(c.contour_bron, tolM / breedteM) };
    }));
  };

  const pxNaarNorm = (px) => {
    const w = stageMaat ? stageMaat.width * zoom.scale : 800;
    return w > 0 ? px / w : 0.0125;
  };
  const resultFotoBbox = () => (clusters[0] && Array.isArray(clusters[0].foto_bbox) ? clusters[0].foto_bbox : null);

  // Snap een punt aan dichtstbijzijnde hoekpunt/segment van BAG, perceel, andere
  // componenten en de lopende tekening. Vertex-snap wint van segment-snap.
  const snapPunt = (p, excludeCluster) => {
    const thr = pxNaarNorm(14);
    let bestV = null, bestVd = thr, bestVsoort = null;
    let bestS = null, bestSd = thr, bestSeg = null, bestSsoort = null;
    const ringen = [];
    bagRingen.forEach(r => ringen.push({ pts: r, closed: true, soort: 'BAG' }));
    perceelRingen.forEach(r => ringen.push({ pts: r, closed: true, soort: 'perceel' }));
    clusters.forEach((c, ci) => {
      if (ci === excludeCluster) return;
      const pts = puntenVan(c);
      if (Array.isArray(pts) && pts.length) ringen.push({ pts, closed: true, soort: 'vlak' });
    });
    if (tekenModus && tekenModus.punten.length) ringen.push({ pts: tekenModus.punten, closed: false, soort: 'tekening' });
    for (const { pts, closed, soort } of ringen) {
      for (let i = 0; i < pts.length; i++) {
        const d = _afstand(p, pts[i]);
        if (d < bestVd) { bestVd = d; bestV = pts[i]; bestVsoort = soort; }
      }
      const n = closed ? pts.length : pts.length - 1;
      for (let i = 0; i < n; i++) {
        const a = pts[i], b = pts[(i + 1) % pts.length];
        const proj = _projecteerOpSegment(p, a, b);
        const d = _afstand(p, proj);
        if (d < bestSd) { bestSd = d; bestS = proj; bestSeg = [a, b]; bestSsoort = soort; }
      }
    }
    if (bestV) return { punt: bestV, type: 'vertex', soort: bestVsoort };
    if (bestS) return { punt: bestS, type: 'edge', seg: bestSeg, soort: bestSsoort };
    return null;
  };

  // Verwerk een ruw ingevoerd punt: snap (tenzij Alt/uit) → ortho (Ctrl) → meet-label.
  const bewerkPunt = (raw, e, ref, excludeCluster) => {
    let p = raw, hint = null;
    if (snapAan && !(e && e.altKey)) {
      const s = snapPunt(p, excludeCluster);
      if (s) { p = s.punt; hint = s; }
    }
    if (e && e.ctrlKey && ref && !(hint && hint.type === 'vertex')) p = _ortho(p, ref);
    setSnapHint(hint);
    if (ref) {
      const fb = resultFotoBbox();
      const m = fb ? _afstand(ref, p) * (fb[2] - fb[0]) : null;
      const ang = (Math.atan2(-(p[1] - ref[1]), p[0] - ref[0]) * 180 / Math.PI + 360) % 360;
      setMeet({ m, ang });
    }
    return p;
  };

  const onMouseMove = (e) => {
    if (pan) { onPanMove(e); return; }
    if (editable && tekenModus && !drag) {
      // Live preview van snap/ortho terwijl je tekent: cursor-markering volgt de muis en
      // wordt magenta zodra-ie aan een lijn/punt snapt.
      const raw = naarSvgCoords(e);
      const ref = tekenModus.punten.length ? tekenModus.punten[tekenModus.punten.length - 1] : null;
      setCursorPunt(bewerkPunt(raw, e, ref, null));
      return;
    }
    if (!editable || !drag) return;
    const raw = naarSvgCoords(e);
    const pts = puntenVan(clusters[drag.clusterIdx]) || [];
    const ref = pts.length ? pts[(drag.puntIdx + pts.length - 1) % pts.length] : null;
    const p = bewerkPunt(raw, e, ref, drag.clusterIdx);
    setClusters(prev => prev.map((c, ci) =>
      ci === drag.clusterIdx
        ? schrijfPunten(c, puntenVan(c).map((pp, pi) => pi === drag.puntIdx ? p : pp))
        : c
    ));
  };

  const onMouseUp = () => {
    setDrag(null);
    onPanEnd();
    setSnapHint(null);
    setMeet(null);
  };

  const postNieuwCluster = (type_, geomBody) => {
    fetch('/api/review/cluster', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ resultaat_id: resultaatId, type: type_, ...geomBody }),
    })
      .then(r => r.json())
      .then(({ id }) => {
        setClusters(prev => [...prev, { id, type: type_, bron: 'handmatig', ...geomBody }]);
        setTekenModus(null);
        if (onSave) onSave();
      })
      .catch(() => setTekenModus(null));
  };

  const onSvgClick = (e) => {
    if (!editable || !tekenModus) return;
    const raw = naarSvgCoords(e);
    const ref = tekenModus.punten.length ? tekenModus.punten[tekenModus.punten.length - 1] : null;
    const p = bewerkPunt(raw, e, ref, null);
    // Rechthoek-modus: 2 klikken (hoek → tegenoverliggende hoek) → as-gerichte rechthoek.
    if (tekenModus.modus === 'rechthoek') {
      if (!tekenModus.punten.length) { setTekenModus({ ...tekenModus, punten: [p] }); return; }
      const a = tekenModus.punten[0];
      postNieuwCluster('contour', { contour: [[a[0], a[1]], [p[0], a[1]], [p[0], p[1]], [a[0], p[1]]] });
      return;
    }
    // Contour: vrije polygoon — punten blijven verzamelen tot 'Sluiten'.
    // Andere types: vaste 4-punts quad, sluit automatisch op het 4e punt.
    const nieuwePunten = [...tekenModus.punten, p];
    if (tekenModus.type !== 'contour' && nieuwePunten.length === 4) {
      postNieuwCluster(tekenModus.type, { quad: nieuwePunten });
    } else {
      setTekenModus({ ...tekenModus, punten: nieuwePunten });
    }
  };

  const sluitTekening = () => {
    if (!tekenModus || tekenModus.punten.length < 3) return;
    postNieuwCluster(tekenModus.type, { contour: tekenModus.punten });
  };

  const reviewPost = (cid, body) =>
    fetch(`/api/review/cluster/${cid}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    });

  // Huidige (mogelijk versleepte) geometrie van een cluster. Élke review-actie stuurt
  // deze mee, zodat sleepwerk nooit verloren gaat: voorheen bewaarde alleen opslaan()
  // de contour, en sloten subtype/OK af met review_actie='akkoord' zónder geometrie ->
  // verschoven punten verdampten bij reload.
  const geomVan = (cid) => {
    const c = clusters.find(x => x.id === cid);
    if (!c) return {};
    return isContour(c) ? { contour: c.contour } : { quad: c.quad };
  };

  const akkoord = (cid) =>
    reviewPost(cid, { ...geomVan(cid), review_actie: 'akkoord' }).then(() =>
      setClusters(prev =>
        prev.map(c => c.id === cid ? { ...c, review_actie: 'akkoord' } : c)
      )
    );

  // Reviewer kent een subtype toe (contour) -> landt in cluster.subtype, telt mee
  // als 'akkoord'-label in de training-export (zie tools/export_training.py).
  const subtypeWijzigen = (cid, subtype) =>
    reviewPost(cid, { ...geomVan(cid), subtype, review_actie: 'akkoord' }).then(() =>
      setClusters(prev =>
        prev.map(c => c.id === cid ? { ...c, subtype, review_actie: 'akkoord' } : c)
      )
    );

  const opslaan = (cluster) => {
    const body = isContour(cluster)
      ? { contour: cluster.contour, review_actie: 'gewijzigd' }
      : { quad: cluster.quad, review_actie: 'gewijzigd' };
    return reviewPost(cluster.id, body).then(() => {
      setClusters(prev =>
        prev.map(c => c.id === cluster.id ? { ...c, review_actie: 'gewijzigd' } : c)
      );
      if (onSave) onSave();
    });
  };

  const verwijderen = (cid) =>
    reviewPost(cid, { review_actie: 'verwijderd' }).then(() =>
      setClusters(prev => prev.filter(c => c.id !== cid))
    );

  // Orthogonaliseer een contour naar haakse hoeken en sla direct op (gewijzigd).
  const regulariseer = (cluster) => {
    if (!isContour(cluster)) return;
    const nieuw = orthogonalizeRing(cluster.contour);
    setClusters(prev => prev.map(c => c.id === cluster.id ? { ...c, contour: nieuw, review_actie: 'gewijzigd' } : c));
    reviewPost(cluster.id, { contour: nieuw, review_actie: 'gewijzigd' }).then(() => { if (onSave) onSave(); });
  };

  const typeWijzigen = (cid, nieuwType) =>
    reviewPost(cid, { ...geomVan(cid), type: nieuwType, review_actie: 'gewijzigd' }).then(() =>
      setClusters(prev =>
        prev.map(c => c.id === cid ? { ...c, type: nieuwType } : c)
      )
    );

  const clusterTypeOpties = detectieType === 'contour'
    ? [{ value: 'contour', label: 'Contourvlak' }]
    : [
        { value: 'zonnepaneel', label: 'Zonnepaneel' },
        { value: 'dakkapel', label: 'Dakkapel' },
        { value: 'bag_afwijking', label: 'BAG-afwijking' },
      ];

  // Subtype-opties voor contour-review (zonder de niet-selecteerbare placeholder).
  const subtypeOpties = Object.entries(CONTOUR_SUBTYPE_LABEL)
    .filter(([key]) => key !== 'niet_geclassificeerd');

  return (
    <div className={'review-canvas' + (editable ? ' is-editing' : ' is-viewing')}>
      <div className="rc-zoom-controls">
        <button className="rc-zoom-btn" onClick={() => zoomStap(1.35)} title="Inzoomen">+</button>
        <button className="rc-zoom-btn" onClick={() => zoomStap(1 / 1.35)} title="Uitzoomen">-</button>
        <button className="rc-zoom-btn" onClick={resetZoom} title="Zoom herstellen">1:1</button>
        <button className="rc-zoom-btn" onClick={() => setRotatie(r => (r + 90) % 360)} title="90° draaien">⟳</button>
        {detectieType === 'contour' && ahnBeschikbaar && (
          <button
            className={'rc-zoom-btn' + (ahnAan ? ' is-active' : '')}
            onClick={() => setAhnAan(v => !v)}
            title="AHN-hoogtekaart aan/uit"
          >AHN</button>
        )}
        {detectieType === 'contour' && (
          <button
            className={'rc-zoom-btn' + (contourAan ? ' is-active' : '')}
            onClick={() => setContourAan(v => !v)}
            title="Gevonden contouren + nummers tonen"
          >Det</button>
        )}
        {editable && (
          <button
            className={'rc-zoom-btn' + (snapAan ? ' is-active' : '')}
            onClick={() => setSnapAan(v => !v)}
            title="Snappen aan BAG/perceel/punten (Alt = tijdelijk uit, Ctrl = haaks)"
          >Snap</button>
        )}
        {editable && (
          <button
            className={'rc-zoom-btn' + (lijnenAan ? ' is-active' : '')}
            onClick={() => setLijnenAan(v => !v)}
            title="BAG/perceel-lijnen tonen/verbergen (snap blijft werken)"
          >Lijnen</button>
        )}
      </div>

      {editable && (
        <div className="rc-toolbar">
          {clusterTypeOpties.map(opt => (
            <button key={opt.value} className="rc-btn" onClick={() => setTekenModus({ type: opt.value, punten: [] })}>
              + {opt.label}
            </button>
          ))}
          {detectieType === 'contour' && (
            <button className="rc-btn" onClick={() => setTekenModus({ type: 'contour', modus: 'rechthoek', punten: [] })}>
              + Rechthoek
            </button>
          )}
          {meet && (
            <span className="rc-hint">
              {meet.m != null ? `${meet.m.toFixed(2)} m` : ''} · {Math.round(meet.ang)}°
            </span>
          )}
          {snapHint && (
            <span className="rc-hint" style={{ color: '#c026d3', fontWeight: 600 }}>
              ⊙ snap: {snapHint.soort} {snapHint.type === 'vertex' ? 'punt' : 'lijn'}
            </span>
          )}
          {tekenModus && tekenModus.type === 'contour' && (
            <span className="rc-hint">Klik punten ({tekenModus.punten.length}), dan Sluiten</span>
          )}
          {tekenModus && tekenModus.type !== 'contour' && (
            <span className="rc-hint">Klik {4 - tekenModus.punten.length} punt{4 - tekenModus.punten.length !== 1 ? 'en' : ''}</span>
          )}
          {tekenModus && tekenModus.type === 'contour' && (
            <button className="rc-btn" disabled={tekenModus.punten.length < 3} onClick={sluitTekening}>Sluiten</button>
          )}
          {tekenModus && (
            <button className="rc-btn rc-btn-cancel" onClick={() => setTekenModus(null)}>Annuleren</button>
          )}
          {detectieType === 'contour' && clusters.some(isContour) && (
            <span className="rc-slider" title="Lager = meer punten / fijnere contour, hoger = grover">
              Vereenvoudig: {simplifyTol.toFixed(2)} m
              <input
                type="range" min="0.05" max="3" step="0.05"
                value={simplifyTol}
                onChange={e => herSimplify(parseFloat(e.target.value))}
              />
              <span className="rc-hint">handmatige punten gaan verloren bij schuiven</span>
            </span>
          )}
        </div>
      )}

      <div
        className={
          'rc-stage-wrap'
          + (zoom.scale > 1 ? ' is-zoomed' : '')
          + (pan ? ' is-panning' : '')
        }
        ref={wrapRef}
        onDoubleClick={onStageDoubleClick}
        onMouseDown={onPanStart}
        onMouseMove={onMouseMove}
        onMouseUp={onMouseUp}
        onMouseLeave={() => { onPanEnd(); setCursorPunt(null); setSnapHint(null); }}
      >
        <div
          className="rc-stage"
          style={stageMaat ? {
            width: stageMaat.width,
            height: stageMaat.height,
            transform: `translate(${zoom.x}px, ${zoom.y}px) scale(${zoom.scale})`,
          } : null}
        >
          <img
            src={bgUrl}
            alt="luchtfoto"
            style={
              rotatie !== 0 && stageMaat
                ? {
                    position: 'absolute', left: '50%', top: '50%', maxWidth: 'none',
                    width: (rotatie === 90 || rotatie === 270) ? stageMaat.height : stageMaat.width,
                    height: (rotatie === 90 || rotatie === 270) ? stageMaat.width : stageMaat.height,
                    transform: `translate(-50%, -50%) rotate(${rotatie}deg)`,
                  }
                : undefined
            }
            onLoad={e => {
              const w = e.currentTarget.naturalWidth || 1;
              const h = e.currentTarget.naturalHeight || 1;
              setNatuurlijkeMaat({ width: w, height: h });
              // Staande foto → automatisch liggend draaien zodat het brede viewport gevuld wordt.
              if (h > w) setRotatie(r => (r === 0 ? 90 : r));
            }}
          />
          <svg
            ref={svgRef}
            viewBox="0 0 1 1"
            preserveAspectRatio="none"
            style={{ cursor: editable && tekenModus ? 'crosshair' : 'default' }}
            onMouseMove={onMouseMove}
            onMouseUp={onMouseUp}
            onClick={onSvgClick}
          >
            {/* Snap-guides: BAG (geel) + perceel (cyaan). Alleen in bewerk-modus + als 'Lijnen'
                aan staat — snap blijft werken ook als ze verborgen zijn. */}
            {editable && lijnenAan && bagRingen.map((r, i) => (
              <polygon key={'bag' + i} points={r.map(p => roteerPunt(p, rotatie).join(',')).join(' ')}
                fill="none" stroke="#facc15" strokeWidth="0.003" strokeOpacity="0.7" pointerEvents="none" />
            ))}
            {editable && lijnenAan && perceelRingen.map((r, i) => (
              <polygon key={'perc' + i} points={r.map(p => roteerPunt(p, rotatie).join(',')).join(' ')}
                fill="none" stroke="#22d3ee" strokeWidth="0.003" strokeOpacity="0.7" pointerEvents="none" />
            ))}
            {clusters.map((c, ci) => {
              const punten = puntenVan(c);
              const contourCluster = isContour(c);
              return (
              <g key={c.id}>
                <polygon
                  points={punten.map(p => roteerPunt(p, rotatie).join(',')).join(' ')}
                  fill={
                    c.review_actie === 'akkoord'
                      ? 'rgba(34,197,94,0.28)'
                      : c.review_actie === 'verwijderd'
                      ? 'rgba(239,68,68,0.16)'
                      : 'rgba(251,146,60,0.28)'
                  }
                  stroke={c.bron === 'handmatig' ? '#0ea5e9' : '#fb923c'}
                  strokeWidth={hoverId === c.id ? '0.011' : '0.005'}
                  style={{ cursor: editable && tekenModus ? 'crosshair' : 'pointer' }}
                  onMouseEnter={() => setHoverId(c.id)}
                  onMouseLeave={() => setHoverId(null)}
                  onClick={(e) => {
                    if (editable && tekenModus) return;  // tekenmodus: klik gaat naar svg
                    e.stopPropagation();
                    setHoverId(c.id);
                    rijRefs.current[c.id]?.scrollIntoView({ block: 'nearest' });
                  }}
                />
                {/* Contour: klik op een rand-midpunt voegt een punt toe. */}
                {editable && contourCluster && punten.map((p, pi) => {
                  const a = p, b = punten[(pi + 1) % punten.length];
                  const mid = roteerPunt([(a[0] + b[0]) / 2, (a[1] + b[1]) / 2], rotatie);
                  return (
                    <circle
                      key={`add-${pi}`}
                      cx={mid[0]} cy={mid[1]} r="0.008"
                      fill="rgba(14,165,233,0.55)" stroke="white" strokeWidth="0.002"
                      style={{ cursor: 'copy' }}
                      onMouseDown={(e) => { e.stopPropagation(); voegPuntToe(ci, pi); }}
                    />
                  );
                })}
                {editable && punten.map((p, pi) => {
                  const dp = roteerPunt(p, rotatie);
                  return (
                  <circle
                    key={pi}
                    cx={dp[0]}
                    cy={dp[1]}
                    r="0.014"
                    fill={geselPunt && geselPunt.clusterIdx === ci && geselPunt.puntIdx === pi ? '#22c55e' : 'white'}
                    stroke="black"
                    strokeWidth="0.003"
                    style={{ cursor: 'grab' }}
                    onMouseDown={(e) => {
                      e.stopPropagation();
                      setGeselPunt({ clusterIdx: ci, puntIdx: pi });
                      setDrag({ clusterIdx: ci, puntIdx: pi });
                    }}
                    onDoubleClick={(e) => {
                      // Contour: dubbelklik verwijdert een punt (min. 3).
                      e.stopPropagation();
                      if (contourCluster) verwijderPunt(ci, pi);
                    }}
                  />
                  );
                })}
              </g>
              );
            })}

            {editable && tekenModus && tekenModus.punten.map((p, i) => {
              const dp = roteerPunt(p, rotatie);
              return <circle key={i} cx={dp[0]} cy={dp[1]} r="0.012" fill="#0ea5e9" />;
            })}

            {editable && tekenModus && tekenModus.punten.length >= 2 && (
              <polyline
                points={tekenModus.punten.map(p => roteerPunt(p, rotatie).join(',')).join(' ')}
                fill="none"
                stroke="#0ea5e9"
                strokeWidth="0.004"
                strokeDasharray="0.02 0.01"
              />
            )}

            {/* Cursor-markering tijdens tekenen (cyaan kruis+ring); magenta + groter zodra
                er gesnapt wordt aan een lijn/punt. Ook zichtbaar bij slepen (snapHint). */}
            {(() => {
              const mp = (tekenModus ? cursorPunt : null) || (snapHint && snapHint.punt);
              if (!mp) return null;
              const dp = roteerPunt(mp, rotatie);
              const gesnapt = !!snapHint;
              const isVertex = gesnapt && snapHint.type === 'vertex';
              const kleur = gesnapt ? '#ff00ff' : '#0ea5e9';
              return (
                <g pointerEvents="none">
                  {/* Gesnapte rand dik oplichten — zo zie je wélke lijn je vastpakt. */}
                  {gesnapt && snapHint.type === 'edge' && snapHint.seg && (() => {
                    const a = roteerPunt(snapHint.seg[0], rotatie), b = roteerPunt(snapHint.seg[1], rotatie);
                    return <line x1={a[0]} y1={a[1]} x2={b[0]} y2={b[1]} stroke="#ff00ff" strokeWidth="0.009" strokeOpacity="0.85" />;
                  })()}
                  {/* Punt-snap = gevulde stip; rand/vrij = open ring. */}
                  <circle cx={dp[0]} cy={dp[1]} r={isVertex ? 0.016 : (gesnapt ? 0.013 : 0.009)}
                    fill={isVertex ? '#ff00ff' : 'none'} fillOpacity="0.85"
                    stroke={kleur} strokeWidth="0.004" />
                  <line x1={dp[0] - 0.024} y1={dp[1]} x2={dp[0] + 0.024} y2={dp[1]} stroke={kleur} strokeWidth="0.004" />
                  <line x1={dp[0]} y1={dp[1] - 0.024} x2={dp[0]} y2={dp[1] + 0.024} stroke={kleur} strokeWidth="0.004" />
                </g>
              );
            })()}
          </svg>
        </div>
      </div>

      {clusters.length > 0 && (
        <div className="rc-list">
          {clusters.map(c => (
            <div key={c.id}
              ref={el => { rijRefs.current[c.id] = el; }}
              className={`rc-rij${c.review_actie === 'akkoord' ? ' is-akkoord' : c.review_actie === 'verwijderd' ? ' is-verwijderd' : ''}${hoverId === c.id ? ' is-hover' : ''}`}
              onMouseEnter={() => setHoverId(c.id)}
              onMouseLeave={() => setHoverId(null)}>
              <span className="rc-rij-label">{c.subtype ? (CONTOUR_SUBTYPE_LABEL[c.subtype] || c.subtype) : c.type}{c.zekerheid ? ` - ${c.zekerheid}` : ''}{c.bron ? ` - ${c.bron}` : ''}</span>
              <button className="rc-btn rc-btn-icon" onClick={() => akkoord(c.id)} title="Object akkoord">OK</button>
              {editable && (
                <button className="rc-btn rc-btn-icon" onClick={() => opslaan(c)} title="Aanpassingen opslaan (geometrie)">Save</button>
              )}
              {editable && isContour(c) && (
                <button className="rc-btn rc-btn-icon" onClick={() => regulariseer(c)} title="Haakse hoeken maken (regulariseer)">⊾</button>
              )}
              <button className="rc-btn rc-btn-icon rc-btn-danger" onClick={() => verwijderen(c.id)} title="Verwijderen">Del</button>
              {detectieType === 'contour' ? (
                <select
                  className="rc-select"
                  value={c.subtype || ''}
                  onChange={e => subtypeWijzigen(c.id, e.target.value)}
                  title="Subtype toekennen"
                >
                  <option value="" disabled>Kies subtype…</option>
                  {subtypeOpties.map(([key, label]) => (
                    <option key={key} value={key}>{label}</option>
                  ))}
                </select>
              ) : (
                <select
                  className="rc-select"
                  value={c.type}
                  onChange={e => typeWijzigen(c.id, e.target.value)}
                >
                  {clusterTypeOpties.some(opt => opt.value === c.type) ? null : (
                    <option value={c.type}>{c.type}</option>
                  )}
                  {clusterTypeOpties.map(opt => (
                    <option key={opt.value} value={opt.value}>{opt.label}</option>
                  ))}
                </select>
              )}
            </div>
          ))}
        </div>
      )}

      {clusters.length === 0 && (
        <div className="rc-empty">Geen vlakken gevonden voor dit resultaat.</div>
      )}
    </div>
  );
}

window.ReviewCanvas = ReviewCanvas;
