<!--
F8-10 planets table. Lists every planet from the rendered report with
4 kind checkboxes (own / foreign / uninhabited / unknown) and a race
dropdown that filters foreign planets by owner. A row click focuses
the planet on the map (selection + camera centre) via the transient
`SelectionStore.focus` channel — `map.svelte` consumes it on mount.

Lives inside the active-view slot; reads the report and the selection
store from context, matching the surrounding tables. No data
fetching here.
-->
<script lang="ts">
	import { getContext } from "svelte";

	import type { ReportPlanet } from "../../api/game-state";
	import { activeView } from "$lib/app-nav.svelte";
	import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
	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";

	type SortColumn =
		| "number"
		| "name"
		| "kind"
		| "owner"
		| "size"
		| "resources";
	type SortDirection = "asc" | "desc";
	type PlanetKind = ReportPlanet["kind"];

	const COLUMN_LABELS: Record<SortColumn, TranslationKey> = {
		number: "game.table.planets.column.number",
		name: "game.table.planets.column.name",
		kind: "game.table.planets.column.kind",
		owner: "game.table.planets.column.owner",
		size: "game.table.planets.column.size",
		resources: "game.table.planets.column.resources",
	};

	const COLUMNS: readonly SortColumn[] = [
		"number",
		"name",
		"kind",
		"owner",
		"size",
		"resources",
	];

	const KIND_LABELS: Record<PlanetKind, TranslationKey> = {
		local: "game.table.planets.kind.own",
		other: "game.table.planets.kind.foreign",
		uninhabited: "game.table.planets.kind.uninhabited",
		unidentified: "game.table.planets.kind.unknown",
	};

	const KIND_ORDER: Record<PlanetKind, number> = {
		local: 0,
		other: 1,
		uninhabited: 2,
		unidentified: 3,
	};

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

	let sortColumn: SortColumn = $state("number");
	let sortDirection: SortDirection = $state("asc");

	// Kind toggles default to all-on; owner narrows the `other` slice
	// only — "" means "all owners". The dropdown is rendered as soon
	// as at least one foreign planet exists in the report, regardless
	// of the foreign toggle, so the user can re-enable foreign and
	// keep their owner pick.
	let showLocal = $state(true);
	let showOther = $state(true);
	let showUninhabited = $state(true);
	let showUnknown = $state(true);
	let ownerFilter: string = $state("");

	const planets = $derived<ReportPlanet[]>(rendered?.report?.planets ?? []);
	const reportLoaded = $derived(
		rendered?.report !== null && rendered?.report !== undefined,
	);

	const owners = $derived.by(() => {
		const set = new Set<string>();
		for (const p of planets) {
			if (p.kind === "other" && p.owner !== null && p.owner !== "") {
				set.add(p.owner);
			}
		}
		return Array.from(set).sort((a, b) => a.localeCompare(b));
	});

	const filtered = $derived.by(() => {
		return planets.filter((p) => {
			if (p.kind === "local" && !showLocal) return false;
			if (p.kind === "uninhabited" && !showUninhabited) return false;
			if (p.kind === "unidentified" && !showUnknown) return false;
			if (p.kind === "other") {
				if (!showOther) return false;
				if (ownerFilter !== "" && p.owner !== ownerFilter) return false;
			}
			return true;
		});
	});

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

	function compare(a: ReportPlanet, b: ReportPlanet, column: SortColumn): number {
		switch (column) {
			case "number":
				return a.number - b.number;
			case "name":
				return a.name.localeCompare(b.name);
			case "kind":
				return KIND_ORDER[a.kind] - KIND_ORDER[b.kind];
			case "owner": {
				const ao = a.owner ?? "";
				const bo = b.owner ?? "";
				return ao.localeCompare(bo);
			}
			case "size":
				return (a.size ?? -1) - (b.size ?? -1);
			case "resources":
				return (a.resources ?? -1) - (b.resources ?? -1);
		}
	}

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

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

	function openOnMap(planet: ReportPlanet): void {
		selection?.focus({ kind: "planet", id: planet.number });
		activeView.select("map");
	}

	function ownerDisplay(p: ReportPlanet): string {
		if (p.kind === "other") return p.owner ?? "";
		return "—";
	}
</script>

<section
	class="active-view"
	data-testid="active-view-table"
	data-entity="planets"
>
	<header>
		<h2>{i18n.t("game.table.planets.title")}</h2>
		<div class="controls">
			<label class="check">
				<input
					type="checkbox"
					data-testid="planets-filter-own"
					bind:checked={showLocal}
				/>
				<span>{i18n.t("game.table.planets.kind.own")}</span>
			</label>
			<label class="check">
				<input
					type="checkbox"
					data-testid="planets-filter-foreign"
					bind:checked={showOther}
				/>
				<span>{i18n.t("game.table.planets.kind.foreign")}</span>
			</label>
			<label class="check">
				<input
					type="checkbox"
					data-testid="planets-filter-uninhabited"
					bind:checked={showUninhabited}
				/>
				<span>{i18n.t("game.table.planets.kind.uninhabited")}</span>
			</label>
			<label class="check">
				<input
					type="checkbox"
					data-testid="planets-filter-unknown"
					bind:checked={showUnknown}
				/>
				<span>{i18n.t("game.table.planets.kind.unknown")}</span>
			</label>
			{#if owners.length > 0}
				<label class="owner">
					<span>{i18n.t("game.table.planets.filter.owner")}</span>
					<select
						data-testid="planets-filter-owner"
						bind:value={ownerFilter}
					>
						<option value=""
							>{i18n.t("game.table.planets.filter.owner.all")}</option
						>
						{#each owners as owner (owner)}
							<option value={owner}>{owner}</option>
						{/each}
					</select>
				</label>
			{/if}
		</div>
	</header>

	{#if !reportLoaded}
		<ViewState
			kind="loading"
			testid="planets-loading"
			message={i18n.t("game.table.planets.loading")}
		/>
	{:else if planets.length === 0}
		<ViewState
			kind="empty"
			testid="planets-empty"
			message={i18n.t("game.table.planets.empty")}
		/>
	{:else}
		<table class="grid" data-testid="planets-table">
			<thead>
				<tr>
					{#each COLUMNS as column (column)}
						<th
							aria-sort={ariaSort(column)}
							class:numeric={column === "number" ||
								column === "size" ||
								column === "resources"}
						>
							<button
								type="button"
								class="sort"
								data-testid="planets-column-{column}"
								onclick={() => toggleSort(column)}
							>
								{i18n.t(COLUMN_LABELS[column])}
								{#if sortColumn === column}
									<span class="sort-indicator" aria-hidden="true">
										{sortDirection === "asc" ? "▲" : "▼"}
									</span>
								{/if}
							</button>
						</th>
					{/each}
					<th class="numeric">
						{i18n.t("game.table.planets.column.coordinates")}
					</th>
				</tr>
			</thead>
			<tbody>
				{#each sorted as p (p.number)}
					<tr
						data-testid="planets-row"
						data-number={p.number}
						data-kind={p.kind}
						onclick={() => openOnMap(p)}
					>
						<td class="numeric" data-testid="planets-cell-number">
							{p.number}
						</td>
						<td data-testid="planets-cell-name">{p.name || "—"}</td>
						<td data-testid="planets-cell-kind">
							{i18n.t(KIND_LABELS[p.kind])}
						</td>
						<td data-testid="planets-cell-owner">{ownerDisplay(p)}</td>
						<td class="numeric" data-testid="planets-cell-size">
							{p.size === null ? "—" : formatFloat(p.size)}
						</td>
						<td class="numeric" data-testid="planets-cell-resources">
							{p.resources === null ? "—" : formatFloat(p.resources)}
						</td>
						<td class="numeric" data-testid="planets-cell-coordinates">
							{formatInt(p.x)},{formatInt(p.y)}
						</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;
	}
	.check {
		display: inline-flex;
		gap: 0.3rem;
		align-items: center;
		font-size: 0.85rem;
		color: var(--color-text-muted);
		cursor: pointer;
	}
	.check input {
		margin: 0;
	}
	.owner {
		display: inline-flex;
		gap: 0.4rem;
		align-items: center;
		font-size: 0.85rem;
		color: var(--color-text-muted);
	}
	.owner 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>
