<script lang="ts">
	import { page } from '$app/stores';
	import { browser } from '$app/environment';
	import { onMount, onDestroy } from 'svelte';
	import type { QueueItem, QueueStatusUpdate } from '$lib/server/queue/types';
	import { replaceState } from '$app/navigation';
	import { pushNotificationManager } from '$lib/client/PushNotificationManager';

	import TopBar from './components/TopBar.svelte';
	import CookingHero from './components/CookingHero.svelte';
	import TimelineRow from './components/TimelineRow.svelte';
	import EmptyState from './components/EmptyState.svelte';
	import RecipeSheet from './components/RecipeSheet.svelte';
	import SectionHead from './components/SectionHead.svelte';
	import AddUrlScreen from './components/AddUrlScreen.svelte';
	import NotificationsScreen from './components/NotificationsScreen.svelte';

	// ── State ──────────────────────────────────────────────────
	let items = $state<QueueItem[]>([]);
	let loading = $state(true);
	let loadError = $state<string | null>(null);
	let filter = $state<'all' | 'in_progress' | 'success' | 'error'>('all');
	let eventSource = $state<EventSource | null>(null);
	let connectionStatus = $state<'connecting' | 'connected' | 'disconnected'>('disconnected');
	let lastPing = $state<string | null>(null);
	let hasAttemptedAutoSubscribe = $state(false);

	// Screen router
	let screen = $state<'home' | 'addurl' | 'notifications'>('home');
	// Recipe detail sheet
	let selectedItem = $state<QueueItem | null>(null);
	// "How it works" dismissible card
	let showHowTo = $state(true);

	// Get highlighted item ID from URL params (when redirected from Share page)
	const highlightId = $derived($page.url.searchParams.get('highlight'));

	// ── Derived ────────────────────────────────────────────────
	const filteredItems = $derived.by(() => {
		if (filter === 'all') return items;
		if (filter === 'error') return items.filter((i) => i.status === 'error' || i.status === 'unhealthy');
		return items.filter((i) => i.status === filter);
	});

	const sseLastPing = $derived(
		lastPing ? relTime(lastPing) + ' ago' : ''
	);

	function relTime(iso: string): string {
		const diff = (Date.now() - new Date(iso).getTime()) / 1000;
		if (diff < 60) return Math.round(diff) + 's';
		if (diff < 3600) return Math.floor(diff / 60) + 'm';
		return Math.floor(diff / 3600) + 'h';
	}

	// ── groupByDate ────────────────────────────────────────────
	type Group = 'Cooking now' | 'In line' | 'Today' | 'Yesterday' | 'Earlier';
	function groupByDate(list: QueueItem[]): Record<Group, QueueItem[]> {
		const g: Record<Group, QueueItem[]> = {
			'Cooking now': [],
			'In line': [],
			Today: [],
			Yesterday: [],
			Earlier: []
		};
		const now = Date.now();
		for (const it of list) {
			if (it.status === 'in_progress') { g['Cooking now'].push(it); continue; }
			if (it.status === 'pending') { g['In line'].push(it); continue; }
			const age = (now - new Date(it.createdAt).getTime()) / 1000;
			if (age < 86400) g['Today'].push(it);
			else if (age < 172800) g['Yesterday'].push(it);
			else g['Earlier'].push(it);
		}
		return g;
	}

	const groups = $derived(groupByDate(filteredItems));
	const cooking = $derived(groups['Cooking now'][0] ?? null);

	// ── Lifecycle ──────────────────────────────────────────────
	let unsubscribeNotifications: (() => void) | undefined;

	onMount(async () => {
		await loadQueueItems();
		if (browser) {
			startSSEConnection();
			setupAutoSubscribe();
			unsubscribeNotifications = pushNotificationManager.onStateChange(() => {});

			// Open RecipeSheet for highlighted item
			if (highlightId) {
				const found = items.find((i) => i.id === highlightId);
				if (found) { selectedItem = found; clearHighlight(); }
			}
		}
	});

	onDestroy(() => {
		eventSource?.close();
		connectionStatus = 'disconnected';
		unsubscribeNotifications?.();
	});

	// ── Data fetching ──────────────────────────────────────────
	async function loadQueueItems() {
		try {
			loading = true;
			loadError = null;
			const response = await fetch('/api/queue');
			if (!response.ok) throw new Error('Failed to load queue items');
			const data = await response.json();
			items = data.items || [];
		} catch (e) {
			loadError = e instanceof Error ? e.message : 'Unknown error';
			console.error('Failed to load queue items:', e);
		} finally {
			loading = false;
		}
	}

	async function submitUrl(url: string) {
		try {
			const response = await fetch('/api/queue', {
				method: 'POST',
				body: JSON.stringify({ url }),
				headers: { 'Content-Type': 'application/json' }
			});
			if (!response.ok) {
				const err = await response.json();
				throw new Error(err.message || 'Failed to enqueue URL');
			}
			const { item: queueItem } = await response.json();
			// Item will arrive via SSE, but add immediately for UX
			items = [queueItem, ...items];
			screen = 'home';
			// Show the new item in RecipeSheet
			selectedItem = queueItem;
		} catch (e) {
			console.error('Failed to submit URL:', e);
		}
	}

	// ── SSE ────────────────────────────────────────────────────
	function startSSEConnection() {
		if (!browser) return;
		connectionStatus = 'connecting';
		try {
			eventSource = new EventSource('/api/queue/stream');
			eventSource.addEventListener('open', () => { connectionStatus = 'connected'; });
			eventSource.addEventListener('connection', () => { connectionStatus = 'connected'; });
			eventSource.addEventListener('queue-update', (event) => {
				updateQueueItem(JSON.parse(event.data) as QueueStatusUpdate);
			});
			eventSource.addEventListener('error', () => {
				connectionStatus = 'disconnected';
				setTimeout(() => {
					if (eventSource?.readyState === 2) startSSEConnection();
				}, 5000);
			});
			eventSource.addEventListener('ping', (event) => {
				lastPing = JSON.parse(event.data).timestamp;
			});
		} catch (e) {
			console.error('[SSE] Failed to start:', e);
			connectionStatus = 'disconnected';
		}
	}

	function setupAutoSubscribe() {
		if (hasAttemptedAutoSubscribe) return;
		const attempt = async () => {
			if (hasAttemptedAutoSubscribe) return;
			hasAttemptedAutoSubscribe = true;
			const state = pushNotificationManager.getState();
			if (state.supported && state.permission !== 'denied' && !state.subscribed) {
				await pushNotificationManager.subscribe();
			}
		};
		document.addEventListener('click', attempt, { once: true });
		document.addEventListener('touchstart', attempt, { once: true });
	}

	function updateQueueItem(update: QueueStatusUpdate) {
		const idx = items.findIndex((i) => i.id === update.itemId);
		if (idx >= 0) {
			items[idx] = {
				...items[idx],
				status: update.status,
				phases: update.progress || items[idx].phases,
				results: update.results || items[idx].results,
				error: update.error || items[idx].error,
				updatedAt: update.timestamp
			};
			// Keep selectedItem in sync
			if (selectedItem?.id === update.itemId) selectedItem = items[idx];
		} else {
			fetchQueueItem(update.itemId);
		}
		items = [...items];
	}

	async function fetchQueueItem(id: string) {
		try {
			const response = await fetch(`/api/queue/${id}`);
			if (response.ok) items = [await response.json(), ...items];
		} catch (e) {
			console.error('Failed to fetch queue item:', e);
		}
	}

	// ── Actions ────────────────────────────────────────────────
	async function retryItem(id: string) {
		try {
			const response = await fetch(`/api/queue/${id}/retry`, { method: 'POST' });
			if (!response.ok) {
				const err = await response.json();
				throw new Error(err.message || 'Failed to retry');
			}
		} catch (e) {
			console.error('Failed to retry item:', e);
		}
	}

	async function removeItem(id: string) {
		try {
			await fetch(`/api/queue/${id}`, { method: 'DELETE' });
		} catch (e) {
			console.error('Failed to remove item:', e);
		} finally {
			items = items.filter((i) => i.id !== id);
			if (selectedItem?.id === id) selectedItem = null;
		}
	}

	function clearHighlight() {
		const url = new URL(window.location.href);
		url.searchParams.delete('highlight');
		replaceState(url, {});
	}

	// Queue positions for pending items
	function queuePos(item: QueueItem): number {
		return items.filter((i) => i.status === 'pending').indexOf(item) + 1;
	}
</script>

<svelte:head>
	<title>InstaChef</title>
	<meta name="description" content="Cook anything from an Instagram link." />
</svelte:head>

<div class="app-root">
	<!-- ── Home screen ────────────────────────────────────────── -->
	{#if screen === 'home'}
		<div class="ic-scroll home-scroll">
			<TopBar
				count={items.length}
				notifCount={0}
				onNotifications={() => (screen = 'notifications')}
			/>

			{#if loading}
				<div class="loading-wrap">
					<div class="spinner"></div>
				</div>
			{:else if loadError}
				<div class="err-banner">Failed to load queue: {loadError}</div>
			{:else if items.length === 0}
				<EmptyState
					onAdd={() => (screen = 'addurl')}
					{showHowTo}
					onDismissHowTo={() => (showHowTo = false)}
				/>
			{:else}
				<!-- Cooking hero -->
				{#if cooking && filter !== 'success' && filter !== 'error'}
					<CookingHero item={cooking} onTap={() => (selectedItem = cooking)} />
				{/if}

				<!-- Filter chips -->
				<div class="ic-scroll filter-row">
					{#each [
						{ id: 'all', label: 'All', count: items.length },
						{ id: 'in_progress', label: 'Cooking', count: items.filter((i) => i.status === 'in_progress').length },
						{ id: 'success', label: 'Saved', count: items.filter((i) => i.status === 'success').length },
						{ id: 'error', label: 'Failed', count: items.filter((i) => i.status === 'error' || i.status === 'unhealthy').length }
					] as f}
						<button
							class="ic-btn-reset filter-chip"
							class:active={filter === f.id}
							onclick={() => (filter = f.id as typeof filter)}
						>
							{f.label}
							<span class="chip-count">{f.count}</span>
						</button>
					{/each}
				</div>

				<!-- Timeline groups -->
				<div class="timeline">
					{#each (['In line', 'Today', 'Yesterday', 'Earlier'] as const) as g}
						{#if groups[g]?.length}
							<SectionHead>{g}</SectionHead>
							{#each groups[g] as it (it.id)}
								<TimelineRow
									item={it}
									queuePosition={queuePos(it)}
									onTap={() => (selectedItem = it)}
									onRetry={retryItem}
								/>
							{/each}
						{/if}
					{/each}
				</div>
			{/if}

			<!-- Sticky add button -->
			<div class="add-fab-wrap">
				<button class="ic-btn-reset add-fab" onclick={() => (screen = 'addurl')}>
					<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round">
						<path d="M9 4v9a4 4 0 004 4h8M9 8h4M5 20h8" />
					</svg>
					Paste Instagram link
				</button>
			</div>
		</div>

	<!-- ── Add URL screen ────────────────────────────────────── -->
	{:else if screen === 'addurl'}
		<div class="ic-scroll screen-scroll">
			<AddUrlScreen
				onBack={() => (screen = 'home')}
				onSubmit={submitUrl}
			/>
		</div>

	<!-- ── Notifications screen ──────────────────────────────── -->
	{:else if screen === 'notifications'}
		<div class="ic-scroll screen-scroll">
			<NotificationsScreen
				onBack={() => (screen = 'home')}
				sseConnected={connectionStatus === 'connected'}
				{sseLastPing}
			/>
		</div>
	{/if}

	<!-- ── Recipe sheet overlay ─────────────────────────────── -->
	{#if selectedItem}
		<RecipeSheet
			item={selectedItem}
			onClose={() => (selectedItem = null)}
			onRetry={(id) => { retryItem(id); selectedItem = null; }}
			onDelete={(id) => { removeItem(id); selectedItem = null; }}
		/>
	{/if}
</div>

<style>
	.app-root {
		position: relative;
		width: 100%;
		min-height: 100vh;
		min-height: 100dvh;
	}
	.home-scroll,
	.screen-scroll {
		height: 100vh;
		height: 100dvh;
		overflow-y: auto;
		-webkit-overflow-scrolling: touch;
	}
	.loading-wrap {
		display: flex;
		justify-content: center;
		padding: 60px 0;
	}
	.spinner {
		width: 32px;
		height: 32px;
		border-radius: 50%;
		border: 3px solid var(--border);
		border-top-color: var(--pink);
		animation: spin 0.75s linear infinite;
	}
	@keyframes spin {
		to { transform: rotate(360deg); }
	}
	.err-banner {
		margin: 20px 16px;
		padding: 14px 16px;
		border-radius: 16px;
		background: #ffe9e9;
		border: 1px solid #f8c2c2;
		color: #7e1717;
		font-size: 14px;
	}
	.filter-row {
		display: flex;
		gap: 8px;
		padding: 20px 20px 6px;
		overflow-x: auto;
	}
	.filter-chip {
		padding: 8px 14px;
		border-radius: 99px;
		background: var(--surface-2);
		color: var(--ink-2);
		font-size: 13px;
		font-weight: 600;
		white-space: nowrap;
		display: inline-flex;
		align-items: center;
		gap: 6px;
		border: 1px solid var(--border);
		transition: background 0.15s, color 0.15s;
	}
	.filter-chip.active {
		background: var(--ink);
		color: var(--bg);
		border-color: var(--ink);
	}
	.chip-count {
		font-size: 11px;
		font-family: var(--font-mono);
		opacity: 0.6;
	}
	.filter-chip.active .chip-count {
		opacity: 0.7;
	}
	.timeline {
		padding-bottom: 100px;
	}
	.add-fab-wrap {
		position: sticky;
		bottom: 0;
		left: 0;
		right: 0;
		padding: 12px 16px 34px;
		background: linear-gradient(to top, var(--bg) 55%, color-mix(in srgb, var(--bg) 80%, transparent));
		pointer-events: none;
		z-index: 40;
	}
	.add-fab {
		width: 100%;
		pointer-events: auto;
		background: var(--brand-gradient);
		color: #fff;
		padding: 16px 20px;
		border-radius: 99px;
		font-size: 15px;
		font-weight: 700;
		display: flex;
		align-items: center;
		justify-content: center;
		gap: 10px;
		box-shadow: var(--shadow-lg);
	}
</style>
