import { SvelteMap } from 'svelte/reactivity';
import type { PersonMention } from '$lib/shared/types';
import { csrfFetch } from '$lib/shared/cookies';

export type SaveState = 'idle' | 'saving' | 'saved' | 'fading' | 'error';

type Options = {
	saveFn: (blockId: string, text: string, mentionedPersons: PersonMention[]) => Promise<void>;
	documentId: string;
};

export function createBlockAutoSave({ saveFn, documentId }: Options) {
	const saveStates = new SvelteMap<string, SaveState>();
	const debounceTimers = new SvelteMap<string, ReturnType<typeof setTimeout>>();
	const pendingTexts = new SvelteMap<string, string>();
	const pendingMentions = new SvelteMap<string, PersonMention[]>();
	const fadeTimers: ReturnType<typeof setTimeout>[] = [];

	function getSaveState(blockId: string): SaveState {
		return saveStates.get(blockId) ?? 'idle';
	}

	function setSaveState(blockId: string, state: SaveState) {
		saveStates.set(blockId, state);
	}

	async function executeSave(blockId: string): Promise<void> {
		const text = pendingTexts.get(blockId);
		if (text === undefined) return;

		const mentions = pendingMentions.get(blockId) ?? [];
		pendingTexts.delete(blockId);
		pendingMentions.delete(blockId);
		setSaveState(blockId, 'saving');

		try {
			await saveFn(blockId, text, mentions);
			setSaveState(blockId, 'saved');
			scheduleSavedFade(blockId);
		} catch {
			// Preserve in-flight payload so the user can retry without re-typing.
			pendingTexts.set(blockId, text);
			pendingMentions.set(blockId, mentions);
			setSaveState(blockId, 'error');
		}
	}

	function scheduleSavedFade(blockId: string): void {
		const t1 = setTimeout(() => {
			if (getSaveState(blockId) === 'saved') {
				setSaveState(blockId, 'fading');
				const t2 = setTimeout(() => {
					if (getSaveState(blockId) === 'fading') {
						setSaveState(blockId, 'idle');
					}
				}, 300);
				fadeTimers.push(t2);
			}
		}, 2000);
		fadeTimers.push(t1);
	}

	function scheduleDebounce(blockId: string): void {
		clearDebounce(blockId);
		const timer = setTimeout(() => {
			debounceTimers.delete(blockId);
			executeSave(blockId);
		}, 1500);
		debounceTimers.set(blockId, timer);
	}

	function clearDebounce(blockId: string): void {
		const existing = debounceTimers.get(blockId);
		if (existing !== undefined) {
			clearTimeout(existing);
			debounceTimers.delete(blockId);
		}
	}

	function handleTextChange(
		blockId: string,
		text: string,
		mentionedPersons: PersonMention[]
	): void {
		pendingTexts.set(blockId, text);
		pendingMentions.set(blockId, mentionedPersons);
		scheduleDebounce(blockId);
	}

	function handleBlur(): void {
		for (const [blockId] of [...debounceTimers]) {
			clearDebounce(blockId);
			executeSave(blockId);
		}
	}

	async function handleRetry(
		blockId: string,
		currentText: string,
		currentMentions: PersonMention[]
	): Promise<void> {
		const text = pendingTexts.get(blockId) ?? currentText;
		const mentions = pendingMentions.get(blockId) ?? currentMentions;
		pendingTexts.set(blockId, text);
		pendingMentions.set(blockId, mentions);
		await executeSave(blockId);
	}

	function clearBlock(blockId: string): void {
		clearDebounce(blockId);
		pendingTexts.delete(blockId);
		pendingMentions.delete(blockId);
		saveStates.delete(blockId);
	}

	function flushOnUnload(): void {
		for (const [blockId, text] of pendingTexts) {
			const mentions = pendingMentions.get(blockId) ?? [];
			clearDebounce(blockId);
			void csrfFetch(`/api/documents/${documentId}/transcription-blocks/${blockId}`, {
				method: 'PUT',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ text, mentionedPersons: mentions }),
				keepalive: true
			});
			pendingTexts.delete(blockId);
			pendingMentions.delete(blockId);
		}
	}

	function destroy(): void {
		for (const timer of debounceTimers.values()) {
			clearTimeout(timer);
		}
		debounceTimers.clear();
		for (const timer of fadeTimers) {
			clearTimeout(timer);
		}
		fadeTimers.length = 0;
	}

	return {
		getSaveState,
		handleTextChange,
		handleBlur,
		handleRetry,
		clearBlock,
		flushOnUnload,
		destroy
	};
}
