<script lang="ts">
	import { goto } from '$app/navigation';
	import { page } from '$app/stores';
	import * as api from '$lib/api';
	import type { EventTrigger, EventTriggerInput, NotificationTestResult } from '$lib/api';
	import { eventTriggerDetailCache } from '$lib/stores/caches';
	import ForgeHero from '$lib/components/ForgeHero.svelte';
	import ToggleSwitch from '$lib/components/ToggleSwitch.svelte';
	import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
	import { t } from '$lib/i18n';

	// Parse + validate the URL id once. SvelteKit gives the raw string;
	// a non-numeric path segment lands here as NaN, which would otherwise
	// reach the API as the literal "NaN". Treat invalid ids as a hard
	// failure so the rest of the page doesn't fire bogus requests.
	const id = $derived.by(() => {
		const n = Number($page.params.id);
		return Number.isFinite(n) && n > 0 ? n : null;
	});

	let trigger = $state<EventTrigger | null>(null);
	let loading = $state(true);
	let saving = $state(false);
	let testing = $state(false);
	let confirmDelete = $state(false);
	let deleting = $state(false);
	let error = $state('');
	let testResult = $state<NotificationTestResult | null>(null);

	// Form fields — initialized from the loaded trigger.
	let name = $state('');
	let filterSeverity = $state('');
	let filterSource = $state('');
	let filterMessageRegex = $state('');
	let actionTarget = $state('');
	// Secret state: the backend returns a placeholder ("********") when
	// a secret is configured so we never expose the real value on read.
	// The PATCH path treats an unchanged placeholder as "no change."
	// secretConfigured tracks whether a real secret is stored so the
	// UI can show a "Configured" badge without revealing the value.
	let actionSecret = $state('');
	let secretConfigured = $state(false);
	let enabled = $state(true);

	const regexValid = $derived.by(() => {
		if (!filterMessageRegex) return true;
		try {
			new RegExp(filterMessageRegex);
			return true;
		} catch {
			return false;
		}
	});

	function seedForm(tr: EventTrigger): void {
		name = tr.name;
		filterSeverity = tr.filter_severity;
		filterSource = tr.filter_source;
		filterMessageRegex = tr.filter_message_regex;
		actionTarget = tr.action_target;
		// Server returns either "" (no secret) or a placeholder when one is
		// configured. Keep the value so an unchanged echo round-trips as "no
		// change."
		actionSecret = tr.action_secret;
		secretConfigured = tr.action_secret !== '';
		enabled = tr.enabled;
	}

	// Keyed warm-seed: on revisit, render the cached trigger instantly and seed
	// the editable form ONCE per id (a background revalidation never re-seeds,
	// so unsaved edits survive). `seededKey` is intentionally a plain (non-
	// reactive) var so the $effect below depends only on `id`.
	let seededKey: string | null = null;

	async function load(): Promise<void> {
		if (id === null) {
			error = 'Invalid trigger id';
			loading = false;
			return;
		}
		const k = String(id);
		const cached = eventTriggerDetailCache.peek(k);
		if (cached.value) {
			trigger = cached.value;
			if (seededKey !== k) { seedForm(cached.value); seededKey = k; }
			loading = false;
		} else {
			loading = true;
		}
		error = '';
		await eventTriggerDetailCache.refresh(k);
		// 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 === null || String(id) !== k) return;
		const entry = eventTriggerDetailCache.peek(k);
		if (entry.error && !cached.value) {
			error = entry.error || 'Failed to load trigger';
			loading = false;
			return;
		}
		if (entry.value) {
			trigger = entry.value;
			if (seededKey !== k) { seedForm(entry.value); seededKey = k; }
		}
		loading = false;
	}

	async function save(e?: Event): Promise<void> {
		e?.preventDefault();
		if (!trigger || id === null || saving) return;
		saving = true;
		error = '';
		try {
			const body: EventTriggerInput = {
				name: name.trim(),
				filter_severity: filterSeverity.trim(),
				filter_source: filterSource.trim(),
				filter_message_regex: filterMessageRegex,
				action_type: 'webhook',
				action_target: actionTarget.trim(),
				action_secret: actionSecret,
				enabled
			};
			trigger = await api.updateEventTrigger(id, body);
			actionSecret = trigger.action_secret;
			secretConfigured = trigger.action_secret !== '';
			// Keep the cache warm-fresh for the next visit (doesn't touch the
			// form — seededKey already matches this id).
			eventTriggerDetailCache.set(String(id), trigger);
		} catch (e) {
			error = e instanceof Error ? e.message : 'Save failed';
		} finally {
			saving = false;
		}
	}

	async function sendTest(): Promise<void> {
		if (id === null) return;
		testing = true;
		testResult = null;
		error = '';
		try {
			testResult = await api.testEventTrigger(id);
		} catch (e) {
			error = e instanceof Error ? e.message : 'Test failed';
		} finally {
			testing = false;
		}
	}

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

	// Reset the secret to a blank field — operator can then type a new
	// one. The PATCH path treats blank as "clear stored secret."
	function clearSecret(): void {
		actionSecret = '';
	}

	const testOk = $derived(
		testResult !== null && testResult.status_code >= 200 && testResult.status_code < 300
	);

	// Reload when route id changes — see the apps/[id] page for the
	// same rationale (SvelteKit reuses the component instance).
	$effect(() => {
		const _ = id;
		load();
	});
</script>

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

<div class="forge" aria-busy={loading}>
	<ForgeHero
		backHref="/event-triggers"
		backLabel={$t('triggers.toolbar.backToList')}
		eyebrowSuffix={$t('triggers.titleSingular').toUpperCase()}
		title={trigger?.name ?? $t('observability.loading')}
		size="lg"
	/>

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

	{#if loading || !trigger}
		<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('triggers.detail.config')}<span class="title-accent">.</span></h2>
				<span class="panel-sub">
					{$t('triggers.detail.configSub', { id: String(trigger.id), updatedAt: trigger.updated_at })}
				</span>
			</header>

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

			<div class="row two">
				<div class="sub">
					<label for="t-sev" class="sub-label">{$t('triggers.form.severityCsv')}</label>
					<input
						id="t-sev"
						type="text"
						class="input"
						bind:value={filterSeverity}
						placeholder={$t('triggers.form.severityPlaceholder')}
					/>
				</div>
				<div class="sub">
					<label for="t-src" class="sub-label">{$t('triggers.form.sourceCsv')}</label>
					<input
						id="t-src"
						type="text"
						class="input"
						bind:value={filterSource}
						placeholder={$t('triggers.form.sourcePlaceholder')}
					/>
				</div>
			</div>

			<div class="field">
				<label for="t-msg" class="sub-label">{$t('triggers.form.messageRegex')}</label>
				<input
					id="t-msg"
					type="text"
					class="input mono"
					class:bad={!regexValid}
					bind:value={filterMessageRegex}
					placeholder={$t('triggers.form.messageRegexPlaceholder')}
					spellcheck="false"
					aria-invalid={!regexValid}
					aria-describedby={!regexValid ? 't-msg-err' : undefined}
				/>
				{#if !regexValid}
					<span id="t-msg-err" class="hint danger" role="alert">
						{$t('triggers.form.invalidRegex')}
					</span>
				{/if}
			</div>

			<div class="field">
				<label for="t-target" class="sub-label">{$t('triggers.form.webhookUrl')}</label>
				<input id="t-target" type="url" class="input" bind:value={actionTarget} />
			</div>

			<div class="field">
				<div class="secret-head">
					<label for="t-secret" class="sub-label">{$t('triggers.form.secretLabel')}</label>
					{#if secretConfigured}
						<span class="secret-badge" title={$t('triggers.form.secretRotateHint')}>
							{$t('observability.configured')}
						</span>
						<button
							type="button"
							class="forge-btn-ghost xs"
							onclick={clearSecret}
							title={$t('triggers.form.secretRotateHint')}
						>
							{$t('observability.clear')}
						</button>
					{/if}
				</div>
				<input
					id="t-secret"
					type="password"
					class="input"
					bind:value={actionSecret}
					autocomplete="new-password"
					placeholder={$t('triggers.form.secretPlaceholder')}
					aria-describedby="t-secret-hint"
				/>
				<span id="t-secret-hint" class="hint">
					{$t('triggers.form.secretRotateHint')}
				</span>
			</div>

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

			<div class="actions">
				<button
					type="button"
					class="forge-btn-ghost"
					onclick={sendTest}
					disabled={testing}
					aria-busy={testing}
				>
					{testing ? $t('triggers.detail.sending') : $t('triggers.detail.sendTest')}
				</button>
				<button
					type="submit"
					class="forge-btn"
					disabled={saving || !name.trim() || !actionTarget.trim() || !regexValid}
					aria-busy={saving}
				>
					{saving ? $t('observability.saving') : $t('observability.save')}
				</button>
			</div>

			<div class="result-slot" aria-live="polite" aria-atomic="true">
				{#if testResult}
					<div class="test-result" class:ok={testOk} class:fail={!testOk}>
						<div class="tr-head">
							<span class="tr-tag">{testOk ? $t('triggers.detail.testOk') : $t('triggers.detail.testFail')}</span>
							<span class="tr-status">{$t('triggers.detail.testHttp', { code: String(testResult.status_code) })}</span>
							<span class="muted">{testResult.latency_ms}ms</span>
							{#if testResult.signature_sent}
								<span class="muted">· {$t('triggers.detail.testSigned')}</span>
							{/if}
						</div>
						{#if testResult.error}
							<pre class="tr-body danger">{testResult.error}</pre>
						{/if}
						{#if testResult.response_snippet}
							<pre class="tr-body">{testResult.response_snippet}</pre>
						{/if}
					</div>
				{/if}
			</div>
		</form>

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

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

<style>
	.forge {
		display: flex;
		flex-direction: column;
		gap: 1.25rem;
		max-width: 760px;
		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 {
		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);
	}
	.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.bad {
		border-color: var(--color-danger);
	}
	.input.bad:focus {
		box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-danger) 22%, transparent);
	}

	.row.two {
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: 0.9rem;
	}
	@media (max-width: 600px) {
		.row.two {
			grid-template-columns: 1fr;
		}
	}

	/* ── Hints ──────────────────────────────────────── */
	.hint {
		font-size: 0.78rem;
		color: var(--text-tertiary);
		line-height: 1.5;
		margin: 0;
	}
	.hint.danger {
		color: var(--color-danger);
	}

	/* ── Secret affordance ─────────────────────────── */
	.secret-head {
		display: flex;
		align-items: center;
		flex-wrap: wrap;
		gap: 0.5rem;
		margin-bottom: 0.2rem;
	}
	.secret-badge {
		display: inline-flex;
		align-items: center;
		padding: 0.1rem 0.4rem;
		background: color-mix(in srgb, var(--color-success) 14%, transparent);
		color: var(--color-success-dark);
		border: 1px solid color-mix(in srgb, var(--color-success) 35%, transparent);
		border-radius: var(--radius-sm);
		font-family: var(--forge-mono);
		font-size: 0.58rem;
		font-weight: 700;
		letter-spacing: 0.14em;
	}
	:global([data-theme='dark']) .secret-badge {
		color: color-mix(in srgb, var(--color-success) 50%, var(--text-primary));
	}
	:global(.forge-btn-ghost.xs) {
		padding: 0.2rem 0.55rem;
		font-size: 0.6rem;
	}

	/* ── 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);
	}

	/* ── Actions ────────────────────────────────────── */
	.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;
		}
	}

	/* ── Test result ────────────────────────────────── */
	.result-slot {
		min-height: 0;
	}
	.test-result {
		border: 1px solid var(--border-primary);
		border-radius: var(--radius-lg);
		padding: 0.75rem 0.9rem;
		background: var(--surface-card-hover);
	}
	.test-result.ok {
		border-color: var(--color-success);
		background: color-mix(in srgb, var(--color-success) 8%, var(--surface-card));
	}
	.test-result.fail {
		border-color: var(--color-danger);
		background: color-mix(in srgb, var(--color-danger) 8%, var(--surface-card));
	}
	.tr-head {
		display: flex;
		align-items: center;
		flex-wrap: wrap;
		gap: 0.55rem;
		font-family: var(--forge-mono);
		font-size: 0.7rem;
		letter-spacing: 0.08em;
	}
	.tr-tag {
		padding: 0.1rem 0.4rem;
		background: var(--text-primary);
		color: var(--surface-card);
		border-radius: var(--radius-sm);
		font-weight: 700;
		letter-spacing: 0.16em;
	}
	.test-result.ok .tr-tag {
		background: var(--color-success);
		color: var(--surface-card);
	}
	.test-result.fail .tr-tag {
		background: var(--color-danger);
		color: var(--surface-card);
	}
	.tr-status {
		font-weight: 600;
		color: var(--text-primary);
	}
	.tr-body {
		margin: 0.5rem 0 0;
		font-family: var(--forge-mono);
		font-size: 0.78rem;
		color: var(--text-secondary);
		white-space: pre-wrap;
		word-break: break-word;
		max-height: 240px;
		overflow: auto;
	}
	.tr-body.danger {
		color: var(--color-danger);
	}
	.muted {
		color: var(--text-tertiary);
	}
</style>
