/** Pure pan/zoom viewport math for the map canvas (no DOM, unit-tested). */
import type { Point } from './types';

export interface Viewport {
  /** multiplier; 1 = natural image resolution */
  zoom: number;
  /** translation in screen pixels, applied before scale */
  panX: number;
  panY: number;
}

export const MIN_ZOOM = 0.1;
export const MAX_ZOOM = 6;

export function clampZoom(z: number): number {
  return Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, z));
}

/**
 * Zoom by `factor` keeping the world point under (cx, cy) fixed on screen.
 * cx/cy are cursor coordinates relative to the viewport's top-left.
 */
export function zoomToPoint(vp: Viewport, factor: number, cx: number, cy: number): Viewport {
  const zoom = clampZoom(vp.zoom * factor);
  const wx = (cx - vp.panX) / vp.zoom;
  const wy = (cy - vp.panY) / vp.zoom;
  return { zoom, panX: cx - wx * zoom, panY: cy - wy * zoom };
}

/** Center a natural-size image inside a viewport, never upscaling past 1:1. */
export function fitViewport(vw: number, vh: number, naturalW: number, naturalH: number): Viewport {
  if (naturalW <= 0 || naturalH <= 0 || vw <= 0 || vh <= 0) return { zoom: 1, panX: 0, panY: 0 };
  const zoom = Math.min(vw / naturalW, vh / naturalH, 1);
  return { zoom, panX: (vw - naturalW * zoom) / 2, panY: (vh - naturalH * zoom) / 2 };
}

/**
 * Convert a screen point to world (natural image) pixels, given the bounding rect
 * of the already-transformed world layer. Because the layer's rect reflects the
 * CSS transform, dividing by zoom is all that's needed.
 */
export function worldFromScreen(zoom: number, clientX: number, clientY: number, rectLeft: number, rectTop: number): Point {
  return { x: (clientX - rectLeft) / zoom, y: (clientY - rectTop) / zoom };
}
