// Pure-state coverage for the pick-mode overlay helper. The
// renderer owns the Pixi side (`render.ts.openPickMode`); this file
// asserts that `computePickOverlay` produces the correct draw spec
// for every meaningful input combination — Pixi-free, so it stays
// fast and stable against renderer plumbing changes.

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

import {
	ANCHOR_PADDING_WORLD,
	HOVER_PADDING_WORLD,
	computePickOverlay,
	type PickModeOptions,
} from "../src/map/pick-mode";
import {
	DEFAULT_POINT_RADIUS_PX,
	type PointPrim,
	type PrimitiveID,
} from "../src/map/world";

function makePoint(
	id: PrimitiveID,
	x: number,
	y: number,
	pointRadiusPx?: number,
): PointPrim {
	return {
		kind: "point",
		id,
		priority: 0,
		hitSlopPx: 0,
		x,
		y,
		style: pointRadiusPx === undefined ? {} : { pointRadiusPx },
	};
}

function makeOptions(
	overrides: Partial<PickModeOptions> = {},
): PickModeOptions {
	return {
		sourcePrimitiveId: 1,
		sourceX: 100,
		sourceY: 100,
		reachableIds: new Set([2, 3]),
		onPick: () => {},
		...overrides,
	};
}

describe("computePickOverlay", () => {
	const points = new Map<PrimitiveID, PointPrim>([
		[1, makePoint(1, 100, 100, 6)],
		[2, makePoint(2, 200, 100, 5)],
		[3, makePoint(3, 100, 200)],
		[4, makePoint(4, 300, 300, 4)],
	]);
	const allIds: PrimitiveID[] = [1, 2, 3, 4];

	test("anchor radius equals source pointRadiusPx + ANCHOR_PADDING_WORLD", () => {
		const spec = computePickOverlay(makeOptions(), null, null, points, allIds);
		expect(spec.anchor.x).toBe(100);
		expect(spec.anchor.y).toBe(100);
		expect(spec.anchor.radius).toBe(6 + ANCHOR_PADDING_WORLD);
	});

	test("anchor radius falls back to default when source has no pointRadiusPx", () => {
		const sourceless = new Map(points);
		sourceless.set(1, makePoint(1, 100, 100));
		const spec = computePickOverlay(
			makeOptions(),
			null,
			null,
			sourceless,
			allIds,
		);
		expect(spec.anchor.radius).toBe(
			DEFAULT_POINT_RADIUS_PX + ANCHOR_PADDING_WORLD,
		);
	});

	test("dimmedIds covers everything outside source + reachable", () => {
		const spec = computePickOverlay(makeOptions(), null, null, points, allIds);
		expect(Array.from(spec.dimmedIds).sort()).toEqual([4]);
	});

	test("dimmedIds is empty when every primitive is either source or reachable", () => {
		const spec = computePickOverlay(
			makeOptions({ reachableIds: new Set([2, 3, 4]) }),
			null,
			null,
			points,
			allIds,
		);
		expect(spec.dimmedIds.size).toBe(0);
	});

	test("line is null while the cursor is off-canvas", () => {
		const spec = computePickOverlay(makeOptions(), null, null, points, allIds);
		expect(spec.line).toBeNull();
	});

	test("line endpoints follow the cursor when present", () => {
		const spec = computePickOverlay(
			makeOptions(),
			{ x: 250, y: 320 },
			null,
			points,
			allIds,
		);
		expect(spec.line).toEqual({
			x1: 100,
			y1: 100,
			x2: 250,
			y2: 320,
		});
	});

	test("hoverOutline is null when nothing is hovered", () => {
		const spec = computePickOverlay(
			makeOptions(),
			{ x: 1, y: 1 },
			null,
			points,
			allIds,
		);
		expect(spec.hoverOutline).toBeNull();
	});

	test("hoverOutline is null when the hover targets a non-reachable primitive", () => {
		const spec = computePickOverlay(
			makeOptions(),
			{ x: 1, y: 1 },
			4,
			points,
			allIds,
		);
		expect(spec.hoverOutline).toBeNull();
	});

	test("hoverOutline is null when the hover targets the source planet", () => {
		const spec = computePickOverlay(
			makeOptions(),
			{ x: 1, y: 1 },
			1,
			points,
			allIds,
		);
		expect(spec.hoverOutline).toBeNull();
	});

	test("hoverOutline reflects the reachable target with HOVER_PADDING_WORLD", () => {
		const spec = computePickOverlay(
			makeOptions(),
			{ x: 1, y: 1 },
			2,
			points,
			allIds,
		);
		expect(spec.hoverOutline).toEqual({
			x: 200,
			y: 100,
			radius: 5 + HOVER_PADDING_WORLD,
		});
	});

	test("hoverOutline radius falls back to default radius for default-style points", () => {
		const spec = computePickOverlay(
			makeOptions(),
			{ x: 1, y: 1 },
			3,
			points,
			allIds,
		);
		expect(spec.hoverOutline?.radius).toBe(
			DEFAULT_POINT_RADIUS_PX + HOVER_PADDING_WORLD,
		);
	});
});
