<!--
Phase 27 — active-view wrapper around the BattleViewer. Loads the
BattleReport for the supplied `gameId`/`turn`/`battleId` and either
shows the radial playback (BattleViewer), a loading skeleton, or a
not-found state.

This wrapper also bridges the surrounding GameReport's ship-class
tables into a `(race, className) → ShipClassRef` lookup the viewer
needs to size class circles by ship mass. The back-navigation
buttons (`back to map` / `back to report`) live INSIDE the viewer
header now — we just hand the routes down as callbacks so the
viewer keeps its prop-driven contract.
-->
<script lang="ts">
	import { getContext } from "svelte";
	import { goto } from "$app/navigation";

	import {
		BattleFetchError,
		fetchBattle,
		type BattleReport,
	} from "../../api/battle-fetch";
	import { i18n } from "$lib/i18n/index.svelte";
	import {
		RENDERED_REPORT_CONTEXT_KEY,
		type RenderedReportSource,
	} from "$lib/rendered-report.svelte";
	import {
		MapShipClassLookup,
		type ShipClassLookup,
		type ShipClassRef,
	} from "../battle-player/mass";

	import BattleViewer from "../battle-player/battle-viewer.svelte";

	let {
		gameId,
		turn,
		battleId,
	}: {
		gameId: string;
		turn: number;
		battleId: string;
	} = $props();

	const rendered = getContext<RenderedReportSource | undefined>(
		RENDERED_REPORT_CONTEXT_KEY,
	);

	const shipClassLookup = $derived.by<ShipClassLookup>(() => {
		const map = new Map<string, ShipClassRef>();
		const report = rendered?.report;
		if (report) {
			for (const cls of report.localShipClass) {
				map.set(`${report.race}::${cls.name}`, {
					drive: cls.drive,
					weapons: cls.weapons,
					armament: cls.armament,
					shields: cls.shields,
					cargo: cls.cargo,
				});
			}
			for (const cls of report.otherShipClass) {
				map.set(`${cls.race}::${cls.name}`, {
					drive: cls.drive,
					weapons: cls.weapons,
					armament: cls.armament,
					shields: cls.shields,
					cargo: cls.cargo,
				});
			}
		}
		return new MapShipClassLookup(map);
	});

	let state = $state<
		| { kind: "loading" }
		| { kind: "ready"; report: BattleReport }
		| { kind: "not_found" }
		| { kind: "error"; message: string }
	>({ kind: "loading" });

	$effect(() => {
		if (!battleId) {
			state = { kind: "not_found" };
			return;
		}
		state = { kind: "loading" };
		fetchBattle(gameId, turn, battleId)
			.then((report) => {
				state = { kind: "ready", report };
			})
			.catch((err: unknown) => {
				if (err instanceof BattleFetchError && err.status === 404) {
					state = { kind: "not_found" };
				} else {
					state = {
						kind: "error",
						message: err instanceof Error ? err.message : String(err),
					};
				}
			});
	});

	function backToReport() {
		goto(`/games/${gameId}/report`);
	}
	function backToMap() {
		goto(`/games/${gameId}/map`);
	}
</script>

<section
	class="active-view"
	data-testid="active-view-battle"
	data-battle-id={battleId}
>
	{#if state.kind === "loading"}
		<p class="status" data-testid="battle-loading">
			{i18n.t("game.battle.loading")}
		</p>
	{:else if state.kind === "ready"}
		<BattleViewer
			report={state.report}
			{shipClassLookup}
			onBackToMap={backToMap}
			onBackToReport={backToReport}
		/>
	{:else if state.kind === "not_found"}
		<p class="status" data-testid="battle-not-found">
			{i18n.t("game.battle.not_found")}
		</p>
	{:else}
		<p class="status error" data-testid="battle-error">{state.message}</p>
	{/if}
</section>

<style>
	.active-view {
		display: flex;
		flex-direction: column;
		/*
		 * The in-game shell renders this active view inside an
		 * `.active-view-host` with `flex: 1; overflow-y: auto`, but
		 * the surrounding `.game-shell` uses `min-height: 100vh`, so
		 * without a hard upper bound the viewer pushes the whole
		 * shell past the viewport. We pin the active view to `100dvh`
		 * minus a small allowance for the header chrome (in-game
		 * Header + optional HistoryBanner ≈ 66 px on desktop) so the
		 * internal flex chain can split the remaining height between
		 * the scene, scrubber, controls and log without forcing a
		 * page-level scroll.
		 */
		height: calc(100dvh - 80px);
		max-height: calc(100dvh - 80px);
		min-height: 0;
		overflow: hidden;
		box-sizing: border-box;
		font-family: system-ui, sans-serif;
		color: #d6dcf2;
	}
	.status {
		margin: 2rem auto;
		max-width: 880px;
		color: #93a0d0;
		font-size: 0.95rem;
		text-align: center;
	}
	.status.error {
		color: #e08585;
	}
</style>
