// Geometry primitives used by the map renderer.
//
// All distances are in world units (TS numbers, float64). Functions
// in this file are pure and side-effect-free; tests exercise them
// directly.

import type { Camera, Viewport } from "./world";

// clamp returns v constrained to [lo, hi]. If lo > hi the function
// returns lo (callers are expected to keep the bounds well-formed).
export function clamp(v: number, lo: number, hi: number): number {
	if (v < lo) return lo;
	if (v > hi) return hi;
	return v;
}

// torusShortestDelta returns the signed delta from a to b on a circle
// of circumference `size`, picking the direction with the smaller
// absolute distance. Result lies in (-size/2, size/2].
//
// At exactly size/2 the function returns +size/2 (positive direction);
// the lower bound is exclusive so a delta of -size/2 wraps to +size/2.
// This deterministic tie-break keeps the function self-consistent
// regardless of input order. The `+0` at the end normalises -0 (which
// JavaScript produces for some modulo cases) to +0.
export function torusShortestDelta(a: number, b: number, size: number): number {
	if (!(size > 0)) {
		throw new Error(`torusShortestDelta: size must be positive, got ${size}`);
	}
	let d = (b - a) % size;
	if (d > size / 2) d -= size;
	else if (d <= -size / 2) d += size;
	return d + 0;
}

// distSqPointToSegment returns the squared distance from point (px,py)
// to the segment (ax,ay)–(bx,by). For zero-length segments it falls
// back to point-to-point distance.
export function distSqPointToSegment(
	px: number,
	py: number,
	ax: number,
	ay: number,
	bx: number,
	by: number,
): number {
	const dx = bx - ax;
	const dy = by - ay;
	const lenSq = dx * dx + dy * dy;
	if (lenSq === 0) {
		const ex = px - ax;
		const ey = py - ay;
		return ex * ex + ey * ey;
	}
	let t = ((px - ax) * dx + (py - ay) * dy) / lenSq;
	if (t < 0) t = 0;
	else if (t > 1) t = 1;
	const fx = ax + t * dx;
	const fy = ay + t * dy;
	const ex = px - fx;
	const ey = py - fy;
	return ex * ex + ey * ey;
}

// screenToWorld converts cursor pixel coordinates (relative to the
// viewport top-left) to world coordinates under the given camera.
export function screenToWorld(
	cursorPx: { x: number; y: number },
	camera: Camera,
	viewport: Viewport,
): { x: number; y: number } {
	const offX = cursorPx.x - viewport.widthPx / 2;
	const offY = cursorPx.y - viewport.heightPx / 2;
	return {
		x: camera.centerX + offX / camera.scale,
		y: camera.centerY + offY / camera.scale,
	};
}

// worldToScreen converts a world-space point to viewport pixel
// coordinates under the given camera.
export function worldToScreen(
	world: { x: number; y: number },
	camera: Camera,
	viewport: Viewport,
): { x: number; y: number } {
	return {
		x: viewport.widthPx / 2 + (world.x - camera.centerX) * camera.scale,
		y: viewport.heightPx / 2 + (world.y - camera.centerY) * camera.scale,
	};
}
