// Phase 29 coverage for the categories + planetDependents maps that
// `reportToWorld` now returns. The map view consumes both to feed
// `RendererHandle.setHiddenPrimitiveIds`: categories drive the
// per-category toggle, planetDependents drive the cascade (planet
// hidden → markers + in-space + incoming groups flying to it hide
// together).

import { describe, expect, test } from "vitest";

import type {
	GameReport,
	ReportBattle,
	ReportBombing,
	ReportIncomingShipGroup,
	ReportLocalShipGroup,
	ReportOtherShipGroup,
	ReportPlanet,
	ReportUnidentifiedShipGroup,
} from "../src/api/game-state";
import { BATTLE_MARKER_ID_PREFIX, BOMBING_MARKER_ID_PREFIX } from "../src/map/battle-markers";
import { SHIP_GROUP_ID_OFFSETS } from "../src/map/ship-groups";
import { reportToWorld } from "../src/map/state-binding";
import { EMPTY_SHIP_GROUPS } from "./helpers/empty-ship-groups";

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

function makePlanet(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 makeLocalShipGroup(
	overrides: Partial<ReportLocalShipGroup>,
): ReportLocalShipGroup {
	return {
		id: "00000000-0000-0000-0000-000000000000",
		count: 1,
		class: "Scout",
		tech: { drive: 1, weapons: 0, shields: 0, cargo: 0 },
		cargo: "NONE",
		load: 0,
		destination: 0,
		origin: null,
		range: null,
		speed: 1,
		mass: 0,
		state: "InOrbit",
		fleet: null,
		...overrides,
	};
}

function makeOtherShipGroup(
	overrides: Partial<ReportOtherShipGroup>,
): ReportOtherShipGroup {
	return {
		count: 1,
		class: "Cruiser",
		tech: { drive: 1, weapons: 0, shields: 0, cargo: 0 },
		cargo: "NONE",
		load: 0,
		destination: 0,
		origin: null,
		range: null,
		speed: 1,
		mass: 0,
		...overrides,
	};
}

function makeIncoming(
	overrides: Partial<ReportIncomingShipGroup>,
): ReportIncomingShipGroup {
	return {
		origin: 0,
		destination: 0,
		distance: 0,
		speed: 1,
		mass: 1,
		...overrides,
	};
}

function makeUnidentified(
	overrides: Partial<ReportUnidentifiedShipGroup>,
): ReportUnidentifiedShipGroup {
	return { x: 0, y: 0, ...overrides };
}

function makeBattle(overrides: Partial<ReportBattle>): ReportBattle {
	return {
		id: "battle",
		planet: 0,
		shots: 1,
		...overrides,
	};
}

function makeBombing(overrides: Partial<ReportBombing>): ReportBombing {
	return {
		planetNumber: 0,
		planet: "",
		owner: "",
		attacker: "",
		production: "",
		industry: 0,
		population: 0,
		colonists: 0,
		industryStockpile: 0,
		materialsStockpile: 0,
		attackPower: 0,
		wiped: false,
		...overrides,
	};
}

describe("reportToWorld — categories", () => {
	test("planet primitives carry their kind-flavoured category", () => {
		const { categories } = reportToWorld(
			makeReport({
				planets: [
					makePlanet({ number: 1, kind: "local", x: 100, y: 100 }),
					makePlanet({ number: 2, kind: "other", x: 200, y: 200 }),
					makePlanet({ number: 3, kind: "uninhabited", x: 300, y: 300 }),
					makePlanet({ number: 4, kind: "unidentified", x: 400, y: 400 }),
				],
			}),
		);
		expect(categories.get(1)).toBe("planet-local");
		expect(categories.get(2)).toBe("planet-foreign");
		expect(categories.get(3)).toBe("planet-uninhabited");
		expect(categories.get(4)).toBe("planet-unidentified");
	});

	test("ship-group sub-builder tags every primitive id", () => {
		const localGroupPrimId = SHIP_GROUP_ID_OFFSETS.local + 0;
		const localLineId = SHIP_GROUP_ID_OFFSETS.localLine + 0;
		const otherId = SHIP_GROUP_ID_OFFSETS.other + 0;
		const incomingPointId = SHIP_GROUP_ID_OFFSETS.incoming + 0;
		const incomingLineId = SHIP_GROUP_ID_OFFSETS.incomingLine + 0;
		const unidentifiedId = SHIP_GROUP_ID_OFFSETS.unidentified + 0;
		const { categories } = reportToWorld(
			makeReport({
				planets: [
					makePlanet({ number: 1, kind: "local", x: 100, y: 100 }),
					makePlanet({ number: 2, kind: "other", x: 200, y: 200 }),
				],
				localShipGroups: [
					makeLocalShipGroup({ origin: 1, range: 10, destination: 2 }),
				],
				otherShipGroups: [
					makeOtherShipGroup({ origin: 1, range: 10, destination: 2 }),
				],
				incomingShipGroups: [
					makeIncoming({ origin: 1, destination: 2, distance: 5 }),
				],
				unidentifiedShipGroups: [makeUnidentified({ x: 500, y: 500 })],
			}),
		);
		expect(categories.get(localGroupPrimId)).toBe("hyperspaceGroup");
		expect(categories.get(localLineId)).toBe("hyperspaceGroup");
		expect(categories.get(otherId)).toBe("hyperspaceGroup");
		expect(categories.get(incomingPointId)).toBe("incomingGroup");
		expect(categories.get(incomingLineId)).toBe("incomingGroup");
		expect(categories.get(unidentifiedId)).toBe("unidentifiedGroup");
	});

	test("battle markers and bombing markers each carry their own category", () => {
		const { categories } = reportToWorld(
			makeReport({
				planets: [
					makePlanet({ number: 1, kind: "local", x: 100, y: 100 }),
					makePlanet({ number: 2, kind: "other", x: 200, y: 200 }),
				],
				battles: [makeBattle({ id: "b1", planet: 2 })],
				bombings: [makeBombing({ planetNumber: 2 })],
			}),
		);
		// Battle marker emits two LinePrims at `BATTLE_MARKER_ID_PREFIX | (i << 4) | (A|B)`.
		const battleA = BATTLE_MARKER_ID_PREFIX | (0 << 4) | 0;
		const battleB = BATTLE_MARKER_ID_PREFIX | (0 << 4) | 1;
		expect(categories.get(battleA)).toBe("battleMarker");
		expect(categories.get(battleB)).toBe("battleMarker");
		const bombingId = BOMBING_MARKER_ID_PREFIX | 0;
		expect(categories.get(bombingId)).toBe("bombingMarker");
	});
});

describe("reportToWorld — planetDependents", () => {
	test("every planet seeds its own dependents entry with its own id", () => {
		const { planetDependents } = reportToWorld(
			makeReport({
				planets: [
					makePlanet({ number: 1, kind: "local", x: 100, y: 100 }),
					makePlanet({ number: 7, kind: "other", x: 200, y: 200 }),
				],
			}),
		);
		expect(planetDependents.get(1)?.has(1)).toBe(true);
		expect(planetDependents.get(7)?.has(7)).toBe(true);
	});

	test("battle / bombing markers cascade onto their anchor planet", () => {
		const { planetDependents } = reportToWorld(
			makeReport({
				planets: [
					makePlanet({ number: 1, kind: "local", x: 100, y: 100 }),
					makePlanet({ number: 2, kind: "other", x: 200, y: 200 }),
				],
				battles: [makeBattle({ planet: 2 })],
				bombings: [makeBombing({ planetNumber: 2 })],
			}),
		);
		const battleA = BATTLE_MARKER_ID_PREFIX | (0 << 4) | 0;
		const battleB = BATTLE_MARKER_ID_PREFIX | (0 << 4) | 1;
		const bombingId = BOMBING_MARKER_ID_PREFIX | 0;
		const deps = planetDependents.get(2) ?? new Set();
		expect(deps.has(2)).toBe(true);
		expect(deps.has(battleA)).toBe(true);
		expect(deps.has(battleB)).toBe(true);
		expect(deps.has(bombingId)).toBe(true);
	});

	test("in-space groups cascade onto their destination planet", () => {
		const { planetDependents } = reportToWorld(
			makeReport({
				planets: [
					makePlanet({ number: 1, kind: "local", x: 100, y: 100 }),
					makePlanet({ number: 2, kind: "other", x: 200, y: 200 }),
				],
				localShipGroups: [
					makeLocalShipGroup({ origin: 1, range: 10, destination: 2 }),
				],
				otherShipGroups: [
					makeOtherShipGroup({ origin: 1, range: 10, destination: 2 }),
				],
			}),
		);
		const localPointId = SHIP_GROUP_ID_OFFSETS.local + 0;
		const localLineId = SHIP_GROUP_ID_OFFSETS.localLine + 0;
		const otherId = SHIP_GROUP_ID_OFFSETS.other + 0;
		const deps = planetDependents.get(2) ?? new Set();
		expect(deps.has(localPointId)).toBe(true);
		expect(deps.has(localLineId)).toBe(true);
		expect(deps.has(otherId)).toBe(true);
	});

	test("incoming groups cascade onto their destination planet", () => {
		const { planetDependents } = reportToWorld(
			makeReport({
				planets: [
					makePlanet({ number: 1, kind: "local", x: 100, y: 100 }),
					makePlanet({ number: 2, kind: "other", x: 200, y: 200 }),
				],
				incomingShipGroups: [
					makeIncoming({ origin: 1, destination: 2, distance: 5 }),
				],
			}),
		);
		const incomingPointId = SHIP_GROUP_ID_OFFSETS.incoming + 0;
		const incomingLineId = SHIP_GROUP_ID_OFFSETS.incomingLine + 0;
		const deps = planetDependents.get(2) ?? new Set();
		expect(deps.has(incomingPointId)).toBe(true);
		expect(deps.has(incomingLineId)).toBe(true);
	});

	test("unidentified groups do not contribute to any planet's dependents", () => {
		const { planetDependents } = reportToWorld(
			makeReport({
				planets: [makePlanet({ number: 1, kind: "local", x: 100, y: 100 })],
				unidentifiedShipGroups: [makeUnidentified({ x: 500, y: 500 })],
			}),
		);
		// Only the local planet seeds its own entry; no other entries.
		expect(planetDependents.size).toBe(1);
		expect(planetDependents.get(1)?.size).toBe(1);
	});
});
