<!--
  Shared per-kind trigger configuration form.

  Mirrors the structured form used in /triggers/new so that the
  workload-side "Add trigger" surfaces (apps/new + apps/[id]) can
  reuse the same UX without copy-pasting field markup.

  Owns:
    - kind  (registry | git | manual | <unknown>)
    - name  (always required)
    - webhook_enabled + webhook_require_signature
    - the structured per-kind config slots (image/tag, repo/mode/branch/tag)
    - an advanced JSON escape hatch

  Exposes a `build()` method on the bindable `state` prop so callers can
  read out a `TriggerInput` body when they're ready to submit.
-->
<script lang="ts" module>
	import type { TriggerInput } from '$lib/api';

	export const KNOWN_KINDS = ['registry', 'git', 'manual', 'schedule'] as const;
	export type KnownTriggerKind = (typeof KNOWN_KINDS)[number];

	/** Suggested intervals offered as chips in the schedule form. Operator
	 *  can always type a custom Go duration into the input. */
	export const SCHEDULE_PRESETS = [
		{ key: 'hourly', value: '1h' },
		{ key: 'daily', value: '24h' },
		{ key: 'weekly', value: '168h' }
	] as const;

	/**
	 * State shared between the component and its parent. The parent owns
	 * one of these and binds it; the component mutates fields in place
	 * and exposes `build()` to materialize a submission body.
	 */
	export interface TriggerKindFormState {
		kind: KnownTriggerKind | string;
		name: string;
		webhookEnabled: boolean;
		webhookRequireSig: boolean;
		useAdvancedJson: boolean;

		// registry
		regImage: string;
		regTagPattern: string;
		// git
		gitRepo: string;
		gitMode: 'push' | 'tag';
		gitBranch: string;
		gitBranchPattern: string;
		gitTagPattern: string;
		// schedule
		schInterval: string;
		schReference: string;
		// JSON fallback
		jsonText: string;
	}

	export function createTriggerKindFormState(
		init: Partial<TriggerKindFormState> = {}
	): TriggerKindFormState {
		return {
			kind: init.kind ?? 'registry',
			name: init.name ?? '',
			webhookEnabled: init.webhookEnabled ?? false,
			webhookRequireSig: init.webhookRequireSig ?? true,
			useAdvancedJson: init.useAdvancedJson ?? false,
			regImage: init.regImage ?? '',
			regTagPattern: init.regTagPattern ?? '*',
			gitRepo: init.gitRepo ?? '',
			gitMode: init.gitMode ?? 'push',
			gitBranch: init.gitBranch ?? 'main',
			gitBranchPattern: init.gitBranchPattern ?? '',
			gitTagPattern: init.gitTagPattern ?? 'v*',
			schInterval: init.schInterval ?? '24h',
			schReference: init.schReference ?? '',
			jsonText: init.jsonText ?? ''
		};
	}

	/** Matches MinInterval enforced by the schedule trigger plugin
	 *  (internal/workload/plugin/trigger/schedule). Validation that mirrors
	 *  the backend rule keeps the submit button accurate. Go's
	 *  time.ParseDuration accepts s/m/h (NOT d) — keep this in sync to
	 *  avoid submit-then-server-reject. */
	function isValidInterval(s: string): boolean {
		const trimmed = s.trim();
		if (!trimmed) return false;
		const single = trimmed.match(/^(\d+)\s*(s|m|h)$/i);
		if (single) {
			const n = parseInt(single[1], 10);
			const unit = single[2].toLowerCase();
			if (!Number.isFinite(n) || n <= 0) return false;
			if (unit === 's' && n < 60) return false;
			return true;
		}
		// Compound like "1h30m" / "90m". Tighten the fallback so we
		// don't green-light "1 h" (whitespace inside), "-1h" (negative),
		// or "1.h" — Go's time.ParseDuration rejects all of those.
		return /^([0-9]+(\.[0-9]+)?(h|m|s))+$/i.test(trimmed);
	}

	export function isTriggerFormValid(s: TriggerKindFormState): boolean {
		if (!s.name.trim()) return false;
		if (s.useAdvancedJson) {
			if (!s.jsonText.trim()) return true;
			try {
				const parsed = JSON.parse(s.jsonText);
				return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed);
			} catch {
				return false;
			}
		}
		switch (s.kind) {
			case 'registry':
				return !!s.regImage.trim();
			case 'git':
				return !!s.gitRepo.trim();
			case 'manual':
				return true;
			case 'schedule':
				return isValidInterval(s.schInterval);
			default:
				// Unknown kinds without an advanced JSON payload are unsubmittable.
				return false;
		}
	}

	/** Reset every per-kind slot to its default. Called by
	 *  seedTriggerKindFormState before re-seeding so a caller that
	 *  re-seeds across kinds (draft restore, future flows) does not
	 *  inherit stale state from the previous kind's slots. The factory
	 *  defaults live in createTriggerKindFormState — we restate them
	 *  here rather than re-instantiating because the parent binds
	 *  the state object by reference. */
	function resetKindSlots(s: TriggerKindFormState): void {
		s.regImage = '';
		s.regTagPattern = '*';
		s.gitRepo = '';
		s.gitMode = 'push';
		s.gitBranch = 'main';
		s.gitTagPattern = 'v*';
		s.schInterval = '24h';
		s.schReference = '';
	}

	/** Seed an existing form state in place from a server-returned
	 *  trigger config blob. Used by the /triggers/[id] edit page so the
	 *  same component renders identically on create + edit. Unknown
	 *  kinds force the advanced-JSON fallback. Typed defensively — a
	 *  malformed config value falls back to the default rather than
	 *  stringifying garbage into an input box. Safe to call repeatedly
	 *  across kinds: every per-kind slot is reset before the switch. */
	export function seedTriggerKindFormState(
		s: TriggerKindFormState,
		kind: string,
		name: string,
		config: unknown,
		webhookEnabled: boolean,
		webhookRequireSig: boolean
	): void {
		resetKindSlots(s);
		s.kind = kind;
		s.name = name;
		s.webhookEnabled = webhookEnabled;
		s.webhookRequireSig = webhookRequireSig;
		const cfg = (config ?? {}) as Record<string, unknown>;
		// Prime the JSON text so toggling Advanced reveals the canonical
		// shape rather than a blank box. JSON.stringify of a plain
		// object only throws on cyclic refs, which a JSON-deserialized
		// response cannot contain — no try/catch needed.
		s.jsonText = JSON.stringify(cfg, null, 2);
		// Force JSON-only mode for unknown kinds — the structured form
		// has no branch for them.
		const isKnown = (KNOWN_KINDS as readonly string[]).includes(kind);
		if (!isKnown) {
			s.useAdvancedJson = true;
			return;
		}
		s.useAdvancedJson = false;
		switch (kind) {
			case 'registry':
				s.regImage = typeof cfg.image === 'string' ? cfg.image : '';
				s.regTagPattern = typeof cfg.tag_pattern === 'string' ? cfg.tag_pattern : '*';
				break;
			case 'git':
				s.gitRepo = typeof cfg.repo === 'string' ? cfg.repo : '';
				// Backend Validate enforces mode ∈ {push, tag}. Anything
				// else (undefined, "PUSH" case mismatch) collapses to
				// "push" — the safe default, but worth flagging so an
				// operator who hand-edited an invalid mode in the DB
				// understands the silent rewrite.
				s.gitMode = cfg.mode === 'tag' ? 'tag' : 'push';
				s.gitBranch = typeof cfg.branch === 'string' ? cfg.branch : 'main';
				s.gitBranchPattern = typeof cfg.branch_pattern === 'string' ? cfg.branch_pattern : '';
				s.gitTagPattern = typeof cfg.tag_pattern === 'string' ? cfg.tag_pattern : 'v*';
				break;
			case 'manual':
				// no structured fields
				break;
			case 'schedule':
				s.schInterval = typeof cfg.interval === 'string' ? cfg.interval : '24h';
				s.schReference = typeof cfg.reference === 'string' ? cfg.reference : '';
				break;
		}
	}

	export function buildTriggerInput(s: TriggerKindFormState): TriggerInput {
		let config: unknown;
		if (s.useAdvancedJson) {
			config = s.jsonText.trim() ? JSON.parse(s.jsonText) : {};
		} else if (s.kind === 'registry') {
			config = {
				image: s.regImage.trim(),
				tag_pattern: s.regTagPattern.trim() || '*'
			};
		} else if (s.kind === 'git') {
			if (s.gitMode === 'push') {
				const branchPattern = s.gitBranchPattern.trim();
				const pushCfg: Record<string, unknown> = {
					repo: s.gitRepo.trim(),
					mode: 'push',
					branch: s.gitBranch.trim() || 'main'
				};
				if (branchPattern) pushCfg.branch_pattern = branchPattern;
				config = pushCfg;
			} else {
				config = {
					repo: s.gitRepo.trim(),
					mode: 'tag',
					tag_pattern: s.gitTagPattern.trim() || '*'
				};
			}
		} else if (s.kind === 'manual') {
			config = {};
		} else if (s.kind === 'schedule') {
			const ref = s.schReference.trim();
			config = ref
				? { interval: s.schInterval.trim(), reference: ref }
				: { interval: s.schInterval.trim() };
		} else {
			config = {};
		}
		return {
			kind: s.kind,
			name: s.name.trim(),
			config,
			webhook_enabled: s.webhookEnabled,
			webhook_require_signature: s.webhookRequireSig
		};
	}
</script>

<script lang="ts">
	import ToggleSwitch from '$lib/components/ToggleSwitch.svelte';
	import RegistryImagePicker from '$lib/components/RegistryImagePicker.svelte';
	import { t } from '$lib/i18n';

	interface Props {
		/** Bound state container. */
		state: TriggerKindFormState;
		/** Optional id prefix to make labels unique when several copies are on-page. */
		idPrefix?: string;
		/** Render the name field above the per-kind config. Default true. */
		showName?: boolean;
		/** Render the webhook toggles below the config. Default true. */
		showWebhook?: boolean;
		/** Allow the operator to pick a kind. Default true (false on edit). */
		showKindPicker?: boolean;
	}

	let {
		state = $bindable(),
		idPrefix = 'tk',
		showName = true,
		showWebhook = true,
		showKindPicker = true
	}: Props = $props();

	const jsonValid = $derived.by(() => {
		if (!state.useAdvancedJson) return true;
		if (!state.jsonText.trim()) return true;
		try {
			const parsed = JSON.parse(state.jsonText);
			return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed);
		} catch {
			return false;
		}
	});

	function kindHint(k: string): string {
		const key = `redeployTriggers.kindHint.${k}`;
		const v = $t(key);
		return v === key ? '' : v;
	}

	function toggleAdvanced(): void {
		state.useAdvancedJson = !state.useAdvancedJson;
		if (state.useAdvancedJson && !state.jsonText.trim()) {
			// Seed JSON from current structured values so the operator can
			// refine rather than retype.
			try {
				const built = buildTriggerInput(state);
				state.jsonText = JSON.stringify(built.config ?? {}, null, 2);
			} catch {
				state.jsonText = '{\n  \n}';
			}
		}
	}

	// Registry-image picker writes the chosen full_ref back into the
	// registry trigger's image field. The picker's own reactive state
	// lives in RegistryImagePicker (a prop named `state` here shadows the
	// $state rune, so the picker is isolated in its own component).
	function onRegistryImagePicked(fullRef: string): void {
		state.regImage = fullRef;
	}
</script>

<div class="tk-form">
	{#if showKindPicker}
		<fieldset class="field group">
			<legend class="sub-label">{$t('redeployTriggers.form.kindLabel')}</legend>
			<div
				class="kind-grid"
				role="radiogroup"
				aria-label={$t('redeployTriggers.form.kindLabel')}
			>
				{#each KNOWN_KINDS as k (k)}
					<button
						type="button"
						role="radio"
						aria-checked={state.kind === k}
						class="kind-card"
						class:active={state.kind === k}
						onclick={() => (state.kind = k)}
					>
						<span class="kind-card-tag mono">
							{$t(`redeployTriggers.kindShort.${k}`)}
						</span>
						<span class="kind-card-name">{$t(`redeployTriggers.kind.${k}`)}</span>
						<span class="kind-card-hint">{kindHint(k)}</span>
					</button>
				{/each}
			</div>
		</fieldset>
	{:else}
		<div class="kind-static">
			<span class="kind-tag mono">{$t(`redeployTriggers.kindShort.${state.kind}`)}</span>
			<span>
				{$t(`redeployTriggers.kind.${state.kind}`) === `redeployTriggers.kind.${state.kind}`
					? state.kind
					: $t(`redeployTriggers.kind.${state.kind}`)}
			</span>
		</div>
	{/if}

	{#if showName}
		<div class="field">
			<label for="{idPrefix}-name" class="sub-label">
				{$t('redeployTriggers.form.name')}
				<span class="req">{$t('redeployTriggers.form.required')}</span>
			</label>
			<input
				id="{idPrefix}-name"
				type="text"
				class="input"
				bind:value={state.name}
				placeholder={$t('redeployTriggers.form.namePlaceholder')}
				autocomplete="off"
				required
			/>
		</div>
	{/if}

	<fieldset class="field group">
		<legend class="sub-label legend-row">
			<span>{$t('redeployTriggers.form.configLabel')}</span>
			<span class="opt">{$t(`redeployTriggers.kindShort.${state.kind}`)}</span>
			<button
				type="button"
				class="adv-toggle"
				class:on={state.useAdvancedJson}
				onclick={toggleAdvanced}
			>
				{$t('redeployTriggers.form.advancedToggle')}
			</button>
		</legend>

		{#if state.useAdvancedJson}
			<label class="sub" for="{idPrefix}-json">
				<span class="sub-label">{$t('redeployTriggers.form.configJson')}</span>
				<textarea
					id="{idPrefix}-json"
					class="input mono code"
					class:bad={!jsonValid}
					bind:value={state.jsonText}
					rows="6"
					spellcheck="false"
					placeholder={'{ }'}
					aria-invalid={!jsonValid}
				></textarea>
				{#if !jsonValid}
					<span class="hint danger" role="alert">
						{$t('redeployTriggers.form.invalidJson')}
					</span>
				{:else}
					<span class="hint">{$t('redeployTriggers.form.configJsonHint')}</span>
				{/if}
			</label>
		{:else if state.kind === 'registry'}
			<label class="sub" for="{idPrefix}-image">
				<span class="sub-label">{$t('redeployTriggers.form.image')}</span>
				<div class="input-with-button">
					<input
						id="{idPrefix}-image"
						type="text"
						class="input mono"
						bind:value={state.regImage}
						placeholder={$t('redeployTriggers.form.imagePlaceholder')}
						autocomplete="off"
						spellcheck="false"
						required
					/>
					<RegistryImagePicker current={state.regImage} onpick={onRegistryImagePicked} />
				</div>
				<span class="hint">{$t('redeployTriggers.form.imageHint')}</span>
			</label>
			<label class="sub" for="{idPrefix}-tag">
				<span class="sub-label">{$t('redeployTriggers.form.tagPattern')}</span>
				<input
					id="{idPrefix}-tag"
					type="text"
					class="input mono"
					bind:value={state.regTagPattern}
					placeholder={$t('redeployTriggers.form.tagPatternPlaceholder')}
					autocomplete="off"
					spellcheck="false"
				/>
				<span class="hint">{$t('redeployTriggers.form.tagPatternHint')}</span>
			</label>
		{:else if state.kind === 'git'}
			<label class="sub" for="{idPrefix}-repo">
				<span class="sub-label">{$t('redeployTriggers.form.repo')}</span>
				<input
					id="{idPrefix}-repo"
					type="text"
					class="input mono"
					bind:value={state.gitRepo}
					placeholder={$t('redeployTriggers.form.repoPlaceholder')}
					autocomplete="off"
					spellcheck="false"
					required
				/>
				<span class="hint">{$t('redeployTriggers.form.repoHint')}</span>
			</label>
			<div class="sub">
				<span class="sub-label">{$t('redeployTriggers.form.mode')}</span>
				<div
					class="mode-row"
					role="radiogroup"
					aria-label={$t('redeployTriggers.form.mode')}
				>
					<button
						type="button"
						role="radio"
						aria-checked={state.gitMode === 'push'}
						class="mode-chip"
						class:active={state.gitMode === 'push'}
						onclick={() => (state.gitMode = 'push')}
					>
						{$t('redeployTriggers.form.modePush')}
					</button>
					<button
						type="button"
						role="radio"
						aria-checked={state.gitMode === 'tag'}
						class="mode-chip"
						class:active={state.gitMode === 'tag'}
						onclick={() => (state.gitMode = 'tag')}
					>
						{$t('redeployTriggers.form.modeTag')}
					</button>
				</div>
			</div>
			{#if state.gitMode === 'push'}
				<label class="sub" for="{idPrefix}-branch">
					<span class="sub-label">{$t('redeployTriggers.form.branch')}</span>
					<input
						id="{idPrefix}-branch"
						type="text"
						class="input mono"
						bind:value={state.gitBranch}
						placeholder={$t('redeployTriggers.form.branchPlaceholder')}
						autocomplete="off"
						spellcheck="false"
					/>
					<span class="hint">{$t('redeployTriggers.form.branchHint')}</span>
				</label>
				<label class="sub" for="{idPrefix}-branch-pattern">
					<span class="sub-label">{$t('redeployTriggers.form.branchPattern')}</span>
					<input
						id="{idPrefix}-branch-pattern"
						type="text"
						class="input mono"
						bind:value={state.gitBranchPattern}
						placeholder={$t('redeployTriggers.form.branchPatternPlaceholder')}
						autocomplete="off"
						spellcheck="false"
					/>
					<span class="hint">{$t('redeployTriggers.form.branchPatternHint')}</span>
				</label>
			{:else}
				<label class="sub" for="{idPrefix}-gtag">
					<span class="sub-label">{$t('redeployTriggers.form.tagPattern')}</span>
					<input
						id="{idPrefix}-gtag"
						type="text"
						class="input mono"
						bind:value={state.gitTagPattern}
						placeholder={$t('redeployTriggers.form.tagPatternPlaceholder')}
						autocomplete="off"
						spellcheck="false"
					/>
					<span class="hint">{$t('redeployTriggers.form.tagPatternHint')}</span>
				</label>
			{/if}
		{:else if state.kind === 'manual'}
			<div class="note">
				<span class="note-tag">MANUAL</span>
				<p>{$t('redeployTriggers.form.manualNote')}</p>
			</div>
		{:else if state.kind === 'schedule'}
			<div class="note">
				<span class="note-tag">CRN</span>
				<p>{$t('redeployTriggers.form.scheduleNote')}</p>
			</div>
			<div class="sub">
				<span class="sub-label">{$t('redeployTriggers.form.intervalPresets')}</span>
				<div
					class="mode-row"
					role="radiogroup"
					aria-label={$t('redeployTriggers.form.intervalPresets')}
				>
					{#each SCHEDULE_PRESETS as p (p.key)}
						<button
							type="button"
							role="radio"
							aria-checked={state.schInterval === p.value}
							class="mode-chip"
							class:active={state.schInterval === p.value}
							onclick={() => (state.schInterval = p.value)}
						>
							{$t(`redeployTriggers.form.intervalPreset.${p.key}`)}
						</button>
					{/each}
				</div>
			</div>
			<label class="sub" for="{idPrefix}-interval">
				<span class="sub-label">{$t('redeployTriggers.form.interval')}</span>
				<input
					id="{idPrefix}-interval"
					type="text"
					class="input mono"
					class:bad={!isValidInterval(state.schInterval)}
					bind:value={state.schInterval}
					placeholder="24h"
					autocomplete="off"
					spellcheck="false"
					required
				/>
				<span class="hint">{$t('redeployTriggers.form.intervalHint')}</span>
			</label>
			<label class="sub" for="{idPrefix}-schref">
				<span class="sub-label">{$t('redeployTriggers.form.scheduleReference')}</span>
				<input
					id="{idPrefix}-schref"
					type="text"
					class="input mono"
					bind:value={state.schReference}
					placeholder={$t('redeployTriggers.form.scheduleReferencePlaceholder')}
					autocomplete="off"
					spellcheck="false"
				/>
				<span class="hint">{$t('redeployTriggers.form.scheduleReferenceHint')}</span>
			</label>
		{:else}
			<div class="note">
				<span class="note-tag">?</span>
				<p>{$t('redeployTriggers.form.unknownNote')}</p>
			</div>
		{/if}
	</fieldset>

	{#if showWebhook}
		<fieldset class="field group">
			<legend class="sub-label legend-row">
				<span>{$t('redeployTriggers.detail.webhook')}</span>
				<span class="opt">OPTIONAL</span>
			</legend>
			<div class="row-toggle">
				<div class="toggle-copy">
					<span class="lbl small">{$t('redeployTriggers.form.webhookEnabled')}</span>
					<p class="hint">{$t('redeployTriggers.form.webhookEnabledHint')}</p>
				</div>
				<ToggleSwitch
					bind:checked={state.webhookEnabled}
					label={$t('redeployTriggers.form.webhookEnabled')}
				/>
			</div>
			{#if state.webhookEnabled}
				<div class="row-toggle indent">
					<div class="toggle-copy">
						<span class="lbl small">{$t('redeployTriggers.form.webhookRequireSig')}</span>
						<p class="hint">{$t('redeployTriggers.form.webhookRequireSigHint')}</p>
					</div>
					<ToggleSwitch
						bind:checked={state.webhookRequireSig}
						label={$t('redeployTriggers.form.webhookRequireSig')}
					/>
				</div>
			{/if}
		</fieldset>
	{/if}
</div>

<style>
	.tk-form {
		display: flex;
		flex-direction: column;
		gap: 1rem;
	}

	.field {
		display: flex;
		flex-direction: column;
		gap: 0.45rem;
		margin: 0;
		padding: 0;
		border: 0;
	}
	.field.group {
		gap: 0.6rem;
	}

	.sub {
		display: flex;
		flex-direction: column;
		gap: 0.35rem;
		min-width: 0;
	}
	.sub-label {
		font-family: var(--forge-mono);
		font-size: 0.62rem;
		font-weight: 600;
		letter-spacing: 0.14em;
		text-transform: uppercase;
		color: var(--text-secondary);
	}
	.legend-row {
		display: inline-flex;
		align-items: center;
		gap: 0.5rem;
		width: 100%;
	}

	.req,
	.opt {
		font-family: var(--forge-mono);
		font-size: 0.58rem;
		font-weight: 600;
		text-transform: uppercase;
		letter-spacing: 0.14em;
	}
	.req {
		color: var(--color-danger);
	}
	.opt {
		color: var(--text-tertiary);
	}

	.adv-toggle {
		margin-left: auto;
		padding: 0.2rem 0.55rem;
		background: transparent;
		border: 1px solid var(--border-primary);
		border-radius: var(--radius-full);
		font-family: var(--forge-mono);
		font-size: 0.56rem;
		font-weight: 600;
		letter-spacing: 0.12em;
		text-transform: uppercase;
		color: var(--text-secondary);
		cursor: pointer;
		transition: border-color 150ms ease, background 150ms ease, color 150ms ease;
	}
	.adv-toggle:hover {
		border-color: var(--forge-accent);
		color: var(--forge-accent);
	}
	.adv-toggle.on {
		background: var(--forge-accent);
		border-color: var(--forge-accent);
		color: var(--surface-card);
	}

	.input {
		width: 100%;
		background: var(--surface-input);
		border: 1px solid var(--border-input);
		border-radius: var(--radius-lg);
		padding: 0.55rem 0.75rem;
		font-size: 0.9rem;
		color: var(--text-primary);
		font-family: inherit;
		outline: none;
		transition: border-color 120ms ease, box-shadow 120ms ease;
	}
	.input:focus {
		border-color: var(--border-focus);
		box-shadow: 0 0 0 3px var(--forge-accent-soft);
	}
	.input.mono {
		font-family: var(--forge-mono);
		font-size: 0.85rem;
	}
	.input.code {
		resize: vertical;
		min-height: 110px;
		line-height: 1.5;
	}
	.input.bad {
		border-color: var(--color-danger);
	}
	.input.bad:focus {
		box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-danger) 22%, transparent);
	}

	.hint {
		font-size: 0.76rem;
		color: var(--text-tertiary);
		line-height: 1.45;
		margin: 0;
	}
	.hint.danger {
		color: var(--color-danger);
	}

	/* ── Registry image picker affordance ─────────────
	   The image field becomes an input + "browse" button row (the button
	   is rendered by RegistryImagePicker). Manual text entry stays fully
	   functional — the picker is purely additive. */
	.input-with-button {
		display: flex;
		align-items: stretch;
		gap: 0.4rem;
	}
	.input-with-button > .input {
		flex: 1;
		min-width: 0;
	}

	.kind-grid {
		display: grid;
		grid-template-columns: repeat(4, minmax(0, 1fr));
		gap: 0.55rem;
	}
	@media (max-width: 900px) {
		.kind-grid {
			grid-template-columns: repeat(2, minmax(0, 1fr));
		}
	}
	@media (max-width: 480px) {
		.kind-grid {
			grid-template-columns: 1fr;
		}
	}
	.kind-card {
		display: flex;
		flex-direction: column;
		gap: 0.35rem;
		padding: 0.75rem 0.8rem;
		text-align: left;
		background: var(--surface-card);
		border: 1px solid var(--border-primary);
		border-radius: var(--radius-lg);
		cursor: pointer;
		transition: border-color 150ms ease, background 150ms ease, transform 150ms ease;
		position: relative;
	}
	.kind-card:hover {
		border-color: color-mix(in srgb, var(--forge-accent) 40%, var(--border-primary));
		transform: translateY(-1px);
	}
	.kind-card.active {
		border-color: var(--forge-accent);
		background: var(--forge-accent-soft);
		box-shadow: inset 0 0 0 1px var(--forge-accent);
	}
	.kind-card-tag {
		display: inline-flex;
		align-self: flex-start;
		padding: 0.18rem 0.5rem;
		background: var(--text-primary);
		color: var(--surface-card);
		font-family: var(--forge-mono);
		font-size: 0.58rem;
		font-weight: 700;
		letter-spacing: 0.18em;
		border-radius: var(--radius-sm);
		line-height: 1;
	}
	.kind-card.active .kind-card-tag {
		background: var(--forge-accent);
	}
	.kind-card-name {
		font-weight: 600;
		font-size: 0.9rem;
		color: var(--text-primary);
		letter-spacing: -0.01em;
	}
	.kind-card-hint {
		font-size: 0.68rem;
		color: var(--text-tertiary);
		line-height: 1.45;
	}

	.kind-static {
		display: inline-flex;
		align-items: center;
		gap: 0.55rem;
		font-size: 0.95rem;
		color: var(--text-primary);
		font-weight: 600;
	}
	.kind-tag {
		display: inline-flex;
		padding: 0.16rem 0.5rem;
		background: var(--text-primary);
		color: var(--surface-card);
		font-family: var(--forge-mono);
		font-size: 0.58rem;
		font-weight: 700;
		letter-spacing: 0.18em;
		border-radius: var(--radius-sm);
		line-height: 1;
	}

	.mode-row {
		display: inline-flex;
		gap: 0;
		background: var(--surface-card-hover);
		border: 1px solid var(--border-primary);
		border-radius: var(--radius-full);
		padding: 2px;
		width: fit-content;
	}
	.mode-chip {
		padding: 0.28rem 0.8rem;
		background: transparent;
		border: 0;
		border-radius: var(--radius-full);
		font-family: var(--forge-mono);
		font-size: 0.58rem;
		font-weight: 600;
		letter-spacing: 0.12em;
		text-transform: uppercase;
		color: var(--text-secondary);
		cursor: pointer;
		transition: background 150ms ease, color 150ms ease;
	}
	.mode-chip:hover {
		color: var(--text-primary);
	}
	.mode-chip.active {
		background: var(--text-primary);
		color: var(--surface-card);
	}

	.note {
		display: flex;
		gap: 0.7rem;
		align-items: flex-start;
		padding: 0.7rem 0.85rem;
		background: var(--surface-card-hover);
		border: 1px dashed var(--border-primary);
		border-radius: var(--radius-lg);
	}
	.note-tag {
		padding: 0.16rem 0.4rem;
		background: var(--text-primary);
		color: var(--surface-card);
		font-family: var(--forge-mono);
		font-size: 0.56rem;
		font-weight: 700;
		letter-spacing: 0.18em;
		border-radius: var(--radius-sm);
		flex: 0 0 auto;
		line-height: 1.4;
	}
	.note p {
		margin: 0;
		font-size: 0.83rem;
		color: var(--text-secondary);
		line-height: 1.5;
	}

	.row-toggle {
		display: flex;
		flex-direction: row;
		align-items: flex-start;
		justify-content: space-between;
		gap: 0.85rem;
		padding-top: 0.55rem;
		border-top: 1px dashed var(--border-primary);
	}
	.row-toggle.indent {
		border-top: 0;
		padding-top: 0.05rem;
		padding-left: 0.95rem;
		border-left: 2px solid var(--forge-accent-soft);
		margin-left: 0.4rem;
	}
	.toggle-copy {
		flex: 1;
		display: flex;
		flex-direction: column;
		gap: 0.2rem;
	}
	.lbl {
		font-weight: 600;
		font-size: 0.95rem;
		color: var(--text-primary);
	}
	.lbl.small {
		font-size: 0.9rem;
	}
</style>
