<!--
Phase 21 science designer. Two modes driven by the optional
`scienceId` URL segment:

  - **new (no scienceId)** — empty form with four percent fields
    plus name. Save is disabled until `validateScience` returns
    `ok`; the localised tooltip mirrors `validateEntityName`'s
    invalid-reason messages and the value-rule mirrors the engine's
    `pkg/calc/validator.go.ValidateScienceValues`. Save adds a
    `createScience` to the local order draft (with the four
    percentages converted to fractions in `[0, 1]`) and returns to
    the table.
  - **view (scienceId set)** — read-only render of the matching row
    from the optimistic overlay. Sciences are defined once and
    cannot be modified after creation (Phase 21 decision: the wire
    has only Create + Remove, no Update); the view exposes a Delete
    affordance (engine-side checks ensure the science is not
    referenced by an active production target) and a Back button.

The four tech proportions are entered as percentages (`step="0.1"`)
with a strict sum-equals-100 gate. The running sum is shown live so
the player can chase it down; conversion to wire fractions happens
inside `validateScience` only on Save. The choice of percent vs.
fractions is a Phase 21 decision documented in
`ui/docs/science-designer-ux.md`.
-->
<script lang="ts">
	import { getContext, tick } from "svelte";
	import { goto } from "$app/navigation";
	import { page } from "$app/state";

	import type { ScienceSummary } from "../../api/game-state";
	import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
	import {
		RENDERED_REPORT_CONTEXT_KEY,
		type RenderedReportSource,
	} from "$lib/rendered-report.svelte";
	import {
		ORDER_DRAFT_CONTEXT_KEY,
		OrderDraftStore,
	} from "../../sync/order-draft.svelte";
	import {
		fractionsToPercent,
		validateScience,
		type ScienceInvalidReason,
	} from "$lib/util/science-validation";

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

	const gameId = $derived(page.params.id ?? "");
	const scienceId = $derived(page.params.scienceId ?? "");
	const isViewMode = $derived(scienceId !== "");

	const localScience = $derived<ScienceSummary[]>(
		rendered?.report?.localScience ?? [],
	);
	const existingNames = $derived(localScience.map((sci) => sci.name));
	const viewing = $derived(
		isViewMode
			? (localScience.find((sci) => sci.name === scienceId) ?? null)
			: null,
	);
	const viewingPercent = $derived(
		viewing === null ? null : fractionsToPercent(viewing),
	);

	let name = $state("");
	let drive = $state(0);
	let weapons = $state(0);
	let shields = $state(0);
	let cargo = $state(0);
	let nameInputEl: HTMLInputElement | null = $state(null);

	const invalidReasonKeyMap: Record<ScienceInvalidReason, TranslationKey> = {
		empty: "game.designer.science.invalid.empty",
		too_long: "game.designer.science.invalid.too_long",
		starts_with_special: "game.designer.science.invalid.starts_with_special",
		ends_with_special: "game.designer.science.invalid.ends_with_special",
		consecutive_specials: "game.designer.science.invalid.consecutive_specials",
		whitespace: "game.designer.science.invalid.whitespace",
		disallowed_character: "game.designer.science.invalid.disallowed_character",
		duplicate_name: "game.designer.science.invalid.duplicate_name",
		drive_value: "game.designer.science.invalid.drive_value",
		weapons_value: "game.designer.science.invalid.weapons_value",
		shields_value: "game.designer.science.invalid.shields_value",
		cargo_value: "game.designer.science.invalid.cargo_value",
		sum_not_hundred: "game.designer.science.invalid.sum_not_hundred",
	};

	const validation = $derived(
		validateScience(
			{ name, drive, weapons, shields, cargo },
			{ existingNames },
		),
	);
	const invalidMessage = $derived(
		validation.ok ? "" : i18n.t(invalidReasonKeyMap[validation.reason]),
	);
	const canSave = $derived(validation.ok && draft !== undefined);

	const sumPercent = $derived(drive + weapons + shields + cargo);
	const sumDisplay = $derived(
		sumPercent.toLocaleString(undefined, {
			minimumFractionDigits: 0,
			maximumFractionDigits: 1,
		}),
	);

	$effect(() => {
		if (!isViewMode) {
			void tick().then(() => nameInputEl?.focus());
		}
	});

	function formatPercent(fraction: number): string {
		return (fraction * 100).toLocaleString(undefined, {
			minimumFractionDigits: 0,
			maximumFractionDigits: 1,
		});
	}

	function backToTable(): void {
		void goto(`/games/${gameId}/table/sciences`);
	}

	async function save(): Promise<void> {
		if (!validation.ok || draft === undefined) return;
		await draft.add({
			kind: "createScience",
			id: crypto.randomUUID(),
			name: validation.value.name,
			drive: validation.value.drive,
			weapons: validation.value.weapons,
			shields: validation.value.shields,
			cargo: validation.value.cargo,
		});
		backToTable();
	}

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

<section
	class="active-view"
	data-testid="active-view-designer-science"
	data-mode={isViewMode ? "view" : "new"}
>
	{#if isViewMode}
		{#if viewing === null || viewingPercent === null}
			<h2>{i18n.t("game.view.designer.science")}</h2>
			<p class="not-found" data-testid="designer-science-not-found">
				{i18n.t("game.designer.science.not_found", { name: scienceId })}
			</p>
			<div class="actions">
				<button
					type="button"
					data-testid="designer-science-back"
					onclick={backToTable}
				>
					{i18n.t("game.designer.science.action.back")}
				</button>
			</div>
		{:else}
			<h2 data-testid="designer-science-title">
				{i18n.t("game.designer.science.title.view", { name: viewing.name })}
			</h2>
			<p class="notice" data-testid="designer-science-notice">
				{i18n.t("game.designer.science.read_only_notice")}
			</p>
			<dl class="fields">
				<div class="field">
					<dt>{i18n.t("game.designer.science.field.name")}</dt>
					<dd data-testid="designer-science-view-name">{viewing.name}</dd>
				</div>
				<div class="field">
					<dt>{i18n.t("game.designer.science.field.drive")}</dt>
					<dd data-testid="designer-science-view-drive">
						{formatPercent(viewing.drive)}
					</dd>
				</div>
				<div class="field">
					<dt>{i18n.t("game.designer.science.field.weapons")}</dt>
					<dd data-testid="designer-science-view-weapons">
						{formatPercent(viewing.weapons)}
					</dd>
				</div>
				<div class="field">
					<dt>{i18n.t("game.designer.science.field.shields")}</dt>
					<dd data-testid="designer-science-view-shields">
						{formatPercent(viewing.shields)}
					</dd>
				</div>
				<div class="field">
					<dt>{i18n.t("game.designer.science.field.cargo")}</dt>
					<dd data-testid="designer-science-view-cargo">
						{formatPercent(viewing.cargo)}
					</dd>
				</div>
			</dl>
			<div class="actions">
				<button
					type="button"
					class="back"
					data-testid="designer-science-back"
					onclick={backToTable}
				>
					{i18n.t("game.designer.science.action.back")}
				</button>
				<button
					type="button"
					class="delete"
					data-testid="designer-science-delete"
					disabled={draft === undefined}
					onclick={() => void deleteThis()}
				>
					{i18n.t("game.designer.science.action.delete")}
				</button>
			</div>
		{/if}
	{:else}
		<h2 data-testid="designer-science-title">
			{i18n.t("game.designer.science.title.new")}
		</h2>
		<p class="hint" data-testid="designer-science-hint">
			{i18n.t("game.designer.science.hint.values")}
		</p>
		<form
			class="form"
			data-testid="designer-science-form"
			onsubmit={(event) => {
				event.preventDefault();
				void save();
			}}
		>
			<label class="row">
				<span>{i18n.t("game.designer.science.field.name")}</span>
				<input
					type="text"
					bind:this={nameInputEl}
					bind:value={name}
					data-testid="designer-science-input-name"
					maxlength="30"
					aria-invalid={validation.ok ? "false" : "true"}
				/>
			</label>
			<label class="row">
				<span>{i18n.t("game.designer.science.field.drive")}</span>
				<input
					type="number"
					step="0.1"
					min="0"
					max="100"
					bind:value={drive}
					data-testid="designer-science-input-drive"
				/>
			</label>
			<label class="row">
				<span>{i18n.t("game.designer.science.field.weapons")}</span>
				<input
					type="number"
					step="0.1"
					min="0"
					max="100"
					bind:value={weapons}
					data-testid="designer-science-input-weapons"
				/>
			</label>
			<label class="row">
				<span>{i18n.t("game.designer.science.field.shields")}</span>
				<input
					type="number"
					step="0.1"
					min="0"
					max="100"
					bind:value={shields}
					data-testid="designer-science-input-shields"
				/>
			</label>
			<label class="row">
				<span>{i18n.t("game.designer.science.field.cargo")}</span>
				<input
					type="number"
					step="0.1"
					min="0"
					max="100"
					bind:value={cargo}
					data-testid="designer-science-input-cargo"
				/>
			</label>
			<p
				class="sum"
				data-testid="designer-science-sum"
				data-sum-ok={validation.ok || (validation.reason !== "sum_not_hundred")
					? "true"
					: "false"}
			>
				{i18n.t("game.designer.science.field.sum", { value: sumDisplay })}
			</p>
			{#if !validation.ok}
				<p class="error" data-testid="designer-science-error">
					{invalidMessage}
				</p>
			{/if}
			<div class="actions">
				<button
					type="button"
					class="cancel"
					data-testid="designer-science-cancel"
					onclick={backToTable}
				>
					{i18n.t("game.designer.science.action.cancel")}
				</button>
				<button
					type="submit"
					class="save"
					data-testid="designer-science-save"
					disabled={!canSave}
					title={canSave ? "" : invalidMessage}
				>
					{i18n.t("game.designer.science.action.save")}
				</button>
			</div>
		</form>
	{/if}
</section>

<style>
	.active-view {
		padding: 1.5rem;
		font-family: system-ui, sans-serif;
		display: flex;
		flex-direction: column;
		gap: 0.85rem;
	}
	.active-view h2 {
		margin: 0;
		font-size: 1.1rem;
	}
	.notice,
	.hint,
	.not-found {
		margin: 0;
		color: var(--color-text-muted);
		font-size: 0.85rem;
	}
	.form {
		display: flex;
		flex-direction: column;
		gap: 0.55rem;
		max-width: 30rem;
	}
	.row {
		display: grid;
		grid-template-columns: 8rem 1fr;
		align-items: center;
		gap: 0.6rem;
	}
	.row span {
		color: var(--color-text-muted);
		font-size: 0.85rem;
	}
	.row input {
		font: inherit;
		padding: 0.3rem 0.5rem;
		background: var(--color-bg);
		color: var(--color-text);
		border: 1px solid var(--color-border);
		border-radius: 3px;
	}
	.row input[aria-invalid="true"] {
		border-color: var(--color-danger);
	}
	.sum {
		margin: 0;
		font-size: 0.85rem;
		color: var(--color-text-muted);
	}
	.sum[data-sum-ok="false"] {
		color: var(--color-danger);
	}
	.error {
		margin: 0;
		font-size: 0.8rem;
		color: var(--color-danger);
	}
	.fields {
		margin: 0;
		display: grid;
		grid-template-columns: max-content 1fr;
		row-gap: 0.25rem;
		column-gap: 0.75rem;
		max-width: 30rem;
	}
	.field {
		display: contents;
	}
	.field dt {
		color: var(--color-text-muted);
		font-size: 0.85rem;
	}
	.field dd {
		margin: 0;
		font-variant-numeric: tabular-nums;
		font-size: 0.9rem;
	}
	.actions {
		display: flex;
		gap: 0.5rem;
	}
	.actions button {
		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;
	}
	.actions button:not(:disabled):hover {
		color: var(--color-text);
		border-color: var(--color-accent);
	}
	.actions button:disabled {
		cursor: not-allowed;
		opacity: 0.5;
	}
	.actions .delete {
		color: var(--color-danger);
	}
	.actions .delete:not(:disabled):hover {
		border-color: var(--color-danger);
		color: var(--color-danger);
	}
</style>
