<!--
Phase 11 map active view: integrates the Phase 9 renderer with the
per-game `GameStateStore` provided through context by
`routes/games/[id]/+layout.svelte`. The view mounts the renderer
once the store has produced a report and re-mounts when the
report's turn changes (a refresh that returns the same turn keeps
the existing renderer instance alive). Empty-planet reports render
the empty world without errors — the regression test in
`tests/e2e/game-shell-map.spec.ts` covers this.

Phase 9 owns the renderer's hit-test and pan/zoom semantics; Phase 13
will plug map clicks into the inspector. Phase 29 wires the wrap-mode
toggle on top of the per-game `wrapMode` preference the store
already manages.
-->
<script lang="ts">
	import { getContext, onDestroy, onMount, untrack } from "svelte";
	import { i18n } from "$lib/i18n/index.svelte";
	import {
		createRenderer,
		minScaleNoWrap,
		type RendererHandle,
	} from "../../map/index";
	import { reportToWorld } from "../../map/state-binding";
	import {
		GAME_STATE_CONTEXT_KEY,
		type GameStateStore,
	} from "$lib/game-state.svelte";

	const store = getContext<GameStateStore | undefined>(GAME_STATE_CONTEXT_KEY);

	let canvasEl: HTMLCanvasElement | null = $state(null);
	let containerEl: HTMLDivElement | null = $state(null);
	let mountError: string | null = $state(null);

	let handle: RendererHandle | null = null;
	let mountedTurn: number | null = null;
	let mountedGameId: string | null = null;
	let onResize: (() => void) | null = null;
	let mounted = false;

	$effect(() => {
		const report = store?.report;
		const status = store?.status ?? "idle";
		// Track the wrap mode so the renderer remounts when Phase 29's
		// toggle UI flips it; the read here also subscribes the effect.
		const mode = store?.wrapMode ?? "torus";
		const gameId = store?.gameId ?? "";
		if (!mounted || canvasEl === null || containerEl === null) return;
		if (status !== "ready" || !report) return;

		// Skip a re-mount when the same turn is reloaded for the same
		// game and the wrap mode did not change. The store's `refresh`
		// path lands here on tab focus; an unchanged snapshot must not
		// flicker the canvas.
		const sameSnapshot =
			mountedTurn === report.turn &&
			mountedGameId === gameId &&
			handle !== null &&
			handle.getMode() === mode;
		if (sameSnapshot) return;

		untrack(() => {
			void mountRenderer(report, mode);
		});
	});

	async function mountRenderer(
		report: NonNullable<GameStateStore["report"]>,
		mode: "torus" | "no-wrap",
	): Promise<void> {
		if (canvasEl === null || containerEl === null) return;
		if (handle !== null) {
			handle.dispose();
			handle = null;
		}
		try {
			const world = reportToWorld(report);
			handle = await createRenderer({
				canvas: canvasEl,
				world,
				mode,
				preference: ["webgpu", "webgl"],
			});
			handle.viewport.moveCenter(world.width / 2, world.height / 2);
			const minScale = minScaleNoWrap(
				{
					widthPx: containerEl.clientWidth,
					heightPx: containerEl.clientHeight,
				},
				world,
			);
			handle.viewport.setZoom(minScale * 1.05, true);
			if (mode === "no-wrap") handle.setMode("no-wrap");
			mountedTurn = report.turn;
			mountedGameId = store?.gameId ?? "";
			mountError = null;
		} catch (err) {
			mountError = err instanceof Error ? err.message : String(err);
		}
	}

	onMount(() => {
		mounted = true;
		onResize = (): void => {
			if (handle === null || containerEl === null) return;
			handle.resize(containerEl.clientWidth, containerEl.clientHeight);
		};
		window.addEventListener("resize", onResize);
	});

	onDestroy(() => {
		mounted = false;
		if (onResize !== null) {
			window.removeEventListener("resize", onResize);
			onResize = null;
		}
		if (handle !== null) {
			handle.dispose();
			handle = null;
		}
	});
</script>

<section class="active-view" data-testid="active-view-map" data-status={store?.status ?? "idle"}>
	{#if store?.status === "error"}
		<p class="overlay error" role="alert" data-testid="map-error">
			{store.error ?? "request failed"}
		</p>
	{:else if mountError !== null}
		<p class="overlay error" role="alert" data-testid="map-mount-error">
			{mountError}
		</p>
	{:else if store?.status !== "ready"}
		<p class="overlay" data-testid="map-loading">{i18n.t("common.loading")}</p>
	{/if}
	<div
		class="canvas-wrap"
		data-testid="map-canvas-wrap"
		data-planet-count={store?.report?.planets.length ?? 0}
		bind:this={containerEl}
	>
		<canvas bind:this={canvasEl}></canvas>
	</div>
</section>

<style>
	.active-view {
		position: relative;
		display: flex;
		flex-direction: column;
		height: 100%;
		min-height: 0;
	}
	.canvas-wrap {
		flex: 1;
		min-height: 0;
		position: relative;
		overflow: hidden;
		background: #0a0e1a;
	}
	canvas {
		display: block;
		width: 100%;
		height: 100%;
	}
	.overlay {
		position: absolute;
		top: 0.75rem;
		left: 50%;
		transform: translateX(-50%);
		padding: 0.4rem 0.9rem;
		background: rgba(20, 24, 42, 0.85);
		color: #e8eaf6;
		border: 1px solid #2a3150;
		border-radius: 6px;
		z-index: 10;
		font-family: system-ui, sans-serif;
		font-size: 0.9rem;
		margin: 0;
	}
	.overlay.error {
		background: #4a1820;
		border-color: #6d2530;
		color: #ffb4b4;
	}
</style>
