<script lang="ts">
import { onMount, onDestroy, tick } from 'svelte';
import { m } from '$lib/paraglide/messages.js';
import { page } from '$app/state';
import { replaceState } from '$app/navigation';
import DocumentTopBar from '$lib/document/DocumentTopBar.svelte';
import DocumentViewer from '$lib/document/DocumentViewer.svelte';
import TranscriptionEditView from '$lib/document/transcription/TranscriptionEditView.svelte';
import TranscriptionReadView from '$lib/document/transcription/TranscriptionReadView.svelte';
import TranscriptionPanelHeader from '$lib/document/transcription/TranscriptionPanelHeader.svelte';
import type { TranscriptionBlockData } from '$lib/shared/types';
import { getErrorMessage } from '$lib/shared/errors';
import { translateOcrProgress } from '$lib/ocr/translateOcrProgress';
import { createFileLoader } from '$lib/document/viewer/useFileLoader.svelte';
import { scrollToCommentFromQuery } from '$lib/shared/utils/deepLinkScroll';
import { getConfirmService } from '$lib/shared/services/confirm.svelte.js';

let { data } = $props();

const { confirm } = getConfirmService();

const doc = $derived(data.document);
const canWrite = $derived(data.canWrite ?? false);
const currentUserId = $derived((data.user?.id as string | undefined) ?? null);

// ── File loading ──────────────────────────────────────────────────────────────

const fileLoader = createFileLoader();

$effect(() => {
	if (doc?.id && doc?.filePath) {
		fileLoader.loadFile(`/api/documents/${doc.id}/file`);
	}
});

onDestroy(() => fileLoader.destroy());

// ── Mode state ───────────────────────────────────────────────────────────────

let transcribeMode = $state(false);
let panelMode = $state<'read' | 'edit'>('read');
let activeAnnotationId = $state<string | null>(null);
let highlightBlockId = $state<string | null>(null);
let flashAnnotationId = $state<string | null>(null);
let pdfStripExpanded = $state(false);
// Flag set by the deep-link helper so the transcribe-mode $effect does not
// overwrite the panelMode it picked (e.g. forcing 'edit' on notification
// click-through). One-shot: consumed after the effect's loadBlocks resolves.
let skipInitialPanelMode = $state(false);

const prefersReducedMotion = $derived(
	typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches
);

// ── Transcription blocks ─────────────────────────────────────────────────────

let transcriptionBlocks = $state<TranscriptionBlockData[]>([]);
let annotationReloadKey = $state(0);

const blockNumbers = $derived(
	Object.fromEntries(
		[...transcriptionBlocks]
			.sort((a, b) => a.sortOrder - b.sortOrder)
			.map((b, i) => [b.annotationId, i + 1])
	)
);

const hasBlocks = $derived(transcriptionBlocks.length > 0);

const lastEditedAt = $derived.by(() => {
	if (transcriptionBlocks.length === 0) return null;
	const dates = transcriptionBlocks
		.filter((b) => b.updatedAt)
		.map((b) => new Date(b.updatedAt!).getTime());
	if (dates.length === 0) return null;
	return new Date(Math.max(...dates)).toISOString();
});

async function loadTranscriptionBlocks() {
	if (!doc?.id) return;
	try {
		const res = await fetch(`/api/documents/${doc.id}/transcription-blocks`);
		if (res.ok) {
			transcriptionBlocks = await res.json();
		}
	} catch (e) {
		console.error('Failed to load transcription blocks:', e);
	}
}

async function saveBlock(
	blockId: string,
	text: string,
	mentionedPersons: import('$lib/shared/types').PersonMention[]
) {
	const { saveBlockWithConflictRetry } =
		await import('$lib/document/transcription/saveBlockWithConflictRetry');
	const { BlockConflictResolvedError } =
		await import('$lib/document/transcription/blockConflictMerge');
	try {
		const updated = await saveBlockWithConflictRetry({
			fetchImpl: fetch,
			documentId: doc.id,
			blockId,
			text,
			mentionedPersons
		});
		transcriptionBlocks = transcriptionBlocks.map((b) => (b.id === blockId ? updated : b));
	} catch (err) {
		if (err instanceof BlockConflictResolvedError && err.merged) {
			transcriptionBlocks = transcriptionBlocks.map((b) => (b.id === blockId ? err.merged! : b));
		}
		throw err;
	}
}

async function deleteBlock(blockId: string) {
	const res = await fetch(`/api/documents/${doc.id}/transcription-blocks/${blockId}`, {
		method: 'DELETE'
	});
	if (!res.ok) throw new Error('Delete failed');
	transcriptionBlocks = transcriptionBlocks.filter((b) => b.id !== blockId);
	annotationReloadKey++;
}

async function handleAnnotationDeleteRequest(annotationId: string) {
	const confirmed = await confirm({
		title: m.transcription_block_delete_confirm(),
		destructive: true
	});
	if (!confirmed) return;

	const block = transcriptionBlocks.find((b) => b.annotationId === annotationId);
	if (block) {
		await deleteBlock(block.id);
	} else {
		// Annotation has no linked block — delete the annotation directly
		const res = await fetch(`/api/documents/${doc.id}/annotations/${annotationId}`, {
			method: 'DELETE'
		});
		if (!res.ok) throw new Error('Delete annotation failed');
		annotationReloadKey++;
	}
}

async function reviewToggle(blockId: string) {
	const res = await fetch(`/api/documents/${doc.id}/transcription-blocks/${blockId}/review`, {
		method: 'PUT'
	});
	if (!res.ok) return;
	const updated = await res.json();
	transcriptionBlocks = transcriptionBlocks.map((b) => (b.id === blockId ? updated : b));
}

async function markAllReviewed() {
	const res = await fetch(`/api/documents/${doc.id}/transcription-blocks/review-all`, {
		method: 'PUT'
	});
	if (!res.ok) return;
	const updated = await res.json();
	for (const b of updated) {
		const existing = transcriptionBlocks.find((x) => x.id === b.id);
		if (existing) existing.reviewed = b.reviewed;
	}
}

async function toggleTrainingLabel(label: string, enrolled: boolean) {
	const res = await fetch(`/api/documents/${doc.id}/training-labels`, {
		method: 'PATCH',
		headers: { 'Content-Type': 'application/json' },
		body: JSON.stringify({ label, enrolled })
	});
	if (!res.ok) throw new Error('Failed to update training label');
}

let ocrRunning = $state(false);
let ocrProgressMessage = $state('');
let ocrErrorMessage = $state('');
let ocrPollTimer = $state<ReturnType<typeof setInterval> | null>(null);
let ocrSkippedPages = $state(0);

async function triggerOcr(scriptType: string, useExistingAnnotations: boolean = false) {
	ocrRunning = true;
	ocrErrorMessage = '';
	try {
		const res = await fetch(`/api/documents/${doc.id}/ocr`, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({ scriptType, useExistingAnnotations })
		});
		if (res.ok) {
			const data = await res.json();
			pollOcrJob(data.jobId);
		} else {
			ocrRunning = false;
			const body = await res.json().catch(() => null);
			const code = (body as { code?: string } | null)?.code;
			ocrErrorMessage = code ? getErrorMessage(code) : m.ocr_status_error();
		}
	} catch (e) {
		console.error('Failed to trigger OCR:', e);
		ocrRunning = false;
		ocrErrorMessage = m.ocr_status_error();
	}
}

function pollOcrJob(jobId: string) {
	if (ocrPollTimer) clearInterval(ocrPollTimer);
	ocrPollTimer = setInterval(async () => {
		try {
			const res = await fetch(`/api/ocr/jobs/${jobId}`);
			if (!res.ok) return;
			const job = await res.json();
			const rawCode = job.progressMessage ?? '';
			const progress = translateOcrProgress(rawCode);
			ocrProgressMessage = progress.message;
			if (progress.skippedPages !== undefined) ocrSkippedPages = progress.skippedPages;
			if (job.status === 'DONE' || job.status === 'FAILED') {
				if (ocrPollTimer) clearInterval(ocrPollTimer);
				ocrPollTimer = null;
				setTimeout(() => {
					ocrRunning = false;
					ocrProgressMessage = '';
					ocrSkippedPages = 0;
				}, 1000);
				if (job.status === 'FAILED') {
					ocrErrorMessage = m.ocr_status_error();
				}
				await loadTranscriptionBlocks();
				annotationReloadKey++;
				panelMode = transcriptionBlocks.length > 0 ? 'read' : 'edit';
			}
		} catch {
			// polling is best-effort
		}
	}, 2000);
}

async function createBlockFromDraw(rect: {
	x: number;
	y: number;
	width: number;
	height: number;
	pageNumber: number;
}) {
	try {
		const res = await fetch(`/api/documents/${doc.id}/transcription-blocks`, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({
				pageNumber: rect.pageNumber,
				x: rect.x,
				y: rect.y,
				width: rect.width,
				height: rect.height,
				text: '',
				label: null
			})
		});
		if (res.ok) {
			const created = (await res.json()) as TranscriptionBlockData;
			transcriptionBlocks = [...transcriptionBlocks, created];
			activeAnnotationId = created.annotationId;
		}
	} catch (e) {
		console.error('Failed to create transcription block:', e);
	}
}

function handleBlockFocus(blockId: string) {
	const block = transcriptionBlocks.find((b) => b.id === blockId);
	if (block) {
		activeAnnotationId = block.annotationId;
	}
}

async function handleAnnotationClick(annotationId: string) {
	activeAnnotationId = annotationId;

	if (!transcribeMode) {
		transcribeMode = true;
		await loadTranscriptionBlocks();
	}

	// In read mode, highlight the matching paragraph
	const block = transcriptionBlocks.find((b) => b.annotationId === annotationId);
	if (block) {
		highlightBlockId = block.id;
		setTimeout(
			() => {
				highlightBlockId = null;
			},
			prefersReducedMotion ? 2000 : 1500
		);
	}

	// Wait for DOM to render, then scroll to the matching block
	const scrollBehavior = prefersReducedMotion ? 'instant' : 'smooth';
	requestAnimationFrame(() => {
		if (block) {
			const el = document.querySelector(`[data-block-id="${block.id}"]`);
			el?.scrollIntoView({ behavior: scrollBehavior, block: 'nearest' });
		}
	});
}

function handleParagraphClick(annotationId: string) {
	activeAnnotationId = annotationId;
	flashAnnotationId = annotationId;
	pdfStripExpanded = true;
	setTimeout(
		() => {
			flashAnnotationId = null;
		},
		prefersReducedMotion ? 2000 : 1500
	);
}

async function checkOcrStatus() {
	if (!doc?.id) return;
	try {
		const res = await fetch(`/api/documents/${doc.id}/ocr-status`);
		if (!res.ok) return;
		const status = await res.json();
		if ((status.status === 'PENDING' || status.status === 'RUNNING') && status.jobId) {
			ocrRunning = true;
			pollOcrJob(status.jobId);
		}
	} catch {
		// best-effort
	}
}

// Load blocks and check OCR status when transcribe mode is entered
$effect(() => {
	if (transcribeMode) {
		loadTranscriptionBlocks().then(() => {
			if (skipInitialPanelMode) {
				skipInitialPanelMode = false;
			} else {
				panelMode = transcriptionBlocks.length > 0 ? 'read' : 'edit';
			}
		});
		checkOcrStatus();
	}
});

// ── Navigation / init ─────────────────────────────────────────────────────────

let navHeight = $state(0);

async function waitForPanelRender(): Promise<void> {
	await tick();
	await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
}

onMount(() => {
	navHeight = document.querySelector('header')?.getBoundingClientRect().height ?? 0;

	if (doc?.id) {
		localStorage.setItem(
			'familienarchiv.lastVisited',
			JSON.stringify({ id: doc.id, title: doc.title ?? '' })
		);
	}

	if (page.url.searchParams.get('task') === 'transcribe') {
		transcribeMode = true;
		tick()
			.then(() => {
				const closeBtn = document.querySelector<HTMLElement>('[data-testid="panel-close"]');
				closeBtn?.scrollIntoView({
					behavior: prefersReducedMotion ? 'instant' : 'smooth',
					block: 'nearest'
				});
				closeBtn?.focus({ preventScroll: true });
				replaceState(page.url.pathname, page.state ?? {});
			})
			.catch((e) => console.error('task deep-link failed', e));
	}

	scrollToCommentFromQuery(new URL(page.url), {
		transcribeMode,
		setTranscribeMode: (v) => (transcribeMode = v),
		setPanelMode: (m) => {
			skipInitialPanelMode = true;
			panelMode = m;
		},
		loadBlocks: loadTranscriptionBlocks,
		setActiveAnnotationId: (id) => (activeAnnotationId = id),
		flashAnnotation: (annotationId) => {
			flashAnnotationId = annotationId;
			setTimeout(() => (flashAnnotationId = null), prefersReducedMotion ? 2000 : 1500);
		},
		prefersReducedMotion,
		afterTick: waitForPanelRender,
		getElement: (id) => document.getElementById(id),
		onStripUrl: () => replaceState(page.url.pathname, page.state ?? {})
	}).catch((e) => console.error('deep-link scroll failed', e));

	function onKeyDown(e: KeyboardEvent) {
		if (e.key === 'Escape' && transcribeMode) {
			transcribeMode = false;
		}
	}
	document.addEventListener('keydown', onKeyDown);
	return () => {
		document.removeEventListener('keydown', onKeyDown);
		if (ocrPollTimer) clearInterval(ocrPollTimer);
	};
});
</script>

<svelte:head>
	<title>{doc.title || doc.originalFilename || 'Dokument'}</title>
</svelte:head>

<div
	class="fixed right-0 bottom-0 left-0 z-40 flex flex-col overflow-hidden bg-surface"
	style="top: {navHeight}px"
	data-hydrated
>
	<DocumentTopBar
		doc={doc}
		canWrite={canWrite}
		fileUrl={fileLoader.fileUrl}
		bind:transcribeMode={transcribeMode}
		inferredRelationship={data.inferredRelationship}
		geschichten={data.geschichten ?? []}
		canBlogWrite={data.canBlogWrite ?? false}
	/>

	<div class="relative flex-1 overflow-hidden {transcribeMode ? 'flex flex-col md:flex-row' : ''}">
		<div
			class={transcribeMode
				? `relative flex-1 overflow-hidden md:min-h-0 ${panelMode === 'read' ? (pdfStripExpanded ? 'max-h-[50vh] min-h-[50vh] md:max-h-none md:min-h-0' : 'max-h-[70px] min-h-[70px] md:max-h-none md:min-h-0') : 'min-h-[40vh]'} transition-[min-height,max-height] duration-300`
				: 'absolute inset-0'}
		>
			<DocumentViewer
				doc={doc}
				fileUrl={fileLoader.fileUrl}
				isLoading={fileLoader.isLoading}
				error={fileLoader.fileError}
				transcribeMode={transcribeMode && !ocrRunning}
				blockNumbers={blockNumbers}
				annotationReloadKey={annotationReloadKey}
				annotationsDimmed={transcribeMode && panelMode === 'read'}
				flashAnnotationId={flashAnnotationId}
				bind:activeAnnotationId={activeAnnotationId}
				onAnnotationClick={handleAnnotationClick}
				onTranscriptionDraw={createBlockFromDraw}
				onDeleteAnnotationRequest={handleAnnotationDeleteRequest}
			/>
		</div>

		{#if transcribeMode && panelMode === 'read'}
			<button
				type="button"
				onclick={() => (pdfStripExpanded = !pdfStripExpanded)}
				class="flex h-7 w-full shrink-0 items-center justify-center border-t border-line bg-muted text-xs font-semibold text-ink-2 md:hidden"
				aria-label={pdfStripExpanded ? m.scan_collapse() : m.scan_expand()}
			>
				<svg
					class="mr-1 h-3 w-3 transition-transform duration-200 {pdfStripExpanded ? 'rotate-180' : ''}"
					viewBox="0 0 24 24"
					fill="none"
					stroke="currentColor"
					stroke-width="2.5"
					aria-hidden="true"
				>
					<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
				</svg>
				{pdfStripExpanded ? m.scan_collapse() : m.scan_expand()}
			</button>
		{/if}

		{#if transcribeMode}
			<div
				class="flex min-h-0 flex-1 shrink-0 flex-col border-t border-line md:w-[400px] md:flex-none md:border-t-0 md:border-l lg:w-[480px]"
			>
				<TranscriptionPanelHeader
					mode={panelMode}
					hasBlocks={hasBlocks}
					blockCount={transcriptionBlocks.length}
					lastEditedAt={lastEditedAt}
					onModeChange={(newMode) => (panelMode = newMode)}
					onClose={() => (transcribeMode = false)}
				/>
				<div class="flex-1 overflow-y-auto">
					{#if ocrErrorMessage}
						<div class="mx-4 mt-4 rounded-sm border border-red-200 bg-red-50 px-4 py-3">
							<p class="text-sm text-red-700">{ocrErrorMessage}</p>
						</div>
					{/if}
					{#if ocrRunning}
						<div class="flex flex-1 flex-col items-center justify-center px-6 py-12 text-center">
							<svg
								class="mb-4 h-8 w-8 animate-spin text-brand-mint"
								viewBox="0 0 24 24"
								fill="none"
							>
								<circle
									class="opacity-25"
									cx="12"
									cy="12"
									r="10"
									stroke="currentColor"
									stroke-width="4"
								></circle>
								<path
									class="opacity-75"
									fill="currentColor"
									d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
								></path>
							</svg>
							<p class="text-xs font-bold tracking-widest text-gray-400 uppercase">
								{m.ocr_progress_heading()}
							</p>
							<p class="mt-2 text-sm text-ink-2">
								{ocrProgressMessage}
							</p>
							{#if ocrSkippedPages > 0}
								<p class="mt-1 text-xs text-amber-600">
									{ocrSkippedPages} Seiten übersprungen
								</p>
							{/if}
						</div>
					{:else if panelMode === 'read'}
						<TranscriptionReadView
							blocks={transcriptionBlocks}
							highlightBlockId={highlightBlockId}
							onParagraphClick={handleParagraphClick}
						/>
					{:else}
						<TranscriptionEditView
							documentId={doc.id}
							blocks={transcriptionBlocks}
							canComment={canWrite}
							currentUserId={currentUserId}
							activeAnnotationId={activeAnnotationId}
							storedScriptType={doc.scriptType ?? ''}
							canRunOcr={canWrite && !!doc.filePath}
							canWrite={canWrite}
							trainingLabels={doc.trainingLabels ?? []}
							onBlockFocus={handleBlockFocus}
							onSaveBlock={saveBlock}
							onDeleteBlock={deleteBlock}
							onReviewToggle={reviewToggle}
							onMarkAllReviewed={markAllReviewed}
							onTriggerOcr={triggerOcr}
							onToggleTrainingLabel={toggleTrainingLabel}
						/>
					{/if}
				</div>
			</div>
		{/if}
	</div>
</div>
