<!--
Phase 26 header turn navigator. Replaces the Phase 11 inline turn
number with a `← Turn N →` triplet. The arrows step ±1 (disabled at
boundaries `0` and `currentTurn`), the middle button opens a popover
listing every turn `Turn #0`…`Turn #currentTurn` with the current row
tagged. No free-text input — every reachable turn is in the list, so
there is nothing to validate.

Desktop and mobile share the same component: an absolute-positioned
popover anchored to the trigger on desktop becomes a fixed full-width
drawer below the 768 px breakpoint, mirroring `view-menu.svelte`.

Selecting a row calls `gameState.viewTurn(N)`; the row that matches
`currentTurn` delegates to `gameState.returnToCurrent()` so the
"leave history" path always flows through one method. Selecting the
row already on screen (`viewedTurn`) is a pure no-op — most visibly
at turn 0, where the sole row is the live turn — so the navigator
never re-fetches the report just to redraw the same snapshot.
-->
<script lang="ts">
	import { getContext, onMount } from "svelte";
	import { i18n } from "$lib/i18n/index.svelte";
	import { restoreFocus } from "$lib/a11y/restore-focus";
	import {
		GAME_STATE_CONTEXT_KEY,
		type GameStateStore,
	} from "$lib/game-state.svelte";

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

	let open = $state(false);
	let rootEl: HTMLDivElement | null = $state(null);

	const currentTurn = $derived(gameState?.currentTurn ?? 0);
	const viewedTurn = $derived(gameState?.viewedTurn ?? 0);
	const ready = $derived(gameState?.status === "ready");
	const canPrev = $derived(ready && viewedTurn > 0);
	const canNext = $derived(ready && viewedTurn < currentTurn);
	// Until the boot completes the store has no report, so the
	// counter falls back to the same `?` placeholder Phase 11 used in
	// the static headline. Avoids briefly flashing `turn 0` while the
	// lobby / report calls are in flight.
	const turnText = $derived(
		ready ? String(viewedTurn) : i18n.t("game.shell.unknown"),
	);

	// Descending list newest → oldest. The popover stays compact for
	// short games and scrolls for long ones; the current-turn row is
	// pinned at the top with an explicit badge so the affordance to
	// jump back is always reachable without scrolling.
	const turns = $derived.by(() => {
		if (!ready) return [] as number[];
		const out: number[] = [];
		for (let i = currentTurn; i >= 0; i--) {
			out.push(i);
		}
		return out;
	});

	function toggleOpen(): void {
		if (!ready) return;
		open = !open;
	}

	async function goToTurn(turn: number): Promise<void> {
		open = false;
		if (gameState === undefined) return;
		// Re-selecting the turn already on screen changes nothing, so just
		// close the popover. Without this guard the current-turn row routes
		// through `returnToCurrent()` → `viewTurn(currentTurn)`, which
		// re-fetches the live report and flips the view through `loading` —
		// most visibly at turn 0, where the only row is the live turn.
		// Leaving history still works: there the viewed turn differs.
		if (turn === gameState.viewedTurn) return;
		if (turn === gameState.currentTurn) {
			await gameState.returnToCurrent();
			return;
		}
		await gameState.viewTurn(turn);
	}

	async function step(delta: number): Promise<void> {
		if (gameState === undefined) return;
		const next = viewedTurn + delta;
		if (next < 0 || next > currentTurn) return;
		if (next === gameState.currentTurn) {
			await gameState.returnToCurrent();
			return;
		}
		await gameState.viewTurn(next);
	}

	function onKeyDown(event: KeyboardEvent): void {
		if (event.key === "Escape" && open) {
			open = false;
		}
	}

	onMount(() => {
		const handleClick = (event: MouseEvent): void => {
			if (!open || rootEl === null) return;
			const target = event.target;
			if (target instanceof Node && rootEl.contains(target)) return;
			open = false;
		};
		document.addEventListener("click", handleClick, true);
		document.addEventListener("keydown", onKeyDown);
		return () => {
			document.removeEventListener("click", handleClick, true);
			document.removeEventListener("keydown", onKeyDown);
		};
	});
</script>

<div class="turn-navigator" bind:this={rootEl} data-testid="turn-navigator">
	<button
		type="button"
		class="step"
		data-testid="turn-navigator-prev"
		aria-label={i18n.t("game.shell.turn.prev")}
		disabled={!canPrev}
		onclick={() => void step(-1)}
	>
		←
	</button>
	<button
		type="button"
		class="trigger"
		data-testid="turn-navigator-trigger"
		aria-haspopup="menu"
		aria-expanded={open}
		aria-label={open
			? i18n.t("game.shell.turn.close_navigator")
			: i18n.t("game.shell.turn.open_navigator")}
		disabled={!ready}
		onclick={toggleOpen}
	>
		{i18n.t("game.shell.turn.label", { turn: turnText })}
	</button>
	<button
		type="button"
		class="step"
		data-testid="turn-navigator-next"
		aria-label={i18n.t("game.shell.turn.next")}
		disabled={!canNext}
		onclick={() => void step(1)}
	>
		→
	</button>
	{#if open}
		<div
			class="surface"
			role="menu"
			data-testid="turn-navigator-list"
			use:restoreFocus
		>
			{#each turns as turn (turn)}
				<button
					type="button"
					role="menuitem"
					class="row"
					class:viewed={turn === viewedTurn}
					data-testid={`turn-navigator-item-${turn}`}
					onclick={() => void goToTurn(turn)}
				>
					<span class="label">
						{i18n.t("game.shell.turn.list_item", { turn: String(turn) })}
					</span>
					{#if turn === currentTurn}
						<span class="badge" data-testid="turn-navigator-current-badge">
							{i18n.t("game.shell.history.current_badge")}
						</span>
					{/if}
				</button>
			{/each}
		</div>
	{/if}
</div>

<style>
	.turn-navigator {
		position: relative;
		display: inline-flex;
		align-items: stretch;
		gap: 0.25rem;
	}
	.step,
	.trigger {
		font: inherit;
		font-size: 1rem;
		padding: 0.25rem 0.55rem;
		background: transparent;
		color: inherit;
		border: 1px solid var(--color-border);
		border-radius: 4px;
		cursor: pointer;
		line-height: 1;
	}
	.step:hover:not(:disabled),
	.trigger:hover:not(:disabled) {
		background: var(--color-surface-hover);
	}
	.step:disabled,
	.trigger:disabled {
		opacity: 0.4;
		cursor: not-allowed;
	}
	.trigger {
		min-width: 5rem;
		font-weight: 500;
	}
	.surface {
		position: absolute;
		top: calc(100% + 0.25rem);
		left: 0;
		min-width: 10rem;
		max-height: 18rem;
		overflow-y: auto;
		display: flex;
		flex-direction: column;
		background: var(--color-surface-overlay);
		border: 1px solid var(--color-border);
		border-radius: 6px;
		box-shadow: var(--shadow-lg);
		z-index: 50;
	}
	.row {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 0.75rem;
		text-align: left;
		font: inherit;
		padding: 0.4rem 0.75rem;
		background: transparent;
		color: inherit;
		border: 0;
		cursor: pointer;
	}
	.row:hover {
		background: var(--color-surface-hover);
	}
	.row.viewed {
		font-weight: 600;
		background: var(--color-surface-raised);
	}
	.label {
		flex: 1;
		min-width: 0;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.badge {
		flex: 0 0 auto;
		font-size: 0.75rem;
		text-transform: uppercase;
		letter-spacing: 0.04em;
		padding: 0.05rem 0.4rem;
		background: var(--color-border);
		color: var(--color-text);
		border-radius: 999px;
	}
	@media (max-width: 767.98px) {
		.surface {
			position: fixed;
			top: 3rem;
			right: 0;
			left: 0;
			min-width: 0;
			max-height: calc(100vh - 3rem);
			border-radius: 0;
			border-left: 0;
			border-right: 0;
		}
	}
</style>
