<script lang="ts">
	import { browser } from '$app/environment';
	import type { PageData } from './$types';

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

	// svelte-ignore state_referenced_locally
	const imgModels = data.imgModels ?? [];

	// ── Config persistence ────────────────────────────────────────────────────────
	const CONFIG_KEY = 'admin_catalogue_tools_v1';

	interface SavedConfig {
		imgModel: string;
		numSteps: number;
		width: number;
		height: number;
	}

	function loadConfig(): Partial<SavedConfig> {
		if (!browser) return {};
		try {
			const raw = localStorage.getItem(CONFIG_KEY);
			return raw ? (JSON.parse(raw) as Partial<SavedConfig>) : {};
		} catch { return {}; }
	}
	function saveConfig() {
		if (!browser) return;
		localStorage.setItem(CONFIG_KEY, JSON.stringify({ imgModel, numSteps, width, height }));
	}

	const saved = loadConfig();

	let imgModel = $state(saved.imgModel ?? (imgModels[0]?.id ?? ''));
	let numSteps = $state(saved.numSteps ?? 20);
	let width = $state(saved.width ?? 0);
	let height = $state(saved.height ?? 0);

	$effect(() => { void imgModel; void numSteps; void width; void height; saveConfig(); });

	// ── Batch covers ──────────────────────────────────────────────────────────────
	let fromItem = $state(0);
	let toItem = $state(0);
	let resumeJobID = $state('');
	let running = $state(false);
	let jobID = $state('');
	let done = $state(0);
	let total = $state(0);
	let events = $state<{ slug: string; skipped?: boolean; error?: string }[]>([]);
	let finished = $state(false);
	let error = $state('');
	let cancelling = $state(false);

	let progress = $derived(total > 0 ? Math.round((done / total) * 100) : 0);

	async function startBatch() {
		running = true; finished = false; error = ''; done = 0; total = 0; events = []; jobID = ''; cancelling = false;

		try {
			const res = await fetch('/api/admin/catalogue/batch-covers', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					model: imgModel || undefined,
					num_steps: numSteps || undefined,
					width: width || undefined,
					height: height || undefined,
					from_item: fromItem || undefined,
					to_item: toItem || undefined,
					job_id: resumeJobID.trim() || undefined,
				})
			});
			if (!res.ok) {
				const body = await res.json().catch(() => ({}));
				error = body.error ?? `Error ${res.status}`;
				return;
			}

			const reader = res.body!.getReader();
			const decoder = new TextDecoder();
			let buf = '';

			outer: while (true) {
				const { value, done: streamDone } = await reader.read();
				if (streamDone) break;
				buf += decoder.decode(value, { stream: true });
				const lines = buf.split('\n');
				buf = lines.pop() ?? '';
				for (const line of lines) {
					if (!line.startsWith('data: ')) continue;
					const payload = line.slice(6).trim();
					if (!payload) continue;
					let evt: {
						job_id?: string;
						done?: number;
						total?: number;
						slug?: string;
						skipped?: boolean;
						error?: string;
						finish?: boolean;
					};
					try { evt = JSON.parse(payload); } catch { continue; }

					if (evt.job_id) jobID = evt.job_id;
					if (evt.total != null) total = evt.total;
					if (evt.done != null) done = evt.done;
					if (evt.finish) { finished = true; running = false; break outer; }
					if (evt.slug) {
						events = [{ slug: evt.slug, skipped: evt.skipped, error: evt.error }, ...events].slice(0, 200);
					}
				}
			}
		} catch (e) {
			error = String(e);
		} finally {
			running = false;
		}
	}

	async function cancelBatch() {
		if (!jobID) return;
		cancelling = true;
		try {
			await fetch('/api/admin/catalogue/batch-covers/cancel', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ job_id: jobID })
			});
		} catch { /* ignore */ } finally {
			cancelling = false;
		}
	}
</script>

<svelte:head>
	<title>Catalogue Tools — Admin</title>
</svelte:head>

<div class="space-y-8 max-w-4xl">
	<div>
		<h1 class="text-2xl font-bold text-(--color-text)">Catalogue Tools</h1>
		<p class="text-(--color-muted) text-sm mt-1">Bulk AI operations for your book catalogue.</p>
	</div>

	<!-- Batch cover generation -->
	<div class="space-y-4">
		<h2 class="text-lg font-semibold text-(--color-text)">Batch Cover Generation</h2>
		<p class="text-sm text-(--color-muted)">
			Generates AI covers for every book that has no cover stored in MinIO.
			Books with existing covers are skipped. The job can be cancelled at any time.
		</p>

		<!-- Config -->
		<div class="grid grid-cols-2 sm:grid-cols-4 gap-4 bg-(--color-surface) border border-(--color-border) rounded-xl p-4">
			{#if imgModels.length > 0}
				<div class="col-span-2 sm:col-span-4 space-y-1">
					<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="img-model">Image model</label>
					<select id="img-model" bind:value={imgModel}
						class="w-full bg-(--color-surface-2) border border-(--color-border) rounded-lg px-3 py-2 text-(--color-text) text-sm focus:outline-none focus:ring-2 focus:ring-(--color-brand)">
						{#each imgModels as m}
							<option value={m.id}>{m.label} — {m.provider}</option>
						{/each}
					</select>
				</div>
			{/if}
			<div class="space-y-1">
				<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="steps">Steps</label>
				<input id="steps" type="number" bind:value={numSteps} min="1" max="50"
					class="w-full bg-(--color-surface-2) border border-(--color-border) rounded-lg px-3 py-2 text-(--color-text) text-sm focus:outline-none focus:ring-2 focus:ring-(--color-brand)" />
			</div>
			<div class="space-y-1">
				<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="width">Width <span class="font-normal">(0=default)</span></label>
				<input id="width" type="number" bind:value={width} min="0" step="64"
					class="w-full bg-(--color-surface-2) border border-(--color-border) rounded-lg px-3 py-2 text-(--color-text) text-sm focus:outline-none focus:ring-2 focus:ring-(--color-brand)" />
			</div>
			<div class="space-y-1">
				<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="height">Height <span class="font-normal">(0=default)</span></label>
				<input id="height" type="number" bind:value={height} min="0" step="64"
					class="w-full bg-(--color-surface-2) border border-(--color-border) rounded-lg px-3 py-2 text-(--color-text) text-sm focus:outline-none focus:ring-2 focus:ring-(--color-brand)" />
			</div>
			<div class="space-y-1">
				<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="from-item">From <span class="font-normal">(0=start)</span></label>
				<input id="from-item" type="number" bind:value={fromItem} min="0"
					class="w-full bg-(--color-surface-2) border border-(--color-border) rounded-lg px-3 py-2 text-(--color-text) text-sm focus:outline-none focus:ring-2 focus:ring-(--color-brand)" />
			</div>
			<div class="space-y-1">
				<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="to-item">To <span class="font-normal">(0=end)</span></label>
				<input id="to-item" type="number" bind:value={toItem} min="0"
					class="w-full bg-(--color-surface-2) border border-(--color-border) rounded-lg px-3 py-2 text-(--color-text) text-sm focus:outline-none focus:ring-2 focus:ring-(--color-brand)" />
			</div>
			<div class="col-span-2 sm:col-span-4 space-y-1">
				<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="resume-job">Resume job ID <span class="font-normal">(leave blank to start fresh)</span></label>
				<input id="resume-job" type="text" bind:value={resumeJobID} placeholder="optional PB job ID"
					class="w-full bg-(--color-surface-2) border border-(--color-border) rounded-lg px-3 py-2 text-(--color-text) text-sm placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-(--color-brand)" />
			</div>
		</div>

		<!-- Controls -->
		<div class="flex gap-3">
			<button
				onclick={startBatch}
				disabled={running}
				class="px-6 py-2.5 rounded-lg bg-(--color-brand) text-(--color-surface) font-semibold text-sm
					hover:bg-(--color-brand-dim) transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
			>
				{#if running}
					<svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
						<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
						<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
					</svg>
					Running…
				{:else}
					Start batch
				{/if}
			</button>
			{#if running && jobID}
				<button
					onclick={cancelBatch}
					disabled={cancelling}
					class="px-5 py-2.5 rounded-lg bg-(--color-surface-2) border border-(--color-border) text-(--color-muted) text-sm
						hover:text-(--color-text) transition-colors disabled:opacity-50"
				>
					{cancelling ? 'Cancelling…' : 'Cancel'}
				</button>
			{/if}
		</div>

		{#if error}
			<p class="text-sm text-(--color-danger) bg-(--color-danger)/10 rounded-lg px-3 py-2">{error}</p>
		{/if}

		<!-- Progress -->
		{#if total > 0 || running}
			<div class="space-y-2">
				<div class="flex items-center justify-between text-sm">
					<span class="text-(--color-muted)">
						{done} / {total} books processed
						{#if finished} — done{/if}
					</span>
					<span class="text-(--color-muted)">{progress}%</span>
				</div>
				<div class="w-full h-2 bg-(--color-surface-2) rounded-full overflow-hidden">
					<div
						class="h-full rounded-full transition-all duration-300 {finished ? 'bg-green-500' : 'bg-(--color-brand)'}"
						style="width: {progress}%"
					></div>
				</div>
			</div>
		{/if}

		<!-- Event log -->
		{#if events.length > 0}
			<div class="bg-(--color-surface) border border-(--color-border) rounded-xl overflow-hidden">
				<div class="px-4 py-2 border-b border-(--color-border)">
					<p class="text-xs font-semibold text-(--color-muted) uppercase tracking-widest">Activity log (newest first)</p>
				</div>
				<div class="max-h-80 overflow-y-auto divide-y divide-(--color-border)">
					{#each events as evt}
						<div class="px-4 py-2 flex items-center gap-3 text-sm">
							{#if evt.error}
								<span class="w-2 h-2 rounded-full bg-(--color-danger) shrink-0"></span>
								<span class="font-mono text-(--color-muted)">{evt.slug}</span>
								<span class="text-(--color-danger) text-xs truncate">{evt.error}</span>
							{:else if evt.skipped}
								<span class="w-2 h-2 rounded-full bg-(--color-surface-2) shrink-0"></span>
								<span class="font-mono text-(--color-muted)">{evt.slug}</span>
								<span class="text-xs text-(--color-muted)">skipped (has cover)</span>
							{:else}
								<span class="w-2 h-2 rounded-full bg-green-500 shrink-0"></span>
								<span class="font-mono text-(--color-text)">{evt.slug}</span>
								<span class="text-xs text-green-400">generated</span>
							{/if}
						</div>
					{/each}
				</div>
			</div>
		{/if}
	</div>
</div>
