<script lang="ts">
	import { onDestroy, onMount } from "svelte";
	import { page } from "$app/state";
	import {
		createRenderer,
		minScaleNoWrap,
		sampleWorld,
		type RendererHandle,
		type RendererPreference,
		type WrapMode,
	} from "../../../map/index";

	interface DebugMapSurface {
		ready: true;
		getMode(): WrapMode;
		setMode(mode: WrapMode): void;
		getCamera(): { centerX: number; centerY: number; scale: number };
		setCameraCenter(centerX: number, centerY: number): void;
		getViewport(): { widthPx: number; heightPx: number };
		getBackend(): string;
		getWorldSize(): { width: number; height: number };
		hitAt(x: number, y: number): number | null;
	}

	type DebugMapWindow = typeof globalThis & { __galaxyMap?: DebugMapSurface };

	let canvasEl: HTMLCanvasElement | null = $state(null);
	let containerEl: HTMLDivElement | null = $state(null);
	let mode: WrapMode = $state("torus");
	let backend = $state("");
	let initError: string | null = $state(null);

	let handle: RendererHandle | null = null;
	let onResize: (() => void) | null = null;

	function readPreference(): RendererPreference | RendererPreference[] {
		const v = page.url.searchParams.get("renderer");
		if (v === "webgl") return "webgl";
		if (v === "webgpu") return "webgpu";
		return ["webgpu", "webgl"];
	}

	function describe(err: unknown): string {
		if (err instanceof Error) return `${err.name}: ${err.message}`;
		return String(err);
	}

	onMount(() => {
		(async () => {
			if (canvasEl === null || containerEl === null) return;
			const world = sampleWorld();
			try {
				handle = await createRenderer({
					canvas: canvasEl,
					world,
					mode,
					preference: readPreference(),
				});
			} catch (err) {
				initError = describe(err);
				return;
			}
			backend = handle.getBackend();

			// Initial camera: world centre, zoomed slightly past the
			// fits-the-viewport floor so neighbouring torus copies are
			// visible too.
			handle.viewport.moveCenter(world.width / 2, world.height / 2);
			const minScale = minScaleNoWrap(
				{ widthPx: containerEl.clientWidth, heightPx: containerEl.clientHeight },
				world,
			);
			handle.viewport.setZoom(minScale * 1.2, true);
			if (mode === "no-wrap") handle.setMode("no-wrap"); // re-clamp post zoom

			const surface: DebugMapSurface = {
				ready: true,
				getMode: () => handle?.getMode() ?? "torus",
				setMode: (m) => {
					if (handle === null) return;
					handle.setMode(m);
					mode = m;
				},
				getCamera: () => handle?.getCamera() ?? { centerX: 0, centerY: 0, scale: 1 },
				setCameraCenter: (cx, cy) => {
					if (handle === null) return;
					// `pixi-viewport`'s built-in plugins (drag, wheel,
					// decelerate, …) emit `'moved'` themselves, but
					// programmatic `moveCenter` does not. Emit it
					// manually so the renderer's torus-wrap listener
					// (and any future per-move callback) sees the
					// change — matches the semantics of a user drag.
					handle.viewport.moveCenter(cx, cy);
					// `MovedEvent.type` is a closed literal union over the
					// built-in plugin names; `"animate"` is the closest
					// match for a programmatic move and the renderer's
					// listeners read only `viewport`.
					handle.viewport.emit("moved", {
						viewport: handle.viewport,
						type: "animate",
					});
				},
				getViewport: () =>
					handle?.getViewport() ?? { widthPx: 0, heightPx: 0 },
				getBackend: () => handle?.getBackend() ?? "",
				getWorldSize: () => ({ width: world.width, height: world.height }),
				hitAt: (x, y) => {
					if (handle === null) return null;
					const hit = handle.hitAt({ x, y });
					return hit?.primitive.id ?? null;
				},
			};
			(window as DebugMapWindow).__galaxyMap = surface;

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

	onDestroy(() => {
		if (onResize !== null) {
			window.removeEventListener("resize", onResize);
			onResize = null;
		}
		if (handle !== null) {
			handle.dispose();
			handle = null;
		}
		const w = window as DebugMapWindow;
		if (w.__galaxyMap !== undefined) delete w.__galaxyMap;
	});

	function toggleMode(): void {
		if (handle === null) return;
		const next: WrapMode = mode === "torus" ? "no-wrap" : "torus";
		handle.setMode(next);
		mode = next;
	}
</script>

<main>
	<header>
		<h1>map debug</h1>
		<div class="controls">
			<button type="button" data-testid="mode-toggle" onclick={toggleMode}>
				mode: {mode}
			</button>
			<span data-testid="backend" data-backend={backend}>backend: {backend || "…"}</span>
		</div>
	</header>
	{#if initError !== null}
		<p class="error" data-testid="init-error">{initError}</p>
	{/if}
	<div class="canvas-wrap" bind:this={containerEl}>
		<canvas bind:this={canvasEl}></canvas>
	</div>
</main>

<style>
	main {
		display: flex;
		flex-direction: column;
		height: 100vh;
		margin: 0;
		font-family: system-ui, sans-serif;
		color: #e8eaf6;
		background: #0a0e1a;
	}
	header {
		padding: 0.5rem 1rem;
		display: flex;
		gap: 1rem;
		align-items: center;
		border-bottom: 1px solid #20253a;
	}
	h1 {
		margin: 0;
		font-size: 1rem;
		font-weight: 500;
	}
	.controls {
		display: flex;
		gap: 0.75rem;
		align-items: center;
	}
	button {
		padding: 0.25rem 0.75rem;
		background: #1c2238;
		color: #e8eaf6;
		border: 1px solid #2a3150;
		border-radius: 4px;
		cursor: pointer;
		font: inherit;
	}
	button:hover {
		background: #232b48;
	}
	.canvas-wrap {
		flex: 1;
		position: relative;
		overflow: hidden;
	}
	canvas {
		display: block;
		width: 100%;
		height: 100%;
	}
	.error {
		padding: 0.5rem 1rem;
		background: #4a1820;
		color: #ffb4b4;
	}
</style>
