// Shared pan/zoom helpers for the three visual node-graph editors (LogicEditor,
// ControlLogicEditor, SyntheticGraphEditor). Zoom is implemented as a CSS
// `transform: scale()` on an inner "zoom layer"; the outer `.flow-canvas-inner`
// is sized to the scaled dimensions so the scroll container reports the right
// scrollable area. Node coordinates stay in unscaled ("logical") space — the
// editors' `toCanvas` divides pointer offsets by the current zoom.
import { useState, useRef, useEffect, useCallback } from 'preact/hooks';

export const ZOOM_MIN = 0.3;
export const ZOOM_MAX = 2.5;
export const ZOOM_FACTOR = 1.25; // multiplier per zoom-in / -out step

export const clampZoom = (z: number) => Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, z));

export interface Box { x: number; y: number; w: number; h: number; }

/** Axis-aligned bounding box over a set of rectangles (null when empty). */
export function contentBounds(rects: Box[]): Box | null {
  if (rects.length === 0) return null;
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
  for (const r of rects) {
    minX = Math.min(minX, r.x); minY = Math.min(minY, r.y);
    maxX = Math.max(maxX, r.x + r.w); maxY = Math.max(maxY, r.y + r.h);
  }
  return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
}

interface ZoomScroll { zoom: number; scrollX: number; scrollY: number; }

/** New zoom + scroll that keeps the viewport centre fixed while scaling. */
export function zoomAnchored(z0: number, factor: number, scrollX: number, scrollY: number, vw: number, vh: number): ZoomScroll {
  const z1 = clampZoom(z0 * factor);
  const cx = (scrollX + vw / 2) / z0;
  const cy = (scrollY + vh / 2) / z0;
  return { zoom: z1, scrollX: Math.max(0, cx * z1 - vw / 2), scrollY: Math.max(0, cy * z1 - vh / 2) };
}

/** Zoom + scroll so `content` is centred and fits within the viewport. */
export function zoomToFit(content: Box, vw: number, vh: number, pad = 48): ZoomScroll {
  const z = clampZoom(Math.min(vw / (content.w + 2 * pad), vh / (content.h + 2 * pad)));
  return {
    zoom: z,
    scrollX: Math.max(0, (content.x + content.w / 2) * z - vw / 2),
    scrollY: Math.max(0, (content.y + content.h / 2) * z - vh / 2),
  };
}

/**
 * Zoom controller shared by all three editors. `zoomRef` is kept in sync with
 * the live zoom so each editor's `toCanvas` can read it without stale closures.
 * `getRects` returns the current node rectangles (logical coords) for fit.
 */
export function useFlowZoom(
  canvasRef: { current: HTMLDivElement | null },
  zoomRef: { current: number },
  baseW: number,
  baseH: number,
  getRects: () => Box[],
) {
  const [zoom, setZoom] = useState(1);
  zoomRef.current = zoom;
  // Scroll target applied after the zoomed layout commits.
  const pending = useRef<{ x: number; y: number } | null>(null);

  useEffect(() => {
    const el = canvasRef.current;
    if (el && pending.current) {
      el.scrollLeft = pending.current.x;
      el.scrollTop = pending.current.y;
      pending.current = null;
    }
  }, [zoom]);

  const apply = (r: ZoomScroll) => { pending.current = { x: r.scrollX, y: r.scrollY }; setZoom(r.zoom); };

  const zoomIn = useCallback(() => {
    const el = canvasRef.current; if (!el) return;
    apply(zoomAnchored(zoomRef.current, ZOOM_FACTOR, el.scrollLeft, el.scrollTop, el.clientWidth, el.clientHeight));
  }, []);
  const zoomOut = useCallback(() => {
    const el = canvasRef.current; if (!el) return;
    apply(zoomAnchored(zoomRef.current, 1 / ZOOM_FACTOR, el.scrollLeft, el.scrollTop, el.clientWidth, el.clientHeight));
  }, []);
  const zoomHome = useCallback(() => { apply({ zoom: 1, scrollX: 0, scrollY: 0 }); }, []);
  const zoomFit = useCallback(() => {
    const el = canvasRef.current; if (!el) return;
    const b = contentBounds(getRects());
    if (!b) { apply({ zoom: 1, scrollX: 0, scrollY: 0 }); return; }
    apply(zoomToFit(b, el.clientWidth, el.clientHeight));
  }, [getRects]);

  // Outer sizer reserves the scaled scroll area; inner layer carries the scale.
  const innerStyle = `width:${baseW * zoom}px; height:${baseH * zoom}px;`;
  const zoomStyle = `width:${baseW}px; height:${baseH}px; transform: scale(${zoom}); transform-origin: 0 0;`;
  return { zoom, zoomIn, zoomOut, zoomHome, zoomFit, innerStyle, zoomStyle };
}
