<script lang="ts">
	import { goto } from '$app/navigation';
	import { t } from '$lib/i18n';
	import type { EventLog } from '$lib/types';
	import { requestHighlight } from '$lib/highlight';
	import Modal from './Modal.svelte';
	import MdiIcon from './MdiIcon.svelte';

	interface Props {
		event: EventLog | null;
		onclose: () => void;
	}
	let { event, onclose }: Props = $props();

	// Retain the last non-null event so the modal body stays populated
	// while the close transition plays after the parent clears `event`.
	let displayEvent = $state<EventLog | null>(null);
	$effect(() => {
		if (event) displayEvent = event;
	});

	function fmtDateTime(iso: string): string {
		try {
			const d = new Date(iso);
			return d.toLocaleString();
		} catch {
			return iso;
		}
	}

	/** Humanize a duration in seconds into ``Xd Yh`` / ``Xh Ym`` / ``Xm`` / ``Xs``.
	 *
	 * Used by the deferred-dispatch lifecycle banner to render
	 * ``deferred_for_seconds`` ("held for 8h 23m") rather than an opaque
	 * integer that the user has to mentally divide. Keeps two units so
	 * the magnitude reads correctly across hours-long quiet windows
	 * without becoming noisy for short ones. */
	function humanDuration(totalSeconds: number): string {
		if (!Number.isFinite(totalSeconds) || totalSeconds < 0) return '';
		if (totalSeconds < 60) return `${Math.floor(totalSeconds)}s`;
		const minutes = Math.floor(totalSeconds / 60);
		if (minutes < 60) return `${minutes}m`;
		const hours = Math.floor(minutes / 60);
		const remMin = minutes % 60;
		if (hours < 24) return remMin ? `${hours}h ${remMin}m` : `${hours}h`;
		const days = Math.floor(hours / 24);
		const remHours = hours % 24;
		return remHours ? `${days}d ${remHours}h` : `${days}d`;
	}

	/** Render an absolute ISO timestamp as a future-relative string.
	 *
	 * "in 8h 23m" / "in 12m". Returns an empty string for past times — the
	 * deferred-until banner shouldn't show a relative offset once the
	 * window has already ended (a follow-up event_log row marks delivery).
	 */
	function timeFromNow(iso: string | undefined): string {
		if (!iso) return '';
		try {
			const target = new Date(iso).getTime();
			const diff = Math.floor((target - Date.now()) / 1000);
			if (diff <= 0) return '';
			return humanDuration(diff);
		} catch {
			return '';
		}
	}

	function issuerLabel(issuer: { id?: number; username?: string; first_name?: string; last_name?: string } | undefined): string {
		if (!issuer) return '';
		if (issuer.username) return '@' + issuer.username;
		const name = [issuer.first_name, issuer.last_name].filter(Boolean).join(' ');
		if (name) return name;
		if (issuer.id) return 'id ' + issuer.id;
		return '';
	}

	/** Navigate to a list page and highlight the specific entity card.
	 *
	 * The destination page calls ``highlightFromUrl()`` after data loads,
	 * which scrolls to and pulses the card with ``data-entity-id={id}``.
	 * Same mechanism CrossLink uses elsewhere — keeps the UX consistent. */
	function openEntity(path: string, entityId: number | string | null | undefined) {
		if (entityId != null) requestHighlight(entityId);
		onclose();
		goto(path);
	}

	const issuer = $derived(displayEvent?.details?.issuer as { id?: number; username?: string; first_name?: string; last_name?: string } | undefined);
	const issuerText = $derived(issuerLabel(issuer));

	const isCommand = $derived(displayEvent?.event_type?.startsWith('command_') ?? false);
	const isAction = $derived(displayEvent?.event_type?.startsWith('action_') ?? false);

	const detailsJson = $derived.by(() => {
		if (!displayEvent?.details) return '';
		try {
			return JSON.stringify(displayEvent.details, null, 2);
		} catch {
			return String(displayEvent.details);
		}
	});
</script>

<Modal open={event !== null} title={displayEvent ? t('events.detailTitle') : ''} {onclose}>
	{#if displayEvent}
		<div class="event-detail">
			<!-- Subject + verb -->
			<div class="hero-row">
				<MdiIcon name="mdiBell" size={18} />
				<div>
					<div class="hero-subject">{displayEvent.collection_name || displayEvent.event_type}</div>
					<div class="hero-meta">
						<span class="event-type">{displayEvent.event_type}</span>
						<span class="dot">·</span>
						<span>{fmtDateTime(displayEvent.created_at)}</span>
					</div>
				</div>
			</div>

			<!-- Dispatch lifecycle (only when the event went through the
			     quiet-hours defer path). Rendered ABOVE the provenance grid
			     because timing of delivery is more interesting than the
			     bot/tracker names when the event is held back. -->
			{#if displayEvent.details?.dispatch_status === 'deferred'}
				<section class="lifecycle lifecycle--deferred">
					<MdiIcon name="mdiPauseCircleOutline" size={18} />
					<div class="lifecycle-body">
						<div class="lifecycle-title">{t('events.lifecycle.heldTitle')}</div>
						<div class="lifecycle-detail">
							{t('events.lifecycle.heldUntil')}
							<b>{fmtDateTime(displayEvent.details.deferred_until ?? '')}</b>
							{#if timeFromNow(displayEvent.details.deferred_until)}
								<span class="lifecycle-rel">· {t('events.lifecycle.inPrefix')} {timeFromNow(displayEvent.details.deferred_until)}</span>
							{/if}
						</div>
						<div class="lifecycle-hint">{t('events.lifecycle.heldHint')}</div>
					</div>
				</section>
			{:else if displayEvent.details?.dispatch_status === 'delivered_after_quiet_hours'}
				<section class="lifecycle lifecycle--late">
					<MdiIcon name="mdiClockCheckOutline" size={18} />
					<div class="lifecycle-body">
						<div class="lifecycle-title">{t('events.lifecycle.deliveredLateTitle')}</div>
						{#if displayEvent.details.deferred_for_seconds != null}
							<div class="lifecycle-detail">
								{t('events.lifecycle.heldFor')}
								<b>{humanDuration(displayEvent.details.deferred_for_seconds)}</b>
							</div>
						{/if}
						{#if displayEvent.details.original_event_log_id}
							<div class="lifecycle-hint">
								{t('events.lifecycle.originalEvent')} #{displayEvent.details.original_event_log_id}
							</div>
						{/if}
					</div>
				</section>
			{:else if displayEvent.details?.dispatch_status === 'deferred_then_dropped'}
				<section class="lifecycle lifecycle--dropped">
					<MdiIcon name="mdiCloseCircleOutline" size={18} />
					<div class="lifecycle-body">
						<div class="lifecycle-title">{t('events.lifecycle.droppedTitle')}</div>
						{#if displayEvent.details.reason}
							<div class="lifecycle-detail">
								{t('events.lifecycle.reason')}:
								<code class="lifecycle-reason">{displayEvent.details.reason}</code>
							</div>
						{/if}
						{#if displayEvent.details.original_event_log_id}
							<div class="lifecycle-hint">
								{t('events.lifecycle.originalEvent')} #{displayEvent.details.original_event_log_id}
							</div>
						{/if}
					</div>
				</section>
			{:else if displayEvent.details?.dispatch_status === 'deferred_then_failed'}
				<section class="lifecycle lifecycle--dropped">
					<MdiIcon name="mdiAlertCircleOutline" size={18} />
					<div class="lifecycle-body">
						<div class="lifecycle-title">{t('events.lifecycle.failedTitle')}</div>
						{#if displayEvent.details.reason}
							<div class="lifecycle-detail">
								{t('events.lifecycle.reason')}:
								<code class="lifecycle-reason">{displayEvent.details.reason}</code>
							</div>
						{/if}
						{#if displayEvent.details.original_event_log_id}
							<div class="lifecycle-hint">
								{t('events.lifecycle.originalEvent')} #{displayEvent.details.original_event_log_id}
							</div>
						{/if}
					</div>
				</section>
			{:else if displayEvent.details?.dispatch_status === 'suppressed_quiet_hours_nondeferrable'}
				<section class="lifecycle lifecycle--dropped">
					<MdiIcon name="mdiVolumeOff" size={18} />
					<div class="lifecycle-body">
						<div class="lifecycle-title">{t('events.lifecycle.suppressedTitle')}</div>
						<div class="lifecycle-hint">{t('events.lifecycle.suppressedHint')}</div>
					</div>
				</section>
			{/if}

			<!-- Provenance grid -->
			<dl class="provenance">
				{#if displayEvent.bot_name}
					<dt>{t('events.bot')}</dt>
					<dd>{displayEvent.bot_name}</dd>
				{/if}
				{#if displayEvent.collection_id && isCommand}
					<dt>{t('events.chat')}</dt>
					<dd class="font-mono">{displayEvent.collection_id}</dd>
				{/if}
				{#if issuerText}
					<dt>{t('events.issuer')}</dt>
					<dd>
						{issuerText}
						{#if issuer?.id}<span class="muted font-mono">(id {issuer.id})</span>{/if}
					</dd>
				{/if}
				{#if displayEvent.command_tracker_name}
					<dt>{t('events.commandTracker')}</dt>
					<dd>{displayEvent.command_tracker_name}</dd>
				{/if}
				{#if displayEvent.tracker_name}
					<dt>{t('events.tracker')}</dt>
					<dd>{displayEvent.tracker_name}</dd>
				{/if}
				{#if displayEvent.action_name}
					<dt>{t('events.action')}</dt>
					<dd>{displayEvent.action_name}</dd>
				{/if}
				{#if displayEvent.provider_name}
					<dt>{t('events.provider')}</dt>
					<dd>{displayEvent.provider_name}</dd>
				{/if}
				{#if displayEvent.assets_count > 0}
					<dt>{t('events.assetsCount')}</dt>
					<dd class="font-mono">{displayEvent.assets_count}</dd>
				{/if}
			</dl>

			<!-- Action buttons — deep-link + highlight the related entity card.
			     IDs are snapshotted into local consts so the deferred onclick
			     closures don't lose the narrowed type that the `{#if ...}` gate
			     proves at template-render time. -->
			<div class="actions">
				{#if displayEvent.provider_id}
					{@const providerId = displayEvent.provider_id}
					<button type="button" onclick={() => openEntity('/providers', providerId)}>
						<MdiIcon name="mdiServer" size={14} />
						{t('events.openProvider')}
					</button>
				{/if}
				{#if displayEvent.telegram_bot_id && isCommand}
					{@const botId = displayEvent.telegram_bot_id}
					<button type="button" onclick={() => openEntity('/bots', botId)}>
						<MdiIcon name="mdiRobotHappy" size={14} />
						{t('events.openBot')}
					</button>
				{/if}
				{#if displayEvent.command_tracker_id && isCommand}
					{@const cmdTrackerId = displayEvent.command_tracker_id}
					<button type="button" onclick={() => openEntity('/command-trackers', cmdTrackerId)}>
						<MdiIcon name="mdiChat" size={14} />
						{t('events.openCommandTracker')}
					</button>
				{/if}
				{#if displayEvent.action_id && isAction}
					{@const actionId = displayEvent.action_id}
					<button type="button" onclick={() => openEntity('/actions', actionId)}>
						<MdiIcon name="mdiPlayCircle" size={14} />
						{t('events.openAction')}
					</button>
				{/if}
				{#if !isCommand && !isAction && displayEvent.tracker_id}
					{@const trackerId = displayEvent.tracker_id}
					<button type="button" onclick={() => openEntity('/notification-trackers', trackerId)}>
						<MdiIcon name="mdiRadar" size={14} />
						{t('events.openTracker')}
					</button>
				{/if}
			</div>

			<!-- Raw details JSON (always rendered — frequently the most useful piece) -->
			{#if detailsJson && detailsJson !== '{}'}
				<details class="raw-details" open={isCommand}>
					<summary>{t('events.rawDetails')}</summary>
					<pre>{detailsJson}</pre>
				</details>
			{/if}
		</div>
	{/if}
</Modal>

<style>
	.event-detail {
		display: flex; flex-direction: column; gap: 1.1rem;
	}
	.hero-row {
		display: flex; align-items: flex-start; gap: 0.75rem;
	}
	.hero-subject {
		font-family: var(--font-display);
		font-size: 1.05rem;
		font-weight: 500;
		color: var(--color-foreground);
		line-height: 1.3;
		word-break: break-word;
	}
	.hero-meta {
		font-size: 0.7rem;
		color: var(--color-muted-foreground);
		margin-top: 0.25rem;
		display: flex; align-items: center; gap: 0.4rem;
	}
	.event-type {
		font-family: var(--font-mono);
		padding: 0.1rem 0.4rem;
		border-radius: 0.35rem;
		background: color-mix(in oklab, var(--color-foreground) 6%, transparent);
		color: var(--color-foreground);
	}
	.dot { opacity: 0.5; }

	.provenance {
		display: grid;
		grid-template-columns: max-content 1fr;
		gap: 0.45rem 1rem;
		margin: 0;
		padding: 0.85rem 0.95rem;
		border-radius: 0.7rem;
		background: color-mix(in oklab, var(--color-foreground) 4%, transparent);
		font-size: 0.82rem;
	}
	.provenance dt {
		color: var(--color-muted-foreground);
		font-size: 0.7rem;
		text-transform: uppercase;
		letter-spacing: 0.04em;
		align-self: center;
	}
	.provenance dd {
		margin: 0;
		color: var(--color-foreground);
		word-break: break-word;
	}
	.muted { color: var(--color-muted-foreground); margin-left: 0.35rem; font-size: 0.75rem; }

	.actions {
		display: flex; flex-wrap: wrap; gap: 0.5rem;
	}
	.actions button {
		display: inline-flex; align-items: center; gap: 0.4rem;
		padding: 0.45rem 0.8rem;
		font-size: 0.78rem;
		color: var(--color-foreground);
		background: color-mix(in oklab, var(--color-primary) 10%, transparent);
		border: 1px solid color-mix(in oklab, var(--color-primary) 25%, transparent);
		border-radius: 0.55rem;
		cursor: pointer;
		transition: background 150ms, border-color 150ms;
	}
	.actions button:hover {
		background: color-mix(in oklab, var(--color-primary) 18%, transparent);
		border-color: color-mix(in oklab, var(--color-primary) 40%, transparent);
	}

	.raw-details summary {
		font-size: 0.75rem;
		color: var(--color-muted-foreground);
		cursor: pointer;
		user-select: none;
	}
	.raw-details summary:hover { color: var(--color-foreground); }
	.raw-details pre {
		margin: 0.55rem 0 0;
		padding: 0.7rem 0.85rem;
		font-family: var(--font-mono);
		font-size: 0.72rem;
		line-height: 1.5;
		color: var(--color-foreground);
		background: color-mix(in oklab, var(--color-foreground) 6%, transparent);
		border-radius: 0.55rem;
		overflow-x: auto;
		white-space: pre-wrap;
		word-break: break-word;
	}
	.font-mono { font-family: var(--font-mono); }

	/* Dispatch lifecycle banner — appears only when the event took the
	 * quiet-hours defer path. The three colour variants mirror the dashboard
	 * badge palette: primary glow for "held", success for "delivered late",
	 * muted/dim for "dropped" / "failed" / "suppressed".
	 */
	.lifecycle {
		display: flex; align-items: flex-start; gap: 0.7rem;
		padding: 0.75rem 0.95rem;
		border-radius: 0.7rem;
		border: 1px solid var(--color-border);
		background: color-mix(in oklab, var(--color-foreground) 4%, transparent);
		font-size: 0.82rem;
	}
	.lifecycle-body {
		display: flex; flex-direction: column; gap: 0.2rem;
		flex: 1; min-width: 0;
	}
	.lifecycle-title {
		font-weight: 600;
		color: var(--color-foreground);
	}
	.lifecycle-detail {
		color: var(--color-foreground);
	}
	.lifecycle-detail b {
		font-family: var(--font-mono);
		font-weight: 600;
	}
	.lifecycle-rel {
		color: var(--color-muted-foreground);
		font-family: var(--font-mono);
		font-size: 0.75rem;
		margin-left: 0.25rem;
	}
	.lifecycle-hint {
		color: var(--color-muted-foreground);
		font-size: 0.72rem;
	}
	.lifecycle-reason {
		font-family: var(--font-mono);
		font-size: 0.75rem;
		padding: 0.05rem 0.35rem;
		border-radius: 0.3rem;
		background: color-mix(in oklab, var(--color-foreground) 8%, transparent);
		word-break: break-all;
	}
	.lifecycle--deferred {
		border-color: color-mix(in srgb, var(--color-primary) 35%, transparent);
		background: color-mix(in srgb, var(--color-primary) 8%, transparent);
	}
	.lifecycle--deferred :global(svg) {
		color: var(--color-primary);
	}
	.lifecycle--late {
		border-color: color-mix(in srgb, var(--color-success, #16a34a) 35%, transparent);
		background: color-mix(in srgb, var(--color-success, #16a34a) 8%, transparent);
	}
	.lifecycle--late :global(svg) {
		color: var(--color-success, #16a34a);
	}
	.lifecycle--dropped {
		opacity: 0.92;
	}
	.lifecycle--dropped :global(svg) {
		color: var(--color-muted-foreground);
	}
</style>
