import { createContext, createSignal, onCleanup, useContext, type Component, type JSX } from 'solid-js';
import { SPACEDUST_SERVICE } from './api';

export interface LiveEvent {
	receivedAt: string;
	source: string;
	sourceRecord: string;
	subject: string;
}

interface LiveEventsContextValue {
	events: () => LiveEvent[];
	unread: () => number;
	subscribe: (config: { subjects: string[]; sources: string[] } | null) => void;
	clear: () => void;
	markRead: () => void;
}

const LiveEventsContext = createContext<LiveEventsContextValue>();

export const LiveEventsProvider: Component<{ children: JSX.Element }> = (props) => {
	const [events, setEvents] = createSignal<LiveEvent[]>([]);
	const [unread, setUnread] = createSignal(0);
	let socket: WebSocket | null = null;
	let currentKey = '';

	const clear = () => {
		currentKey = '';
		socket?.close();
		socket = null;
		setEvents([]);
		setUnread(0);
	};

	const subscribe = (config: { subjects: string[]; sources: string[] } | null) => {
		if (!config || config.subjects.length === 0 || config.sources.length === 0) {
			clear();
			return;
		}

		const key = JSON.stringify(config);
		if (key === currentKey) {
			return;
		}

		currentKey = key;
		socket?.close();
		setEvents([]);
		setUnread(0);

		const url = new URL(SPACEDUST_SERVICE);
		for (const subject of config.subjects) {
			url.searchParams.append('wantedSubjects', subject);
		}
		for (const source of config.sources) {
			url.searchParams.append('wantedSources', source);
		}
		url.searchParams.set('instant', 'true');

		socket = new WebSocket(url);
		socket.onmessage = (event) => {
			try {
				const data = JSON.parse(event.data) as {
					kind?: string;
					link?: {
						source: string;
						source_record: string;
						subject: string;
					};
				};

					if (data.kind !== 'link' || !data.link) {
						return;
					}

					const link = data.link;

					setEvents((prev) =>
						[
							{
								receivedAt: new Date().toISOString(),
								source: link.source,
								sourceRecord: link.source_record,
								subject: link.subject,
							},
							...prev,
						].slice(0, 20),
					);
				setUnread((count) => count + 1);
			} catch {
				// ignore malformed messages
			}
		};
	};

	const markRead = () => {
		setUnread(0);
	};

	onCleanup(() => {
		socket?.close();
	});

	return (
		<LiveEventsContext.Provider value={{ events, unread, subscribe, clear, markRead }}>
			{props.children}
		</LiveEventsContext.Provider>
	);
};

export const useLiveEvents = (): LiveEventsContextValue => {
	const context = useContext(LiveEventsContext);
	if (!context) {
		throw new Error('useLiveEvents must be used within its provider');
	}

	return context;
};
