// Phase 27 battle markers on the map. Bombing markers used to live
// here as a separate ring primitive but F8-12 / #30 turned them into
// a planet-outline overlay drawn by `render.ts.setPlanetOutlines`,
// driven from `map.svelte`. The remaining surface here is the battle
// X-cross:
//
//   * Battle marker — an X cross drawn through the corners of the
//     square that circumscribes the planet circle. Two yellow
//     LinePrim, stroke width scales linearly with the number of
//     shots: 1 shot → 1px, 100+ shots → 5px (capped). Clicking
//     either line opens the Battle Viewer for the corresponding
//     UUID.

import type { GameReport, ReportPlanet } from "../api/game-state";
import {
	DARK_THEME,
	type LinePrim,
	type Primitive,
	type PrimitiveID,
	type Style,
	type Theme,
} from "./world";

/** Battle marker primitive ids use a high-bit prefix to avoid
 * colliding with planet numbers or cargo-route line ids. */
export const BATTLE_MARKER_ID_PREFIX = 0xa0000000;

const PLANET_RADIUS_WORLD = 6;
const BATTLE_CROSS_HALF = PLANET_RADIUS_WORLD + 2;

/** Battle marker priority sits between planets (1..4) and cargo
 * routes; the cross is over the planet but loses clicks against the
 * planet glyph itself. */
const BATTLE_MARKER_PRIORITY = 9;

const BATTLE_LINE_INDEX_A = 0;
const BATTLE_LINE_INDEX_B = 1;

export interface BattleMarkerTarget {
	kind: "battle";
	battleId: string;
	planet: number;
}

export type MarkerTarget = BattleMarkerTarget;

/**
 * MarkerCategory tags every emitted primitive with the toggleable
 * surface it belongs to so the Phase 29 hide-set machinery can flip
 * each independently. Battle markers are the only category left here;
 * the `bombingMarker` toggle now hides the planet-outline overlay
 * built in `map.svelte.applyPlanetOutlines` (F8-12 / #30).
 */
export type MarkerCategory = "battleMarker";

export interface BuildMarkersResult {
	primitives: Primitive[];
	lookup: Map<PrimitiveID, MarkerTarget>;
	categories: Map<PrimitiveID, MarkerCategory>;
	/**
	 * planetDependents maps the anchor planet number to the ids of
	 * markers drawn on it; the Phase 29 cascade hides the markers
	 * together with the planet when the planet itself is filtered out
	 * (kind toggle off or unreachable filter on).
	 */
	planetDependents: Map<number, Set<PrimitiveID>>;
}

/**
 * battleMarkerStrokeWidth maps a battle's `shots` count to a stroke
 * width in pixels. 1 shot → 1 px (the thinnest visible), 100+ shots
 * → 5 px (the cap). Linearly interpolated between those bounds.
 */
export function battleMarkerStrokeWidth(shots: number): number {
	if (shots <= 1) return 1;
	if (shots >= 100) return 5;
	return 1 + ((shots - 1) * 4) / 99;
}

/**
 * buildBattleAndBombingMarkers emits battle X-cross primitives plus a
 * hit-lookup mapping for the current-turn report. Battles whose
 * planet is not visible (e.g. observer-only without a report.planets
 * entry) are skipped — they have no on-map location to anchor
 * against. Bombing visuals are no longer produced here (F8-12 / #30);
 * the renderer paints them as a planet-outline overlay driven from
 * `map.svelte.applyPlanetOutlines`.
 */
export function buildBattleAndBombingMarkers(
	report: GameReport,
	theme: Theme = DARK_THEME,
): BuildMarkersResult {
	const planetByNumber = new Map<number, ReportPlanet>();
	for (const planet of report.planets) {
		planetByNumber.set(planet.number, planet);
	}

	const primitives: Primitive[] = [];
	const lookup = new Map<PrimitiveID, MarkerTarget>();
	const categories = new Map<PrimitiveID, MarkerCategory>();
	const planetDependents = new Map<number, Set<PrimitiveID>>();
	const addDependent = (planetNumber: number, id: PrimitiveID): void => {
		let set = planetDependents.get(planetNumber);
		if (set === undefined) {
			set = new Set();
			planetDependents.set(planetNumber, set);
		}
		set.add(id);
	};

	for (let i = 0; i < report.battles.length; i++) {
		const battle = report.battles[i];
		const planet = planetByNumber.get(battle.planet);
		if (planet === undefined) continue;
		const strokeWidthPx = battleMarkerStrokeWidth(battle.shots);
		const style: Style = {
			strokeColor: theme.battleMarker,
			strokeAlpha: 0.95,
			strokeWidthPx,
			// F8-12 / #4 follow-up: grow the X-cross length sub-linearly
			// with zoom (the planet disc does the same, so the marker
			// stays proportional). Endpoints listed below are the "at
			// reference scale" geometry.
			softLengthAnchor: "center",
		};
		const baseId = BATTLE_MARKER_ID_PREFIX | (i << 4);
		const lineA: LinePrim = {
			kind: "line",
			id: baseId | BATTLE_LINE_INDEX_A,
			priority: BATTLE_MARKER_PRIORITY,
			style,
			hitSlopPx: 0,
			x1: planet.x - BATTLE_CROSS_HALF,
			y1: planet.y - BATTLE_CROSS_HALF,
			x2: planet.x + BATTLE_CROSS_HALF,
			y2: planet.y + BATTLE_CROSS_HALF,
		};
		const lineB: LinePrim = {
			kind: "line",
			id: baseId | BATTLE_LINE_INDEX_B,
			priority: BATTLE_MARKER_PRIORITY,
			style,
			hitSlopPx: 0,
			x1: planet.x - BATTLE_CROSS_HALF,
			y1: planet.y + BATTLE_CROSS_HALF,
			x2: planet.x + BATTLE_CROSS_HALF,
			y2: planet.y - BATTLE_CROSS_HALF,
		};
		const target: BattleMarkerTarget = {
			kind: "battle",
			battleId: battle.id,
			planet: battle.planet,
		};
		primitives.push(lineA, lineB);
		lookup.set(lineA.id, target);
		lookup.set(lineB.id, target);
		categories.set(lineA.id, "battleMarker");
		categories.set(lineB.id, "battleMarker");
		addDependent(battle.planet, lineA.id);
		addDependent(battle.planet, lineB.id);
	}

	// Bombing visuals are produced by `setPlanetOutlines` in the
	// renderer (F8-12 / #30); the data still lives on
	// `report.bombings`, but no primitive is emitted here.
	return { primitives, lookup, categories, planetDependents };
}
