<!--
Order composer tool. Resolves the per-game `OrderDraftStore` from
context (set by `routes/games/[id]/+layout.svelte`) and renders the
local draft as a vertical list with per-row status and a delete
button.

Phase 14 wires the auto-sync pipeline directly into the draft
store: every successful `add` / `remove` / `move` triggers a
`submitOrder` call so the server always mirrors the local draft.
Per-command status (`valid`, `submitting`, `applied`, `rejected`,
`conflict`, `invalid`) is conveyed by the card's background tint
(`status-{status}` class → `--color-{success,danger,warning}-subtle`),
while the textual status name stays in the DOM as an `sr-only`
node so screen readers and the existing tests still observe it.
A small status bar at the bottom surfaces the latest sync result.
The earlier explicit Submit button is gone — there is no separate
"send" step anymore.

Tests exercise the tab through `__galaxyDebug.seedOrderDraft`
(Playwright) and via direct store / mocked-client construction
(Vitest).
-->
<script lang="ts">
	import { getContext } from "svelte";
	import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
	import { productionDisplayFromCommand } from "../../api/game-state";
	import {
		ORDER_DRAFT_CONTEXT_KEY,
		OrderDraftStore,
	} from "../../sync/order-draft.svelte";
	import type { CommandStatus, OrderCommand } from "../../sync/order-types";

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

	const statusKeyMap: Record<CommandStatus, TranslationKey> = {
		draft: "game.sidebar.order.status.draft",
		valid: "game.sidebar.order.status.valid",
		invalid: "game.sidebar.order.status.invalid",
		submitting: "game.sidebar.order.status.submitting",
		applied: "game.sidebar.order.status.applied",
		rejected: "game.sidebar.order.status.rejected",
		conflict: "game.sidebar.order.status.conflict",
	};

	function describe(cmd: OrderCommand): string {
		switch (cmd.kind) {
			case "placeholder":
				return i18n.t("game.sidebar.order.label.placeholder", {
					label: cmd.label,
				});
			case "planetRename":
				return i18n.t("game.sidebar.order.label.planet_rename", {
					planet: String(cmd.planetNumber),
					name: cmd.name,
				});
			case "setProductionType":
				return i18n.t("game.sidebar.order.label.planet_production", {
					planet: String(cmd.planetNumber),
					target: productionDisplayFromCommand(
						cmd.productionType,
						cmd.subject,
					),
				});
			case "setCargoRoute":
				return i18n.t("game.sidebar.order.label.cargo_route_set", {
					loadType: cmd.loadType,
					source: String(cmd.sourcePlanetNumber),
					destination: String(cmd.destinationPlanetNumber),
				});
			case "removeCargoRoute":
				return i18n.t("game.sidebar.order.label.cargo_route_remove", {
					loadType: cmd.loadType,
					source: String(cmd.sourcePlanetNumber),
				});
			case "createShipClass":
				return i18n.t("game.sidebar.order.label.ship_class_create", {
					name: cmd.name,
				});
			case "removeShipClass":
				return i18n.t("game.sidebar.order.label.ship_class_remove", {
					name: cmd.name,
				});
			case "createScience":
				return i18n.t("game.sidebar.order.label.science_create", {
					name: cmd.name,
				});
			case "removeScience":
				return i18n.t("game.sidebar.order.label.science_remove", {
					name: cmd.name,
				});
			case "breakShipGroup":
				return i18n.t("game.sidebar.order.label.ship_group_break", {
					group: shortGroupId(cmd.groupId),
					quantity: String(cmd.quantity),
				});
			case "sendShipGroup":
				return i18n.t("game.sidebar.order.label.ship_group_send", {
					group: shortGroupId(cmd.groupId),
					destination: String(cmd.destinationPlanetNumber),
				});
			case "loadShipGroup":
				return i18n.t("game.sidebar.order.label.ship_group_load", {
					group: shortGroupId(cmd.groupId),
					cargo: cmd.cargo,
					quantity: String(cmd.quantity),
				});
			case "unloadShipGroup":
				return i18n.t("game.sidebar.order.label.ship_group_unload", {
					group: shortGroupId(cmd.groupId),
					quantity: String(cmd.quantity),
				});
			case "upgradeShipGroup":
				return i18n.t("game.sidebar.order.label.ship_group_upgrade", {
					group: shortGroupId(cmd.groupId),
					tech: cmd.tech,
					level: String(cmd.level),
				});
			case "dismantleShipGroup":
				return i18n.t("game.sidebar.order.label.ship_group_dismantle", {
					group: shortGroupId(cmd.groupId),
				});
			case "transferShipGroup":
				return i18n.t("game.sidebar.order.label.ship_group_transfer", {
					group: shortGroupId(cmd.groupId),
					acceptor: cmd.acceptor,
				});
			case "joinFleetShipGroup":
				return i18n.t("game.sidebar.order.label.ship_group_join_fleet", {
					group: shortGroupId(cmd.groupId),
					fleet: cmd.name,
				});
			case "setDiplomaticStance":
				return i18n.t("game.sidebar.order.label.race_relation", {
					relation: cmd.relation,
					acceptor: cmd.acceptor,
				});
			case "setVoteRecipient":
				return i18n.t("game.sidebar.order.label.race_vote", {
					acceptor: cmd.acceptor,
				});
		}
	}

	// Short identifier for the order-tab so the human-readable label
	// stays glanceable; the full UUID is still in the underlying
	// command and visible in the inspector overlay.
	function shortGroupId(uuid: string): string {
		return uuid.length > 8 ? uuid.slice(0, 8) : uuid;
	}

	function statusOf(cmd: OrderCommand): CommandStatus {
		return draft?.statuses[cmd.id] ?? "draft";
	}
</script>

<section class="tool" data-testid="sidebar-tool-order">
	<h3>{i18n.t("game.sidebar.tab.order")}</h3>
	{#if draft !== undefined && draft.pausedBanner !== null}
		<div
			class="banner banner-paused"
			data-testid="order-paused-banner"
			data-paused-reason={draft.pausedBanner.reason}
			role="status"
		>
			{i18n.t("game.sidebar.order.paused.banner")}
		</div>
	{/if}
	{#if draft !== undefined && draft.conflictBanner !== null}
		<div
			class="banner banner-conflict"
			data-testid="order-conflict-banner"
			data-conflict-turn={draft.conflictBanner.turn ?? ""}
			role="status"
		>
			{#if draft.conflictBanner.turn !== null}
				{i18n.t("game.sidebar.order.conflict.banner", {
					turn: String(draft.conflictBanner.turn),
				})}
			{:else}
				{i18n.t("game.sidebar.order.conflict.banner_no_turn")}
			{/if}
		</div>
	{/if}
	{#if draft === undefined || draft.commands.length === 0}
		<p class="empty" data-testid="order-empty">
			{i18n.t("game.sidebar.empty.order")}
		</p>
	{:else}
		<ol class="commands" data-testid="order-list">
			{#each draft.commands as cmd, index (cmd.id)}
				{@const status = statusOf(cmd)}
				<li
					class="command status-{status}"
					data-testid="order-command-{index}"
					data-command-status={status}
				>
					<span class="index" aria-hidden="true">{index + 1}.</span>
					<span class="label" data-testid="order-command-label-{index}">
						{describe(cmd)}
					</span>
					<span
						class="sr-only"
						data-testid="order-command-status-{index}"
					>
						{i18n.t(statusKeyMap[status])}
					</span>
					<button
						type="button"
						class="delete"
						data-testid="order-command-delete-{index}"
						aria-label={i18n.t("game.sidebar.order.command_delete")}
						title={i18n.t("game.sidebar.order.command_delete")}
						onclick={() => draft?.remove(cmd.id)}
					>
						<span aria-hidden="true">✕</span>
					</button>
				</li>
			{/each}
		</ol>
	{/if}
	{#if draft !== undefined}
		<div
			class="sync sync-{draft.syncStatus}"
			data-testid="order-sync"
			data-sync-status={draft.syncStatus}
		>
			<span class="sync-label">
				{#if draft.syncStatus === "syncing"}
					{i18n.t("game.sidebar.order.sync.in_flight")}
				{:else if draft.syncStatus === "synced"}
					{i18n.t("game.sidebar.order.sync.synced")}
				{:else if draft.syncStatus === "error"}
					{i18n.t("game.sidebar.order.sync.error", {
						message: draft.syncError ?? "",
					})}
				{:else if draft.syncStatus === "offline"}
					{i18n.t("game.sidebar.order.sync.offline")}
				{:else if draft.syncStatus === "conflict"}
					{i18n.t("game.sidebar.order.sync.conflict")}
				{:else if draft.syncStatus === "paused"}
					{i18n.t("game.sidebar.order.sync.paused")}
				{:else}
					{i18n.t("game.sidebar.order.sync.idle")}
				{/if}
			</span>
			{#if draft.syncStatus === "error"}
				<button
					type="button"
					class="sync-retry"
					data-testid="order-sync-retry"
					onclick={() => draft.forceSync()}
				>
					{i18n.t("game.sidebar.order.sync.retry")}
				</button>
			{/if}
		</div>
	{/if}
</section>

<style>
	.tool {
		padding: 1rem;
		font-family: system-ui, sans-serif;
	}
	.tool h3 {
		margin: 0 0 0.5rem;
		font-size: 1rem;
	}
	.empty {
		margin: 0;
		color: var(--color-text-muted);
	}
	.commands {
		list-style: none;
		margin: 0 0 0.75rem;
		padding: 0;
		display: flex;
		flex-direction: column;
		gap: 0.25rem;
	}
	.command {
		position: relative;
		display: grid;
		grid-template-columns: auto 1fr;
		align-items: baseline;
		gap: 0.4rem;
		padding: 0.3rem 1.1rem 0.3rem 0.5rem;
		font-size: 0.8rem;
		line-height: 1.3;
		background: var(--color-surface-overlay);
		border: 1px solid var(--color-border-subtle);
		border-radius: 4px;
	}
	.command.status-applied {
		background: var(--color-success-subtle);
	}
	.command.status-invalid,
	.command.status-rejected,
	.command.status-conflict {
		background: var(--color-danger-subtle);
	}
	.command.status-draft,
	.command.status-valid,
	.command.status-submitting {
		background: var(--color-warning-subtle);
	}
	.index {
		color: var(--color-text-muted);
		font-variant-numeric: tabular-nums;
	}
	.label {
		min-width: 0;
		overflow-wrap: anywhere;
	}
	.banner {
		margin: 0 0 0.5rem;
		padding: 0.5rem 0.75rem;
		border-radius: 4px;
		font-size: 0.85rem;
		line-height: 1.3;
	}
	.banner-conflict {
		color: var(--color-warning);
		background: var(--color-warning-subtle);
		border: 1px solid var(--color-warning);
	}
	.banner-paused {
		color: var(--color-text-muted);
		background: var(--color-surface);
		border: 1px solid var(--color-border);
	}
	.delete {
		position: absolute;
		top: 0;
		right: 0;
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 0.95rem;
		height: 0.95rem;
		padding: 0;
		font: inherit;
		font-size: 0.65rem;
		line-height: 1;
		background: transparent;
		color: var(--color-text-muted);
		border: 1px solid var(--color-border-subtle);
		border-radius: 0 3px 0 3px;
		cursor: pointer;
	}
	.delete:hover,
	.delete:focus-visible {
		color: var(--color-text);
		border-color: var(--color-accent);
	}
	.sync {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 0.5rem;
		font-size: 0.8rem;
		color: var(--color-text-muted);
	}
	.sync-error {
		color: var(--color-danger);
	}
	.sync-synced {
		color: var(--color-success);
	}
	.sync-syncing {
		color: var(--color-accent);
	}
	.sync-offline {
		color: var(--color-warning);
	}
	.sync-conflict {
		color: var(--color-warning);
	}
	.sync-paused {
		color: var(--color-text-muted);
	}
	.sync-retry {
		font: inherit;
		font-size: 0.8rem;
		padding: 0.15rem 0.5rem;
		background: transparent;
		color: var(--color-text-muted);
		border: 1px solid var(--color-border);
		border-radius: 3px;
		cursor: pointer;
	}
	.sync-retry:hover {
		color: var(--color-text);
		border-color: var(--color-accent);
	}
</style>
