<script lang="ts">
import { m } from '$lib/paraglide/messages.js';
import { SvelteMap } from 'svelte/reactivity';
import TranscriptionBlock from './TranscriptionBlock.svelte';
import type { TranscriptionBlockData } from '$lib/types';

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

type Props = {
	documentId: string;
	blocks: TranscriptionBlockData[];
	onBlockFocus: (blockId: string) => void;
	onSaveBlock: (blockId: string, text: string) => Promise<void>;
	onDeleteBlock: (blockId: string) => Promise<void>;
};

let { documentId, blocks, onBlockFocus, onSaveBlock, onDeleteBlock }: Props = $props();

let activeBlockId: string | null = $state(null);
let saveStates = new SvelteMap<string, SaveState>();
let debounceTimers = new SvelteMap<string, ReturnType<typeof setTimeout>>();
let pendingTexts = new SvelteMap<string, string>();

let sortedBlocks = $derived([...blocks].sort((a, b) => a.sortOrder - b.sortOrder));
let hasBlocks = $derived(blocks.length > 0);

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) {
	const text = pendingTexts.get(blockId);
	if (text === undefined) return;

	pendingTexts.delete(blockId);
	setSaveState(blockId, 'saving');

	try {
		await onSaveBlock(blockId, text);
		setSaveState(blockId, 'saved');
		scheduleSavedFade(blockId);
	} catch {
		setSaveState(blockId, 'error');
	}
}

function scheduleSavedFade(blockId: string) {
	setTimeout(() => {
		if (getSaveState(blockId) === 'saved') {
			setSaveState(blockId, 'fading');
			setTimeout(() => {
				if (getSaveState(blockId) === 'fading') {
					setSaveState(blockId, 'idle');
				}
			}, 300);
		}
	}, 2000);
}

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

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

function flushAllPending() {
	for (const [blockId] of debounceTimers) {
		clearDebounce(blockId);
		executeSave(blockId);
	}
}

function handleTextChange(blockId: string, text: string) {
	pendingTexts.set(blockId, text);
	scheduleDebounce(blockId);
}

function handleFocus(blockId: string) {
	activeBlockId = blockId;
	onBlockFocus(blockId);
}

function handleBlur() {
	flushAllPending();
}

async function handleRetry(blockId: string) {
	const block = blocks.find((b) => b.id === blockId);
	if (!block) return;

	const pending = pendingTexts.get(blockId);
	const text = pending ?? block.text;
	pendingTexts.set(blockId, text);
	await executeSave(blockId);
}

function handleDelete(blockId: string) {
	clearDebounce(blockId);
	pendingTexts.delete(blockId);
	saveStates.delete(blockId);
	onDeleteBlock(blockId);
}

function flushViaBeacon() {
	for (const [blockId, text] of pendingTexts) {
		clearDebounce(blockId);
		const url = `/api/documents/${documentId}/transcription-blocks/${blockId}`;
		const body = JSON.stringify({ text });
		navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }));
		pendingTexts.delete(blockId);
	}
}

$effect(() => {
	function onBeforeUnload() {
		flushViaBeacon();
	}

	window.addEventListener('beforeunload', onBeforeUnload);

	return () => {
		window.removeEventListener('beforeunload', onBeforeUnload);
		for (const timer of debounceTimers.values()) {
			clearTimeout(timer);
		}
	};
});
</script>

<div class="flex h-full flex-col overflow-y-auto bg-surface p-4">
	{#if hasBlocks}
		<div class="flex flex-col gap-3">
			{#each sortedBlocks as block, i (block.id)}
				<div onblur={handleBlur}>
					<TranscriptionBlock
						blockId={block.id}
						blockNumber={i + 1}
						text={block.text}
						label={block.label}
						active={activeBlockId === block.id}
						saveState={getSaveState(block.id)}
						onTextChange={(text) => handleTextChange(block.id, text)}
						onFocus={() => handleFocus(block.id)}
						onDeleteClick={() => handleDelete(block.id)}
						onRetry={() => handleRetry(block.id)}
					/>
				</div>
			{/each}
		</div>
	{:else}
		<div class="flex flex-1 flex-col items-center justify-center px-6 py-12 text-center">
			<svg
				class="mb-4 h-16 w-16 text-ink-3"
				fill="none"
				viewBox="0 0 24 24"
				stroke="currentColor"
				stroke-width="1"
			>
				<path
					stroke-linecap="round"
					stroke-linejoin="round"
					d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
				/>
			</svg>
			<p class="max-w-xs text-sm leading-relaxed text-ink-3">
				{m.transcription_empty_cta()}
			</p>
		</div>
	{/if}
</div>
