// Data model for the map renderer.
//
// World coordinates are TypeScript numbers (float64). The world is a
// rectangle [0, W) × [0, H). When wrap mode is 'torus', the world
// behaves toroidally — primitives near the right edge are visible at
// the left edge once the camera scrolls past, etc. When wrap mode is
// 'no-wrap', the world is a bounded plane and the camera is clamped
// at its edges.
//
// The algorithm specification for hit-test, torus wrap, and no-wrap
// camera behaviour lives in ui/docs/renderer.md. See that document
// before changing the contract of the types in this file.

export type PrimitiveID = number;

export type WrapMode = "torus" | "no-wrap";

// Style describes the visual appearance of a primitive. Any field may
// be omitted; missing fields fall back to the active theme defaults.
export interface Style {
	fillColor?: number; // 0xRRGGBB
	fillAlpha?: number; // 0..1
	strokeColor?: number; // 0xRRGGBB
	strokeAlpha?: number; // 0..1
	strokeWidthPx?: number; // pixels at any zoom
	pointRadiusPx?: number; // pixels at any zoom (for kind === 'point')
	// strokeDashPx — when set on a `LinePrim`, the line is rendered as
	// a dashed pattern whose dash and gap are both this length. When
	// unset (or zero), the stroke is solid. Interpreted in the same
	// world-unit space as `strokeWidthPx`, so the dash spacing scales
	// with the camera. Phase 19 uses this for the IncomingGroup
	// trajectory line; ignored on point and circle primitives.
	strokeDashPx?: number;
}

// PrimitiveBase carries the fields shared by every primitive kind.
//
// priority is used for deterministic ordering during hit-test: higher
// priority wins ties. hitSlopPx is an optional per-primitive override
// of the kind default, in screen pixels.
export interface PrimitiveBase {
	id: PrimitiveID;
	priority: number;
	style: Style;
	hitSlopPx: number; // 0 = use kind default
}

export interface PointPrim extends PrimitiveBase {
	kind: "point";
	x: number;
	y: number;
}

export interface CirclePrim extends PrimitiveBase {
	kind: "circle";
	x: number;
	y: number;
	radius: number; // world units
}

export interface LinePrim extends PrimitiveBase {
	kind: "line";
	x1: number;
	y1: number;
	x2: number;
	y2: number;
}

export type Primitive = PointPrim | CirclePrim | LinePrim;

export type PrimitiveKind = Primitive["kind"];

// Default hit slop in screen pixels per primitive kind. Added on top
// of the visible footprint of each primitive — for points, the
// effective hit radius is `pointRadiusPx + slopPx`. Chosen for touch
// ergonomics; per-primitive `hitSlopPx` overrides the default.
export const DEFAULT_HIT_SLOP_PX: Record<PrimitiveKind, number> = {
	point: 4,
	circle: 6,
	line: 6,
};

// Default world-unit radius drawn for a `PointPrim` when its
// `style.pointRadiusPx` is unset. Shared between the renderer
// (`render.ts.drawPoint`) and the hit-test
// (`hit-test.ts.matchPoint`) so the click target always covers the
// visible disc.
export const DEFAULT_POINT_RADIUS_PX = 3;

// kindOrder is the deterministic tie-break order used during hit-test
// when two primitives match a cursor at identical priority and
// distance. Smaller value wins.
export const KIND_ORDER: Record<PrimitiveKind, number> = {
	point: 0,
	line: 1,
	circle: 2,
};

// Camera describes the world point at the centre of the viewport and
// the scale (pixels per world unit). Pan/zoom mutate this struct;
// `pixi-viewport` keeps its own internal state and we mirror it here
// for hit-test and for tests that read camera state directly.
export interface Camera {
	centerX: number;
	centerY: number;
	scale: number;
}

export interface Viewport {
	widthPx: number;
	heightPx: number;
}

// World is the immutable container of primitives plus the toroidal
// dimensions. The renderer reindexes nothing — the brute-force
// hit-test walks all primitives on every pointer event, which is
// adequate for the ~1000-primitive Phase 9 budget.
export class World {
	readonly width: number;
	readonly height: number;
	readonly primitives: Primitive[];

	constructor(width: number, height: number, primitives: Primitive[] = []) {
		if (!(width > 0) || !(height > 0)) {
			throw new Error(`World: width and height must be positive, got ${width}×${height}`);
		}
		this.width = width;
		this.height = height;
		this.primitives = primitives;
	}
}

// Theme carries the default colours used when a primitive's `style`
// leaves a colour unset. Phase 9 ships a single dark theme; runtime
// theme switching is deferred to Phase 35.
export interface Theme {
	background: number;
	pointFill: number;
	circleStroke: number;
	lineStroke: number;
}

export const DARK_THEME: Theme = {
	background: 0x0a0e1a,
	pointFill: 0xe8eaf6,
	circleStroke: 0x4fc3f7,
	lineStroke: 0xa5d6a7,
};
