import { getContext, setContext } from 'svelte';
import { type NotificationItem, parseNotificationEvent } from '$lib/notification/notifications';

export type { NotificationItem };

export const NOTIFICATION_KEY = Symbol('notification');

export interface NotificationStore {
	readonly notifications: NotificationItem[];
	readonly unreadCount: number;
	fetchNotifications(): Promise<void>;
	fetchUnreadCount(): Promise<void>;
	optimisticMarkRead(id: string): void;
	optimisticMarkAllRead(): void;
	init(): void;
	destroy(): void;
	/** Test-only: seed the notification list and derive the unread count. */
	setNotifications(items: NotificationItem[]): void;
	/** Test-only: override the 401 → redirect side-effect. */
	setNavigate(fn: (url: string) => void): void;
}

export function createNotificationStore(): NotificationStore {
	let notifications = $state<NotificationItem[]>([]);
	let unreadCount = $state(0);
	let eventSource: EventSource | null = null;
	let refCount = 0;
	let errorCount = 0;
	let navigate: (url: string) => void = (url) => {
		window.location.href = url;
	};

	async function fetchNotifications(): Promise<void> {
		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(): Promise<void> {
		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);
		}
	}

	function optimisticMarkRead(id: string): void {
		const notification = notifications.find((n) => n.id === id);
		if (notification && !notification.read) {
			notification.read = true;
			unreadCount = Math.max(0, unreadCount - 1);
		}
	}

	function optimisticMarkAllRead(): void {
		for (const n of notifications) {
			n.read = true;
		}
		unreadCount = 0;
	}

	function init(): void {
		refCount += 1;
		if (refCount > 1) return;

		fetchUnreadCount();
		eventSource = new EventSource('/api/notifications/stream');
		eventSource.addEventListener('notification', (e) => {
			const notification = parseNotificationEvent((e as MessageEvent).data);
			if (!notification) return;
			notifications = [notification, ...notifications];
			if (!notification.read) unreadCount += 1;
		});
		eventSource.onopen = () => {
			fetchUnreadCount();
			errorCount = 0;
		};
		eventSource.onerror = async () => {
			if (eventSource?.readyState === EventSource.CLOSED) {
				const res = await fetch('/api/notifications/unread-count');
				if (res.status === 401) navigate('/login');
				return;
			}
			errorCount += 1;
			if (errorCount >= 3) {
				eventSource?.close();
				eventSource = null;
				errorCount = 0;
				const res = await fetch('/api/notifications/unread-count');
				if (res.status === 401) navigate('/login');
			}
		};
	}

	function destroy(): void {
		if (refCount === 0) return;
		refCount -= 1;
		if (refCount === 0) {
			eventSource?.close();
			eventSource = null;
		}
	}

	return {
		get notifications() {
			return notifications;
		},
		get unreadCount() {
			return unreadCount;
		},
		fetchNotifications,
		fetchUnreadCount,
		optimisticMarkRead,
		optimisticMarkAllRead,
		init,
		destroy,
		setNotifications(items: NotificationItem[]): void {
			notifications = items;
			unreadCount = items.filter((n) => !n.read).length;
		},
		setNavigate(fn: (url: string) => void): void {
			navigate = fn;
		}
	};
}

/**
 * Create a notification store and put it on the context. Call once high in the
 * tree (root +layout.svelte). Descendants read it via getNotificationStore().
 */
export function provideNotificationStore(): NotificationStore {
	const store = createNotificationStore();
	setContext(NOTIFICATION_KEY, store);
	return store;
}

export function getNotificationStore(): NotificationStore {
	let store: NotificationStore | undefined;
	try {
		store = getContext<NotificationStore>(NOTIFICATION_KEY);
	} catch {
		throw new Error(
			'NotificationStore not found — call provideNotificationStore() in +layout.svelte'
		);
	}
	if (!store)
		throw new Error(
			'NotificationStore not found — call provideNotificationStore() in +layout.svelte'
		);
	return store;
}
