<!--
F8-10 fleets table. The report only carries the player's own fleets
(`localFleets`) — there is no foreign analogue — so the table is a
single block without owner toggles. Filters are reduced to the
shared planet dropdown (matching destination OR origin).

Click semantics:
  - on-planet fleet (origin === null && range === null) focuses the
    destination planet via `SelectionStore.focus`, identical to the
    ship-groups behaviour;
  - in-space fleet centres the camera on the interpolated fleet
    position via `SelectionStore.focusPoint`, leaving the selection
    unchanged (the `Selected` union has no "fleet" variant and
    extending it for one table would be needless surface area).
-->
<script lang="ts">
	import { getContext } from "svelte";

	import type { ReportLocalFleet, ReportPlanet } from "../../api/game-state";
	import { activeView } from "$lib/app-nav.svelte";
	import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
	import { planetLabel } from "./report/format";
	import {
		RENDERED_REPORT_CONTEXT_KEY,
		type RenderedReportSource,
	} from "$lib/rendered-report.svelte";
	import {
		SELECTION_CONTEXT_KEY,
		type SelectionStore,
	} from "$lib/selection.svelte";
	import ViewState from "$lib/ui/view-state.svelte";
	import { formatFloat, formatInt } from "$lib/util/number-format";
	import {
		fleetsTableState as persistent,
		type FleetsSortColumn as SortColumn,
	} from "./table-fleets-state.svelte";

	const COLUMN_LABELS: Record<SortColumn, TranslationKey> = {
		name: "game.table.fleets.column.name",
		groupCount: "game.table.fleets.column.groups",
		state: "game.table.fleets.column.state",
		location: "game.table.fleets.column.location",
		speed: "game.table.fleets.column.speed",
	};

	const COLUMNS: readonly SortColumn[] = [
		"name",
		"groupCount",
		"state",
		"location",
		"speed",
	];

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

	// `persistent` (module-level rune above) drives the dropdown
	// selection so the user's planet filter survives navigation.

	const reportLoaded = $derived(
		rendered?.report !== null && rendered?.report !== undefined,
	);
	const fleets = $derived<ReportLocalFleet[]>(
		rendered?.report?.localFleets ?? [],
	);
	const allPlanets = $derived<ReportPlanet[]>(rendered?.report?.planets ?? []);
	const planetIndex = $derived(
		new Map(allPlanets.map((p) => [p.number, p])),
	);

	const planets = $derived.by(() => {
		const set = new Set<number>();
		for (const f of fleets) {
			set.add(f.destination);
			if (f.origin !== null) set.add(f.origin);
		}
		return Array.from(set).sort((a, b) => a - b);
	});

	const filtered = $derived.by(() => {
		const planet =
			persistent.planetFilter === "" ? null : Number(persistent.planetFilter);
		return fleets.filter((f) => {
			if (planet === null) return true;
			return f.destination === planet || f.origin === planet;
		});
	});

	const sorted = $derived.by(() => {
		const list = [...filtered];
		const dir = persistent.sortDirection === "asc" ? 1 : -1;
		list.sort((a, b) => compare(a, b, persistent.sortColumn) * dir);
		return list;
	});

	function compare(
		a: ReportLocalFleet,
		b: ReportLocalFleet,
		column: SortColumn,
	): number {
		switch (column) {
			case "name":
				return a.name.localeCompare(b.name);
			case "groupCount":
				return a.groupCount - b.groupCount;
			case "state":
				return a.state.localeCompare(b.state);
			case "location": {
				if (a.destination !== b.destination) return a.destination - b.destination;
				const ao = a.origin ?? Number.MAX_SAFE_INTEGER;
				const bo = b.origin ?? Number.MAX_SAFE_INTEGER;
				return ao - bo;
			}
			case "speed":
				return a.speed - b.speed;
		}
	}

	function toggleSort(column: SortColumn): void {
		if (persistent.sortColumn === column) {
			persistent.sortDirection = persistent.sortDirection === "asc" ? "desc" : "asc";
			return;
		}
		persistent.sortColumn = column;
		persistent.sortDirection = "asc";
	}

	function ariaSort(column: SortColumn): "ascending" | "descending" | "none" {
		if (persistent.sortColumn !== column) return "none";
		return persistent.sortDirection === "asc" ? "ascending" : "descending";
	}

	function isInSpace(f: ReportLocalFleet): boolean {
		return f.origin !== null && f.range !== null;
	}

	function locationLabel(f: ReportLocalFleet): string {
		if (!isInSpace(f)) {
			return `@ ${planetLabel(f.destination, allPlanets)}`;
		}
		const from = planetLabel(f.origin!, allPlanets);
		const to = planetLabel(f.destination, allPlanets);
		return `${from} → ${to} (${formatFloat(f.range!)})`;
	}

	function openOnMap(f: ReportLocalFleet): void {
		if (selection === undefined) {
			activeView.select("map");
			return;
		}
		if (!isInSpace(f)) {
			selection.focus({ kind: "planet", id: f.destination });
			activeView.select("map");
			return;
		}
		const dest = planetIndex.get(f.destination);
		const origin = planetIndex.get(f.origin!);
		if (dest !== undefined && origin !== undefined) {
			const dx = dest.x - origin.x;
			const dy = dest.y - origin.y;
			const total = Math.hypot(dx, dy);
			if (total === 0 || f.range! <= 0) {
				selection.focusPoint(dest.x, dest.y);
			} else {
				const t = Math.min(1, f.range! / total);
				selection.focusPoint(dest.x + t * (origin.x - dest.x), dest.y + t * (origin.y - dest.y));
			}
		}
		activeView.select("map");
	}
</script>

<section
	class="active-view"
	data-testid="active-view-table"
	data-entity="fleets"
>
	<header>
		<h2>{i18n.t("game.table.fleets.title")}</h2>
		<div class="controls">
			{#if planets.length > 0}
				<label class="dropdown">
					<span>{i18n.t("game.table.fleets.filter.planet")}</span>
					<select
						data-testid="fleets-filter-planet"
						bind:value={persistent.planetFilter}
					>
						<option value=""
							>{i18n.t("game.table.fleets.filter.planet.all")}</option
						>
						{#each planets as n (n)}
							<option value={String(n)}
								>{planetLabel(n, allPlanets)}</option
							>
						{/each}
					</select>
				</label>
			{/if}
		</div>
	</header>

	{#if !reportLoaded}
		<ViewState
			kind="loading"
			testid="fleets-loading"
			message={i18n.t("game.table.fleets.loading")}
		/>
	{:else if fleets.length === 0}
		<ViewState
			kind="empty"
			testid="fleets-empty"
			message={i18n.t("game.table.fleets.empty")}
		/>
	{:else}
		<table class="grid" data-testid="fleets-table">
			<thead>
				<tr>
					{#each COLUMNS as column (column)}
						<th
							aria-sort={ariaSort(column)}
							class:numeric={column === "groupCount" || column === "speed"}
						>
							<button
								type="button"
								class="sort"
								data-testid="fleets-column-{column}"
								onclick={() => toggleSort(column)}
							>
								{i18n.t(COLUMN_LABELS[column])}
								{#if persistent.sortColumn === column}
									<span class="sort-indicator" aria-hidden="true">
										{persistent.sortDirection === "asc" ? "▲" : "▼"}
									</span>
								{/if}
							</button>
						</th>
					{/each}
				</tr>
			</thead>
			<tbody>
				{#each sorted as f (f.name)}
					<tr
						data-testid="fleets-row"
						data-name={f.name}
						data-in-space={isInSpace(f)}
						onclick={() => openOnMap(f)}
					>
						<td data-testid="fleets-cell-name">{f.name || "—"}</td>
						<td class="numeric" data-testid="fleets-cell-groups">
							{formatInt(f.groupCount)}
						</td>
						<td data-testid="fleets-cell-state">{f.state || "—"}</td>
						<td data-testid="fleets-cell-location">{locationLabel(f)}</td>
						<td class="numeric" data-testid="fleets-cell-speed">
							{formatFloat(f.speed)}
						</td>
					</tr>
				{/each}
			</tbody>
		</table>
	{/if}
</section>

<style>
	.active-view {
		padding: 1.5rem;
		font-family: system-ui, sans-serif;
		display: flex;
		flex-direction: column;
		gap: 1rem;
	}
	header {
		display: flex;
		flex-direction: column;
		gap: 0.5rem;
	}
	header h2 {
		margin: 0;
		font-size: 1.1rem;
	}
	.controls {
		display: flex;
		gap: 0.75rem;
		align-items: center;
		flex-wrap: wrap;
	}
	.dropdown {
		display: inline-flex;
		gap: 0.4rem;
		align-items: center;
		font-size: 0.85rem;
		color: var(--color-text-muted);
	}
	.dropdown select {
		font: inherit;
		font-size: 0.85rem;
		padding: 0.15rem 0.3rem;
		background: var(--color-bg);
		color: var(--color-text);
		border: 1px solid var(--color-border);
		border-radius: 3px;
	}
	.grid {
		border-collapse: collapse;
		width: 100%;
		font-variant-numeric: tabular-nums;
	}
	.grid th,
	.grid td {
		padding: 0.4rem 0.6rem;
		text-align: left;
		border-bottom: 1px solid var(--color-border-subtle);
		font-size: 0.85rem;
	}
	.grid th {
		color: var(--color-text-muted);
		text-transform: uppercase;
		letter-spacing: 0.04em;
		font-size: 0.75rem;
	}
	.grid th.numeric,
	.grid td.numeric {
		font-family: var(--font-mono);
		text-align: right;
	}
	.grid th.numeric .sort {
		justify-content: flex-end;
	}
	.grid tbody tr {
		cursor: pointer;
	}
	.grid tbody tr:hover {
		background: var(--color-surface);
	}
	.sort {
		font: inherit;
		font-size: inherit;
		text-transform: inherit;
		letter-spacing: inherit;
		color: inherit;
		background: transparent;
		border: none;
		padding: 0;
		cursor: pointer;
		display: inline-flex;
		gap: 0.3rem;
		align-items: baseline;
	}
	.sort-indicator {
		font-size: 0.7em;
	}
</style>
