import type { JSONContent } from '@tiptap/core';
import type { PersonMention } from '$lib/shared/types';

/**
 * Converts stored block text + sidecar into a Tiptap ProseMirror document.
 *
 * The text is split by "\n" into paragraphs. Within each paragraph, sidecar
 * entries are matched against "@displayName" tokens (longest first) and
 * converted to mention nodes. Unmatched text becomes plain text nodes.
 *
 * Round-trip invariant: serialize(deserialize(text, sidecar)).text === text
 */
export function deserialize(text: string, sidecar: PersonMention[]): JSONContent {
	const lines = text === '' ? [''] : text.split('\n');

	// Sort sidecar by displayName length descending so longer names shadow
	// shorter prefix matches (same heuristic as renderTranscriptionBody).
	const sorted = [...sidecar].sort((a, b) => b.displayName.length - a.displayName.length);

	return {
		type: 'doc',
		content: lines.map((line) => ({
			type: 'paragraph',
			content: parseLine(line, sorted)
		}))
	};
}

function parseLine(text: string, sidecar: PersonMention[]): JSONContent[] {
	if (text === '') return [];

	if (sidecar.length === 0) {
		return [{ type: 'text', text }];
	}

	// Build a list of mention ranges: { start, end, mention }
	const ranges: Array<{ start: number; end: number; mention: PersonMention }> = [];

	for (const mention of sidecar) {
		const needle = `@${mention.displayName}`;
		let idx = 0;
		while (idx < text.length) {
			const pos = text.indexOf(needle, idx);
			if (pos === -1) break;
			// Check that the range doesn't overlap an already-found range
			const end = pos + needle.length;
			const overlaps = ranges.some((r) => r.start < end && r.end > pos);
			if (!overlaps) {
				ranges.push({ start: pos, end, mention });
			}
			idx = pos + 1;
		}
	}

	if (ranges.length === 0) {
		return [{ type: 'text', text }];
	}

	// Sort by position
	ranges.sort((a, b) => a.start - b.start);

	const nodes: JSONContent[] = [];
	let cursor = 0;

	for (const { start, end, mention } of ranges) {
		if (start > cursor) {
			nodes.push({ type: 'text', text: text.slice(cursor, start) });
		}
		nodes.push({
			type: 'mention',
			attrs: { displayName: mention.displayName, personId: mention.personId }
		});
		cursor = end;
	}

	if (cursor < text.length) {
		nodes.push({ type: 'text', text: text.slice(cursor) });
	}

	return nodes;
}

/**
 * Converts a Tiptap ProseMirror document back to stored block text + sidecar.
 *
 * Paragraphs are joined with "\n". Mention nodes are emitted as "@displayName"
 * and collected into mentionedPersons (de-duplicated by personId).
 */
export function serialize(doc: JSONContent): { text: string; mentionedPersons: PersonMention[] } {
	const paragraphs = doc.content ?? [];
	const mentionedPersons: PersonMention[] = [];
	const seenIds = new Set<string>();
	const lines: string[] = [];

	for (const para of paragraphs) {
		let line = '';
		for (const node of para.content ?? []) {
			if (node.type === 'text') {
				line += node.text ?? '';
			} else if (node.type === 'mention') {
				const { displayName, personId } = node.attrs ?? {};
				line += `@${displayName}`;
				if (!seenIds.has(personId)) {
					seenIds.add(personId);
					mentionedPersons.push({ personId, displayName });
				}
			}
		}
		lines.push(line);
	}

	return { text: lines.join('\n'), mentionedPersons };
}
