<!--
  Deploy-strategy selector — phase-2 UI for the per-workload `deploy_strategy`
  (backend: internal/workload/plugin/strategy.go). A two-card radiogroup that
  picks between `recreate` (stop old → start new, brief downtime) and
  `blue-green` (start new alongside old, health-check, swap traffic, retire
  old — no downtime). Each card carries a CSS-only motion glyph that animates
  the actual deploy semantics: recreate shows a downtime GAP between versions;
  blue-green shows the two versions OVERLAP while traffic cuts over.

  Empty-string canonical default
  ──────────────────────────────
  `strategy === ''` means "use the source's historical default". We keep it as
  `''` (rather than writing the explicit value) whenever the operator picks the
  source default, so an untouched `source_config` stays byte-identical — see
  DeployStrategy in $lib/workload/sourceForms. The card matching the source
  default therefore reads as selected when `strategy` is empty, and clicking it
  clears `strategy` back to `''`.
-->
<script lang="ts">
	import type { DeployStrategy } from '$lib/workload/sourceForms';
	import { t } from '$lib/i18n';

	interface Props {
		/** Bound strategy. "" = source default (kept canonical for byte-shape). */
		strategy: DeployStrategy;
		/** The source's effective default — labels the "default" pill and is
		 *  the value `strategy === ''` resolves to visually. */
		defaultStrategy: 'recreate' | 'blue-green';
		/** Static-only: warn that storage-backed Deno sites fall back to recreate. */
		denoCaveat?: boolean;
		/** Unique id stem so multiple instances don't collide on the DOM. */
		idPrefix?: string;
	}

	let {
		strategy = $bindable(),
		defaultStrategy,
		denoCaveat = false,
		idPrefix = 'deploy-strategy'
	}: Props = $props();

	type Concrete = 'recreate' | 'blue-green';
	const OPTIONS: Concrete[] = ['recreate', 'blue-green'];

	// What the backend will actually do given the current (possibly empty) value.
	const effective = $derived<Concrete>(strategy === '' ? defaultStrategy : strategy);

	function pick(value: Concrete) {
		// Selecting the source default collapses back to "" so we never persist
		// a redundant explicit value (keeps existing configs byte-identical).
		strategy = value === defaultStrategy ? '' : value;
	}

	// WAI-ARIA radiogroup keyboard pattern: arrows move + select, roving
	// tabindex keeps a single tab stop.
	function onKey(e: KeyboardEvent, index: number) {
		const next =
			e.key === 'ArrowRight' || e.key === 'ArrowDown'
				? (index + 1) % OPTIONS.length
				: e.key === 'ArrowLeft' || e.key === 'ArrowUp'
					? (index - 1 + OPTIONS.length) % OPTIONS.length
					: -1;
		if (next === -1) return;
		e.preventDefault();
		pick(OPTIONS[next]);
		// Move focus to the newly-selected radio (roving tabindex).
		const el = document.getElementById(`${idPrefix}-${OPTIONS[next]}`);
		el?.focus();
	}

	function nameFor(o: Concrete): string {
		return o === 'recreate'
			? $t('apps.new.deployStrategy.recreateName')
			: $t('apps.new.deployStrategy.blueGreenName');
	}
	function descFor(o: Concrete): string {
		return o === 'recreate'
			? $t('apps.new.deployStrategy.recreateDesc')
			: $t('apps.new.deployStrategy.blueGreenDesc');
	}

	const showDenoCaveat = $derived(denoCaveat && effective === 'blue-green');
</script>

<section class="strategy-field">
	<div class="strategy-head">
		<span class="forge-eyebrow">
			<span class="forge-ember"></span>
			<span>{$t('apps.new.deployStrategy.label')}</span>
		</span>
	</div>

	<div
		class="strategy-grid"
		role="radiogroup"
		aria-label={$t('apps.new.deployStrategy.label')}
	>
		{#each OPTIONS as opt, i (opt)}
			{@const selected = effective === opt}
			<button
				type="button"
				id={`${idPrefix}-${opt}`}
				role="radio"
				aria-checked={selected}
				tabindex={selected ? 0 : -1}
				class="opt"
				class:selected
				class:is-recreate={opt === 'recreate'}
				class:is-bluegreen={opt === 'blue-green'}
				onclick={() => pick(opt)}
				onkeydown={(e) => onKey(e, i)}
			>
				<!-- Motion glyph: animates only when the card is selected. -->
				<span class="glyph" aria-hidden="true">
					{#if opt === 'recreate'}
						<span class="rc-bar v1"></span>
						<span class="rc-gap"><span class="rc-gap-mark"></span></span>
						<span class="rc-bar v2"></span>
					{:else}
						<span class="bg-bar v1"></span>
						<span class="bg-bar v2"></span>
						<span class="bg-traffic"></span>
					{/if}
				</span>

				<span class="opt-meta">
					<span class="opt-name">
						{nameFor(opt)}
						{#if opt === defaultStrategy}
							<span class="opt-default">{$t('apps.new.deployStrategy.defaultBadge')}</span>
						{/if}
					</span>
					<span class="opt-desc">{descFor(opt)}</span>
				</span>

				<span class="opt-tick" aria-hidden="true">
					<svg viewBox="0 0 16 16" width="13" height="13">
						<path
							d="M3.5 8.5l3 3 6-7"
							fill="none"
							stroke="currentColor"
							stroke-width="2"
							stroke-linecap="round"
							stroke-linejoin="round"
						/>
					</svg>
				</span>
			</button>
		{/each}
	</div>

	{#if showDenoCaveat}
		<p class="strategy-caveat" role="note">{$t('apps.new.deployStrategy.denoCaveat')}</p>
	{/if}
</section>

<style>
	.strategy-field {
		display: flex;
		flex-direction: column;
		gap: 0.6rem;
	}
	.strategy-head {
		display: flex;
		align-items: center;
	}

	.strategy-grid {
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: 0.6rem;
	}
	@media (max-width: 560px) {
		.strategy-grid {
			grid-template-columns: 1fr;
		}
	}

	/* ── Option card ──────────────────────────────────────────── */
	.opt {
		position: relative;
		display: grid;
		grid-template-rows: auto auto;
		gap: 0.55rem;
		text-align: left;
		padding: 0.75rem 0.8rem 0.7rem;
		border: 1px solid var(--border-primary);
		border-radius: var(--radius-lg);
		background: var(--surface-card);
		color: var(--text-secondary);
		cursor: pointer;
		transition:
			border-color 140ms ease,
			background 140ms ease,
			box-shadow 140ms ease,
			transform 140ms ease;
	}
	.opt:hover:not(.selected) {
		border-color: color-mix(in srgb, var(--forge-accent) 45%, var(--border-primary));
		background: var(--surface-card-hover);
	}
	.opt:focus-visible {
		outline: none;
		border-color: var(--forge-accent);
		box-shadow: 0 0 0 3px var(--forge-accent-soft);
	}
	.opt.selected {
		border-color: var(--forge-accent);
		background: var(--surface-card-hover);
		box-shadow: 0 0 0 3px var(--forge-accent-soft);
	}

	/* ── Meta (title + description) ───────────────────────────── */
	.opt-meta {
		display: flex;
		flex-direction: column;
		gap: 0.22rem;
		min-width: 0;
	}
	.opt-name {
		display: inline-flex;
		align-items: center;
		gap: 0.4rem;
		font-weight: 600;
		font-size: 0.9rem;
		color: var(--text-primary);
		letter-spacing: -0.01em;
	}
	.opt-default {
		font-family: var(--forge-mono);
		font-size: 0.54rem;
		font-weight: 700;
		letter-spacing: 0.14em;
		text-transform: uppercase;
		padding: 0.12rem 0.34rem;
		border-radius: var(--radius-sm);
		color: var(--text-tertiary);
		background: color-mix(in srgb, var(--text-tertiary) 12%, transparent);
		border: 1px solid var(--border-primary);
	}
	.opt.selected .opt-default {
		color: var(--forge-accent);
		background: var(--forge-accent-soft);
		border-color: color-mix(in srgb, var(--forge-accent) 35%, transparent);
	}
	.opt-desc {
		font-size: 0.76rem;
		line-height: 1.45;
		color: var(--text-tertiary);
	}

	/* ── Selected tick ────────────────────────────────────────── */
	.opt-tick {
		position: absolute;
		top: 0.6rem;
		right: 0.6rem;
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 18px;
		height: 18px;
		border-radius: 50%;
		color: #fff;
		background: var(--forge-accent);
		opacity: 0;
		transform: scale(0.6);
		transition:
			opacity 140ms ease,
			transform 160ms cubic-bezier(0.34, 1.56, 0.64, 1);
	}
	.opt.selected .opt-tick {
		opacity: 1;
		transform: scale(1);
	}

	/* ── Motion glyph shell ───────────────────────────────────── */
	.glyph {
		position: relative;
		display: block;
		height: 30px;
		border-radius: var(--radius-md);
		background: color-mix(in srgb, var(--text-tertiary) 7%, transparent);
		border: 1px solid color-mix(in srgb, var(--border-primary) 70%, transparent);
		overflow: hidden;
	}

	/* Version-bar fills (amber = v1/old, green = v2/new). The amber stays on
	   brand; green reads as "the new one going live", anchoring blue-green. */
	.v1 {
		--bar: var(--forge-accent);
	}
	.v2 {
		--bar: var(--color-success);
	}

	/* ── Recreate glyph: v1 | gap | v2 laid out as a timeline ─── */
	.is-recreate .glyph {
		display: flex;
		align-items: center;
		gap: 0;
		padding: 0 0.45rem;
	}
	.rc-bar {
		height: 9px;
		border-radius: 999px;
		background: var(--bar);
		flex: 1 1 0;
	}
	.rc-bar.v1 {
		opacity: 0.85;
	}
	.rc-bar.v2 {
		opacity: 0.85;
	}
	.rc-gap {
		flex: 0 0 22%;
		display: flex;
		align-items: center;
		justify-content: center;
		align-self: stretch;
	}
	/* The "downtime" notch between versions — a dashed seam tinted with the
	   warning hue. This visible gap is the whole point of recreate. */
	.rc-gap-mark {
		width: 100%;
		height: 0;
		border-top: 2px dashed color-mix(in srgb, var(--color-warning) 65%, transparent);
		opacity: 0.7;
	}

	/* ── Blue-green glyph: stacked bars + a traffic dot cutting over ── */
	.is-bluegreen .glyph {
		padding: 0.45rem 0.5rem;
	}
	.bg-bar {
		position: absolute;
		left: 0.5rem;
		right: 1.4rem;
		height: 8px;
		border-radius: 999px;
		background: var(--bar);
	}
	.bg-bar.v1 {
		top: 0.5rem;
		opacity: 0.85;
	}
	.bg-bar.v2 {
		bottom: 0.5rem;
		opacity: 0.85;
	}
	/* Traffic indicator: a brand dot parked on whichever bar serves requests.
	   Static = on v2 (new). When selected it travels v1 → v2 (the cutover). */
	.bg-traffic {
		position: absolute;
		right: 0.55rem;
		width: 9px;
		height: 9px;
		border-radius: 50%;
		background: var(--forge-accent);
		box-shadow: 0 0 0 3px var(--forge-accent-soft);
		bottom: 0.45rem;
	}

	/* ── Animation — only the SELECTED card moves (less visual noise) ── */
	.opt.selected.is-recreate .rc-bar.v1 {
		animation: rc-v1 2.6s ease-in-out infinite;
	}
	.opt.selected.is-recreate .rc-bar.v2 {
		animation: rc-v2 2.6s ease-in-out infinite;
	}
	.opt.selected.is-recreate .rc-gap-mark {
		animation: rc-gap 2.6s ease-in-out infinite;
	}
	@keyframes rc-v1 {
		0%,
		25% {
			opacity: 0.9;
			transform: scaleX(1);
		}
		45%,
		100% {
			opacity: 0.12;
			transform: scaleX(0.96);
		}
	}
	@keyframes rc-v2 {
		0%,
		55% {
			opacity: 0.12;
			transform: scaleX(0.96);
		}
		78%,
		100% {
			opacity: 0.9;
			transform: scaleX(1);
		}
	}
	@keyframes rc-gap {
		0%,
		30% {
			opacity: 0.25;
		}
		45%,
		60% {
			opacity: 1;
		}
		80%,
		100% {
			opacity: 0.25;
		}
	}

	.opt.selected.is-bluegreen .bg-bar.v1 {
		animation: bg-v1 2.8s ease-in-out infinite;
	}
	.opt.selected.is-bluegreen .bg-traffic {
		animation: bg-traffic 2.8s ease-in-out infinite;
	}
	@keyframes bg-v1 {
		0%,
		55% {
			opacity: 0.9;
		}
		/* v1 dims to "retired" AFTER traffic has moved — never a service gap. */
		80%,
		100% {
			opacity: 0.3;
		}
	}
	@keyframes bg-traffic {
		0%,
		20% {
			top: 0.45rem;
			bottom: auto;
		}
		50%,
		100% {
			top: auto;
			bottom: 0.45rem;
		}
	}

	/* Respect reduced-motion: drop the keyframes, keep the legible end-state
	   (recreate keeps its visible gap; blue-green keeps both bars + traffic). */
	@media (prefers-reduced-motion: reduce) {
		.opt .rc-bar,
		.opt .rc-gap-mark,
		.opt .bg-bar,
		.opt .bg-traffic {
			animation: none !important;
		}
		.opt {
			transition: none;
		}
	}

	/* ── Deno fallback caveat ─────────────────────────────────── */
	.strategy-caveat {
		margin: 0;
		font-size: 0.74rem;
		line-height: 1.45;
		color: var(--color-warning);
		padding: 0.4rem 0.55rem;
		border-radius: var(--radius-md);
		background: color-mix(in srgb, var(--color-warning) 9%, transparent);
		border: 1px solid color-mix(in srgb, var(--color-warning) 28%, transparent);
	}
</style>
