// 2D pan/zoom camera for canvas-based adapters. Mirrors the SKITTERDOME
// editor-camera feel: clamped multiplicative wheel zoom, clamped pan.

const clamp = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v));

export class Camera2D {
  /** world coords at the view center */
  cx = 0;
  cy = 0;
  zoom = 1;
  viewW: number;
  viewH: number;
  worldW = 1;
  worldH = 1;
  maxZoom = 6;

  constructor(viewW: number, viewH: number) {
    this.viewW = viewW;
    this.viewH = viewH;
  }

  setWorld(w: number, h: number): void {
    this.worldW = w;
    this.worldH = h;
  }

  private fitZoom(): number {
    return Math.min(this.viewW / this.worldW, this.viewH / this.worldH);
  }

  /** frame the whole world; biasXPx shifts content right (palette clearance) */
  fit(marginFrac = 0.9, biasXPx = 0): void {
    this.zoom = this.fitZoom() * marginFrac;
    this.cx = this.worldW / 2 - biasXPx / this.zoom;
    this.cy = this.worldH / 2;
  }

  /** pan by a view-pixel delta (drag follows the pointer) */
  panViewPx(dx: number, dy: number): void {
    this.cx -= dx / this.zoom;
    this.cy -= dy / this.zoom;
    this.clampCenter();
  }

  /** wheel zoom: one huge delta must not flip the sign (same clamp as SKITTERDOME) */
  zoomWheel(deltaY: number): void {
    const f = clamp(1 + deltaY * 0.0011, 0.55, 1.6);
    this.zoom = clamp(this.zoom / f, this.fitZoom() * 0.5, this.maxZoom);
    this.clampCenter();
  }

  private clampCenter(): void {
    const m = 80 / this.zoom; // allow a little overshoot past the edges
    this.cx = clamp(this.cx, -m, this.worldW + m);
    this.cy = clamp(this.cy, -m, this.worldH + m);
  }

  viewToWorld(vx: number, vy: number): [number, number] {
    return [this.cx + (vx - this.viewW / 2) / this.zoom, this.cy + (vy - this.viewH / 2) / this.zoom];
  }

  worldToView(wx: number, wy: number): [number, number] {
    return [(wx - this.cx) * this.zoom + this.viewW / 2, (wy - this.cy) * this.zoom + this.viewH / 2];
  }

  /** apply the world->view transform to a 2D context (caller saves/restores) */
  applyTransform(ctx: CanvasRenderingContext2D): void {
    ctx.translate(this.viewW / 2, this.viewH / 2);
    ctx.scale(this.zoom, this.zoom);
    ctx.translate(-this.cx, -this.cy);
  }
}
