<!--
Phase 21 sciences table. Renders the player's defined sciences with
sort, filter, double-tap-to-open-designer, and a per-row Delete
action. Source data is the optimistic overlay
(`RenderedReportSource.report.localScience`) so a freshly queued
`createScience` shows up immediately and a freshly queued
`removeScience` disappears immediately — both before the auto-sync
round-trip lands.

The four tech proportions are stored on the wire as fractions in
`[0, 1]` and surfaced here as percentages with one decimal so the
table matches the designer's input units.

The component sits inside the active-view area owned by
`lib/game/game-shell.svelte`, so it inherits the per-game
`OrderDraftStore` and `RenderedReportSource` through context. No
data fetching is performed here — the shell is responsible.
-->
<script lang="ts">
	import { getContext } from "svelte";
	import { activeView } from "$lib/app-nav.svelte";

	import type { ScienceSummary } from "../../api/game-state";
	import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
	import ViewState from "$lib/ui/view-state.svelte";
	import {
		RENDERED_REPORT_CONTEXT_KEY,
		type RenderedReportSource,
	} from "$lib/rendered-report.svelte";
	import {
		ORDER_DRAFT_CONTEXT_KEY,
		OrderDraftStore,
	} from "../../sync/order-draft.svelte";

	type SortColumn = "name" | "drive" | "weapons" | "shields" | "cargo";
	type SortDirection = "asc" | "desc";

	const COLUMN_LABELS: Record<SortColumn, TranslationKey> = {
		name: "game.table.sciences.column.name",
		drive: "game.table.sciences.column.drive",
		weapons: "game.table.sciences.column.weapons",
		shields: "game.table.sciences.column.shields",
		cargo: "game.table.sciences.column.cargo",
	};

	const COLUMNS: readonly SortColumn[] = [
		"name",
		"drive",
		"weapons",
		"shields",
		"cargo",
	];

	const rendered = getContext<RenderedReportSource | undefined>(
		RENDERED_REPORT_CONTEXT_KEY,
	);
	const draft = getContext<OrderDraftStore | undefined>(
		ORDER_DRAFT_CONTEXT_KEY,
	);

	let sortColumn: SortColumn = $state("name");
	let sortDirection: SortDirection = $state("asc");
	let filter: string = $state("");

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

	const filtered = $derived.by(() => {
		const needle = filter.trim().toLowerCase();
		if (needle === "") return localScience;
		return localScience.filter((sci) =>
			sci.name.toLowerCase().includes(needle),
		);
	});

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

	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";
	}

	// Render a fraction in `[0, 1]` as a one-decimal percent
	// (`0.225` → `"22.5"`). The conversion is value-only — no `%`
	// suffix — so callers can decorate independently.
	function formatPercent(fraction: number): string {
		return (fraction * 100).toLocaleString(undefined, {
			minimumFractionDigits: 0,
			maximumFractionDigits: 1,
		});
	}

	function openDesigner(name: string): void {
		activeView.select("designer-science", { scienceId: name });
	}

	function newScience(): void {
		activeView.select("designer-science");
	}

	async function deleteScience(name: string): Promise<void> {
		if (draft === undefined) return;
		await draft.add({
			kind: "removeScience",
			id: crypto.randomUUID(),
			name,
		});
	}
</script>

<section
	class="active-view"
	data-testid="active-view-table"
	data-entity="sciences"
>
	<header>
		<h2>{i18n.t("game.table.sciences.title")}</h2>
		<div class="controls">
			<input
				type="search"
				class="filter"
				data-testid="sciences-filter"
				placeholder={i18n.t("game.table.sciences.filter.placeholder")}
				bind:value={filter}
			/>
			<button
				type="button"
				class="new"
				data-testid="sciences-new"
				onclick={newScience}
			>
				{i18n.t("game.table.sciences.action.new")}
			</button>
		</div>
	</header>

	{#if !reportLoaded}
		<ViewState
			kind="loading"
			testid="sciences-loading"
			message={i18n.t("game.table.sciences.loading")}
		/>
	{:else if localScience.length === 0}
		<ViewState
			kind="empty"
			testid="sciences-empty"
			message={i18n.t("game.table.sciences.empty")}
		/>
	{:else}
		<table class="grid" data-testid="sciences-table">
			<thead>
				<tr>
					{#each COLUMNS as column (column)}
						<th aria-sort={ariaSort(column)}>
							<button
								type="button"
								class="sort"
								data-testid="sciences-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>{i18n.t("game.table.sciences.column.actions")}</th>
				</tr>
			</thead>
			<tbody>
				{#each sorted as sci (sci.name)}
					<tr
						data-testid="sciences-row"
						data-name={sci.name}
						ondblclick={() => openDesigner(sci.name)}
					>
						<td data-testid="sciences-cell-name">{sci.name}</td>
						<td data-testid="sciences-cell-drive">{formatPercent(sci.drive)}</td>
						<td data-testid="sciences-cell-weapons">
							{formatPercent(sci.weapons)}
						</td>
						<td data-testid="sciences-cell-shields">
							{formatPercent(sci.shields)}
						</td>
						<td data-testid="sciences-cell-cargo">{formatPercent(sci.cargo)}</td>
						<td>
							<button
								type="button"
								class="delete"
								data-testid="sciences-delete"
								onclick={() => void deleteScience(sci.name)}
							>
								{i18n.t("game.table.sciences.action.delete")}
							</button>
						</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.5rem;
		align-items: center;
		flex-wrap: wrap;
	}
	.filter {
		font: inherit;
		padding: 0.3rem 0.5rem;
		background: var(--color-bg);
		color: var(--color-text);
		border: 1px solid var(--color-border);
		border-radius: 3px;
		flex: 1 1 12rem;
		min-width: 8rem;
	}
	.new {
		font: inherit;
		font-size: 0.9rem;
		padding: 0.3rem 0.7rem;
		background: transparent;
		color: var(--color-text-muted);
		border: 1px solid var(--color-border);
		border-radius: 3px;
		cursor: pointer;
	}
	.new:hover {
		color: var(--color-text);
		border-color: var(--color-accent);
	}
	.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.9rem;
	}
	.grid th {
		color: var(--color-text-muted);
		text-transform: uppercase;
		letter-spacing: 0.04em;
		font-size: 0.75rem;
	}
	.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;
	}
	.delete {
		font: inherit;
		font-size: 0.85rem;
		padding: 0.15rem 0.5rem;
		background: transparent;
		color: var(--color-danger);
		border: 1px solid var(--color-border);
		border-radius: 3px;
		cursor: pointer;
	}
	.delete:hover {
		border-color: var(--color-danger);
	}
</style>
