<script lang="ts">
	import { goto } from '$app/navigation';
	import { resolve as resolveRoute } from '$app/paths';
	import { onMount } from 'svelte';
	import type { PageData } from './$types';
	import type { Repository, IndexingJob } from '$lib/types';
	import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
	import IndexingProgress from '$lib/components/IndexingProgress.svelte';
	import StatBadge from '$lib/components/StatBadge.svelte';

	let { data }: { data: PageData } = $props();

	let repoOverride = $state<
		(Repository & { indexedVersions?: string[]; embeddingCount?: number }) | null
	>(null);
	const repo = $derived(
		repoOverride ??
			((data.repo ?? {}) as Repository & {
				indexedVersions?: string[];
				embeddingCount?: number;
			})
	);
	const recentJobs = $derived((data.recentJobs ?? []) as IndexingJob[]);
	let showDeleteConfirm = $state(false);
	let activeJobId = $state<string | null>(null);
	let errorMessage = $state<string | null>(null);
	let successMessage = $state<string | null>(null);

	// Version management state
	interface VersionDto {
		id: string;
		repositoryId: string;
		tag: string;
		title: string | null;
		commitHash: string | null;
		state: 'pending' | 'indexing' | 'indexed' | 'error';
		totalSnippets: number;
		indexedAt: string | null;
		createdAt: string;
	}
	let versions = $state<VersionDto[]>([]);
	let versionsLoading = $state(false);

	// Add version form
	let addVersionTag = $state('');
	let addVersionBusy = $state(false);

	// Discover tags state
	let discoverBusy = $state(false);
	let discoveredTags = $state<Array<{ tag: string; commitHash: string }>>([]);
	let selectedDiscoveredTags = $state<Set<string>>(new Set());
	let showDiscoverPanel = $state(false);
	let registerBusy = $state(false);

	// Active version indexing jobs: tag -> jobId
	let activeVersionJobs = $state<Record<string, string | undefined>>({});

	// Remove confirm
	let removeTag = $state<string | null>(null);

	const stateColors: Record<string, string> = {
		pending: 'bg-gray-100 text-gray-600',
		indexing: 'bg-blue-100 text-blue-700',
		indexed: 'bg-green-100 text-green-700',
		error: 'bg-red-100 text-red-700'
	};

	const stateLabels: Record<string, string> = {
		pending: 'Pending',
		indexing: 'Indexing...',
		indexed: 'Indexed',
		error: 'Error'
	};

	async function refreshRepo() {
		try {
			const res = await fetch(`/api/v1/libs/${encodeURIComponent(repo.id)}`);
			if (res.ok) {
				repoOverride = await res.json();
			}
		} catch {
			// ignore
		}
	}

	async function loadVersions() {
		versionsLoading = true;
		try {
			const res = await fetch(`/api/v1/libs/${encodeURIComponent(repo.id)}/versions`);
			if (res.ok) {
				const data = await res.json();
				versions = data.versions ?? [];
			}
		} catch {
			// ignore
		} finally {
			versionsLoading = false;
		}
	}

	onMount(() => {
		loadVersions();
	});

	async function handleReindex() {
		errorMessage = null;
		successMessage = null;
		try {
			const res = await fetch(`/api/v1/libs/${encodeURIComponent(repo.id)}/index`, {
				method: 'POST'
			});
			if (!res.ok) {
				const d = await res.json();
				throw new Error(d.error ?? 'Failed to trigger re-indexing');
			}
			const d = await res.json();
			if (d.job?.id) {
				activeJobId = d.job.id;
			}
			const versionCount = d.versionJobs?.length ?? 0;
			if (versionCount > 0) {
				let next = { ...activeVersionJobs };
				for (const vj of d.versionJobs) {
					const matched = versions.find((v) => v.id === vj.versionId);
					if (matched) {
						next = { ...next, [matched.tag]: vj.id };
					}
				}
				activeVersionJobs = next;
			}
			successMessage =
				versionCount > 0
					? `Re-indexing started. Also queued ${versionCount} version job${versionCount === 1 ? '' : 's'}.`
					: 'Re-indexing started.';
			await Promise.all([refreshRepo(), loadVersions()]);
		} catch (e) {
			errorMessage = (e as Error).message;
		}
	}

	async function handleDelete() {
		showDeleteConfirm = false;
		errorMessage = null;
		try {
			const res = await fetch(`/api/v1/libs/${encodeURIComponent(repo.id)}`, {
				method: 'DELETE'
			});
			if (!res.ok && res.status !== 204) {
				const d = await res.json();
				throw new Error(d.error ?? 'Failed to delete repository');
			}
			goto(resolveRoute('/'));
		} catch (e) {
			errorMessage = (e as Error).message;
		}
	}

	async function handleAddVersion() {
		const tag = addVersionTag.trim();
		if (!tag) return;
		addVersionBusy = true;
		errorMessage = null;
		try {
			const res = await fetch(`/api/v1/libs/${encodeURIComponent(repo.id)}/versions`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ tag, autoIndex: true })
			});
			if (!res.ok) {
				const d = await res.json();
				throw new Error(d.error ?? 'Failed to add version');
			}
			const d = await res.json();
			if (d.job?.id) {
				activeVersionJobs = { ...activeVersionJobs, [tag]: d.job.id };
			}
			addVersionTag = '';
			await loadVersions();
		} catch (e) {
			errorMessage = (e as Error).message;
		} finally {
			addVersionBusy = false;
		}
	}

	async function handleIndexVersion(tag: string) {
		errorMessage = null;
		try {
			const res = await fetch(
				`/api/v1/libs/${encodeURIComponent(repo.id)}/versions/${encodeURIComponent(tag)}/index`,
				{ method: 'POST' }
			);
			if (!res.ok) {
				const d = await res.json();
				throw new Error(d.error ?? 'Failed to queue version indexing');
			}
			const d = await res.json();
			if (d.job?.id) {
				activeVersionJobs = { ...activeVersionJobs, [tag]: d.job.id };
			}
		} catch (e) {
			errorMessage = (e as Error).message;
		}
	}

	async function handleRemoveVersion() {
		if (!removeTag) return;
		const tag = removeTag;
		removeTag = null;
		errorMessage = null;
		try {
			const res = await fetch(
				`/api/v1/libs/${encodeURIComponent(repo.id)}/versions/${encodeURIComponent(tag)}`,
				{ method: 'DELETE' }
			);
			if (!res.ok && res.status !== 204) {
				const d = await res.json();
				throw new Error(d.error ?? 'Failed to remove version');
			}
			await loadVersions();
		} catch (e) {
			errorMessage = (e as Error).message;
		}
	}

	async function handleDiscoverTags() {
		discoverBusy = true;
		errorMessage = null;
		try {
			const res = await fetch(
				`/api/v1/libs/${encodeURIComponent(repo.id)}/versions/discover`,
				{ method: 'POST' }
			);
			if (!res.ok) {
				const d = await res.json();
				throw new Error(d.error ?? 'Failed to discover tags');
			}
			const d = await res.json();
			const registeredTags = new Set(versions.map((v) => v.tag));
			discoveredTags = (d.tags ?? []).filter(
				(t: { tag: string; commitHash: string }) => !registeredTags.has(t.tag)
			);
			selectedDiscoveredTags = new Set(discoveredTags.map((t) => t.tag));
			showDiscoverPanel = true;
		} catch (e) {
			errorMessage = (e as Error).message;
		} finally {
			discoverBusy = false;
		}
	}

	function toggleDiscoveredTag(tag: string) {
		const next = new Set(selectedDiscoveredTags);
		if (next.has(tag)) {
			next.delete(tag);
		} else {
			next.add(tag);
		}
		selectedDiscoveredTags = next;
	}

	async function handleRegisterSelected() {
		if (selectedDiscoveredTags.size === 0) return;
		registerBusy = true;
		errorMessage = null;
		try {
			const tags = [...selectedDiscoveredTags];
			const responses = await Promise.all(
				tags.map((tag) =>
					fetch(`/api/v1/libs/${encodeURIComponent(repo.id)}/versions`, {
						method: 'POST',
						headers: { 'Content-Type': 'application/json' },
						body: JSON.stringify({ tag, autoIndex: true })
					})
				)
			);
			const results = await Promise.all(responses.map((r) => (r.ok ? r.json() : null)));
			let next = { ...activeVersionJobs };
			for (let i = 0; i < tags.length; i++) {
				const result = results[i];
				if (result?.job?.id) {
					next = { ...next, [tags[i]]: result.job.id };
				}
			}
			activeVersionJobs = next;
			showDiscoverPanel = false;
			discoveredTags = [];
			selectedDiscoveredTags = new Set();
			await loadVersions();
		} catch (e) {
			errorMessage = (e as Error).message;
		} finally {
			registerBusy = false;
		}
	}

	function formatDate(ts: Date | number | string | null | undefined): string {
		if (!ts) return 'Never';
		return new Date(ts as string).toLocaleString();
	}

	const embeddingCount = $derived(repo.embeddingCount ?? 0);
	const totalSnippets = $derived(repo.totalSnippets ?? 0);
	const totalTokens = $derived(repo.totalTokens ?? 0);
	const trustScore = $derived(repo.trustScore ?? 0);
</script>

<svelte:head>
	<title>{repo.title} — TrueRef</title>
</svelte:head>

<div class="mb-6">
	<a href={resolveRoute('/')} class="text-sm text-blue-600 hover:underline">← Repositories</a>
</div>

<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
	<div class="min-w-0 flex-1">
		<div class="flex flex-wrap items-center gap-3">
			<h1 class="text-xl font-semibold text-gray-900">{repo.title}</h1>
			<span
				class="rounded-full px-2.5 py-0.5 text-xs font-medium {stateColors[repo.state] ??
					'bg-gray-100 text-gray-600'}"
			>
				{stateLabels[repo.state] ?? repo.state}
			</span>
		</div>
		<p class="mt-1 font-mono text-sm text-gray-500">{repo.id}</p>
		{#if repo.description}
			<p class="mt-2 text-sm text-gray-600">{repo.description}</p>
		{/if}
		{#if repo.sourceUrl}
			{#if repo.source === 'github'}
				<a
					href={repo.sourceUrl}
					target="_blank"
					rel="external noopener noreferrer"
					class="mt-1 block text-sm text-blue-600 hover:underline"
				>
					{repo.sourceUrl}
				</a>
			{:else}
				<p class="mt-1 font-mono text-sm text-gray-500">{repo.sourceUrl}</p>
			{/if}
		{/if}
	</div>

	<div class="flex shrink-0 gap-2">
		<button
			onclick={handleReindex}
			disabled={repo.state === 'indexing'}
			class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
		>
			{repo.state === 'indexing' ? 'Indexing...' : 'Re-index'}
		</button>
		<button
			onclick={() => (showDeleteConfirm = true)}
			class="rounded-lg border border-red-200 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-50"
		>
			Delete
		</button>
	</div>
</div>

{#if errorMessage}
	<div class="mt-4 rounded-lg bg-red-50 px-4 py-3">
		<p class="text-sm text-red-700">{errorMessage}</p>
	</div>
{/if}

{#if successMessage}
	<div class="mt-4 rounded-lg bg-green-50 px-4 py-3">
		<p class="text-sm text-green-700">{successMessage}</p>
	</div>
{/if}

{#if activeJobId}
	<div class="mt-4 rounded-xl border border-blue-100 bg-blue-50 p-4">
		<p class="mb-2 text-sm font-medium text-blue-700">Indexing in progress</p>
		<IndexingProgress
			jobId={activeJobId}
			oncomplete={() => {
				activeJobId = null;
				refreshRepo();
			}}
		/>
	</div>
{:else if repo.state === 'error'}
	<div class="mt-4 rounded-xl border border-red-100 bg-red-50 p-4">
		<p class="text-sm text-red-700">Last indexing run failed. Trigger re-index to retry.</p>
	</div>
{/if}

<!-- Stats -->
<div class="mt-6 grid grid-cols-2 gap-3 sm:grid-cols-4">
	<StatBadge label="Snippets" value={totalSnippets.toLocaleString()} />
	<StatBadge label="Embeddings" value={embeddingCount.toLocaleString()} />
	<StatBadge label="Tokens" value={totalTokens.toLocaleString()} />
	<StatBadge label="Trust Score" value="{trustScore.toFixed(1)}/10" />
	{#if repo.stars != null}
		<StatBadge label="Stars" value={repo.stars.toLocaleString()} />
	{/if}
</div>

<!-- Versions -->
<div class="mt-6 rounded-xl border border-gray-200 bg-white p-5">
	<div class="mb-4 flex flex-wrap items-center justify-between gap-3">
		<h2 class="text-sm font-semibold text-gray-700">Versions</h2>
		<div class="flex flex-wrap items-center gap-2">
			<!-- Add version inline form -->
			<form
				onsubmit={(e) => {
					e.preventDefault();
					handleAddVersion();
				}}
				class="flex items-center gap-1.5"
			>
				<input
					type="text"
					bind:value={addVersionTag}
					placeholder="e.g. v2.0.0"
					class="rounded-lg border border-gray-200 px-3 py-1.5 text-sm text-gray-900 placeholder-gray-400 focus:border-blue-400 focus:outline-none"
				/>
				<button
					type="submit"
					disabled={addVersionBusy || !addVersionTag.trim()}
					class="rounded-lg bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
				>
					Add
				</button>
			</form>
			<!-- Discover tags button — local repos only -->
			{#if repo.source === 'local'}
				<button
					onclick={handleDiscoverTags}
					disabled={discoverBusy}
					class="rounded-lg border border-gray-200 px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50"
				>
					{discoverBusy ? 'Discovering...' : 'Discover tags'}
				</button>
			{/if}
		</div>
	</div>

	<!-- Discover panel -->
	{#if showDiscoverPanel}
		<div class="mb-4 rounded-lg border border-blue-100 bg-blue-50 p-4">
			<div class="mb-2 flex items-center justify-between">
				<p class="text-sm font-medium text-blue-700">
					{discoveredTags.length === 0
						? 'No new tags found'
						: `${discoveredTags.length} new tag${discoveredTags.length === 1 ? '' : 's'} available`}
				</p>
				<button
					onclick={() => {
						showDiscoverPanel = false;
						discoveredTags = [];
						selectedDiscoveredTags = new Set();
					}}
					class="text-xs text-blue-600 hover:underline"
				>
					Close
				</button>
			</div>
			{#if discoveredTags.length > 0}
				<div class="mb-3 flex flex-col gap-1.5">
					{#each discoveredTags as discovered (discovered.tag)}
						<label class="flex cursor-pointer items-center gap-2 text-sm">
							<input
								type="checkbox"
								checked={selectedDiscoveredTags.has(discovered.tag)}
								onchange={() => toggleDiscoveredTag(discovered.tag)}
								class="rounded border-gray-300"
							/>
							<span class="font-mono text-gray-800">{discovered.tag}</span>
							<span class="font-mono text-xs text-gray-400">{discovered.commitHash.slice(0, 8)}</span>
						</label>
					{/each}
				</div>
				<button
					onclick={handleRegisterSelected}
					disabled={registerBusy || selectedDiscoveredTags.size === 0}
					class="rounded-lg bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
				>
					{registerBusy
						? 'Registering...'
						: `Register ${selectedDiscoveredTags.size} selected`}
				</button>
			{/if}
		</div>
	{/if}

	<!-- Versions list -->
	{#if versionsLoading}
		<p class="text-sm text-gray-400">Loading versions...</p>
	{:else if versions.length === 0}
		<p class="text-sm text-gray-400">No versions registered. Add a tag above to get started.</p>
	{:else}
		<div class="divide-y divide-gray-100">
			{#each versions as version (version.id)}
				<div class="py-2.5">
					<div class="flex items-center justify-between">
						<div class="flex items-center gap-3">
							<span class="font-mono text-sm font-medium text-gray-900">{version.tag}</span>
							<span
								class="rounded-full px-2 py-0.5 text-xs font-medium {stateColors[version.state] ??
									'bg-gray-100 text-gray-600'}"
							>
								{stateLabels[version.state] ?? version.state}
							</span>
						</div>
						<div class="flex items-center gap-2">
							<button
								onclick={() => handleIndexVersion(version.tag)}
								disabled={version.state === 'indexing' || !!activeVersionJobs[version.tag]}
								class="rounded-lg border border-blue-200 px-3 py-1 text-xs font-medium text-blue-600 hover:bg-blue-50 disabled:cursor-not-allowed disabled:opacity-50"
							>
								{version.state === 'indexing' || !!activeVersionJobs[version.tag] ? 'Indexing...' : 'Index'}
							</button>
							<button
								onclick={() => (removeTag = version.tag)}
								class="rounded-lg border border-red-100 px-3 py-1 text-xs font-medium text-red-500 hover:bg-red-50"
							>
								Remove
							</button>
						</div>
					</div>
					{#if version.totalSnippets > 0 || version.commitHash || version.indexedAt}
						{@const metaParts = (
							[
								version.totalSnippets > 0
									? { text: `${version.totalSnippets} snippets`, mono: false }
									: null,
								version.commitHash
									? { text: version.commitHash.slice(0, 8), mono: true }
									: null,
								version.indexedAt
									? { text: formatDate(version.indexedAt), mono: false }
									: null
							] as Array<{ text: string; mono: boolean } | null>
						).filter((p): p is { text: string; mono: boolean } => p !== null)}
						<div class="mt-1 flex items-center gap-1.5">
							{#each metaParts as part, i (i)}
								{#if i > 0}
									<span class="text-xs text-gray-300">·</span>
								{/if}
								<span class="text-xs text-gray-400{part.mono ? ' font-mono' : ''}">{part.text}</span>
							{/each}
						</div>
					{/if}
					{#if !!activeVersionJobs[version.tag]}
						<IndexingProgress
							jobId={activeVersionJobs[version.tag]!}
							oncomplete={() => {
								const { [version.tag]: _, ...rest } = activeVersionJobs;
								activeVersionJobs = rest;
								loadVersions();
								refreshRepo();
							}}
						/>
					{/if}
				</div>
			{/each}
		</div>
	{/if}
</div>

<!-- Metadata -->
<div class="mt-6 rounded-xl border border-gray-200 bg-white p-5">
	<h2 class="mb-3 text-sm font-semibold text-gray-700">Repository Info</h2>
	<dl class="grid grid-cols-1 gap-y-2 text-sm sm:grid-cols-2">
		<div class="flex gap-2">
			<dt class="text-gray-500">Source</dt>
			<dd class="font-medium text-gray-900 capitalize">{repo.source}</dd>
		</div>
		<div class="flex gap-2">
			<dt class="text-gray-500">Branch</dt>
			<dd class="font-medium text-gray-900">{repo.branch ?? 'main'}</dd>
		</div>
		<div class="flex gap-2">
			<dt class="text-gray-500">Last Indexed</dt>
			<dd class="font-medium text-gray-900">{formatDate(repo.lastIndexedAt)}</dd>
		</div>
		<div class="flex gap-2">
			<dt class="text-gray-500">Created</dt>
			<dd class="font-medium text-gray-900">{formatDate(repo.createdAt)}</dd>
		</div>
	</dl>
</div>

<!-- Recent Jobs -->
{#if recentJobs.length > 0}
	<div class="mt-6 rounded-xl border border-gray-200 bg-white p-5">
		<h2 class="mb-3 text-sm font-semibold text-gray-700">Recent Jobs</h2>
		<div class="divide-y divide-gray-100">
			{#each recentJobs as job (job.id)}
				<div class="flex items-center justify-between py-2.5">
					<div>
						<span class="font-mono text-xs text-gray-500">{job.id.slice(0, 8)}...</span>
						{#if job.error}
							<p class="mt-0.5 text-xs text-red-600">{job.error}</p>
						{/if}
					</div>
					<div class="flex items-center gap-3">
						<span
							class="rounded-full px-2 py-0.5 text-xs {job.status === 'done'
								? 'bg-green-100 text-green-700'
								: job.status === 'failed'
									? 'bg-red-100 text-red-700'
									: job.status === 'running'
										? 'bg-blue-100 text-blue-700'
										: 'bg-gray-100 text-gray-600'}"
						>
							{job.status}
						</span>
						{#if job.completedAt}
							<span class="text-xs text-gray-400">{formatDate(job.completedAt)}</span>
						{:else if job.startedAt}
							<span class="text-xs text-gray-400">{formatDate(job.startedAt)}</span>
						{/if}
					</div>
				</div>
			{/each}
		</div>
	</div>
{/if}

{#if showDeleteConfirm}
	<ConfirmDialog
		title="Delete Repository"
		message="Are you sure you want to delete {repo.title}? This will permanently remove all indexed snippets and job history."
		confirmLabel="Delete"
		danger={true}
		onConfirm={handleDelete}
		onCancel={() => (showDeleteConfirm = false)}
	/>
{/if}

{#if removeTag}
	<ConfirmDialog
		title="Remove Version"
		message="Remove version '{removeTag}'? This will delete all indexed snippets for this version."
		confirmLabel="Remove"
		danger={true}
		onConfirm={handleRemoveVersion}
		onCancel={() => (removeTag = null)}
	/>
{/if}
