// Vitest coverage for the Phase 19 ship-group → World binding. The
// `reportToWorld` function now blends planet and ship-group
// primitives in one pass and returns a hitLookup map keyed by the
// primitive id; these tests assert that each ship-group variant
// (own on-planet, own in-hyperspace, foreign in-hyperspace,
// incoming, unidentified) shows up with the expected position,
// style, priority, and lookup entry.

import "@testing-library/jest-dom/vitest";
import { describe, expect, test } from "vitest";

import type {
	GameReport,
	ReportPlanet,
} from "../src/api/game-state";
import { reportToWorld } from "../src/map/state-binding";
import { SHIP_GROUP_ID_OFFSETS } from "../src/map/ship-groups";
import { EMPTY_SHIP_GROUPS } from "./helpers/empty-ship-groups";

function planet(overrides: Partial<ReportPlanet>): ReportPlanet {
	return {
		number: 0,
		name: "",
		x: 0,
		y: 0,
		kind: "local",
		owner: null,
		size: null,
		resources: null,
		industryStockpile: null,
		materialsStockpile: null,
		industry: null,
		population: null,
		colonists: null,
		production: null,
		freeIndustry: null,
		...overrides,
	};
}

function makeReport(overrides: Partial<GameReport> = {}): GameReport {
	return {
		turn: 1,
		mapWidth: 1000,
		mapHeight: 1000,
		planetCount: 0,
		planets: [],
		race: "Earthlings",
		localShipClass: [],
		routes: [],
		localPlayerDrive: 0,
		localPlayerWeapons: 0,
		localPlayerShields: 0,
		localPlayerCargo: 0,
		...EMPTY_SHIP_GROUPS,
		...overrides,
	};
}

describe("reportToWorld — ship groups", () => {
	test("on-planet local group is NOT rendered on the map (planet inspector hosts it)", () => {
		const home = planet({ number: 17, x: 100, y: 100, kind: "local" });
		const { world, hitLookup } = reportToWorld(
			makeReport({
				planets: [home],
				localShipGroups: [
					{
						id: "uuid-local-1",
						count: 2,
						class: "Frontier",
						tech: { drive: 5, weapons: 0, shields: 0, cargo: 1 },
						cargo: "NONE",
						load: 0,
						destination: 17,
						origin: null,
						range: null,
						speed: 0,
						mass: 12,
						state: "In_Orbit",
						fleet: null,
					},
				],
			}),
		);
		// Only the planet itself contributes a primitive; the on-planet
		// group is intentionally invisible on the map. Phase 19's
		// `lib/inspectors/planet/ship-groups.svelte` lists it inside the
		// planet inspector instead.
		expect(world.primitives.length).toBe(1);
		expect(hitLookup.has(SHIP_GROUP_ID_OFFSETS.local + 0)).toBe(false);
		expect(hitLookup.get(17)).toEqual({ kind: "planet", number: 17 });
	});

	test("in-hyperspace local group renders at the interpolated position", () => {
		const dest = planet({ number: 1, x: 0, y: 0 });
		const orig = planet({ number: 2, x: 100, y: 0 });
		const { world } = reportToWorld(
			makeReport({
				planets: [dest, orig],
				localShipGroups: [
					{
						id: "uuid-local-fly",
						count: 1,
						class: "Cruiser",
						tech: { drive: 10, weapons: 0, shields: 0, cargo: 0 },
						cargo: "NONE",
						load: 0,
						destination: 1,
						origin: 2,
						range: 25,
						speed: 0,
						mass: 50,
						state: "In_Space",
						fleet: null,
					},
				],
			}),
		);
		const groupPrimId = SHIP_GROUP_ID_OFFSETS.local + 0;
		const group = world.primitives.find((p) => p.id === groupPrimId);
		if (group?.kind !== "point") throw new Error("expected point");
		// dest=(0,0), orig=(100,0), range=25 → 25 units toward orig from
		// dest along the segment of length 100 → (25, 0).
		expect(group.x).toBe(25);
		expect(group.y).toBe(0);

		// Yellow dashed track from origin to destination matches the
		// in-space point colour.
		const lineId = SHIP_GROUP_ID_OFFSETS.localLine + 0;
		const line = world.primitives.find((p) => p.id === lineId);
		if (line?.kind !== "line") throw new Error("expected line");
		expect(line.x1).toBe(100);
		expect(line.y1).toBe(0);
		expect(line.x2).toBe(0);
		expect(line.y2).toBe(0);
		expect(line.style.strokeColor).toBe(0xfff176);
		expect(line.style.strokeDashPx).toBeGreaterThan(0);
	});

	test("incoming-group line crosses the torus seam via the shortest path", () => {
		const dest = planet({ number: 1, x: 5, y: 50 });
		const orig = planet({ number: 9, x: 95, y: 50 });
		const { world } = reportToWorld(
			makeReport({
				mapWidth: 100,
				mapHeight: 100,
				planets: [dest, orig],
				incomingShipGroups: [
					{
						origin: 9,
						destination: 1,
						distance: 5,
						speed: 5,
						mass: 1,
					},
				],
			}),
		);
		const line = world.primitives.find(
			(p) => p.id === SHIP_GROUP_ID_OFFSETS.incomingLine + 0,
		);
		if (line?.kind !== "line") throw new Error("expected line");
		// Origin (95) → unwrapped destination at 105 (origin.x + (-10) is
		// the no-wrap path). The shortest delta from 95 to 5 on width 100
		// is +10, so we expect line.x2 = 95 + 10 = 105.
		expect(line.x1).toBe(95);
		expect(line.x2).toBe(105);
	});

	test("incoming group emits one dashed line + one clickable point", () => {
		const dest = planet({ number: 1, x: 0, y: 0 });
		const orig = planet({ number: 9, x: 100, y: 0 });
		const { world, hitLookup } = reportToWorld(
			makeReport({
				planets: [dest, orig],
				incomingShipGroups: [
					{
						origin: 9,
						destination: 1,
						distance: 40,
						speed: 20,
						mass: 4,
					},
				],
			}),
		);
		const lineId = SHIP_GROUP_ID_OFFSETS.incomingLine + 0;
		const pointId = SHIP_GROUP_ID_OFFSETS.incoming + 0;
		const line = world.primitives.find((p) => p.id === lineId);
		if (line?.kind !== "line") throw new Error("expected line for incoming");
		expect(line.x1).toBe(100); // origin
		expect(line.x2).toBe(0); // destination
		expect(line.style.strokeDashPx).toBeGreaterThan(0);
		const point = world.primitives.find((p) => p.id === pointId);
		if (point?.kind !== "point") throw new Error("expected point for incoming");
		expect(point.x).toBe(40); // distance=40 from dest along line of len 100
		expect(point.y).toBe(0);
		// Hit lookup is registered only for the clickable point, not
		// the dashed trajectory line.
		expect(hitLookup.get(pointId)).toEqual({
			kind: "shipGroup",
			ref: { variant: "incoming", index: 0 },
		});
		expect(hitLookup.has(lineId)).toBe(false);
	});

	test("unidentified group renders at its absolute coordinates", () => {
		const { world, hitLookup } = reportToWorld(
			makeReport({
				unidentifiedShipGroups: [{ x: 555, y: 222 }],
			}),
		);
		const id = SHIP_GROUP_ID_OFFSETS.unidentified + 0;
		const point = world.primitives.find((p) => p.id === id);
		if (point?.kind !== "point") throw new Error("expected point");
		expect(point.x).toBe(555);
		expect(point.y).toBe(222);
		expect(hitLookup.get(id)).toEqual({
			kind: "shipGroup",
			ref: { variant: "unidentified", index: 0 },
		});
	});

	test("group whose destination is missing from the report is dropped", () => {
		const { world } = reportToWorld(
			makeReport({
				planets: [],
				localShipGroups: [
					{
						id: "uuid-orphan",
						count: 1,
						class: "Drone",
						tech: { drive: 1, weapons: 0, shields: 0, cargo: 0 },
						cargo: "NONE",
						load: 0,
						destination: 999, // not in planets
						origin: null,
						range: null,
						speed: 0,
						mass: 1,
						state: "In_Orbit",
						fleet: null,
					},
				],
			}),
		);
		// Only the (empty) planet list contributes — no group primitive.
		expect(world.primitives.length).toBe(0);
	});

	test("planet hitLookup entries are registered alongside ship groups", () => {
		const { hitLookup } = reportToWorld(
			makeReport({
				planets: [planet({ number: 42, x: 0, y: 0, kind: "local" })],
			}),
		);
		expect(hitLookup.get(42)).toEqual({ kind: "planet", number: 42 });
	});
});
