<!--
  Event Log page.
  Filterable, paginated, real-time event log with timeline-style entries.
-->
<script lang="ts">
	import { onMount, onDestroy } from 'svelte';
	import { t } from '$lib/i18n';
	import { fetchEventLog, fetchEventLogStats, clearAllEvents, deleteEvent } from '$lib/api';
	import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
	import { toasts } from '$lib/stores/toast';
	import { connectGlobalEvents, type SSEConnection, type EventLogSSEPayload } from '$lib/sse';
	import type { EventLogEntry, EventLogStats } from '$lib/types';
	import EventLogEntryComponent from '$lib/components/EventLogEntry.svelte';
	import EventLogFilter from '$lib/components/EventLogFilter.svelte';
	import EmptyState from '$lib/components/EmptyState.svelte';
	import ForgeHero from '$lib/components/ForgeHero.svelte';
	import { IconLoader } from '$lib/components/icons';

	// ── State ─────────────────────────────────────────────────────

	let events = $state<EventLogEntry[]>([]);
	let stats = $state<EventLogStats>({ info: 0, warn: 0, error: 0, total: 0 });
	let loading = $state(true);
	let loadingMore = $state(false);
	let hasMore = $state(true);
	let newEventIds = $state<Set<number>>(new Set());
	let pendingNewEvents = $state<EventLogEntry[]>([]);
	let scrolledDown = $state(false);

	// Filters
	let severities = $state<string[]>(['info', 'warn', 'error']);
	let sources = $state<string[]>(['deploy', 'static_site', 'stale_scanner', 'stale_cleanup', 'admin']);
	let dateRange = $state('all');
	let searchText = $state('');

	const PAGE_SIZE = 50;
	let offset = $state(0);

	let sseConnection: SSEConnection | null = null;
	let listEl: HTMLDivElement | undefined = $state();
	let showClearConfirm = $state(false);

	// ── Date range to ISO string ─────────────────────────────────

	function getDateRangeSince(range: string): string | undefined {
		if (range === 'all') return undefined;
		const now = Date.now();
		const offsets: Record<string, number> = {
			'1h': 60 * 60 * 1000,
			'24h': 24 * 60 * 60 * 1000,
			'7d': 7 * 24 * 60 * 60 * 1000
		};
		const ms = offsets[range];
		if (!ms) return undefined;
		return new Date(now - ms).toISOString();
	}

	// ── Load data ────────────────────────────────────────────────

	async function loadEvents(append = false): Promise<void> {
		const currentOffset = append ? offset : 0;
		if (append) {
			loadingMore = true;
		} else {
			loading = true;
		}

		try {
			const severityParam = severities.length < 3 ? severities.join(',') : undefined;
			const sourceParam = sources.length < 4 ? sources.join(',') : undefined;
			const sinceParam = getDateRangeSince(dateRange);

			const result = await fetchEventLog({
				severity: severityParam,
				source: sourceParam,
				since: sinceParam,
				limit: PAGE_SIZE,
				offset: currentOffset
			});

			if (append) {
				events = [...events, ...result];
			} else {
				events = result;
			}

			hasMore = result.length === PAGE_SIZE;
			offset = (append ? currentOffset : 0) + result.length;
		} catch {
			// Error silently — user sees empty state.
		} finally {
			loading = false;
			loadingMore = false;
		}
	}

	async function loadStats(): Promise<void> {
		try {
			stats = await fetchEventLogStats();
		} catch {
			// Keep default stats on error.
		}
	}

	function loadMore(): void {
		loadEvents(true);
	}

	// ── Filter change handlers ───────────────────────────────────

	function handleSeveritiesChange(v: string[]): void {
		severities = v;
		loadEvents();
	}

	function handleSourcesChange(v: string[]): void {
		sources = v;
		loadEvents();
	}

	function handleDateRangeChange(v: string): void {
		dateRange = v;
		loadEvents();
	}

	function handleSearchChange(v: string): void {
		searchText = v;
	}

	function handleClear(): void {
		severities = ['info', 'warn', 'error'];
		sources = ['deploy', 'container', 'proxy', 'system'];
		dateRange = 'all';
		searchText = '';
		loadEvents();
	}

	// ── Client-side text filter ──────────────────────────────────

	const filteredEvents = $derived(
		searchText.trim() === ''
			? events
			: events.filter((e) => e.message.toLowerCase().includes(searchText.toLowerCase()))
	);

	// ── SSE real-time events ─────────────────────────────────────

	function handleSSEEvent(payload: EventLogSSEPayload): void {
		const newEntry: EventLogEntry = {
			id: payload.id,
			source: payload.source,
			severity: payload.severity as EventLogEntry['severity'],
			message: payload.message,
			metadata: payload.metadata,
			created_at: payload.created_at
		};

		// Update stats.
		stats = {
			...stats,
			[newEntry.severity]: (stats[newEntry.severity] ?? 0) + 1,
			total: stats.total + 1
		};

		if (scrolledDown) {
			pendingNewEvents = [newEntry, ...pendingNewEvents];
		} else {
			events = [newEntry, ...events];
			newEventIds = new Set([...newEventIds, newEntry.id]);
			setTimeout(() => {
				newEventIds = new Set([...newEventIds].filter((id) => id !== newEntry.id));
			}, 3000);
		}
	}

	function showPendingEvents(): void {
		events = [...pendingNewEvents, ...events];
		const ids = new Set([...newEventIds, ...pendingNewEvents.map((e) => e.id)]);
		newEventIds = ids;
		pendingNewEvents = [];

		listEl?.scrollTo({ top: 0, behavior: 'smooth' });

		setTimeout(() => {
			newEventIds = new Set();
		}, 3000);
	}

	// ── Scroll tracking ──────────────────────────────────────────

	function handleScroll(): void {
		if (!listEl) return;
		scrolledDown = listEl.scrollTop > 200;
	}

	// ── Lifecycle ────────────────────────────────────────────────

	onMount(() => {
		loadEvents();
		loadStats();

		// Open SSE connection only while this page is mounted.
		sseConnection = connectGlobalEvents({
			onEventLog(payload) {
				handleSSEEvent(payload);
			}
		});
	});

	onDestroy(() => {
		sseConnection?.close();
		sseConnection = null;
	});
</script>

<div class="space-y-4">
	{#snippet heroToolbar()}
		{#if stats.total > 0}
			<span class="forge-pill"><span class="pulse"></span>{$t('events.totalCount', { count: String(stats.total) })}</span>
			<button
				type="button"
				onclick={() => { showClearConfirm = true; }}
				class="forge-btn-ghost forge-btn-danger"
			>
				{$t('events.clearAll')}
			</button>
		{/if}
	{/snippet}
	<ForgeHero
		eyebrowSuffix="EVENTS"
		title={$t('events.title')}
		size="lg"
		toolbar={heroToolbar}
	/>

	<!-- Filter bar (includes severity stats as pill counts) -->
	<EventLogFilter
		{severities}
		{sources}
		{dateRange}
		{searchText}
		{stats}
		onseveritieschange={handleSeveritiesChange}
		onsourceschange={handleSourcesChange}
		ondaterangechange={handleDateRangeChange}
		onsearchchange={handleSearchChange}
		onclear={handleClear}
	/>

	<!-- "N new events" banner -->
	{#if pendingNewEvents.length > 0}
		<button
			type="button"
			class="w-full rounded-lg bg-[var(--color-brand-600)] px-4 py-2 text-sm font-medium text-white shadow-sm transition-all hover:bg-[var(--color-brand-700)] animate-fade-in"
			onclick={showPendingEvents}
		>
			{pendingNewEvents.length} {$t('events.newEvents')}
		</button>
	{/if}

	<!-- Event list -->
	{#if loading}
		<div class="flex items-center justify-center py-16">
			<IconLoader size={20} class="animate-spin text-[var(--color-brand-500)]" />
		</div>
	{:else if filteredEvents.length === 0}
		<EmptyState
			title={$t('events.noEvents')}
			description={$t('events.noEventsDesc')}
			icon="deploys"
		/>
	{:else}
		<div
			bind:this={listEl}
			onscroll={handleScroll}
			class="rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] divide-y divide-[var(--border-secondary)]"
		>
			{#each filteredEvents as entry (entry.id)}
				<EventLogEntryComponent
					{entry}
					isNew={newEventIds.has(entry.id)}
					ondelete={async (id) => {
						try {
							await deleteEvent(id);
							events = events.filter(e => e.id !== id);
							stats = { ...stats, total: Math.max(0, stats.total - 1) };
						} catch (err) {
							toasts.error(err instanceof Error ? err.message : 'Failed to delete event');
						}
					}}
				/>
			{/each}
		</div>

		<!-- Load more -->
		{#if hasMore && searchText.trim() === ''}
			<div class="flex justify-center pt-2 pb-1">
				<button
					type="button"
					class="inline-flex items-center gap-2 rounded-lg border border-[var(--border-primary)] bg-[var(--surface-card)] px-4 py-2 text-sm font-medium text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-primary)] disabled:opacity-50"
					onclick={loadMore}
					disabled={loadingMore}
				>
					{#if loadingMore}
						<IconLoader size={16} class="animate-spin" />
					{/if}
					{$t('events.loadMore')}
				</button>
			</div>
		{/if}
	{/if}
</div>

<ConfirmDialog
	open={showClearConfirm}
	title={$t('events.clearAllTitle')}
	message={$t('events.clearAllMessage')}
	confirmLabel={$t('events.clearAll')}
	confirmVariant="danger"
	onconfirm={async () => {
		showClearConfirm = false;
		try {
			const result = await clearAllEvents();
			toasts.success($t('events.cleared', { count: String(result.count) }));
			events = [];
			stats = { info: 0, warn: 0, error: 0, total: 0 };
			offset = 0;
		} catch (err) {
			toasts.error(err instanceof Error ? err.message : $t('events.clearFailed'));
		}
	}}
	oncancel={() => { showClearConfirm = false; }}
/>
