<script lang="ts">
import { m } from '$lib/paraglide/messages.js';
import { diffWords } from 'diff';

let { documentId }: { documentId: string } = $props();

type VersionSummary = {
	id: string;
	savedAt: string;
	editorName: string;
	changedFields: string[];
};

type SnapshotDoc = {
	title?: string;
	documentDate?: string;
	location?: string;
	documentLocation?: string;
	transcription?: string;
	summary?: string;
	sender?: { id: string; firstName: string; lastName: string } | null;
	receivers?: { id: string; firstName: string; lastName: string }[];
	tags?: { id: string; name: string }[];
};

type DiffEntry =
	| {
			kind: 'text';
			field: string;
			label: string;
			parts: { value: string; added?: boolean; removed?: boolean }[];
	  }
	| { kind: 'scalar'; field: string; label: string; oldVal: string; newVal: string }
	| { kind: 'relation'; field: string; label: string; removed: string[]; added: string[] };

let historyLoaded = $state(false);
let historyLoading = $state(false);
let versions = $state<VersionSummary[]>([]);

let compareMode = $state(false);
let compareA = $state('');
let compareB = $state('');

let selectedVersionId = $state<string | null>(null);
let diffEntries = $state<DiffEntry[]>([]);
let diffLoading = $state(false);
let noDiff = $state(false);

const fieldLabels: Record<string, () => string> = {
	title: m.history_field_title,
	documentDate: m.history_field_document_date,
	location: m.history_field_location,
	documentLocation: m.history_field_document_location,
	transcription: m.history_field_transcription,
	summary: m.history_field_summary,
	sender: m.history_field_sender,
	receivers: m.history_field_receivers,
	tags: m.history_field_tags
};

const TEXT_FIELDS = ['title', 'summary', 'transcription'] as const;
const SCALAR_FIELDS = ['documentDate', 'location', 'documentLocation'] as const;

function parseSnapshot(raw: string): SnapshotDoc {
	try {
		return JSON.parse(raw) as SnapshotDoc;
	} catch {
		return {};
	}
}

function personLabel(p: { firstName: string; lastName: string }): string {
	return `${p.firstName} ${p.lastName}`.trim();
}

const DIFF_CONTEXT_WORDS = 4;

type DiffPart = { value: string; added?: boolean; removed?: boolean };

function trimContextParts(parts: DiffPart[]): DiffPart[] {
	return parts.flatMap((part, i) => {
		if (part.added || part.removed) return [part];
		const tokens = part.value.split(/(\s+)/).filter(Boolean);
		const wordCount = tokens.filter((t) => /\S/.test(t)).length;
		if (wordCount <= DIFF_CONTEXT_WORDS * 2) return [part];

		function keepFirst(n: number): string {
			let count = 0;
			const out: string[] = [];
			for (const t of tokens) {
				out.push(t);
				if (/\S/.test(t) && ++count >= n) break;
			}
			return out.join('');
		}
		function keepLast(n: number): string {
			let count = 0;
			const out: string[] = [];
			for (const t of [...tokens].reverse()) {
				out.unshift(t);
				if (/\S/.test(t) && ++count >= n) break;
			}
			return out.join('');
		}

		const isFirst = i === 0;
		const isLast = i === parts.length - 1;
		if (isFirst) return [{ value: '… ' + keepLast(DIFF_CONTEXT_WORDS) }];
		if (isLast) return [{ value: keepFirst(DIFF_CONTEXT_WORDS) + ' …' }];
		return [{ value: keepFirst(DIFF_CONTEXT_WORDS) + ' … ' + keepLast(DIFF_CONTEXT_WORDS) }];
	});
}

function buildDiff(older: SnapshotDoc | null, newer: SnapshotDoc): DiffEntry[] {
	const entries: DiffEntry[] = [];

	for (const field of TEXT_FIELDS) {
		const a = older?.[field] ?? '';
		const b = newer[field] ?? '';
		if (a === b) continue;
		const parts = trimContextParts(diffWords(a, b));
		entries.push({ kind: 'text', field, label: fieldLabels[field](), parts });
	}

	for (const field of SCALAR_FIELDS) {
		const a = older?.[field] ?? '';
		const b = newer[field] ?? '';
		if (a === b) continue;
		entries.push({ kind: 'scalar', field, label: fieldLabels[field](), oldVal: a, newVal: b });
	}

	const senderA = older?.sender ? personLabel(older.sender) : '';
	const senderB = newer.sender ? personLabel(newer.sender) : '';
	if (senderA !== senderB) {
		entries.push({
			kind: 'relation',
			field: 'sender',
			label: fieldLabels['sender'](),
			removed: senderA ? [senderA] : [],
			added: senderB ? [senderB] : []
		});
	}

	const receiversA = new Set((older?.receivers ?? []).map(personLabel));
	const receiversB = new Set((newer.receivers ?? []).map(personLabel));
	const removedReceivers = [...receiversA].filter((r) => !receiversB.has(r));
	const addedReceivers = [...receiversB].filter((r) => !receiversA.has(r));
	if (removedReceivers.length > 0 || addedReceivers.length > 0) {
		entries.push({
			kind: 'relation',
			field: 'receivers',
			label: fieldLabels['receivers'](),
			removed: removedReceivers,
			added: addedReceivers
		});
	}

	const tagsA = new Set((older?.tags ?? []).map((t) => t.name));
	const tagsB = new Set((newer.tags ?? []).map((t) => t.name));
	const removedTags = [...tagsA].filter((t) => !tagsB.has(t));
	const addedTags = [...tagsB].filter((t) => !tagsA.has(t));
	if (removedTags.length > 0 || addedTags.length > 0) {
		entries.push({
			kind: 'relation',
			field: 'tags',
			label: fieldLabels['tags'](),
			removed: removedTags,
			added: addedTags
		});
	}

	return entries;
}

async function fetchSnapshot(versionId: string): Promise<SnapshotDoc> {
	const res = await fetch(`/api/documents/${documentId}/versions/${versionId}`);
	if (!res.ok) throw new Error('Failed to fetch version');
	const v = await res.json();
	return parseSnapshot(v.snapshot);
}

async function loadHistory() {
	if (historyLoaded) return;
	historyLoading = true;
	try {
		const res = await fetch(`/api/documents/${documentId}/versions`);
		if (res.ok) {
			versions = await res.json();
		}
		historyLoaded = true;
	} catch {
		// ignore
	} finally {
		historyLoading = false;
	}
}

async function selectVersion(versionId: string) {
	if (selectedVersionId === versionId) {
		selectedVersionId = null;
		diffEntries = [];
		noDiff = false;
		return;
	}
	selectedVersionId = versionId;
	diffEntries = [];
	noDiff = false;
	diffLoading = true;
	try {
		const idx = versions.findIndex((v) => v.id === versionId);
		const newerSnap = await fetchSnapshot(versionId);
		const olderSnap = idx > 0 ? await fetchSnapshot(versions[idx - 1].id) : null;
		const entries = buildDiff(olderSnap, newerSnap);
		if (entries.length === 0) {
			noDiff = true;
		} else {
			diffEntries = entries;
		}
	} catch {
		// ignore
	} finally {
		diffLoading = false;
	}
}

async function applyCompare() {
	if (!compareA || !compareB || compareA === compareB) return;
	selectedVersionId = null;
	diffEntries = [];
	noDiff = false;
	diffLoading = true;
	try {
		const [snapA, snapB] = await Promise.all([fetchSnapshot(compareA), fetchSnapshot(compareB)]);
		const entries = buildDiff(snapA, snapB);
		if (entries.length === 0) {
			noDiff = true;
		} else {
			diffEntries = entries;
		}
	} catch {
		// ignore
	} finally {
		diffLoading = false;
	}
}

function formatDateTime(iso: string): string {
	try {
		return new Intl.DateTimeFormat('de-DE', {
			day: 'numeric',
			month: 'short',
			year: 'numeric',
			hour: '2-digit',
			minute: '2-digit'
		}).format(new Date(iso));
	} catch {
		return iso;
	}
}

function versionLabel(v: VersionSummary, index: number): string {
	return `Version ${index + 1} — ${v.editorName} — ${formatDateTime(v.savedAt)}`;
}

// Load history when this panel mounts.
$effect(() => {
	loadHistory();
});
</script>

<div class="space-y-4 p-6">
	{#if historyLoading}
		<p class="font-sans text-xs text-ink-3">{m.history_loading()}</p>
	{:else if !historyLoaded}
		<!-- initial state before effect runs — show nothing -->
	{:else if versions.length === 0}
		<p class="font-serif text-sm text-ink-3 italic">{m.history_empty()}</p>
	{:else}
		<!-- Compare mode toggle -->
		<div class="flex justify-end">
			<button
				onclick={() => {
					compareMode = !compareMode;
					diffEntries = [];
					noDiff = false;
					selectedVersionId = null;
				}}
				class="font-sans text-xs font-medium transition {compareMode
					? 'text-ink underline'
					: 'text-ink-3 hover:text-ink'}"
			>
				{m.history_compare_mode()}
			</button>
		</div>

		{#if compareMode}
			<div class="space-y-2">
				<div>
					<label for="compare-a" class="mb-1 block font-sans text-[10px] text-ink-3 uppercase"
						>{m.history_compare_select_a()}</label
					>
					<select
						id="compare-a"
						bind:value={compareA}
						class="w-full rounded border border-line bg-surface px-2 py-1 font-sans text-xs text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
					>
						<option value="">—</option>
						{#each versions as v, i (v.id)}
							<option value={v.id}>{versionLabel(v, i)}</option>
						{/each}
					</select>
				</div>
				<div>
					<label for="compare-b" class="mb-1 block font-sans text-[10px] text-ink-3 uppercase"
						>{m.history_compare_select_b()}</label
					>
					<select
						id="compare-b"
						bind:value={compareB}
						class="w-full rounded border border-line bg-surface px-2 py-1 font-sans text-xs text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
					>
						<option value="">—</option>
						{#each versions as v, i (v.id)}
							<option value={v.id}>{versionLabel(v, i)}</option>
						{/each}
					</select>
				</div>
				<button
					onclick={applyCompare}
					disabled={!compareA || !compareB || compareA === compareB}
					class="w-full rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-primary-fg transition hover:bg-primary/80 disabled:cursor-not-allowed disabled:opacity-40"
				>
					{m.history_compare_apply()}
				</button>
			</div>

			<!-- Diff panel for compare mode -->
			{#if diffLoading}
				<p class="font-sans text-xs text-ink-3">{m.history_loading()}</p>
			{:else if noDiff}
				<div
					data-testid="history-diff"
					class="rounded-sm border border-line bg-surface p-4 font-serif text-sm text-ink-3 italic"
				>
					{m.history_diff_no_changes()}
				</div>
			{:else if diffEntries.length > 0}
				<div
					data-testid="history-diff"
					class="space-y-4 rounded-sm border border-line bg-surface p-4"
				>
					{#each diffEntries as entry (entry.field)}
						<div>
							<span
								class="mb-1.5 block font-sans text-[10px] font-bold tracking-wide text-ink-3 uppercase"
								>{entry.label}</span
							>
							{#if entry.kind === 'text'}
								<p class="font-serif text-sm leading-relaxed">
									{#each entry.parts as part, partIdx (partIdx)}
										{#if part.added}
											<span class="bg-green-50 text-green-700">{part.value}</span>
										{:else if part.removed}
											<span class="bg-red-50 text-red-600 line-through">{part.value}</span>
										{:else}
											<span>{part.value}</span>
										{/if}
									{/each}
								</p>
							{:else if entry.kind === 'scalar'}
								<div class="flex items-center gap-2 font-serif text-sm">
									<span class="text-red-600 line-through">{entry.oldVal || '—'}</span>
									<svg
										class="h-3 w-3 flex-shrink-0 text-ink-3"
										viewBox="0 0 20 20"
										fill="currentColor"
										aria-hidden="true"
									>
										<path
											fill-rule="evenodd"
											d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
											clip-rule="evenodd"
										/>
									</svg>
									<span class="text-green-700">{entry.newVal || '—'}</span>
								</div>
							{:else if entry.kind === 'relation'}
								<div class="flex flex-wrap gap-1.5">
									{#each entry.removed as item (item)}
										<span
											class="rounded bg-red-50 px-1.5 py-0.5 font-sans text-[11px] text-red-600 line-through"
											>{item}</span
										>
									{/each}
									{#each entry.added as item (item)}
										<span
											class="rounded bg-green-50 px-1.5 py-0.5 font-sans text-[11px] text-green-700"
											>{item}</span
										>
									{/each}
								</div>
							{/if}
						</div>
					{/each}
				</div>
			{/if}
		{:else}
			<!-- Version list with inline diff below each selected item -->
			<ul class="divide-y divide-line">
				{#each versions as v, i (v.id)}
					<li>
						<button
							onclick={() => selectVersion(v.id)}
							data-testid="history-version"
							class="w-full py-2 text-left transition hover:bg-muted {selectedVersionId ===
							v.id
								? 'border-l-2 border-accent pl-2'
								: 'pl-0'}"
						>
							<div class="flex items-baseline justify-between gap-2">
								<span class="font-sans text-xs font-medium text-ink">
									Version {i + 1}
								</span>
								<span class="font-sans text-[10px] text-ink-3">
									{formatDateTime(v.savedAt)}
								</span>
							</div>
							<span class="font-sans text-[11px] text-ink-2">{v.editorName}</span>
							{#if v.changedFields && v.changedFields.length > 0}
								<div class="mt-1 flex flex-wrap gap-1">
									{#each v.changedFields as field (field)}
										<span
											class="rounded bg-muted px-1.5 py-0.5 font-sans text-[10px] tracking-wide text-ink-2 uppercase"
										>
											{fieldLabels[field] ? fieldLabels[field]() : field}
										</span>
									{/each}
								</div>
							{/if}
						</button>

						<!-- Diff shown inline below the selected version -->
						{#if selectedVersionId === v.id}
							{#if diffLoading}
								<p class="pb-3 pl-2 font-sans text-xs text-ink-3">{m.history_loading()}</p>
							{:else if noDiff}
								<div
									data-testid="history-diff"
									class="mb-2 rounded-sm border border-line bg-surface p-4 font-serif text-sm text-ink-3 italic"
								>
									{m.history_diff_no_changes()}
								</div>
							{:else if diffEntries.length > 0}
								<div
									data-testid="history-diff"
									class="mb-2 space-y-4 rounded-sm border border-line bg-surface p-4"
								>
									{#each diffEntries as entry (entry.field)}
										<div>
											<span
												class="mb-1.5 block font-sans text-[10px] font-bold tracking-wide text-ink-3 uppercase"
												>{entry.label}</span
											>
											{#if entry.kind === 'text'}
												<p class="font-serif text-sm leading-relaxed">
													{#each entry.parts as part, partIdx (partIdx)}
														{#if part.added}
															<span class="bg-green-50 text-green-700">{part.value}</span>
														{:else if part.removed}
															<span class="bg-red-50 text-red-600 line-through">{part.value}</span>
														{:else}
															<span>{part.value}</span>
														{/if}
													{/each}
												</p>
											{:else if entry.kind === 'scalar'}
												<div class="flex items-center gap-2 font-serif text-sm">
													<span class="text-red-600 line-through">{entry.oldVal || '—'}</span>
													<svg
														class="h-3 w-3 flex-shrink-0 text-ink-3"
														viewBox="0 0 20 20"
														fill="currentColor"
														aria-hidden="true"
													>
														<path
															fill-rule="evenodd"
															d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
															clip-rule="evenodd"
														/>
													</svg>
													<span class="text-green-700">{entry.newVal || '—'}</span>
												</div>
											{:else if entry.kind === 'relation'}
												<div class="flex flex-wrap gap-1.5">
													{#each entry.removed as item (item)}
														<span
															class="rounded bg-red-50 px-1.5 py-0.5 font-sans text-[11px] text-red-600 line-through"
															>{item}</span
														>
													{/each}
													{#each entry.added as item (item)}
														<span
															class="rounded bg-green-50 px-1.5 py-0.5 font-sans text-[11px] text-green-700"
															>{item}</span
														>
													{/each}
												</div>
											{/if}
										</div>
									{/each}
								</div>
							{/if}
						{/if}
					</li>
				{/each}
			</ul>
		{/if}
	{/if}
</div>
