<script lang="ts">
import { onMount } from 'svelte';
import { SvelteMap } from 'svelte/reactivity';
import type { PDFDocumentProxy, PDFPageProxy, RenderTask } from 'pdfjs-dist';
import AnnotationLayer from './AnnotationLayer.svelte';
import { m } from '$lib/paraglide/messages.js';

let {
	url,
	documentId = '',
	annotateMode = $bindable(false),
	activeAnnotationId = $bindable<string | null>(null),
	activeAnnotationPage = $bindable<number | null>(null),
	onAnnotationClick,
	documentFileHash
}: {
	url: string;
	documentId?: string;
	annotateMode?: boolean;
	activeAnnotationId?: string | null;
	activeAnnotationPage?: number | null;
	onAnnotationClick?: (id: string) => void;
	documentFileHash?: string | null;
} = $props();

let pdfDoc = $state<PDFDocumentProxy | null>(null);
let currentPage = $state(1);
let totalPages = $state(0);
let scale = $state(1.5);
let loading = $state(false);
let error = $state<string | null>(null);

// Canvas and text layer container refs — bound via bind:this, not reactive state
let canvasEl = $state<HTMLCanvasElement | null>(null);
let textLayerEl = $state<HTMLDivElement | null>(null);

// Internal mutable refs for in-flight tasks — NOT $state to avoid reactive loops
let renderTask: RenderTask | null = null;
let textLayerInstance: { cancel: () => void } | null = null;

// Holds the dynamically-loaded pdfjs module (browser-only)
// Not $state — we use pdfjsReady as the reactive trigger instead
let pdfjsLib: typeof import('pdfjs-dist') | null = null;
let pdfjsReady = $state(false);

type Annotation = {
	id: string;
	documentId: string;
	pageNumber: number;
	x: number;
	y: number;
	width: number;
	height: number;
	color: string;
	createdAt: string;
	fileHash?: string | null;
};

let annotations = $state<Annotation[]>([]);
let annotateColor = $state('#ffff00');
let commentCounts = new SvelteMap<string, number>();
let showAnnotations = $state(true);

const visibleAnnotations = $derived(
	annotations.filter((a) => !a.fileHash || !documentFileHash || a.fileHash === documentFileHash)
);
const outdatedCount = $derived(annotations.length - visibleAnnotations.length);

onMount(async () => {
	// Dynamic import keeps pdfjs out of the SSR bundle entirely
	const [lib, { default: workerUrl }] = await Promise.all([
		import('pdfjs-dist'),
		import('pdfjs-dist/build/pdf.worker.min.mjs?url')
	]);
	lib.GlobalWorkerOptions.workerSrc = workerUrl;
	pdfjsLib = lib;
	pdfjsReady = true;
});

async function loadDocument(src: string) {
	if (!pdfjsLib) return;
	loading = true;
	error = null;
	pdfDoc = null;
	currentPage = 1;
	totalPages = 0;

	try {
		const loadingTask = pdfjsLib.getDocument(src);
		const doc = await loadingTask.promise;
		pdfDoc = doc;
		totalPages = doc.numPages;
	} catch (e) {
		error = e instanceof Error ? e.message : 'Failed to load PDF';
	} finally {
		loading = false;
	}
}

async function renderPage(doc: PDFDocumentProxy, pageNum: number) {
	if (!pdfjsLib || !canvasEl || !textLayerEl) return;

	// Cancel any in-flight render
	if (renderTask) {
		renderTask.cancel();
		renderTask = null;
	}
	if (textLayerInstance) {
		textLayerInstance.cancel();
		textLayerInstance = null;
	}

	let page: PDFPageProxy;
	try {
		page = await doc.getPage(pageNum);
	} catch {
		return;
	}

	const dpr = window.devicePixelRatio || 1;
	const viewport = page.getViewport({ scale: scale * dpr });

	const canvas = canvasEl;
	const ctx = canvas.getContext('2d');
	if (!ctx) return;

	canvas.width = viewport.width;
	canvas.height = viewport.height;
	canvas.style.width = `${viewport.width / dpr}px`;
	canvas.style.height = `${viewport.height / dpr}px`;

	const task = page.render({ canvas, canvasContext: ctx, viewport });
	renderTask = task;

	try {
		await task.promise;
	} catch (e: unknown) {
		if (
			typeof e === 'object' &&
			e !== null &&
			'name' in e &&
			(e as { name: string }).name === 'RenderingCancelledException'
		)
			return;
		return;
	}
	renderTask = null;

	// Text layer
	const textDiv = textLayerEl;
	textDiv.innerHTML = '';
	textDiv.style.width = `${viewport.width / dpr}px`;
	textDiv.style.height = `${viewport.height / dpr}px`;

	const tl = new pdfjsLib.TextLayer({
		textContentSource: page.streamTextContent(),
		container: textDiv,
		viewport
	});
	textLayerInstance = tl;
	try {
		await tl.render();
	} catch {
		// cancelled
	}
}

async function prerender(doc: PDFDocumentProxy, pageNum: number) {
	const neighbors = [pageNum - 1, pageNum + 1].filter((n) => n >= 1 && n <= doc.numPages);
	for (const n of neighbors) {
		try {
			await doc.getPage(n);
		} catch {
			// ignore
		}
	}
}

async function loadCommentCounts(docId: string, anns: Annotation[]) {
	await Promise.all(
		anns.map(async (a) => {
			try {
				const res = await fetch(`/api/documents/${docId}/annotations/${a.id}/comments`);
				if (res.ok) {
					const threads = (await res.json()) as Array<{ replies: unknown[] }>;
					const total = threads.reduce((sum, t) => sum + 1 + t.replies.length, 0);
					commentCounts.set(a.id, total);
				}
			} catch {
				// ignore
			}
		})
	);
}

async function loadAnnotations(docId: string) {
	if (!docId) return;
	try {
		const res = await fetch(`/api/documents/${docId}/annotations`);
		if (res.ok) {
			annotations = await res.json();
			await loadCommentCounts(docId, annotations);
		}
	} catch {
		// ignore
	}
}

async function handleAnnotationDraw(rect: { x: number; y: number; width: number; height: number }) {
	if (!documentId) return;
	try {
		const res = await fetch(`/api/documents/${documentId}/annotations`, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({
				pageNumber: currentPage,
				x: rect.x,
				y: rect.y,
				width: rect.width,
				height: rect.height,
				color: annotateColor
			})
		});
		if (res.ok) {
			const created: Annotation = await res.json();
			annotations = [...annotations, created];
			activeAnnotationId = created.id;
			activeAnnotationPage = created.pageNumber;
			onAnnotationClick?.(created.id);
		}
	} catch {
		// ignore
	}
}

async function handleAnnotationDelete(annotationId: string) {
	if (!documentId) return;
	try {
		const res = await fetch(`/api/documents/${documentId}/annotations/${annotationId}`, {
			method: 'DELETE'
		});
		if (res.ok) {
			annotations = annotations.filter((a) => a.id !== annotationId);
		}
	} catch {
		// ignore
	}
}

function handleAnnotationClick(id: string) {
	activeAnnotationId = id;
	const ann = annotations.find((a) => a.id === id);
	activeAnnotationPage = ann?.pageNumber ?? null;
	onAnnotationClick?.(id);
}

$effect(() => {
	if (pdfjsReady && url) {
		loadDocument(url);
	}
});

$effect(() => {
	// Read scale synchronously so Svelte tracks it as a dependency.
	// Without this, zoom changes don't re-trigger the effect because
	// scale is only read inside the async renderPage call.
	if (pdfDoc && currentPage && scale > 0) {
		renderPage(pdfDoc, currentPage).then(() => {
			if (pdfDoc) prerender(pdfDoc, currentPage);
		});
	}
});

$effect(() => {
	if (documentId) {
		loadAnnotations(documentId);
	}
});

$effect(() => {
	if (annotateMode) showAnnotations = true;
});

function prevPage() {
	if (currentPage > 1) currentPage -= 1;
}

function nextPage() {
	if (pdfDoc && currentPage < pdfDoc.numPages) currentPage += 1;
}

function zoomIn() {
	scale += 0.25;
}

function zoomOut() {
	if (scale > 0.5) scale -= 0.25;
}
</script>

{#if !url}
	<div class="flex h-full w-full items-center justify-center bg-pdf-bg text-ink-3">
		<p class="font-sans text-sm">Keine Datei vorhanden</p>
	</div>
{:else if error}
	<div class="flex h-full w-full flex-col items-center justify-center gap-3 bg-pdf-bg text-ink-3">
		<p class="font-sans text-sm text-red-400">Fehler beim Laden der PDF</p>
		<a
			href={url}
			target="_blank"
			rel="noopener noreferrer"
			class="font-sans text-xs text-accent underline hover:opacity-80"
		>
			Direkt öffnen
		</a>
	</div>
{:else}
	<div class="flex h-full w-full flex-col bg-pdf-bg">
		{#if outdatedCount > 0}
			<div
				class="flex shrink-0 items-center gap-2 border-b border-amber-500/30 bg-amber-500/10 px-4 py-2"
				data-testid="annotation-outdated-notice"
			>
				<svg
					class="h-4 w-4 shrink-0 text-amber-400"
					viewBox="0 0 24 24"
					fill="none"
					stroke="currentColor"
					stroke-width="2"
				>
					<path
						stroke-linecap="round"
						stroke-linejoin="round"
						d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
					/>
				</svg>
				<span class="font-sans text-xs text-amber-300">{m.annotation_outdated_notice()}</span>
			</div>
		{/if}
		<!-- Controls -->
		<div
			class="flex shrink-0 items-center justify-between gap-2 border-b border-pdf-ctrl px-4 py-2"
		>
			<!-- Page navigation -->
			<div class="flex items-center gap-2">
				<button
					onclick={prevPage}
					disabled={currentPage <= 1}
					aria-label="Zurück"
					class="rounded p-1 text-ink-3 transition hover:bg-surface/10 disabled:opacity-40"
				>
					<svg
						class="h-4 w-4"
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						stroke-width="2"
					>
						<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
					</svg>
				</button>

				{#if totalPages > 0}
					<span class="font-sans text-xs text-ink-3 tabular-nums">
						{currentPage} / {totalPages}
					</span>
				{/if}

				<button
					onclick={nextPage}
					disabled={!pdfDoc || currentPage >= totalPages}
					aria-label="Weiter"
					class="rounded p-1 text-ink-3 transition hover:bg-surface/10 disabled:opacity-40"
				>
					<svg
						class="h-4 w-4"
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						stroke-width="2"
					>
						<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
					</svg>
				</button>
			</div>

			<!-- Zoom controls -->
			<div class="flex items-center gap-1">
				<button
					onclick={zoomOut}
					aria-label="Verkleinern"
					class="rounded p-1 text-ink-3 transition hover:bg-surface/10"
				>
					<svg
						class="h-4 w-4"
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						stroke-width="2"
					>
						<circle cx="11" cy="11" r="8" /><path
							stroke-linecap="round"
							d="M21 21l-4.35-4.35M8 11h6"
						/>
					</svg>
				</button>
				<button
					onclick={zoomIn}
					aria-label="Vergrößern"
					class="rounded p-1 text-ink-3 transition hover:bg-surface/10"
				>
					<svg
						class="h-4 w-4"
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						stroke-width="2"
					>
						<circle cx="11" cy="11" r="8" /><path
							stroke-linecap="round"
							d="M21 21l-4.35-4.35M11 8v6M8 11h6"
						/>
					</svg>
				</button>
			</div>

			<!-- Color picker (shown in annotate mode) -->
			{#if annotateMode}
				<input
					type="color"
					bind:value={annotateColor}
					aria-label="Farbe wählen"
					class="h-6 w-6 cursor-pointer rounded border-0 bg-transparent p-0"
					title="Farbe wählen"
				/>
			{/if}
			<!-- Annotation visibility toggle (shown when annotations exist) -->
			{#if annotations.length > 0}
				<button
					onclick={() => (showAnnotations = !showAnnotations)}
					aria-label={showAnnotations ? m.pdf_annotations_hide() : m.pdf_annotations_show()}
					class="flex items-center gap-1.5 rounded px-2 py-1 font-sans text-xs transition {showAnnotations
						? 'text-ink-3 hover:bg-surface/10'
						: 'bg-surface/10 text-accent'}"
				>
					<svg
						class="h-3.5 w-3.5 shrink-0"
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						stroke-width="2"
					>
						{#if showAnnotations}
							<path
								stroke-linecap="round"
								stroke-linejoin="round"
								d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
							/>
							<path
								stroke-linecap="round"
								stroke-linejoin="round"
								d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
							/>
						{:else}
							<path
								stroke-linecap="round"
								stroke-linejoin="round"
								d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
							/>
						{/if}
					</svg>
					{showAnnotations ? m.pdf_annotations_hide() : m.pdf_annotations_show()}
				</button>
			{/if}
		</div>

		<!-- PDF canvas area -->
		<div class="relative flex-1 overflow-auto">
			{#if loading}
				<div class="flex h-full items-center justify-center">
					<div
						class="h-8 w-8 animate-spin rounded-full border-2 border-white/20 border-t-white"
					></div>
				</div>
			{:else}
				<div class="flex min-h-full items-start justify-center p-4">
					<div
						class="pdf-page relative shadow-xl"
						data-page-number={currentPage}
						style="position: relative"
					>
						<canvas bind:this={canvasEl}></canvas>
						<div
							bind:this={textLayerEl}
							class="textLayer"
							style="position: absolute; top: 0; left: 0; overflow: hidden; pointer-events: none; line-height: 1;"
						></div>
						{#if showAnnotations}
							<AnnotationLayer
								annotations={visibleAnnotations.filter((a) => a.pageNumber === currentPage)}
								canAnnotate={annotateMode}
								color={annotateColor}
								onDraw={handleAnnotationDraw}
								onDelete={handleAnnotationDelete}
								commentCounts={Object.fromEntries(commentCounts)}
								onAnnotationClick={handleAnnotationClick}
							/>
						{/if}
					</div>
				</div>
			{/if}
		</div>
	</div>
{/if}
