<script lang="ts">
	import { onMount } from 'svelte';
	import { slide } from 'svelte/transition';
	import { api, parseDate } from '$lib/api';
	import { t } from '$lib/i18n';
	import {
		providersCache,
		notificationTrackersCache,
		targetsCache,
	} from '$lib/stores/caches.svelte';
	import Loading from '$lib/components/Loading.svelte';
	import MdiIcon from '$lib/components/MdiIcon.svelte';
	import NavIcon from '$lib/components/NavIcon.svelte';
	import EventChart from '$lib/components/EventChart.svelte';
	import IconGridSelect from '$lib/components/IconGridSelect.svelte';
	import ConfirmModal from '$lib/components/ConfirmModal.svelte';
	import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
	import { eventTypeFilterItems, sortFilterItems, providerDefaultIcon } from '$lib/grid-items';
	import { globalProviderFilter } from '$lib/stores/provider-filter.svelte';
	import { getDescriptor } from '$lib/providers';

	import type { DashboardStatus } from '$lib/types';

	const CHART_KEY = 'dashboard_chart_visible';
	let chartVisible = $state(typeof localStorage !== 'undefined' ? localStorage.getItem(CHART_KEY) !== 'false' : true);
	function toggleChart() {
		chartVisible = !chartVisible;
		if (typeof localStorage !== 'undefined') localStorage.setItem(CHART_KEY, String(chartVisible));
	}

	let status = $state<DashboardStatus | null>(null);
	let providers = $derived(providersCache.items);
	const providerFilterItems = $derived([
		{ value: '', label: t('dashboard.allProviders'), icon: 'mdiFilterOff' },
		...providers.map(p => ({ value: p.id, label: p.name, icon: providerDefaultIcon(p), desc: p.type })),
	]);
	let chartDays = $state<{ date: string; [eventType: string]: string | number }[]>([]);
	let loaded = $state(false);
	let error = $state('');

	let displayProviders = $state(0);
	let displayActive = $state(0);
	let displayTotal = $state(0);
	let displayTargets = $state(0);
	let displayCommandTrackers = $state(0);

	// Event filters
	let filterEventType = $state('');
	let filterProviderId = $state('');
	let effectiveProviderId = $derived(globalProviderFilter.id ? String(globalProviderFilter.id) : filterProviderId);
	let filterSearch = $state('');
	let filterSort = $state('newest');

	const EVENTS_PER_PAGE_KEY = 'dashboard_events_per_page';
	function loadEventsPerPage(): number {
		if (typeof localStorage === 'undefined') return 10;
		const stored = localStorage.getItem(EVENTS_PER_PAGE_KEY);
		return stored ? parseInt(stored, 10) || 10 : 10;
	}

	let eventsLimit = $state(loadEventsPerPage());
	let eventsOffset = $state(0);
	let eventsLoading = $state(false);
	let confirmClearEvents = $state(false);

	async function clearEvents() {
		try {
			const res = await api<{ deleted: number }>('/status/events', { method: 'DELETE' });
			snackSuccess(t('snack.eventsCleared').replace('{count}', String(res.deleted)));
			eventsOffset = 0;
			await loadEvents();
			await loadChart();
		} catch (err: unknown) {
			snackError(err instanceof Error ? err.message : t('common.error'));
		} finally {
			confirmClearEvents = false;
		}
	}

	let currentPage = $derived(Math.floor(eventsOffset / eventsLimit) + 1);
	let totalPages = $derived(status ? Math.ceil((status.total_events || 0) / eventsLimit) : 0);

	function animateCount(from: number, to: number, setter: (v: number) => void, duration = 600) {
		if (to === 0) { setter(0); return; }
		const start = performance.now();
		function frame(now: number) {
			const elapsed = now - start;
			const progress = Math.min(elapsed / duration, 1);
			const eased = 1 - Math.pow(1 - progress, 3);
			setter(Math.round(from + (to - from) * eased));
			if (progress < 1) requestAnimationFrame(frame);
		}
		requestAnimationFrame(frame);
	}

	/** Build filter query string (shared by events list + chart). */
	function buildFilterParams(): URLSearchParams {
		const params = new URLSearchParams();
		if (filterEventType) params.set('event_type', filterEventType);
		if (effectiveProviderId) params.set('provider_id', effectiveProviderId);
		if (filterSearch) params.set('search', filterSearch);
		return params;
	}

	async function loadEvents() {
		eventsLoading = true;
		try {
			const params = buildFilterParams();
			params.set('sort', filterSort);
			params.set('limit', String(eventsLimit));
			params.set('offset', String(eventsOffset));
			const qs = params.toString();
			status = await api<DashboardStatus>(`/status${qs ? '?' + qs : ''}`);
		} catch (err: unknown) {
			error = err instanceof Error ? err.message : t('common.error');
		} finally {
			eventsLoading = false;
		}
	}

	async function loadChart() {
		try {
			const params = buildFilterParams();
			const qs = params.toString();
			const chartRes = await api<{ days: { date: string; [k: string]: string | number }[] }>(`/status/chart${qs ? '?' + qs : ''}`);
			chartDays = chartRes.days || [];
		} catch (e) { console.warn('Failed to load chart data:', e); }
	}

	// Auto-apply when filter values change (via IconGridSelect bind:value)
	let _prevFilterKey = '';
	$effect(() => {
		const key = `${filterEventType}|${effectiveProviderId}|${filterSort}`;
		if (loaded && key !== _prevFilterKey && _prevFilterKey !== '') {
			applyFilters();
		}
		_prevFilterKey = key;
	});

	function applyFilters() {
		eventsOffset = 0;
		loadEvents();
		loadChart();
	}

	function goToPage(page: number) {
		eventsOffset = (page - 1) * eventsLimit;
		loadEvents();
	}

	let searchTimeout: ReturnType<typeof setTimeout>;
	function onSearchInput() {
		clearTimeout(searchTimeout);
		searchTimeout = setTimeout(applyFilters, 300);
	}

	onMount(() => {
		loadInitial();
		return () => {
			clearTimeout(searchTimeout);
		};
	});

	async function loadInitial() {
		try {
			const [statusRes, , chartRes] = await Promise.all([
				api<DashboardStatus>(`/status?limit=${eventsLimit}`),
				providersCache.fetch(),
				api<{ days: { date: string; [k: string]: string | number }[] }>('/status/chart'),
			]);
			status = statusRes;
			chartDays = chartRes.days || [];
			setTimeout(() => {
				if (!status) return;
				animateCount(0, status.providers, (v) => displayProviders = v);
				animateCount(0, status.trackers.active, (v) => displayActive = v);
				animateCount(0, status.trackers.total, (v) => displayTotal = v);
				animateCount(0, status.targets, (v) => displayTargets = v);
				if (status.command_trackers !== undefined) {
					animateCount(0, status.command_trackers, (v) => displayCommandTrackers = v);
				}
			}, 200);
		} catch (err: unknown) {
			error = err instanceof Error ? err.message : t('common.error');
		} finally {
			loaded = true;
		}
	}

	const filteredProviderCount = $derived(globalProviderFilter.providerType
		? providers.filter(p => p.type === globalProviderFilter.providerType).length
		: displayProviders);

	// === Provider deck — derive activity counts from recent events ===
	const providerEventCounts = $derived.by(() => {
		const counts = new Map<string, number>();
		if (!status) return counts;
		for (const ev of status.recent_events) {
			const k = ev.provider_name || '';
			if (!k) continue;
			counts.set(k, (counts.get(k) || 0) + (ev.assets_count || 1));
		}
		return counts;
	});
	const providerDeck = $derived.by(() => {
		const max = Math.max(1, ...Array.from(providerEventCounts.values()));
		return providers.map(p => {
			const trackers = notificationTrackersCache.items.filter(t => t.provider_id === p.id);
			const events = providerEventCounts.get(p.name) || 0;
			return {
				id: p.id,
				name: p.name,
				type: p.type,
				icon: providerDefaultIcon(p),
				trackerCount: trackers.length,
				armedCount: trackers.filter(t => (t as { enabled?: boolean }).enabled !== false).length,
				events,
				share: events / max,
				descriptor: getDescriptor(p.type),
			};
		}).sort((a, b) => b.events - a.events);
	});

	// === Active wires — derive top tracker → target routes ===
	const activeWires = $derived.by(() => {
		const wires: Array<{
			trackerName: string;
			providerName: string;
			providerType: string;
			providerIcon: string;
			targetName: string;
			targetType: string;
			targetIcon: string;
			events: number;
		}> = [];
		const targetsById = new Map(targetsCache.items.map(tg => [tg.id, tg]));
		const trackerEventCount = new Map<string, number>();
		if (status) {
			for (const ev of status.recent_events) {
				const k = ev.tracker_name || '';
				if (!k) continue;
				trackerEventCount.set(k, (trackerEventCount.get(k) || 0) + (ev.assets_count || 1));
			}
		}
		for (const tracker of notificationTrackersCache.items) {
			const provider = providers.find(p => p.id === tracker.provider_id);
			if (!provider) continue;
			const links = (tracker as { tracker_targets?: { target_id: number }[] }).tracker_targets || [];
			for (const link of links) {
				const target = targetsById.get(link.target_id);
				if (!target) continue;
				wires.push({
					trackerName: tracker.name,
					providerName: provider.name,
					providerType: provider.type,
					providerIcon: providerDefaultIcon(provider),
					targetName: target.name,
					targetType: target.type,
					targetIcon: target.icon || `mdi${target.type[0].toUpperCase() + target.type.slice(1)}`,
					events: trackerEventCount.get(tracker.name) || 0,
				});
			}
		}
		return wires.sort((a, b) => b.events - a.events).slice(0, 6);
	});

	// === Hero summary sentence === */
	const heroSummary = $derived.by(() => {
		if (!status) return null;
		const failing = status.recent_events.filter(e => /fail|error/.test(e.event_type)).length;
		return {
			armed: status.trackers.active,
			total: status.trackers.total,
			providers: status.providers,
			targets: status.targets,
			throughput: status.total_events,
			failing,
			allOk: failing === 0,
		};
	});

	function timeAgo(dateStr: string): string {
		const diff = Date.now() - parseDate(dateStr).getTime();
		const mins = Math.floor(diff / 60000);
		if (mins < 1) return t('dashboard.justNow');
		if (mins < 60) return t('dashboard.minutesAgo').replace('{n}', String(mins));
		const hours = Math.floor(mins / 60);
		if (hours < 24) return t('dashboard.hoursAgo').replace('{n}', String(hours));
		return t('dashboard.daysAgo').replace('{n}', String(Math.floor(hours / 24)));
	}

	function timeShort(dateStr: string): string {
		const d = parseDate(dateStr);
		return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
	}

	const eventLabels: Record<string, string> = {
		assets_added: 'dashboard.assetsAdded',
		assets_removed: 'dashboard.assetsRemoved',
		collection_renamed: 'dashboard.collectionRenamed',
		collection_deleted: 'dashboard.collectionDeleted',
		sharing_changed: 'dashboard.sharingChanged',
		scheduled_message: 'dashboard.scheduledMessage',
		action_success: 'dashboard.actionSuccess',
		action_partial: 'dashboard.actionPartial',
		action_failed: 'dashboard.actionFailed',
	};

	const eventIcons: Record<string, string> = {
		assets_added: 'mdiImagePlus', assets_removed: 'mdiImageMinus',
		collection_renamed: 'mdiRename', collection_deleted: 'mdiDeleteAlert', sharing_changed: 'mdiShareVariant',
		scheduled_message: 'mdiCalendarClock',
		action_success: 'mdiPlayCircle', action_partial: 'mdiAlertCircle', action_failed: 'mdiCloseCircle',
	};

	// Aurora gradient palette per event type — used for the avatar tile
	const eventGradients: Record<string, [string, string]> = {
		assets_added:        ['var(--color-mint)',    'var(--color-sky)'],
		assets_removed:      ['var(--color-coral)',   'var(--color-orchid)'],
		collection_renamed:  ['var(--color-primary)', 'var(--color-orchid)'],
		collection_deleted:  ['var(--color-coral)',   'var(--color-citrus)'],
		sharing_changed:     ['var(--color-citrus)',  'var(--color-coral)'],
		scheduled_message:   ['var(--color-sky)',     'var(--color-mint)'],
		action_success:      ['var(--color-mint)',    'var(--color-primary)'],
		action_partial:      ['var(--color-citrus)',  'var(--color-orchid)'],
		action_failed:       ['var(--color-coral)',   'var(--color-orchid)'],
	};

	const STAT_ACCENTS = [
		'var(--color-primary)',
		'var(--color-sky)',
		'var(--color-citrus)',
		'var(--color-orchid)',
	];
</script>

{#if !loaded}
	<Loading lines={4} />
{:else if error}
	<div class="hero-card error-card">
		<div class="flex items-center gap-2" style="color: var(--color-error-fg);">
			<MdiIcon name="mdiAlertCircle" size={20} />
			<p class="text-sm">{error}</p>
		</div>
	</div>
{:else if status && heroSummary}

	<!-- ==================== HERO ==================== -->
	<section class="hero-card">
		<div class="hero-row">
			<div class="hero-main">
				<div class="hero-crumb">
					<span>{t('dashboard.title')}</span>
					<span class="sep">·</span>
					<span class="live">
						<span class="live-dot"></span>
						{heroSummary.allOk ? t('dashboard.live') : t('dashboard.attention')}
					</span>
				</div>
				<h1 class="hero-title">
					{t('dashboard.heroPrefix')} <em>{t('dashboard.heroEmphasis')}</em><br>
					{t('dashboard.heroSuffix')}
				</h1>
				<p class="hero-sub">
					{t('dashboard.heroSummary')
						.replace('{providers}', String(heroSummary.providers))
						.replace('{armed}', String(heroSummary.armed))
						.replace('{total}', String(heroSummary.total))
						.replace('{throughput}', String(heroSummary.throughput))
						.replace('{targets}', String(heroSummary.targets))}
				</p>
			</div>
			<div class="hero-meter">
				<div class="hero-meter-label">{t('dashboard.throughput24h')}</div>
				<div class="hero-meter-value font-mono">{heroSummary.throughput.toLocaleString()}<sup>{t('dashboard.eventsShort')}</sup></div>
				<div class="hero-meter-row">
					<span class="pill"><span class="dot" style="background: var(--color-mint)"></span><b>{heroSummary.armed}/{heroSummary.total}</b> {t('dashboard.armedShort')}</span>
					<span class="pill"><span class="dot" style="background: var(--color-sky)"></span><b>{heroSummary.providers}</b> {t('dashboard.providersShort')}</span>
					<span class="pill"><span class="dot" style="background: var(--color-orchid)"></span><b>{heroSummary.targets}</b> {t('dashboard.targetsShort')}</span>
				</div>
			</div>
		</div>
	</section>

	<!-- ==================== STATS ==================== -->
	{#snippet statCardSnippet(card: {icon: string; label: string; literalLabel?: string; value: number; literalValue?: string; suffix?: string; accent: string}, idx: number)}
		<div class="stat-card" style="--accent: {card.accent}">
			<div class="stat-card-inner">
				<div class="flex items-center gap-3">
					<div class="stat-icon" style="color: {card.accent};">
						<MdiIcon name={card.icon} size={20} />
					</div>
					<div class="min-w-0">
						<p class="text-sm" style="color: var(--color-muted-foreground);">{card.literalLabel || t(card.label)}</p>
						<p class="{card.literalValue ? 'stat-value-text' : 'stat-value'} font-mono" style="animation-delay: {idx * 80 + 200}ms;">
							{#if card.literalValue}{card.literalValue}{:else}{card.value}{/if}{#if card.suffix}<span class="stat-suffix">{card.suffix}</span>{/if}
						</p>
					</div>
				</div>
			</div>
		</div>
	{/snippet}

	{#snippet statCards()}
		<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 stagger-children">
			{#if globalProviderFilter.provider}
				{@render statCardSnippet({
					icon: providerDefaultIcon(globalProviderFilter.provider),
					label: '',
					literalLabel: getDescriptor(globalProviderFilter.provider.type)?.defaultName ?? globalProviderFilter.provider.type,
					value: 0,
					literalValue: globalProviderFilter.provider.name,
					accent: STAT_ACCENTS[0],
				}, 0)}
			{:else}
				{@render statCardSnippet({
					icon: 'mdiServer',
					label: 'dashboard.providers',
					value: filteredProviderCount,
					accent: STAT_ACCENTS[0],
				}, 0)}
			{/if}
			{@render statCardSnippet({
				icon: 'mdiRadar',
				label: 'dashboard.activeTrackers',
				value: displayActive,
				suffix: ` / ${displayTotal}`,
				accent: STAT_ACCENTS[1],
			}, 1)}
			{@render statCardSnippet({
				icon: 'mdiTarget',
				label: 'dashboard.targets',
				value: displayTargets,
				accent: STAT_ACCENTS[2],
			}, 2)}
			{#if status?.command_trackers !== undefined}
				{@render statCardSnippet({
					icon: 'mdiConsoleLine',
					label: 'nav.commandTrackers',
					value: displayCommandTrackers,
					accent: STAT_ACCENTS[3],
				}, 3)}
			{:else}
				{@render statCardSnippet({
					icon: 'mdiPulse',
					label: 'dashboard.eventsTotal',
					value: heroSummary?.throughput ?? 0,
					accent: STAT_ACCENTS[3],
				}, 3)}
			{/if}
		</div>
	{/snippet}
	{@render statCards()}

	<!-- ==================== TWO COL: stream + provider deck ==================== -->
	<div class="two-col" class:two-col--single={!!globalProviderFilter.id}>
		<!-- Signal stream -->
		<section class="panel">
			<header class="panel-head">
				<div>
					<h2 class="panel-title">{t('dashboard.streamTitle')} <em>{t('dashboard.streamEmphasis')}</em></h2>
					<p class="panel-meta">
						<b>{status.total_events}</b> {t('dashboard.eventsLabel')}
					</p>
				</div>
				{#if status.total_events > 0}
					<button type="button" onclick={() => confirmClearEvents = true}
						class="clear-events-btn flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg transition-colors"
						title={t('dashboard.clearEvents')}>
						<MdiIcon name="mdiTrashCanOutline" size={14} />
						<span class="hidden sm:inline">{t('dashboard.clearEvents')}</span>
					</button>
				{/if}
			</header>

			<div class="panel-filters">
				<div class="flex-1 min-w-[150px] max-w-[280px]">
					<input type="text" bind:value={filterSearch} oninput={onSearchInput}
						placeholder={t('dashboard.searchEvents')}
						class="search-input" />
				</div>
				<div class="w-40"><IconGridSelect items={eventTypeFilterItems()} bind:value={filterEventType} columns={3} compact /></div>
				{#if !globalProviderFilter.id}<div class="w-40"><IconGridSelect items={providerFilterItems} bind:value={filterProviderId} columns={2} compact /></div>{/if}
				<div class="w-36"><IconGridSelect items={sortFilterItems()} bind:value={filterSort} columns={2} compact /></div>
			</div>

			{#snippet paginator()}
				<div class="flex items-center justify-center gap-1">
					{#if totalPages > 1}
						<button onclick={() => goToPage(currentPage - 1)} disabled={currentPage <= 1}
							class="paginator-btn">
							<NavIcon name="mdiChevronLeft" size={14} />
						</button>
						{#each Array.from({ length: totalPages }, (_, i) => i + 1) as p}
							{#if p === 1 || p === totalPages || (p >= currentPage - 1 && p <= currentPage + 1)}
								<button onclick={() => goToPage(p)}
									class="paginator-num font-mono {p === currentPage ? 'active' : ''}">
									{p}
								</button>
							{:else if p === currentPage - 2 || p === currentPage + 2}
								<span class="paginator-ellipsis">…</span>
							{/if}
						{/each}
						<button onclick={() => goToPage(currentPage + 1)} disabled={currentPage >= totalPages}
							class="paginator-btn">
							<NavIcon name="mdiChevronRight" size={14} />
						</button>
					{/if}
					<select value={eventsLimit}
						onchange={(e) => { const v = parseInt((e.target as HTMLSelectElement).value, 10); eventsLimit = v; eventsOffset = 0; if (typeof localStorage !== 'undefined') localStorage.setItem(EVENTS_PER_PAGE_KEY, String(v)); loadEvents(); }}
						class="paginator-select">
						{#each [5, 10, 20, 50] as n}
							<option value={n}>{n}</option>
						{/each}
					</select>
				</div>
			{/snippet}

			{#if eventsLoading}
				<div class="empty-state"><p>{t('dashboard.loadingEvents')}</p></div>
			{:else if status.recent_events.length === 0}
				<div class="empty-state">
					<MdiIcon name="mdiCalendarBlank" size={36} />
					<p>{t('dashboard.noEvents')}</p>
				</div>
			{:else}
				<div class="signal-list stagger-children">
					{#each status.recent_events as event, i}
						<div class="signal-row" style="animation-delay: {i * 60}ms;">
							<div class="signal-avatar"
								style="--g1: {eventGradients[event.event_type]?.[0] ?? 'var(--color-primary)'}; --g2: {eventGradients[event.event_type]?.[1] ?? 'var(--color-orchid)'};">
								<MdiIcon name={eventIcons[event.event_type] || 'mdiBell'} size={18} />
							</div>
							<div class="signal-body min-w-0">
								<div class="signal-sentence">
									{#if event.provider_name}<b class="signal-emph">{event.provider_name}</b>{/if}
									<span class="signal-verb">{t(eventLabels[event.event_type] || event.event_type)}</span>
									{#if event.assets_count > 0}
										<b class="signal-emph">{event.assets_count} {event.assets_count === 1 ? t('dashboard.asset') : t('dashboard.assets')}</b>
									{/if}
									{#if event.collection_name}
										<span class="signal-conn">·</span>
										<b class="signal-emph signal-emph--accent truncate">«{event.collection_name}»</b>
									{/if}
								</div>
								{#if event.tracker_name}
									<div class="signal-trail">
										<span class="ch"><MdiIcon name="mdiRadar" size={11} />{event.tracker_name}</span>
										{#if event.provider_name}
											<span class="arrow">→</span>
											<span class="ch"><MdiIcon name="mdiServer" size={11} />{event.provider_name}</span>
										{/if}
									</div>
								{/if}
							</div>
							<div class="signal-when font-mono">
								<b>{timeShort(event.created_at)}</b>
								<small>{timeAgo(event.created_at)}</small>
							</div>
						</div>
					{/each}
				</div>

				<div class="panel-foot">
					{@render paginator()}
				</div>
			{/if}
		</section>

		<!-- On watch — provider deck. Hidden when a global provider filter is
		     active (the deck would only show that one provider — redundant). -->
		{#if !globalProviderFilter.id}
		<section class="panel">
			<header class="panel-head">
				<div>
					<h2 class="panel-title">{t('dashboard.onWatchTitle')} <em>{t('dashboard.onWatchEmphasis')}</em></h2>
					<p class="panel-meta"><b>{providerDeck.length}</b> {t('dashboard.providersShort')}</p>
				</div>
			</header>

			{#if providerDeck.length === 0}
				<div class="empty-state">
					<MdiIcon name="mdiServerNetwork" size={32} />
					<p>{t('dashboard.noProviders')}</p>
					<a href="/providers" class="empty-state-link">{t('dashboard.addProvider')} →</a>
				</div>
			{:else}
				<div class="provider-deck">
					{#each providerDeck as p}
						<a href="/providers" class="provider-row" style="--accent: {STAT_ACCENTS[p.id % STAT_ACCENTS.length]}">
							<div class="provider-icon">
								<MdiIcon name={p.icon} size={20} />
							</div>
							<div class="min-w-0 flex-1">
								<div class="provider-name truncate">
									{#if p.armedCount > 0}<span class="aurora-pulse"></span>{:else}<span class="aurora-pulse idle"></span>{/if}
									{p.name}
								</div>
								<div class="provider-sub font-mono">
									{p.descriptor?.defaultName ?? p.type} · {p.trackerCount} {t('dashboard.trackersShort')}
								</div>
							</div>
							<div class="provider-meter">
								<div class="provider-num font-mono">{p.events}</div>
								<div class="provider-bar"><div class="provider-bar-fill" style="width: {Math.max(6, p.share * 100)}%"></div></div>
							</div>
						</a>
					{/each}
					<a href="/providers/new" class="provider-row provider-row--add">
						<div class="provider-icon dashed">
							<NavIcon name="mdiPlus" size={18} />
						</div>
						<div class="min-w-0 flex-1">
							<div class="provider-name muted">{t('dashboard.addProvider')}</div>
							<div class="provider-sub font-mono">{t('dashboard.addProviderHint')}</div>
						</div>
					</a>
				</div>
			{/if}
		</section>
		{/if}
	</div>

	<!-- ==================== PULSE CHART ==================== -->
	<section class="panel">
		<header class="panel-head">
			<div>
				<h2 class="panel-title">{t('dashboard.pulseTitle')} <em>{t('dashboard.pulseEmphasis')}</em></h2>
				<p class="panel-meta">{t('dashboard.pulseSub')}</p>
			</div>
			<button type="button" onclick={toggleChart}
				class="ghost-icon-btn"
				title={chartVisible ? t('common.hide') : t('common.show')}>
				<NavIcon name={chartVisible ? 'mdiChevronDown' : 'mdiChevronRight'} size={16} />
			</button>
		</header>
		{#if chartVisible}
			<div class="panel-body" in:slide={{ duration: 200 }}>
				<EventChart days={chartDays} />
			</div>
		{/if}
	</section>

	<!-- ==================== ACTIVE WIRES ==================== -->
	{#if activeWires.length > 0}
		<section class="panel">
			<header class="panel-head">
				<div>
					<h2 class="panel-title">{t('dashboard.wiresTitle')} <em>{t('dashboard.wiresEmphasis')}</em></h2>
					<p class="panel-meta"><b>{activeWires.length}</b> {t('dashboard.wiresSub')}</p>
				</div>
			</header>
			<div class="wires-body">
				{#each activeWires as wire}
					<div class="wire">
						<div class="wire-from">
							<MdiIcon name={wire.providerIcon} size={15} />
							<div class="min-w-0">
								<div class="wire-name truncate">{wire.trackerName}</div>
								<div class="wire-sub font-mono truncate">{wire.providerName}</div>
							</div>
						</div>
						<div class="wire-pipe">
							<span class="wire-count font-mono">{wire.events > 0 ? wire.events : '—'}</span>
						</div>
						<div class="wire-to">
							<div class="min-w-0">
								<div class="wire-name truncate">{wire.targetName}</div>
								<div class="wire-sub font-mono truncate">{wire.targetType}</div>
							</div>
							<MdiIcon name={wire.targetIcon} size={15} />
						</div>
					</div>
				{/each}
			</div>
		</section>
	{/if}

	<!-- ==================== COMPOSE BAND ==================== -->
	<section class="compose-band">
		<div class="compose-body">
			<h3 class="compose-title">
				{t('dashboard.composeTitle')} <em>{t('dashboard.composeEmphasis')}</em>
			</h3>
			<p class="compose-sub">{t('dashboard.composeSub')}</p>
		</div>
		<div class="compose-cta">
			<a href="/notification-trackers" class="ghost-btn">
				<NavIcon name="mdiRadar" size={14} />
				{t('dashboard.viewTrackers')}
			</a>
			<a href="/notification-trackers" class="primary-btn-aurora">
				<NavIcon name="mdiArrowRight" size={14} />
				{t('dashboard.newTracker')}
			</a>
		</div>
	</section>
{/if}

<ConfirmModal open={confirmClearEvents} message={t('dashboard.confirmClearEvents')}
	onconfirm={clearEvents} oncancel={() => confirmClearEvents = false} />

<style>
	/* ============================================================
	   HERO
	============================================================ */
	.hero-card {
		position: relative;
		background: var(--color-glass);
		backdrop-filter: blur(28px) saturate(160%);
		-webkit-backdrop-filter: blur(28px) saturate(160%);
		border: 1px solid var(--color-border);
		border-radius: 22px;
		box-shadow: var(--shadow-card);
		padding: 2rem 2.4rem;
		margin-bottom: 1.5rem;
		overflow: hidden;
	}
	.hero-card::after {
		content: '';
		position: absolute; inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: linear-gradient(180deg, var(--color-highlight), transparent 30%);
		opacity: 0.4;
	}
	.error-card { padding: 1rem 1.4rem; }
	.hero-row {
		position: relative; z-index: 1;
		display: grid;
		grid-template-columns: 1fr auto;
		gap: 2rem;
		align-items: end;
	}
	.hero-crumb {
		display: flex; align-items: center; gap: 0.6rem;
		font-size: 0.78rem;
		color: var(--color-muted-foreground);
		margin-bottom: 0.85rem;
		font-weight: 500;
	}
	.hero-crumb .sep { color: var(--color-rule-strong); }
	.hero-crumb .live {
		display: inline-flex; align-items: center; gap: 0.4rem;
		color: var(--color-mint);
	}
	.live-dot {
		width: 6px; height: 6px; border-radius: 50%;
		background: var(--color-mint);
		box-shadow: 0 0 8px var(--color-mint);
		animation: aurora-pulse 1.4s ease-in-out infinite;
	}
	.hero-title {
		font-family: var(--font-display);
		font-weight: 400;
		font-size: 3rem;
		line-height: 1;
		letter-spacing: -0.025em;
		margin: 0 0 0.85rem;
		color: var(--color-foreground);
	}
	.hero-title em {
		font-style: italic;
		background: linear-gradient(135deg, var(--color-orchid), var(--color-primary) 60%, var(--color-sky));
		-webkit-background-clip: text;
		background-clip: text;
		color: transparent;
	}
	.hero-sub {
		font-size: 0.95rem;
		color: var(--color-muted-foreground);
		max-width: 36rem;
		line-height: 1.55;
	}
	.hero-meter { text-align: right; }
	.hero-meter-label {
		font-size: 0.68rem;
		text-transform: uppercase;
		letter-spacing: 0.13em;
		color: var(--color-muted-foreground);
		margin-bottom: 0.55rem;
		font-weight: 500;
	}
	.hero-meter-value {
		font-size: 3rem;
		font-weight: 500;
		font-variant-numeric: tabular-nums;
		line-height: 1;
		letter-spacing: -0.03em;
		color: var(--color-foreground);
	}
	.hero-meter-value sup {
		font-size: 0.75rem;
		color: var(--color-muted-foreground);
		margin-left: 0.4rem;
		font-weight: 400;
		background: var(--color-glass-strong);
		padding: 0.25rem 0.5rem;
		border-radius: 0.5rem;
		vertical-align: middle;
		font-family: var(--font-sans);
	}
	.hero-meter-row {
		margin-top: 0.85rem;
		display: flex; gap: 0.5rem;
		justify-content: flex-end;
		flex-wrap: wrap;
	}
	.pill {
		display: inline-flex; align-items: center; gap: 0.4rem;
		padding: 0.3rem 0.7rem;
		border-radius: 999px;
		background: var(--color-glass-strong);
		border: 1px solid var(--color-border);
		font-size: 0.72rem;
		color: var(--color-muted-foreground);
	}
	.pill b {
		color: var(--color-foreground);
		font-weight: 500;
		font-variant-numeric: tabular-nums;
	}
	.pill .dot { width: 6px; height: 6px; border-radius: 50%; }

	@media (max-width: 880px) {
		.hero-card { padding: 1.4rem 1.4rem 1.6rem; }
		.hero-row { grid-template-columns: 1fr; }
		.hero-title { font-size: 2.2rem; }
		.hero-meter { text-align: left; }
		.hero-meter-row { justify-content: flex-start; }
		.hero-meter-value { font-size: 2.4rem; }
	}

	/* ============================================================
	   STAT CARDS — same pattern as foundation pass
	============================================================ */
	.stat-card {
		position: relative;
		border-radius: 22px;
		background: var(--color-glass);
		backdrop-filter: blur(28px) saturate(160%);
		-webkit-backdrop-filter: blur(28px) saturate(160%);
		border: 1px solid var(--color-border);
		box-shadow: var(--shadow-card);
		overflow: hidden;
		transition: transform 0.25s cubic-bezier(.4,.4,0,1);
		cursor: pointer;
	}
	.stat-card::before {
		content: '';
		position: absolute;
		width: 220px; height: 220px;
		border-radius: 50%;
		right: -100px; top: -90px;
		background: radial-gradient(circle, var(--accent) 0%, transparent 70%);
		opacity: 0.35;
		pointer-events: none;
		filter: blur(20px);
		transition: transform 0.4s;
	}
	.stat-card::after {
		content: '';
		position: absolute;
		inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: linear-gradient(180deg, var(--color-highlight), transparent 30%);
		opacity: 0.4;
	}
	.stat-card:hover { transform: translateY(-3px); }
	.stat-card:hover::before { transform: scale(1.15); opacity: 0.55; }
	.stat-card-inner { position: relative; z-index: 1; padding: 1.3rem 1.4rem; }
	.stat-icon {
		display: flex; align-items: center; justify-content: center;
		width: 2.5rem; height: 2.5rem;
		border-radius: 0.85rem;
		flex-shrink: 0;
		background: var(--color-glass-elev) !important;
		border: 1px solid var(--color-border);
		box-shadow: inset 0 1px 0 var(--color-highlight);
	}
	.stat-value {
		font-size: 1.85rem;
		font-weight: 500;
		line-height: 1;
		letter-spacing: -0.025em;
		font-variant-numeric: tabular-nums;
		animation: countUp 0.5s ease-out both;
	}
	.stat-value-text {
		font-size: 1rem;
		font-weight: 600;
		line-height: 1.3;
		animation: countUp 0.5s ease-out both;
	}
	.stat-suffix { font-size: 1rem; font-weight: 400; color: var(--color-muted-foreground); }

	/* ============================================================
	   TWO COL — stream + provider deck
	============================================================ */
	.two-col {
		display: grid;
		grid-template-columns: 1.55fr 1fr;
		gap: 1.5rem;
		margin-top: 1.5rem;
	}
	.two-col.two-col--single { grid-template-columns: 1fr; }
	@media (max-width: 1100px) { .two-col { grid-template-columns: 1fr; } }

	/* ============================================================
	   PANEL — generic glass panel for all sections
	============================================================ */
	.panel {
		position: relative;
		background: var(--color-glass);
		backdrop-filter: blur(28px) saturate(160%);
		-webkit-backdrop-filter: blur(28px) saturate(160%);
		border: 1px solid var(--color-border);
		border-radius: 22px;
		box-shadow: var(--shadow-card);
		overflow: hidden;
		margin-top: 1.5rem;
	}
	.panel::after {
		content: '';
		position: absolute; inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: linear-gradient(180deg, var(--color-highlight), transparent 30%);
		opacity: 0.4;
	}
	.two-col > .panel { margin-top: 0; }
	.panel-head {
		position: relative; z-index: 1;
		display: flex; align-items: flex-start; justify-content: space-between;
		padding: 1.25rem 1.5rem 0.85rem;
		gap: 1rem;
	}
	.panel-title {
		font-family: var(--font-display);
		font-weight: 400;
		font-size: 1.35rem;
		letter-spacing: -0.02em;
		color: var(--color-foreground);
		margin: 0;
		line-height: 1.1;
	}
	.panel-title em {
		font-style: italic;
		background: linear-gradient(135deg, var(--color-orchid), var(--color-primary));
		-webkit-background-clip: text;
		background-clip: text;
		color: transparent;
	}
	.panel-meta {
		font-size: 0.72rem;
		color: var(--color-muted-foreground);
		margin-top: 0.25rem;
	}
	.panel-meta b {
		color: var(--color-foreground);
		font-weight: 500;
		font-variant-numeric: tabular-nums;
		font-family: var(--font-mono);
		margin-right: 0.25rem;
	}
	.panel-body {
		position: relative; z-index: 1;
		padding: 0.5rem 1.5rem 1.5rem;
	}
	.panel-foot {
		position: relative; z-index: 1;
		padding: 0.85rem 1.5rem 1.25rem;
		border-top: 1px solid var(--color-border);
	}
	.panel-filters {
		position: relative; z-index: 1;
		display: flex; flex-wrap: wrap; gap: 0.5rem;
		padding: 0.5rem 1.5rem 0.85rem;
		border-bottom: 1px solid var(--color-border);
		align-items: center;
	}
	.search-input {
		width: 100%;
		padding: 0.5rem 0.85rem;
		border-radius: 10px;
		background: var(--color-input-bg);
		border: 1px solid var(--color-border);
		color: var(--color-foreground);
		font-size: 0.85rem;
	}

	/* Empty state */
	.empty-state {
		position: relative; z-index: 1;
		padding: 3rem 1.5rem;
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 0.75rem;
		color: var(--color-muted-foreground);
		text-align: center;
	}
	.empty-state p { font-size: 0.85rem; }
	.empty-state-link {
		font-size: 0.85rem;
		color: var(--color-primary);
		text-decoration: none;
		font-weight: 500;
		margin-top: 0.5rem;
	}
	.empty-state-link:hover { text-decoration: underline; text-underline-offset: 4px; }

	/* ============================================================
	   SIGNAL STREAM — events with routing trail
	============================================================ */
	.signal-list { position: relative; z-index: 1; padding-bottom: 0.25rem; }
	.signal-row {
		display: grid;
		grid-template-columns: 40px 1fr auto;
		gap: 0.85rem;
		align-items: center;
		padding: 0.85rem 1.5rem;
		transition: background 0.15s;
		cursor: pointer;
	}
	.signal-row + .signal-row { border-top: 1px solid var(--color-border); }
	.signal-row:hover { background: var(--color-glass-strong); }
	.signal-avatar {
		width: 40px; height: 40px;
		border-radius: 12px;
		display: grid; place-items: center;
		background: linear-gradient(135deg, var(--g1, var(--color-primary)), var(--g2, var(--color-orchid)));
		color: white;
		box-shadow: 0 4px 14px -4px var(--g1, var(--color-primary)), inset 0 1px 0 rgba(255,255,255,0.3);
	}
	.signal-body { min-width: 0; }
	.signal-sentence {
		display: flex; align-items: baseline;
		gap: 0.4rem;
		font-size: 0.92rem;
		line-height: 1.45;
		color: var(--color-foreground);
		flex-wrap: wrap;
	}
	.signal-emph {
		font-weight: 600;
		color: var(--color-foreground);
	}
	.signal-emph--accent {
		background: linear-gradient(135deg, var(--color-primary), var(--color-orchid));
		-webkit-background-clip: text;
		background-clip: text;
		color: transparent;
		font-weight: 600;
		max-width: 24ch;
	}
	.signal-verb {
		font-weight: 400;
		color: var(--color-muted-foreground);
	}
	.signal-conn {
		color: var(--color-rule-strong);
	}
	.signal-trail {
		display: flex; align-items: center; gap: 0.4rem;
		font-size: 0.72rem;
		color: var(--color-muted-foreground);
		margin-top: 0.35rem;
		flex-wrap: wrap;
	}
	.signal-trail .ch {
		display: inline-flex; align-items: center; gap: 0.3rem;
		color: var(--color-foreground);
		font-family: var(--font-mono);
		font-size: 0.7rem;
		background: var(--color-glass-strong);
		padding: 0.15rem 0.45rem;
		border-radius: 6px;
	}
	.signal-trail .arrow { color: var(--color-muted-foreground); }
	.signal-when {
		text-align: right;
		font-size: 0.7rem;
		color: var(--color-muted-foreground);
	}
	.signal-when b {
		display: block;
		color: var(--color-foreground);
		font-size: 0.82rem;
		font-weight: 500;
		font-variant-numeric: tabular-nums;
	}
	.signal-when small { font-size: 0.65rem; color: var(--color-muted-foreground); }

	.clear-events-btn {
		color: var(--color-muted-foreground);
		background: var(--color-glass-strong);
		border: 1px solid var(--color-border);
	}
	.clear-events-btn:hover {
		background: var(--color-error-bg);
		border-color: color-mix(in srgb, var(--color-error-fg) 40%, var(--color-border));
		color: var(--color-error-fg);
	}

	/* Paginator */
	.paginator-btn {
		padding: 0.3rem 0.6rem;
		border-radius: 8px;
		border: 1px solid var(--color-border);
		background: var(--color-glass-strong);
		color: var(--color-foreground);
		cursor: pointer;
		transition: all 0.15s;
	}
	.paginator-btn:hover { background: var(--color-glass-elev); }
	.paginator-btn:disabled { opacity: 0.4; cursor: default; }
	.paginator-num {
		padding: 0.3rem 0.65rem;
		border-radius: 8px;
		border: 1px solid var(--color-border);
		background: transparent;
		color: var(--color-muted-foreground);
		cursor: pointer;
		transition: all 0.15s;
		font-size: 0.75rem;
	}
	.paginator-num:hover { color: var(--color-foreground); background: var(--color-glass-strong); }
	.paginator-num.active {
		background: linear-gradient(135deg, var(--color-primary), var(--color-orchid));
		color: white;
		border-color: transparent;
		box-shadow: 0 4px 12px -4px var(--color-glow-strong);
	}
	.paginator-ellipsis { padding: 0 0.4rem; color: var(--color-muted-foreground); font-size: 0.7rem; }
	.paginator-select {
		margin-left: 0.5rem;
		padding: 0.3rem 0.5rem;
		border-radius: 8px;
		border: 1px solid var(--color-border);
		background: var(--color-input-bg);
		color: var(--color-foreground);
		font-size: 0.7rem;
	}

	/* ============================================================
	   PROVIDER DECK
	============================================================ */
	.provider-deck { position: relative; z-index: 1; padding-bottom: 0.5rem; }
	.provider-row {
		display: grid;
		grid-template-columns: 44px 1fr auto;
		gap: 0.85rem;
		align-items: center;
		padding: 0.85rem 1.5rem;
		text-decoration: none;
		color: inherit;
		transition: background 0.15s;
	}
	.provider-row + .provider-row { border-top: 1px solid var(--color-border); }
	.provider-row:hover { background: var(--color-glass-strong); }
	.provider-icon {
		width: 44px; height: 44px;
		border-radius: 14px;
		display: grid; place-items: center;
		background: var(--color-glass-elev);
		border: 1px solid var(--color-border);
		color: var(--accent, var(--color-primary));
		box-shadow: inset 0 1px 0 var(--color-highlight);
	}
	.provider-icon.dashed {
		border-style: dashed;
		color: var(--color-muted-foreground);
		background: transparent;
		box-shadow: none;
	}
	.provider-name {
		font-size: 0.92rem;
		font-weight: 500;
		color: var(--color-foreground);
		display: flex; align-items: center; gap: 0.5rem;
	}
	.provider-name.muted { color: var(--color-muted-foreground); }
	.provider-sub {
		font-size: 0.7rem;
		color: var(--color-muted-foreground);
		margin-top: 0.2rem;
	}
	.provider-meter {
		text-align: right;
		min-width: 80px;
	}
	.provider-num {
		font-size: 1rem;
		color: var(--color-foreground);
		font-weight: 500;
		font-variant-numeric: tabular-nums;
		line-height: 1;
	}
	.provider-bar {
		margin-top: 0.4rem;
		height: 4px;
		border-radius: 2px;
		background: var(--color-glass-strong);
		overflow: hidden;
	}
	.provider-bar-fill {
		height: 100%;
		background: linear-gradient(90deg, var(--accent, var(--color-primary)), var(--color-orchid));
		border-radius: 2px;
		box-shadow: 0 0 8px -2px var(--accent, var(--color-primary));
	}
	.provider-row--add { opacity: 0.7; }
	.provider-row--add:hover { opacity: 1; }

	/* ============================================================
	   ACTIVE WIRES
	============================================================ */
	.wires-body {
		position: relative; z-index: 1;
		padding: 0.4rem 1.5rem 1.25rem;
	}
	.wire {
		display: grid;
		grid-template-columns: 1fr auto 1fr;
		gap: 0.85rem;
		align-items: center;
		padding: 0.7rem 0;
	}
	.wire + .wire { border-top: 1px dashed var(--color-border); }
	.wire-from, .wire-to {
		display: flex; align-items: center; gap: 0.6rem;
		font-size: 0.85rem;
		min-width: 0;
	}
	.wire-to { justify-content: flex-end; text-align: right; }
	.wire-from, .wire-to { color: var(--color-muted-foreground); }
	.wire-from .wire-name, .wire-to .wire-name { color: var(--color-foreground); }
	.wire-name { color: var(--color-foreground); font-weight: 500; }
	.wire-sub { font-size: 0.65rem; color: var(--color-muted-foreground); margin-top: 0.15rem; }
	.wire-pipe {
		position: relative;
		min-width: 100px;
		display: flex; align-items: center; justify-content: center;
		height: 22px;
	}
	.wire-pipe::before {
		content: '';
		position: absolute; inset: 50% 0 auto 0; height: 2px;
		background: linear-gradient(90deg, var(--color-primary), var(--color-orchid), var(--color-mint));
		opacity: 0.5;
		border-radius: 2px;
	}
	.wire-count {
		position: relative; z-index: 1;
		background: var(--color-glass-elev);
		border: 1px solid var(--color-border);
		padding: 0.15rem 0.6rem;
		border-radius: 999px;
		font-size: 0.7rem;
		color: var(--color-foreground);
		font-variant-numeric: tabular-nums;
	}

	/* ============================================================
	   COMPOSE BAND
	============================================================ */
	.compose-band {
		position: relative;
		margin-top: 1.5rem;
		padding: 2rem 2.4rem;
		border-radius: 22px;
		background:
			linear-gradient(135deg, rgba(126, 232, 196, 0.10), rgba(142, 201, 255, 0.10), rgba(184, 167, 255, 0.12)),
			var(--color-glass);
		backdrop-filter: blur(28px) saturate(160%);
		-webkit-backdrop-filter: blur(28px) saturate(160%);
		border: 1px solid var(--color-border);
		box-shadow: var(--shadow-card);
		display: grid;
		grid-template-columns: 1fr auto;
		gap: 2rem;
		align-items: center;
		overflow: hidden;
	}
	.compose-band::after {
		content: '';
		position: absolute; inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: linear-gradient(180deg, var(--color-highlight), transparent 30%);
		opacity: 0.4;
	}
	.compose-body { position: relative; z-index: 1; }
	.compose-title {
		font-family: var(--font-display);
		font-weight: 400;
		font-size: 1.85rem;
		line-height: 1.1;
		letter-spacing: -0.02em;
		margin: 0 0 0.5rem;
		color: var(--color-foreground);
	}
	.compose-title em {
		font-style: italic;
		background: linear-gradient(135deg, var(--color-mint), var(--color-sky));
		-webkit-background-clip: text;
		background-clip: text;
		color: transparent;
	}
	.compose-sub {
		font-size: 0.9rem;
		color: var(--color-muted-foreground);
		max-width: 36rem;
		line-height: 1.55;
	}
	.compose-cta {
		position: relative; z-index: 1;
		display: flex; gap: 0.6rem;
	}
	.ghost-btn {
		display: inline-flex; align-items: center; gap: 0.5rem;
		padding: 0 1rem; height: 40px;
		border-radius: 12px;
		border: 1px solid var(--color-rule-strong);
		background: var(--color-glass-strong);
		color: var(--color-foreground);
		font-size: 0.82rem;
		font-weight: 500;
		text-decoration: none;
		cursor: pointer;
		transition: all 0.15s;
	}
	.ghost-btn:hover { background: var(--color-glass-elev); }
	.primary-btn-aurora {
		display: inline-flex; align-items: center; gap: 0.5rem;
		padding: 0 1.25rem; height: 40px;
		border-radius: 12px;
		border: 0;
		background: linear-gradient(135deg, var(--color-primary), var(--color-orchid));
		color: white;
		font-size: 0.82rem;
		font-weight: 500;
		text-decoration: none;
		cursor: pointer;
		flex-direction: row-reverse;
		box-shadow: 0 6px 20px -8px var(--color-glow-strong), inset 0 1px 0 rgba(255,255,255,0.3);
		transition: transform 0.15s;
	}
	.primary-btn-aurora:hover { transform: translateY(-1px); }
	.ghost-icon-btn {
		width: 32px; height: 32px;
		display: grid; place-items: center;
		border-radius: 10px;
		background: var(--color-glass-strong);
		border: 1px solid var(--color-border);
		color: var(--color-muted-foreground);
		cursor: pointer;
		transition: all 0.15s;
	}
	.ghost-icon-btn:hover { background: var(--color-glass-elev); color: var(--color-foreground); }

	@media (max-width: 880px) {
		.compose-band { grid-template-columns: 1fr; padding: 1.4rem; }
		.compose-title { font-size: 1.4rem; }
	}
</style>
