<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { goto } from '$app/navigation';
import { m } from '$lib/paraglide/messages.js';
import {
	type NotificationItem,
	relativeTime,
	parseNotificationEvent
} from '$lib/utils/notifications';

let notifications: NotificationItem[] = $state([]);
let unreadCount: number = $state(0);
let open = $state(false);

// DOM refs managed via attachments
let bellButtonEl: HTMLButtonElement | null = null;
let firstFocusableEl: HTMLButtonElement | null = null;

let eventSource: EventSource | null = null;

async function fetchNotifications() {
	try {
		const res = await fetch('/api/notifications?size=10');
		if (res.ok) {
			const data = await res.json();
			notifications = data.content ?? [];
		}
	} catch (e) {
		console.error('Failed to fetch notifications', e);
	}
}

async function fetchUnreadCount() {
	try {
		const res = await fetch('/api/notifications/unread-count');
		if (res.ok) {
			const data = await res.json();
			unreadCount = data.count;
		}
	} catch (e) {
		console.error('Failed to fetch unread count', e);
	}
}

async function toggleDropdown() {
	open = !open;
	if (open) {
		await fetchNotifications();
		// defer focus until DOM updates
		setTimeout(() => {
			firstFocusableEl?.focus();
		}, 0);
	}
}

function closeDropdown() {
	open = false;
	bellButtonEl?.focus();
}

async function markRead(notification: NotificationItem) {
	if (!notification.read) {
		try {
			await fetch(`/api/notifications/${notification.id}/read`, { method: 'PATCH' });
			notification.read = true;
			unreadCount = Math.max(0, unreadCount - 1);
		} catch (e) {
			console.error('Failed to mark notification as read', e);
		}
	}
	const url = notification.annotationId
		? `/documents/${notification.documentId}?commentId=${notification.referenceId}&annotationId=${notification.annotationId}`
		: `/documents/${notification.documentId}?commentId=${notification.referenceId}`;
	closeDropdown();
	goto(url);
}

async function markAllRead() {
	try {
		await fetch('/api/notifications/read-all', { method: 'POST' });
		for (const n of notifications) {
			n.read = true;
		}
		unreadCount = 0;
	} catch (e) {
		console.error('Failed to mark all notifications as read', e);
	}
}

function handleKeydown(event: KeyboardEvent) {
	if (event.key === 'Escape' && open) {
		event.stopPropagation();
		closeDropdown();
	}
}

// Attachment: stores the element reference for the bell button
function attachBellButton(node: HTMLButtonElement) {
	bellButtonEl = node;
	return () => {
		bellButtonEl = null;
	};
}

// Attachment: stores the element reference for the first focusable element in the dropdown
function attachFirstFocusable(node: HTMLButtonElement) {
	firstFocusableEl = node;
	return () => {
		firstFocusableEl = null;
	};
}

// Attachment: closes dropdown when clicking outside the wrapper element
function attachClickOutside(node: HTMLElement) {
	const handleClick = (event: MouseEvent) => {
		if (!node.contains(event.target as Node) && !event.defaultPrevented) {
			if (open) {
				open = false;
			}
		}
	};
	document.addEventListener('click', handleClick, true);
	return () => {
		document.removeEventListener('click', handleClick, true);
	};
}

onMount(() => {
	fetchUnreadCount();
	eventSource = new EventSource('/api/notifications/stream');
	eventSource.addEventListener('notification', (e) => {
		const notification = parseNotificationEvent(e.data);
		if (!notification) return;
		notifications = [notification, ...notifications];
		if (!notification.read) unreadCount += 1;
	});
});

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

<svelte:window onkeydown={handleKeydown} />

<div class="relative" {@attach attachClickOutside}>
	<!-- Bell button -->
	<button
		{@attach attachBellButton}
		type="button"
		onclick={toggleDropdown}
		aria-label={unreadCount > 0
			? m.notification_bell_unread_label({ count: unreadCount })
			: m.notification_bell_label()}
		aria-expanded={open}
		aria-haspopup="true"
		class="relative rounded-sm p-2 text-white/65 transition-colors hover:bg-white/10 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"
	>
		<!-- Bell SVG -->
		<svg
			xmlns="http://www.w3.org/2000/svg"
			class="h-5 w-5"
			fill="none"
			viewBox="0 0 24 24"
			stroke="currentColor"
			stroke-width="2"
			aria-hidden="true"
		>
			<path
				stroke-linecap="round"
				stroke-linejoin="round"
				d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"
			/>
		</svg>

		<!-- Unread badge -->
		{#if unreadCount > 0}
			<span
				aria-live="polite"
				aria-atomic="true"
				class="absolute -top-1 -right-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-bold text-primary-fg"
			>
				{unreadCount}
			</span>
		{/if}
	</button>

	<!-- Dropdown -->
	{#if open}
		<div
			role="dialog"
			aria-modal="true"
			aria-label={m.notification_bell_label()}
			class="absolute right-0 z-50 mt-2 w-80 overflow-hidden rounded-sm border border-line bg-surface shadow-lg"
		>
			<!-- Header -->
			<div class="flex items-center justify-between border-b border-line px-4 py-3">
				<span class="text-xs font-bold tracking-widest text-ink-2 uppercase">
					{m.notification_bell_label()}
				</span>
				{#if notifications.length > 0}
					<button
						{@attach attachFirstFocusable}
						type="button"
						onclick={markAllRead}
						class="text-xs font-medium text-ink-3 transition-colors hover:text-ink"
					>
						{m.notification_mark_all_read()}
					</button>
				{/if}
			</div>

			<!-- Notification list -->
			{#if notifications.length === 0}
				<!-- Empty state -->
				<div class="flex flex-col items-center gap-2 px-4 py-8 text-center text-sm text-ink-3">
					<svg
						xmlns="http://www.w3.org/2000/svg"
						class="h-8 w-8 text-ink-3 opacity-40"
						fill="none"
						viewBox="0 0 24 24"
						stroke="currentColor"
						stroke-width="1.5"
						aria-hidden="true"
					>
						<path
							stroke-linecap="round"
							stroke-linejoin="round"
							d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"
						/>
					</svg>
					<span>{m.notification_empty()}</span>
				</div>
			{:else}
				<ul role="list">
					{#each notifications as notification (notification.id)}
						<li>
							<button
								type="button"
								onclick={() => markRead(notification)}
								class="flex w-full cursor-pointer items-start gap-3 border-b border-line px-4 py-3 text-left last:border-b-0 hover:bg-canvas
									{!notification.read ? 'bg-accent-bg/20' : ''}"
							>
								<!-- Type icon -->
								<span class="mt-0.5 shrink-0 text-ink-3" aria-hidden="true">
									{#if notification.type === 'REPLY'}
										<!-- Reply icon -->
										<svg
											xmlns="http://www.w3.org/2000/svg"
											class="h-4 w-4"
											fill="none"
											viewBox="0 0 24 24"
											stroke="currentColor"
											stroke-width="2"
										>
											<path
												stroke-linecap="round"
												stroke-linejoin="round"
												d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"
											/>
										</svg>
									{:else}
										<!-- Mention icon -->
										<svg
											xmlns="http://www.w3.org/2000/svg"
											class="h-4 w-4"
											fill="none"
											viewBox="0 0 24 24"
											stroke="currentColor"
											stroke-width="2"
										>
											<path
												stroke-linecap="round"
												stroke-linejoin="round"
												d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"
											/>
										</svg>
									{/if}
								</span>

								<!-- Text + time -->
								<div class="min-w-0 flex-1">
									<p class="text-sm leading-snug text-ink">
										{notification.type === 'REPLY'
											? m.notification_type_reply({ actor: notification.actorName })
											: m.notification_type_mention({ actor: notification.actorName })}
									</p>
									<p class="mt-1 text-xs text-ink-3">{relativeTime(notification.createdAt)}</p>
								</div>

								<!-- Unread dot -->
								{#if !notification.read}
									<span
										class="mt-1.5 h-2 w-2 shrink-0 rounded-full bg-primary"
										aria-label={m.notification_unread()}
									></span>
								{/if}
							</button>
						</li>
					{/each}
				</ul>
			{/if}

			<div class="border-t border-line px-4 py-2">
				<a
					href="/notifications"
					onclick={closeDropdown}
					class="text-xs font-medium text-ink-2 transition-colors hover:text-ink"
				>
					{m.notification_view_all()}
				</a>
			</div>
		</div>
	{/if}
</div>
