// Square image cropper — modal overlay that takes an image File and lets
// the user pan + zoom inside a 1:1 mask. On confirm, returns a new File
// (webp, full crop area at native resolution capped to the cropper's
// display) via the onCrop callback.
//
// Pure React + canvas, no dependencies. Math is straight matrix transforms:
// the loaded image starts at "fit-cover" (smallest scale that fills the
// 1:1 mask), the user can drag to pan and pinch / wheel to zoom. On
// commit we draw the visible mask area into an offscreen canvas at the
// chosen output size and toBlob() it.

(function () {
  // Default crop output size — capped at 1024 to match the server's
  // MAX_IMG. Smaller sources are exported at their natural side length
  // (no upscaling).
  const TARGET_SIZE = 1024;
  const MIN_SIDE    = 256;  // matches server-side probeImage gate
  const PADDING     = 24;   // viewport padding inside the modal

  function ImageCropper({ file, onCrop, onCancel, dark = true }) {
    const [src, setSrc] = React.useState(null);
    const [img, setImg] = React.useState(null);   // HTMLImageElement once loaded
    const [scale, setScale] = React.useState(1);
    const [minScale, setMinScale] = React.useState(1);
    const [pos, setPos] = React.useState({ x: 0, y: 0 });
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState('');

    const dragRef = React.useRef(null);   // { startX, startY, baseX, baseY }
    const containerRef = React.useRef(null);
    const [containerSize, setContainerSize] = React.useState(360);

    // Load file → object URL → HTMLImageElement, set initial fit-cover
    // scale + center the image inside the mask.
    React.useEffect(() => {
      if (!file) return;
      const url = URL.createObjectURL(file);
      setSrc(url);
      const im = new Image();
      im.onload = () => {
        if (im.naturalWidth < MIN_SIDE || im.naturalHeight < MIN_SIDE) {
          setErr(`Bild zu klein (${im.naturalWidth}×${im.naturalHeight}). Minimum: ${MIN_SIDE}×${MIN_SIDE}.`);
          return;
        }
        setImg(im);
      };
      im.onerror = () => setErr('Bild konnte nicht geladen werden.');
      im.src = url;
      return () => URL.revokeObjectURL(url);
    }, [file]);

    // Recompute the minimum scale (= fit-cover) whenever the container
    // resizes or a fresh image loads. Picks whichever axis has more
    // headroom so the smaller of width/height fully covers the mask.
    React.useLayoutEffect(() => {
      if (!img || !containerRef.current) return;
      const rect = containerRef.current.getBoundingClientRect();
      const size = Math.min(rect.width, rect.height) - PADDING * 2;
      setContainerSize(size);
      const m = Math.max(size / img.naturalWidth, size / img.naturalHeight);
      setMinScale(m);
      setScale(m);
      setPos({ x: 0, y: 0 });   // centered
    }, [img]);

    // Drag-to-pan. Position is stored in *image-pixel* offset from the
    // mask's center (so zoom in/out feels natural — the same image
    // point stays under the user's pointer).
    const onPointerDown = (e) => {
      if (!img) return;
      e.preventDefault();
      dragRef.current = {
        startX: e.clientX, startY: e.clientY,
        baseX: pos.x, baseY: pos.y,
      };
      e.currentTarget.setPointerCapture?.(e.pointerId);
    };
    const onPointerMove = (e) => {
      if (!dragRef.current) return;
      e.preventDefault();
      const dx = e.clientX - dragRef.current.startX;
      const dy = e.clientY - dragRef.current.startY;
      setPos(clampPos({
        x: dragRef.current.baseX + dx,
        y: dragRef.current.baseY + dy,
      }, scale));
    };
    const onPointerUp = (e) => {
      if (e.currentTarget.releasePointerCapture && e.pointerId != null) {
        try { e.currentTarget.releasePointerCapture(e.pointerId); } catch {}
      }
      dragRef.current = null;
    };

    // Wheel = zoom around the center. Stays bounded to [minScale, 4×min]
    // so the user can't shrink the image past fit-cover (would expose
    // the mask edges) or zoom beyond a reasonable detail level.
    const onWheel = (e) => {
      if (!img) return;
      e.preventDefault();
      const delta = e.deltaY < 0 ? 1.08 : 1 / 1.08;
      const next = Math.max(minScale, Math.min(minScale * 4, scale * delta));
      setScale(next);
      setPos(p => clampPos(p, next));
    };

    // Constrain pan so the image always covers the mask. Position units
    // are screen px relative to the mask's center.
    const clampPos = (p, s) => {
      if (!img) return p;
      const halfW = (img.naturalWidth  * s - containerSize) / 2;
      const halfH = (img.naturalHeight * s - containerSize) / 2;
      return {
        x: Math.max(-halfW, Math.min(halfW, p.x)),
        y: Math.max(-halfH, Math.min(halfH, p.y)),
      };
    };

    // Render the cropped square to an offscreen canvas at TARGET_SIZE
    // (or smaller if the source can't reach that), encode as webp Blob,
    // wrap in a File, hand to the parent.
    const commit = async () => {
      if (!img) return;
      setBusy(true);
      try {
        const out = Math.min(TARGET_SIZE, Math.round(containerSize / scale));
        // The visible mask in image-pixel coordinates:
        const cx = img.naturalWidth  / 2 - pos.x / scale;
        const cy = img.naturalHeight / 2 - pos.y / scale;
        const half = (containerSize / scale) / 2;
        const sx = Math.max(0, cx - half);
        const sy = Math.max(0, cy - half);
        const sSize = Math.min(img.naturalWidth - sx, img.naturalHeight - sy, half * 2);

        const canvas = document.createElement('canvas');
        canvas.width = canvas.height = out;
        const cx2d = canvas.getContext('2d');
        cx2d.drawImage(img, sx, sy, sSize, sSize, 0, 0, out, out);

        const blob = await new Promise(res => canvas.toBlob(res, 'image/webp', 0.92));
        if (!blob) throw new Error('toBlob returned null');
        const cropped = new File([blob], 'cropped.webp', { type: 'image/webp' });
        onCrop(cropped);
      } catch (e) {
        setErr('Zuschneiden fehlgeschlagen: ' + e.message);
        setBusy(false);
      }
    };

    return (
      <div style={{
        position: 'fixed', inset: 0, zIndex: 2000,
        background: 'rgba(10,10,20,0.85)',
        backdropFilter: 'blur(12px)',
        WebkitBackdropFilter: 'blur(12px)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        padding: 20,
      }}>
        <div style={{
          width: 'min(560px, 92vw)', maxHeight: '92vh',
          background: dark ? 'rgba(20,15,32,0.95)' : 'rgba(255,255,255,0.95)',
          border: dark ? '1px solid rgba(255,255,255,0.12)' : '1px solid rgba(0,0,0,0.08)',
          borderRadius: 18,
          color: dark ? '#fff' : '#1a0d2e',
          boxShadow: '0 24px 80px rgba(0,0,0,0.6)',
          display: 'flex', flexDirection: 'column',
        }}>
          <div style={{ padding: '18px 22px', borderBottom: dark ? '1px solid rgba(255,255,255,0.08)' : '1px solid rgba(0,0,0,0.06)' }}>
            <div style={{ fontSize: 18, fontWeight: 700, letterSpacing: -0.3 }}>Bild zuschneiden</div>
            <div style={{ fontSize: 12, opacity: 0.65, marginTop: 4 }}>
              Ziehen zum Verschieben, scrollen zum Zoomen. Das Quadrat wird übernommen.
            </div>
          </div>

          {err ? (
            <div style={{ padding: 24, textAlign: 'center' }}>
              <ApIcon name="x" size={28} color="#f87171"/>
              <div style={{ fontSize: 14, marginTop: 10, color: '#f87171' }}>{err}</div>
            </div>
          ) : (
            <div
              ref={containerRef}
              onPointerDown={onPointerDown}
              onPointerMove={onPointerMove}
              onPointerUp={onPointerUp}
              onPointerCancel={onPointerUp}
              onWheel={onWheel}
              style={{
                position: 'relative', flex: 1, minHeight: 320,
                aspectRatio: '1',
                cursor: dragRef.current ? 'grabbing' : 'grab',
                touchAction: 'none', userSelect: 'none',
                overflow: 'hidden',
                background: dark ? '#0a0a14' : '#f3eef8',
              }}
            >
              {img && (
                <img
                  src={src}
                  draggable={false}
                  alt=""
                  style={{
                    position: 'absolute',
                    left: '50%', top: '50%',
                    width:  img.naturalWidth  * scale,
                    height: img.naturalHeight * scale,
                    transform: `translate(calc(-50% + ${pos.x}px), calc(-50% + ${pos.y}px))`,
                    pointerEvents: 'none',
                  }}
                />
              )}
              {/* Mask — bright square in the middle, dimmed margins. The
                  4 dimmer panels each cover one side. */}
              <div style={{
                position: 'absolute', inset: 0, pointerEvents: 'none',
                boxShadow: `inset 0 0 0 ${PADDING}px rgba(0,0,0,0.55)`,
                outline: '2px solid rgba(255,255,255,0.6)',
                outlineOffset: -PADDING,
              }}/>
            </div>
          )}

          {/* Zoom slider so users without a scroll wheel still have
              control. Bounded to [minScale, 4×minScale]. */}
          {!err && img && (
            <div style={{ padding: '10px 22px', display: 'flex', alignItems: 'center', gap: 10 }}>
              <ApIcon name="image" size={16} color="rgba(255,255,255,0.5)"/>
              <input
                type="range"
                min={minScale} max={minScale * 4} step={(minScale * 3) / 100}
                value={scale}
                onChange={(e) => {
                  const next = parseFloat(e.target.value);
                  setScale(next);
                  setPos(p => clampPos(p, next));
                }}
                style={{ flex: 1 }}
              />
            </div>
          )}

          <div style={{
            display: 'flex', gap: 8, padding: '14px 22px 18px',
            borderTop: dark ? '1px solid rgba(255,255,255,0.08)' : '1px solid rgba(0,0,0,0.06)',
          }}>
            <button onClick={onCancel} disabled={busy} style={{
              flex: 1, padding: '10px 16px', borderRadius: 10,
              background: dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)',
              border: 'none', color: 'inherit', cursor: busy ? 'default' : 'pointer',
              fontSize: 13, fontWeight: 600,
            }}>Abbrechen</button>
            <button onClick={commit} disabled={busy || !img || !!err} style={{
              flex: 2, padding: '10px 16px', borderRadius: 10,
              background: 'linear-gradient(135deg, #ff4d8d, #c026d3)',
              border: 'none', color: '#fff', cursor: busy ? 'default' : 'pointer',
              fontSize: 13, fontWeight: 700,
              opacity: busy || !img || !!err ? 0.5 : 1,
            }}>{busy ? 'Schneide…' : 'Übernehmen'}</button>
          </div>
        </div>
      </div>
    );
  }

  window.ImageCropper = ImageCropper;
})();
