/* ============================================================
   drag.jsx — pointer-based drag-and-drop for the builder
   ------------------------------------------------------------
   Replaces native HTML5 drag with a custom pointer system.
   Provides:
     - DragProvider — wraps the app, owns the active-drag state
     - useDragSource({ kind, payload }) — returns props for a
       handle/card that should start a drag
     - DropZone — visible drop indicator that registers itself
       so the provider knows where pointer can drop
     - useCanvasAutoScroll — scrolls the canvas wrapper when
       pointer approaches an edge during drag
   ============================================================ */

const DragCtx = React.createContext(null);
const useDrag = () => React.useContext(DragCtx);

/* DragProvider ---------------------------------------------------------- */
function DragProvider({ children }) {
  const [drag, setDrag] = React.useState(null);
  // drag = { kind: 'new'|'move', payload, label, ghostRect, pointer:{x,y} }

  const dropZonesRef = React.useRef(new Map()); // id -> { el, index, onDrop }
  const activeDropRef = React.useRef(null); // currently-hovered drop id
  const [activeDropId, setActiveDropId] = React.useState(null);

  const registerDropZone = React.useCallback((id, info) => {
    dropZonesRef.current.set(id, info);
    return () => dropZonesRef.current.delete(id);
  }, []);

  // pointer move/up handlers attach only while dragging
  React.useEffect(() => {
    if (!drag) return;

    const findHit = (x, y) => {
      // check every registered dropzone, return the closest center
      let best = null;
      let bestDist = Infinity;
      dropZonesRef.current.forEach((info, id) => {
        const r = info.el?.getBoundingClientRect?.();
        if (!r) return;
        // expand vertically a bit so gaps catch the pointer
        const expandY = 12;
        if (x >= r.left - 4 && x <= r.right + 4 &&
            y >= r.top - expandY && y <= r.bottom + expandY) {
          const cx = (r.left + r.right) / 2;
          const cy = (r.top + r.bottom) / 2;
          const d = Math.abs(y - cy) + Math.abs(x - cx) * 0.1;
          if (d < bestDist) { bestDist = d; best = id; }
        }
      });
      return best;
    };

    const onMove = (e) => {
      const t = e.touches?.[0] || e;
      const x = t.clientX, y = t.clientY;
      setDrag(d => d ? { ...d, pointer: { x, y } } : d);

      // hit-test
      const hit = findHit(x, y);
      if (hit !== activeDropRef.current) {
        activeDropRef.current = hit;
        setActiveDropId(hit);
      }

      // auto-scroll the canvas if pointer near edges
      const wrap = document.querySelector('.canvas-wrap');
      if (wrap) {
        const r = wrap.getBoundingClientRect();
        const margin = 80;
        if (y < r.top + margin) wrap.scrollBy({ top: -14, behavior: 'auto' });
        else if (y > r.bottom - margin) wrap.scrollBy({ top: 14, behavior: 'auto' });
      }

      // prevent text selection / scroll while dragging on touch
      if (e.cancelable) e.preventDefault();
    };

    const finish = () => {
      const hitId = activeDropRef.current;
      if (hitId) {
        const info = dropZonesRef.current.get(hitId);
        if (info?.onDrop) info.onDrop(drag);
      }
      activeDropRef.current = null;
      setActiveDropId(null);
      setDrag(null);
    };

    const onUp = () => finish();
    const onKey = (e) => { if (e.key === 'Escape') { activeDropRef.current = null; setActiveDropId(null); setDrag(null); } };

    window.addEventListener('pointermove', onMove, { passive: false });
    window.addEventListener('pointerup', onUp);
    window.addEventListener('pointercancel', onUp);
    window.addEventListener('keydown', onKey);
    return () => {
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
      window.removeEventListener('pointercancel', onUp);
      window.removeEventListener('keydown', onKey);
    };
  }, [drag]);

  // class on body so we can disable user-select / set cursor
  React.useEffect(() => {
    if (drag) {
      document.body.classList.add('tender-dragging');
      document.body.style.cursor = 'grabbing';
    } else {
      document.body.classList.remove('tender-dragging');
      document.body.style.cursor = '';
    }
  }, [drag]);

  const startDrag = React.useCallback((info, pointer) => {
    setDrag({ ...info, pointer });
  }, []);

  const value = React.useMemo(() => ({
    drag,
    activeDropId,
    startDrag,
    registerDropZone,
  }), [drag, activeDropId, startDrag, registerDropZone]);

  return (
    <DragCtx.Provider value={value}>
      {children}
      {drag && <DragGhost drag={drag}/>}
    </DragCtx.Provider>
  );
}

/* DragGhost — follows pointer ----------------------------------------- */
function DragGhost({ drag }) {
  const { pointer, label, kind, ghostRect } = drag;
  const w = ghostRect?.width || 220;
  const h = ghostRect?.height || 36;
  const style = {
    position: 'fixed',
    left: pointer.x - 14,
    top: pointer.y - 14,
    width: w,
    minHeight: h,
    maxHeight: 120,
    pointerEvents: 'none',
    zIndex: 9999,
    transform: 'rotate(-1.2deg)',
  };
  return (
    <div className={'drag-ghost drag-ghost-' + kind} style={style}>
      <span className="drag-ghost-dot"/>
      <span className="drag-ghost-label">{label || (kind === 'new' ? 'Nieuw blok' : 'Verplaatsen')}</span>
    </div>
  );
}

/* useDragSource — returns onPointerDown handler ------------------------ */
function useDragSource({ kind, payload, label, getGhostRect }) {
  const { startDrag } = useDrag();

  return React.useCallback((e) => {
    // only left-button / primary pointer
    if (e.button !== undefined && e.button !== 0) return;
    e.preventDefault();
    e.stopPropagation();

    const startX = e.clientX, startY = e.clientY;
    let activated = false;

    const tryActivate = (mx, my) => {
      if (activated) return;
      const dx = Math.abs(mx - startX), dy = Math.abs(my - startY);
      if (dx + dy < 4) return; // tiny threshold so clicks still work
      activated = true;
      const ghostRect = typeof getGhostRect === 'function' ? getGhostRect() : null;
      startDrag({ kind, payload, label, ghostRect }, { x: mx, y: my });
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
    };

    const onMove = (ev) => tryActivate(ev.clientX, ev.clientY);
    const onUp = () => {
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
    };
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onUp);
  }, [kind, payload, label, getGhostRect, startDrag]);
}

/* DropZone — visible bar that registers itself with provider ---------- */
function DropZone({ id, onDrop, trailing, full }) {
  const { drag, activeDropId, registerDropZone } = useDrag();
  const ref = React.useRef(null);

  React.useEffect(() => {
    if (!ref.current) return;
    return registerDropZone(id, { el: ref.current, onDrop });
  }, [id, onDrop, registerDropZone]);

  const visible = !!drag;
  const active  = activeDropId === id;

  return (
    <div
      ref={ref}
      className={
        'drop-zone' +
        (visible ? ' visible' : '') +
        (active ? ' active' : '') +
        (trailing ? ' trailing' : '') +
        (full ? ' full' : '')
      }
    >
      <div className="drop-zone-bar"/>
    </div>
  );
}

Object.assign(window, { DragProvider, useDrag, useDragSource, DropZone });
