/**
 * Canvas Primitive — shared types for the freeform canvas component.
 * Used by whiteboard, storyboard, and future canvas-based views.
 */

/** Camera state: 2D affine transformation */
export interface Camera {
	x: number;      // pan offset X (world coords)
	y: number;      // pan offset Y (world coords)
	zoom: number;   // scale factor (1.0 = 100%)
}

/** Generic canvas object — consumer determines rendering */
export interface CanvasObject {
	id: string;
	x: number;
	y: number;
	width: number;
	height: number;
}

/** Axis-aligned bounding box for intersection tests */
export interface Rect {
	x: number;
	y: number;
	width: number;
	height: number;
}

/** Events emitted by the canvas for consumer integration (e.g. WebSocket sync) */
export interface CanvasEvents {
	onObjectMove?: (id: string, x: number, y: number) => void;
	onObjectResize?: (id: string, w: number, h: number) => void;
	onCameraChange?: (camera: Camera) => void;
	onSelectionChange?: (ids: string[]) => void;
}

/** Snap-to-grid configuration */
export interface GridConfig {
	enabled: boolean;
	size: number; // grid cell size in world pixels (default 20)
}

/** Viewport size in screen pixels */
export interface ViewportSize {
	width: number;
	height: number;
}

// --- Utility functions ---

/** Snap a value to the nearest grid point */
export function snap(value: number, gridSize: number): number {
	return Math.round(value / gridSize) * gridSize;
}

/** Convert screen coordinates to world coordinates */
export function screenToWorld(screenX: number, screenY: number, camera: Camera): { x: number; y: number } {
	return {
		x: (screenX - camera.x) / camera.zoom,
		y: (screenY - camera.y) / camera.zoom,
	};
}

/** Convert world coordinates to screen coordinates */
export function worldToScreen(worldX: number, worldY: number, camera: Camera): { x: number; y: number } {
	return {
		x: worldX * camera.zoom + camera.x,
		y: worldY * camera.zoom + camera.y,
	};
}

/** Get the visible world-space rectangle for the current viewport */
export function getVisibleWorldRect(camera: Camera, viewport: ViewportSize): Rect {
	const topLeft = screenToWorld(0, 0, camera);
	const bottomRight = screenToWorld(viewport.width, viewport.height, camera);
	return {
		x: topLeft.x,
		y: topLeft.y,
		width: bottomRight.x - topLeft.x,
		height: bottomRight.y - topLeft.y,
	};
}

/** Check if two rectangles intersect */
export function intersects(a: Rect, b: Rect): boolean {
	return (
		a.x < b.x + b.width &&
		a.x + a.width > b.x &&
		a.y < b.y + b.height &&
		a.y + a.height > b.y
	);
}

/** Filter objects to only those visible in the viewport (with margin for smooth scrolling) */
export function visibleObjects<T extends CanvasObject>(
	objects: T[],
	camera: Camera,
	viewport: ViewportSize,
	margin: number = 200
): T[] {
	const worldRect = getVisibleWorldRect(camera, viewport);
	// Expand by margin in world space
	const expanded: Rect = {
		x: worldRect.x - margin,
		y: worldRect.y - margin,
		width: worldRect.width + margin * 2,
		height: worldRect.height + margin * 2,
	};
	return objects.filter(obj =>
		intersects({ x: obj.x, y: obj.y, width: obj.width, height: obj.height }, expanded)
	);
}

/** Clamp zoom to allowed range */
export const ZOOM_MIN = 0.05;
export const ZOOM_MAX = 3.0;
/** Lower bound for zoomToFit — allows fitting very spread-out layouts */
export const ZOOM_FIT_MIN = 0.02;

export function clampZoom(zoom: number): number {
	return Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, zoom));
}
