<script lang="ts">
	import { onMount } from 'svelte';
	import { goto } from '$app/navigation';
	import { page } from '$app/stores';
	import * as api from '$lib/api';
	import type { SharedSecret, SharedSecretInput } from '$lib/api';
	import type { EntityPickerItem, App } from '$lib/types';
	import { sharedSecretDetailCache } from '$lib/stores/caches';
	import ForgeHero from '$lib/components/ForgeHero.svelte';
	import ToggleSwitch from '$lib/components/ToggleSwitch.svelte';
	import EntityPicker from '$lib/components/EntityPicker.svelte';
	import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
	import { IconX } from '$lib/components/icons';
	import { t } from '$lib/i18n';

	// IDs are uuid strings — pass through untouched. An empty segment is
	// the only invalid case worth guarding.
	const id = $derived($page.params.id ?? '');

	let secret = $state<SharedSecret | null>(null);
	let loading = $state(true);
	let saving = $state(false);
	let deleting = $state(false);
	let confirmDelete = $state(false);
	let error = $state('');

	let name = $state('');
	// The value field starts blank and is NEVER pre-filled — the API
	// doesn't return the stored secret, only `has_value`. A blank field
	// on save means "keep the stored value"; a typed value rotates it.
	let value = $state('');
	let encrypted = $state(true);
	// The encrypted flag as it was loaded. Flipping it requires a new
	// value (the server 400s otherwise), so we compare against this.
	let loadedEncrypted = $state(true);
	let scope = $state<'global' | 'app'>('global');
	let appID = $state('');
	let description = $state('');
	let enabled = $state(true);

	// App picker state.
	let apps = $state<App[]>([]);
	let pickerOpen = $state(false);

	const pickerItems = $derived<EntityPickerItem[]>(
		apps.map((a) => ({
			value: a.id,
			label: a.name,
			description: a.description
		}))
	);

	const selectedApp = $derived(apps.find((a) => a.id === appID));

	// The encrypted toggle changed from its loaded state. Flipping it is
	// only valid alongside a fresh value — the server rejects a bare flip.
	const encryptedChanged = $derived(encrypted !== loadedEncrypted);

	// Flipping encrypted without entering a value is the one blocked
	// combination. We surface a hint and disable Save for it.
	const encryptedFlipBlocked = $derived(encryptedChanged && value === '');

	// app scope needs an app_id; the encrypted-flip rule must hold.
	const canSave = $derived(
		name.trim() !== '' &&
			(scope === 'global' || appID.trim() !== '') &&
			!encryptedFlipBlocked
	);

	function onScopeChange(): void {
		if (scope === 'global') appID = '';
	}

	function pickApp(v: string): void {
		appID = v;
		pickerOpen = false;
	}

	function clearApp(): void {
		appID = '';
	}

	function seedForm(s: SharedSecret): void {
		name = s.name;
		// value intentionally NOT populated — write-only.
		value = '';
		encrypted = s.encrypted;
		loadedEncrypted = s.encrypted;
		scope = s.scope;
		appID = s.app_id;
		description = s.description;
		enabled = s.enabled;
	}

	// Keyed warm-seed: render the cached secret metadata instantly on revisit
	// and seed the editable form ONCE per id (a background revalidation never
	// re-seeds, so unsaved edits survive). The cached object carries no
	// plaintext value (the API returns only `has_value`). Plain (non-reactive)
	// guard. Apps (for the picker) are best-effort and loaded each visit.
	let seededKey: string | null = null;

	async function load(): Promise<void> {
		if (id === '') {
			error = 'Invalid secret id';
			loading = false;
			return;
		}
		const k = id;
		const cached = sharedSecretDetailCache.peek(k);
		if (cached.value) {
			secret = cached.value;
			if (seededKey !== k) { seedForm(cached.value); seededKey = k; }
			loading = false;
		} else {
			loading = true;
		}
		error = '';
		const appsPromise = api.listApps().catch(() => [] as App[]);
		await sharedSecretDetailCache.refresh(k);
		apps = await appsPromise;
		// Bail if the route id changed while we were fetching — a newer load()
		// now owns the page state; applying this stale result would clobber it.
		if (id !== k) return;
		const entry = sharedSecretDetailCache.peek(k);
		if (entry.error && !cached.value) {
			error = entry.error || 'Failed to load secret';
			loading = false;
			return;
		}
		if (entry.value) {
			secret = entry.value;
			if (seededKey !== k) { seedForm(entry.value); seededKey = k; }
		}
		loading = false;
	}

	async function save(e?: Event): Promise<void> {
		e?.preventDefault();
		if (!secret || id === '' || saving || !canSave) return;
		saving = true;
		error = '';
		try {
			const body: SharedSecretInput = {
				name: name.trim(),
				encrypted,
				scope,
				app_id: scope === 'app' ? appID.trim() : '',
				description: description.trim(),
				enabled
			};
			// PATCH semantics: omit `value` to keep the stored secret;
			// include it only when the operator typed a new one (rotate).
			if (value !== '') body.value = value;
			secret = await api.updateSharedSecret(id, body);
			sharedSecretDetailCache.set(id, secret);
			// Re-baseline after a successful save: the new encrypted flag
			// is now the loaded state, and the value field clears so a
			// stale rotation can't be re-submitted.
			loadedEncrypted = secret.encrypted;
			encrypted = secret.encrypted;
			scope = secret.scope;
			appID = secret.app_id;
			value = '';
		} catch (e) {
			error = conflictMessage(e);
		} finally {
			saving = false;
		}
	}

	async function doDelete(): Promise<void> {
		if (id === '') return;
		deleting = true;
		error = '';
		try {
			await api.deleteSharedSecret(id);
			goto('/shared-secrets');
		} catch (e) {
			error = e instanceof Error ? e.message : 'Delete failed';
			deleting = false;
			confirmDelete = false;
		}
	}

	// A duplicate (scope, app_id, name) returns 409 — surface a friendly
	// message instead of the raw backend error.
	function conflictMessage(e: unknown): string {
		const msg = e instanceof Error ? e.message : 'Save failed';
		if (/\b409\b/.test(msg) || /conflict/i.test(msg) || /already exists/i.test(msg)) {
			return $t('sharedsecrets.form.conflict');
		}
		return msg;
	}

	function scopeLabel(s: SharedSecret | null): string {
		if (!s) return '';
		if (s.scope === 'app') {
			const label = selectedApp?.name || s.app_id.slice(0, 8);
			return $t('sharedsecrets.scope.app', { name: label });
		}
		return $t('sharedsecrets.scope.global');
	}

	onMount(load);
</script>

<svelte:head>
	<title>{secret?.name ?? $t('sharedsecrets.titleSingular')} · Tinyforge</title>
</svelte:head>

<div class="forge" aria-busy={loading}>
	{#snippet detailLede()}
		{#if secret}
			<span class="lede-meta">
				{$t('sharedsecrets.list.scope')} <code>{scopeLabel(secret)}</code> ·
				{$t('sharedsecrets.list.value')}
				<code>{secret.has_value ? $t('sharedsecrets.list.valueSet') : $t('sharedsecrets.list.valueNone')}</code>
			</span>
		{/if}
	{/snippet}

	<ForgeHero
		backHref="/shared-secrets"
		backLabel={$t('sharedsecrets.toolbar.backToList')}
		eyebrowSuffix={$t('sharedsecrets.titleSingular').toUpperCase()}
		title={secret?.name ?? $t('observability.loading')}
		size="lg"
		lede_html={secret ? detailLede : undefined}
	/>

	{#if error}
		<div class="alert" role="alert">
			<span class="alert-tag">ERR</span><span>{error}</span>
		</div>
	{/if}

	{#if loading || !secret}
		<div class="skeleton-rows" aria-busy="true" aria-live="polite" aria-label={$t('observability.loading')}>
			{#each Array(3) as _, i}
				<div class="skeleton-row" style:--i={i}></div>
			{/each}
		</div>
	{:else}
		<form class="panel" onsubmit={save} aria-busy={saving}>
			<header class="panel-head">
				<h2 class="panel-title">{$t('sharedsecrets.detail.config')}<span class="title-accent">.</span></h2>
				<span class="panel-sub">
					{$t('sharedsecrets.detail.configSub', { scope: scopeLabel(secret) })}
				</span>
			</header>

			<div class="field">
				<label for="s-name" class="sub-label">{$t('sharedsecrets.form.name')}</label>
				<input id="s-name" type="text" class="input mono" bind:value={name} required autocomplete="off" />
			</div>

			<div class="field">
				<label for="s-value" class="sub-label">{$t('sharedsecrets.form.value')}</label>
				<input
					id="s-value"
					type="password"
					class="input mono"
					bind:value
					autocomplete="new-password"
					placeholder={$t('sharedsecrets.form.valuePlaceholderEdit')}
				/>
				<p class="hint">
					{secret.has_value
						? $t('sharedsecrets.form.valueHintEditSet')
						: $t('sharedsecrets.form.valueHintEditUnset')}
				</p>
			</div>

			<div class="row-toggle">
				<div class="toggle-copy">
					<span class="lbl" aria-hidden="true">{$t('sharedsecrets.form.encrypted')}</span>
					<p class="hint">{$t('sharedsecrets.form.encryptedHint')}</p>
					{#if encryptedFlipBlocked}
						<p class="hint warn">{$t('sharedsecrets.form.encryptedFlipWarning')}</p>
					{/if}
				</div>
				<ToggleSwitch bind:checked={encrypted} label={$t('sharedsecrets.form.encrypted')} />
			</div>

			<div class="field">
				<label for="s-scope" class="sub-label">{$t('sharedsecrets.form.scope')}</label>
				<select id="s-scope" class="input" bind:value={scope} onchange={onScopeChange}>
					<option value="global">{$t('sharedsecrets.form.scopeGlobalOption')}</option>
					<option value="app">{$t('sharedsecrets.form.scopeAppOption')}</option>
				</select>
				{#if scope === 'app'}
					<div class="scope-picker">
						{#if appID === ''}
							<div class="scope-state global">
								<span class="scope-icon" aria-hidden="true">●</span>
								<span class="scope-text muted">{$t('sharedsecrets.form.scopeAppEmpty')}</span>
								<button type="button" class="forge-btn-ghost xs" onclick={() => (pickerOpen = true)}>
									{$t('sharedsecrets.form.scopePick')}
								</button>
							</div>
						{:else}
							<div class="scope-state app">
								<span class="scope-tag">{$t('sharedsecrets.form.scopeSelected')}</span>
								{#if selectedApp}
									<span class="scope-text">{selectedApp.name}</span>
									{#if selectedApp.description}
										<code class="scope-meta">{selectedApp.description}</code>
									{/if}
								{:else}
									<span class="scope-text muted">{$t('sharedsecrets.form.scopeUnknown')}</span>
									<code class="scope-meta">{appID}</code>
								{/if}
								<button type="button" class="forge-btn-ghost xs" onclick={() => (pickerOpen = true)}>
									{$t('observability.edit')}
								</button>
								<button
									type="button"
									class="scope-clear"
									onclick={clearApp}
									aria-label={$t('sharedsecrets.form.scopeClear')}
									title={$t('sharedsecrets.form.scopeClear')}
								>
									<IconX size={14} />
								</button>
							</div>
						{/if}
					</div>
				{/if}
			</div>

			<div class="field">
				<label for="s-description" class="sub-label">{$t('sharedsecrets.form.description')}</label>
				<input id="s-description" type="text" class="input" bind:value={description} />
			</div>

			<div class="row-toggle">
				<div class="toggle-copy">
					<span class="lbl" aria-hidden="true">{$t('sharedsecrets.form.enabled')}</span>
					<p class="hint">{$t('sharedsecrets.form.enabledHint')}</p>
				</div>
				<ToggleSwitch bind:checked={enabled} label={$t('sharedsecrets.form.enabled')} />
			</div>

			<div class="actions">
				<button
					type="submit"
					class="forge-btn"
					disabled={saving || !canSave}
					aria-busy={saving}
				>
					{saving ? $t('observability.saving') : $t('observability.save')}
				</button>
			</div>
		</form>

		<section class="panel danger-panel" aria-labelledby="danger-heading">
			<header class="panel-head">
				<h2 class="panel-title" id="danger-heading">{$t('sharedsecrets.detail.dangerZone')}<span class="title-accent">.</span></h2>
				<span class="panel-sub">{$t('sharedsecrets.detail.dangerZoneSub')}</span>
			</header>
			<div class="danger-actions">
				<button
					type="button"
					class="forge-btn-ghost forge-btn-danger"
					onclick={() => (confirmDelete = true)}
				>
					{$t('sharedsecrets.detail.deleteButton')}
				</button>
			</div>
		</section>

		<ConfirmDialog
			open={confirmDelete}
			title={$t('sharedsecrets.detail.deleteTitle')}
			message={$t('sharedsecrets.detail.deleteMessage', { name: name.trim() || secret.name })}
			confirmLabel={deleting ? $t('observability.deleting') : $t('observability.delete')}
			confirmVariant="danger"
			onconfirm={doDelete}
			oncancel={() => (confirmDelete = false)}
		/>

		<EntityPicker
			bind:open={pickerOpen}
			items={pickerItems}
			current={appID}
			title={$t('sharedsecrets.form.scopePickTitle')}
			onselect={pickApp}
			onclose={() => (pickerOpen = false)}
		/>
	{/if}
</div>

<style>
	.forge {
		display: flex;
		flex-direction: column;
		gap: 1.25rem;
		max-width: 820px;
		margin: 0 auto;
	}

	/* ── Alert ─────────────────────────────────────── */
	.alert {
		display: flex;
		gap: 0.7rem;
		align-items: center;
		padding: 0.7rem 0.9rem;
		background: var(--color-danger-light);
		color: var(--color-danger-dark);
		border: 1px solid var(--color-danger);
		border-left-width: 4px;
		border-radius: var(--radius-lg);
		font-size: 0.875rem;
	}
	.alert-tag {
		font-family: var(--forge-mono);
		font-weight: 700;
		font-size: 0.65rem;
		letter-spacing: 0.16em;
		padding: 0.15rem 0.4rem;
		background: var(--color-danger);
		color: var(--surface-card);
		border-radius: var(--radius-sm);
	}
	:global([data-theme='dark']) .alert {
		background: color-mix(in srgb, var(--color-danger) 14%, transparent);
		color: color-mix(in srgb, var(--color-danger) 60%, var(--text-primary));
	}

	/* ── Skeleton ──────────────────────────────────── */
	.skeleton-rows {
		display: flex;
		flex-direction: column;
		gap: 0.55rem;
	}
	.skeleton-row {
		height: 64px;
		border: 1px solid var(--border-primary);
		border-radius: var(--radius-2xl);
		background: linear-gradient(
			110deg,
			var(--surface-card) 20%,
			var(--surface-card-hover) 50%,
			var(--surface-card) 80%
		);
		background-size: 200% 100%;
		animation: shimmer 1.6s linear infinite;
		animation-delay: calc(var(--i) * 120ms);
	}
	@keyframes shimmer {
		0% {
			background-position: 200% 0;
		}
		100% {
			background-position: -200% 0;
		}
	}

	/* ── Panel ─────────────────────────────────────── */
	.panel {
		display: flex;
		flex-direction: column;
		gap: 0.9rem;
		background: var(--surface-card);
		border: 1px solid var(--border-primary);
		border-radius: var(--radius-2xl);
		padding: 1.5rem;
	}
	@media (max-width: 600px) {
		.panel {
			padding: 1.1rem;
			gap: 1rem;
		}
	}
	.panel.danger-panel {
		border-color: color-mix(in srgb, var(--color-danger) 35%, var(--border-primary));
	}
	.panel-head {
		display: flex;
		flex-direction: column;
		gap: 0.25rem;
		margin-bottom: 0.2rem;
	}
	.panel-title {
		margin: 0;
		font-size: 1.1rem;
		font-weight: 700;
		letter-spacing: -0.01em;
		color: var(--text-primary);
	}
	.title-accent {
		color: var(--forge-accent);
	}
	.panel-sub {
		font-family: var(--forge-mono);
		font-size: 0.7rem;
		color: var(--text-tertiary);
		line-height: 1.5;
	}

	/* ── Fields ────────────────────────────────────── */
	.field {
		display: flex;
		flex-direction: column;
		gap: 0.35rem;
	}
	.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);
	}
	.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.mono {
		font-family: var(--forge-mono);
	}
	.input:focus {
		border-color: var(--border-focus);
		box-shadow: 0 0 0 3px var(--forge-accent-soft);
	}

	/* ── Hints ──────────────────────────────────────── */
	.hint {
		font-size: 0.78rem;
		color: var(--text-tertiary);
		line-height: 1.5;
		margin: 0;
	}
	.hint.warn {
		color: var(--color-warning-dark, #b45309);
		font-weight: 600;
	}

	/* ── Toggle row ─────────────────────────────────── */
	.row-toggle {
		display: flex;
		align-items: flex-start;
		justify-content: space-between;
		gap: 1rem;
		padding-top: 0.5rem;
		border-top: 1px dashed var(--border-primary);
		margin-top: 0.2rem;
	}
	.toggle-copy {
		flex: 1;
		display: flex;
		flex-direction: column;
		gap: 0.25rem;
	}
	.lbl {
		font-weight: 600;
		font-size: 0.95rem;
		color: var(--text-primary);
	}

	/* ── Action footers ─────────────────────────────── */
	.actions {
		display: flex;
		justify-content: flex-end;
		gap: 0.55rem;
		padding-top: 0.4rem;
		flex-wrap: wrap;
	}
	.danger-actions {
		display: flex;
		justify-content: flex-end;
	}
	@media (max-width: 480px) {
		.actions {
			flex-direction: column-reverse;
			align-items: stretch;
		}
		.actions :global(.forge-btn),
		.actions :global(.forge-btn-ghost) {
			justify-content: center;
		}
		.danger-actions :global(.forge-btn-ghost) {
			width: 100%;
			justify-content: center;
		}
	}

	/* ── Scope picker chip ──────────────────────────── */
	.scope-picker {
		display: flex;
		flex-direction: column;
		gap: 0.4rem;
		margin-top: 0.15rem;
	}
	.scope-state {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 0.55rem;
		padding: 0.55rem 0.75rem;
		background: var(--surface-card-hover);
		border: 1px solid var(--border-primary);
		border-radius: var(--radius-lg);
	}
	.scope-state.global {
		border-style: dashed;
	}
	.scope-state.app {
		background: color-mix(in srgb, var(--color-brand-500) 8%, var(--surface-card-hover));
		border-color: color-mix(in srgb, var(--color-brand-500) 35%, var(--border-primary));
	}
	.scope-icon {
		font-size: 0.7rem;
		color: var(--text-tertiary);
	}
	.scope-text {
		font-weight: 600;
		font-size: 0.92rem;
		color: var(--text-primary);
	}
	.scope-text.muted {
		color: var(--text-tertiary);
		font-style: italic;
	}
	.scope-tag {
		font-family: var(--forge-mono);
		font-size: 0.6rem;
		font-weight: 700;
		letter-spacing: 0.14em;
		text-transform: uppercase;
		padding: 0.12rem 0.4rem;
		background: var(--color-brand-500);
		color: var(--surface-card);
		border-radius: var(--radius-sm);
	}
	.scope-meta {
		font-family: var(--forge-mono);
		font-size: 0.72rem;
		color: var(--text-secondary);
		padding: 0.1rem 0.35rem;
		background: var(--surface-card);
		border: 1px solid var(--border-primary);
		border-radius: var(--radius-sm);
	}
	.scope-state .forge-btn-ghost {
		margin-left: auto;
	}
	.scope-state.app .forge-btn-ghost {
		margin-left: auto;
		margin-right: 0;
	}
	.scope-clear {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 26px;
		height: 26px;
		background: transparent;
		border: 1px solid var(--border-primary);
		border-radius: var(--radius-md);
		color: var(--text-tertiary);
		cursor: pointer;
		transition: color 120ms ease, border-color 120ms ease;
	}
	.scope-clear:hover {
		color: var(--color-danger);
		border-color: var(--color-danger);
	}
	:global(.forge-btn-ghost.xs) {
		padding: 0.2rem 0.55rem;
		font-size: 0.62rem;
	}
</style>
