// State binding between the typed game report and the renderer's
// World. Phase 11 emitted primitives only for planets; Phase 19
// extends the binding with ship-group primitives (own / foreign / in-
// hyperspace / incoming / unidentified) plus a `hitLookup` map so the
// click handler can dispatch a renderer-side hit back to the right
// selection variant. Later phases extend with ship-class reach
// circles (Phase 17 / 18 in `ui/core/calc/`), reach / visibility
// zones, and battle / bombing markers (Phase 27).
//
// The four planet kinds in the report each map to a distinct style so
// the user can tell own / other-race / uninhabited / unidentified
// planets apart at a glance. The exact colours are Phase 11 defaults
// chosen against the dark theme; Phase 35 polish picks final
// colours and adds theme switching.

import type { GameReport, ReportPlanet } from "../api/game-state";
import type { ShipGroupRef } from "../lib/selection.svelte";
import { buildBattleAndBombingMarkers } from "./battle-markers";
import { shipGroupsToPrimitives } from "./ship-groups";
import { World, type Primitive, type PrimitiveID, type Style } from "./world";

const STYLE_LOCAL: Style = {
	fillColor: 0x6dd2ff,
	fillAlpha: 1,
	pointRadiusPx: 6,
};

const STYLE_OTHER: Style = {
	fillColor: 0xff8a65,
	fillAlpha: 1,
	pointRadiusPx: 5,
};

const STYLE_UNINHABITED: Style = {
	fillColor: 0xb0bec5,
	fillAlpha: 0.85,
	pointRadiusPx: 4,
};

const STYLE_UNIDENTIFIED: Style = {
	fillColor: 0x546e7a,
	fillAlpha: 0.7,
	pointRadiusPx: 3,
};

// PlanetIDs occupy the [0, 4_000_000_000) range — well below
// JavaScript's `Number.MAX_SAFE_INTEGER` — so the engine `number`
// (uint64) fits in a primitive id (number) without truncation. The
// binding uses the engine number directly as the primitive id so the
// click handler can recover a planet by hit-test result without an
// extra lookup.
function styleFor(kind: ReportPlanet["kind"]): Style {
	switch (kind) {
		case "local":
			return STYLE_LOCAL;
		case "other":
			return STYLE_OTHER;
		case "uninhabited":
			return STYLE_UNINHABITED;
		case "unidentified":
			return STYLE_UNIDENTIFIED;
	}
}

function priorityFor(kind: ReportPlanet["kind"]): number {
	switch (kind) {
		case "local":
			return 4;
		case "other":
			return 3;
		case "uninhabited":
			return 2;
		case "unidentified":
			return 1;
	}
}

/**
 * HitTarget describes which game entity a renderer-side hit-test
 * resolves to. The click handler in `lib/active-view/map.svelte`
 * looks the hit primitive's id up in the binding's hitLookup map
 * and dispatches `selection.selectPlanet` or
 * `selection.selectShipGroup` accordingly.
 */
export type HitTarget =
	| { kind: "planet"; number: number }
	| { kind: "shipGroup"; ref: ShipGroupRef }
	| { kind: "battle"; battleId: string; planet: number }
	| { kind: "bombing"; planet: number };

export interface ReportToWorldResult {
	world: World;
	hitLookup: Map<PrimitiveID, HitTarget>;
}

/**
 * reportToWorld translates a GameReport into a renderer-ready World
 * containing one Point primitive per planet (all four planet kinds)
 * plus the Phase 19 ship-group surface — own / foreign groups
 * (on-planet or in-hyperspace), incoming groups (dashed trajectory
 * line + clickable point), and unidentified-group blips. The world
 * rectangle matches `report.mapWidth` × `report.mapHeight`.
 *
 * If the report carries zero planets (turn-zero edge cases or seeded
 * tests), the World is still well-formed: the renderer mounts on an
 * empty primitive list without errors.
 */
export function reportToWorld(report: GameReport): ReportToWorldResult {
	const primitives: Primitive[] = [];
	const hitLookup = new Map<PrimitiveID, HitTarget>();

	for (const planet of report.planets) {
		primitives.push({
			kind: "point",
			id: planet.number,
			priority: priorityFor(planet.kind),
			style: styleFor(planet.kind),
			hitSlopPx: 0,
			x: planet.x,
			y: planet.y,
		});
		hitLookup.set(planet.number, { kind: "planet", number: planet.number });
	}

	const groups = shipGroupsToPrimitives(report);
	for (const prim of groups.primitives) {
		primitives.push(prim);
	}
	for (const [primId, ref] of groups.lookup) {
		hitLookup.set(primId, { kind: "shipGroup", ref });
	}

	const markers = buildBattleAndBombingMarkers(report);
	for (const prim of markers.primitives) {
		primitives.push(prim);
	}
	for (const [primId, target] of markers.lookup) {
		hitLookup.set(primId, target);
	}

	const width = report.mapWidth > 0 ? report.mapWidth : 1;
	const height = report.mapHeight > 0 ? report.mapHeight : 1;
	return { world: new World(width, height, primitives), hitLookup };
}
