<script lang="ts">
	import { browser } from '$app/environment';
	import type { PageData } from './$types';
	import type { TextModelInfo, BookSummary } from './+page.server';
	import * as m from '$lib/paraglide/messages.js';

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

	// Server data is static per page load — intentional one-time snapshot.
	// svelte-ignore state_referenced_locally
	const models: TextModelInfo[] = data.models ?? [];
	// Books arrive as a streamed promise — start empty and populate on resolve.
	let books = $state<BookSummary[]>([]);
	$effect(() => {
		data.books.then((resolved) => { books = resolved; });
	});

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

	interface SavedConfig {
		selectedModel: string;
		activeTab: string;
		chPattern: string;
		dInstructions: string;
	}

	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;
		const cfg: SavedConfig = { selectedModel, activeTab, chPattern, dInstructions };
		localStorage.setItem(CONFIG_KEY, JSON.stringify(cfg));
	}

	const saved = loadConfig();

	// ── Shared ────────────────────────────────────────────────────────────────────
	type ActiveTab = 'chapters' | 'description' | 'tagline' | 'genres' | 'warnings' | 'quality';
	let activeTab = $state<ActiveTab>((saved.activeTab as ActiveTab) ?? 'chapters');
	let selectedModel = $state(saved.selectedModel ?? (models[0]?.id ?? ''));

	let selectedModelInfo = $derived(models.find((m) => m.id === selectedModel) ?? null);

	$effect(() => { void selectedModel; void activeTab; void chPattern; void dInstructions; saveConfig(); });

	function fmtCtx(n: number) {
		if (n >= 1000) return `${(n / 1000).toFixed(0)}k ctx`;
		return `${n} ctx`;
	}

	// ── Book autocomplete (shared component logic) ───────────────────────────────
	function makeBookAC() {
		let inputVal = $state('');
		let focused = $state(false);

		const suggestions = $derived(
			inputVal.trim().length === 0
				? []
				: books
					.filter((b) =>
						b.slug.includes(inputVal.toLowerCase()) ||
						b.title.toLowerCase().includes(inputVal.toLowerCase())
					)
					.slice(0, 8)
		);

		return {
			get inputVal() { return inputVal; },
			set inputVal(v: string) { inputVal = v; },
			get focused() { return focused; },
			set focused(v: boolean) { focused = v; },
			get suggestions() { return suggestions; }
		};
	}

	// ── Chapter names state ───────────────────────────────────────────────────────
	let chAC = makeBookAC();
	let chSlug = $state('');
	let chPattern = $state(saved.chPattern ?? 'Chapter {n}: {scene}');
	let chFromChapter = $state(0);
	let chToChapter = $state(0);
	let chJobID = $state('');
	let chGenerating = $state(false);
	let chError = $state('');

	interface ProposedChapter {
		number: number;
		old_title: string;
		new_title: string;
		edited: string;
	}
	let chProposals = $state<ProposedChapter[]>([]);
	let chUsedModel = $state('');

	let chBatchProgress = $state('');
	let chBatchWarnings = $state<string[]>([]);

	let chApplying = $state(false);
	let chApplyError = $state('');
	let chApplySuccess = $state(false);

	let chCanGenerate = $derived(chSlug.trim().length > 0 && chPattern.trim().length > 0 && !chGenerating);
	let chCanApply = $derived(chProposals.length > 0 && !chApplying);

	function selectChBook(b: BookSummary) {
		chSlug = b.slug;
		chAC.inputVal = b.slug;
		chAC.focused = false;
	}

	function onChSlugInput() {
		chSlug = chAC.inputVal;
	}

	async function generateChapterNames() {
		if (!chCanGenerate) return;
		chGenerating = true;
		chError = '';
		chProposals = [];
		chUsedModel = '';
		chBatchProgress = '';
		chBatchWarnings = [];
		chApplySuccess = false;
		chApplyError = '';
		chJobID = '';

		try {
			const res = await fetch('/api/admin/text-gen/chapter-names', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					slug: chSlug.trim(),
					pattern: chPattern.trim(),
					model: selectedModel,
					from_chapter: chFromChapter || undefined,
					to_chapter: chToChapter || undefined
				})
			});

			// Non-SSE error response (e.g. 400/404/502 before streaming started).
			if (!res.ok) {
				const body = await res.json().catch(() => ({}));
				chError = body.error ?? body.message ?? `Error ${res.status}`;
				return;
			}

			// Stream SSE events line by line.
			const reader = res.body!.getReader();
			const decoder = new TextDecoder();
			let buffer = '';

			outer: while (true) {
				const { value, done } = await reader.read();
				if (done) break;
				buffer += decoder.decode(value, { stream: true });

				// Process all complete SSE messages in the buffer.
				const lines = buffer.split('\n');
				buffer = lines.pop() ?? ''; // keep incomplete last line

				for (const line of lines) {
					if (!line.startsWith('data: ')) continue;
					const payload = line.slice(6).trim();
					if (!payload) continue;

					let evt: {
						job_id?: string;
						batch?: number;
						total_batches?: number;
						chapters_done?: number;
						total_chapters?: number;
						model?: string;
						chapters?: { number: number; old_title: string; new_title: string }[];
						error?: string;
						done?: boolean;
					};
					try {
						evt = JSON.parse(payload);
					} catch {
						continue;
					}

					if (evt.job_id) chJobID = evt.job_id;

					if (evt.done) {
						chBatchProgress = `Done — ${evt.total_chapters ?? chProposals.length} chapters`;
						chGenerating = false;
						break outer;
					}

					if (evt.model) chUsedModel = evt.model;

					if (evt.error) {
						chBatchWarnings = [
							...chBatchWarnings,
							`Batch ${evt.batch}/${evt.total_batches} failed: ${evt.error}`
						];
					} else if (evt.chapters) {
						const incoming = (evt.chapters as { number: number; old_title: string; new_title: string }[]).map(
							(p) => ({ ...p, edited: p.new_title })
						);
						chProposals = [...chProposals, ...incoming];
					}

					if (evt.batch != null && evt.total_batches != null) {
						chBatchProgress = `Batch ${evt.batch}/${evt.total_batches} · ${evt.chapters_done ?? chProposals.length}/${evt.total_chapters ?? '?'} chapters`;
					}
				}
			}

			if (chProposals.length === 0 && chBatchWarnings.length === 0) {
				chError = 'No proposals returned. The model may have failed to parse the chapters.';
			}
		} catch {
			chError = 'Network error.';
		} finally {
			chGenerating = false;
		}
	}

	async function applyChapterNames() {
		if (!chCanApply) return;
		chApplying = true;
		chApplyError = '';
		chApplySuccess = false;

		try {
			const res = await fetch('/api/admin/text-gen/chapter-names/apply', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					slug: chSlug.trim(),
					chapters: chProposals.map((p) => ({ number: p.number, title: p.edited.trim() || p.new_title }))
				})
			});
			const body = await res.json().catch(() => ({}));
			if (!res.ok) {
				chApplyError = body.error ?? body.message ?? `Error ${res.status}`;
				return;
			}
			chApplySuccess = true;
		} catch {
			chApplyError = 'Network error.';
		} finally {
			chApplying = false;
		}
	}

	// ── Description state ─────────────────────────────────────────────────────────
	let dAC = makeBookAC();
	let dSlug = $state('');
	let dInstructions = $state(saved.dInstructions ?? '');
	let dGenerating = $state(false);
	let dError = $state('');

	let dOldDesc = $state('');
	let dNewDesc = $state('');
	let dUsedModel = $state('');

	let dApplying = $state(false);
	let dApplyError = $state('');
	let dApplySuccess = $state(false);

	let dCanGenerate = $derived(dSlug.trim().length > 0 && !dGenerating);
	let dCanApply = $derived(dNewDesc.trim().length > 0 && !dApplying);

	function selectDBook(b: BookSummary) {
		dSlug = b.slug;
		dAC.inputVal = b.slug;
		dAC.focused = false;
	}

	function onDSlugInput() {
		dSlug = dAC.inputVal;
	}

	async function generateDescription() {
		if (!dCanGenerate) return;
		dGenerating = true;
		dError = '';
		dOldDesc = '';
		dNewDesc = '';
		dApplySuccess = false;
		dApplyError = '';

		try {
			const res = await fetch('/api/admin/text-gen/description', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					slug: dSlug.trim(),
					instructions: dInstructions.trim(),
					model: selectedModel
				})
			});
			const body = await res.json().catch(() => ({}));
			if (!res.ok) {
				dError = body.error ?? body.message ?? `Error ${res.status}`;
				return;
			}
			dOldDesc = body.old_description ?? '';
			dNewDesc = body.new_description ?? '';
			dUsedModel = body.model ?? '';
		} catch {
			dError = 'Network error.';
		} finally {
			dGenerating = false;
		}
	}

	async function applyDescription() {
		if (!dCanApply) return;
		dApplying = true;
		dApplyError = '';
		dApplySuccess = false;

		try {
			const res = await fetch('/api/admin/text-gen/description/apply', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					slug: dSlug.trim(),
					description: dNewDesc.trim()
				})
			});
			const body = await res.json().catch(() => ({}));
			if (!res.ok) {
				dApplyError = body.error ?? body.message ?? `Error ${res.status}`;
				return;
			}
			dApplySuccess = true;
		} catch {
			dApplyError = 'Network error.';
		} finally {
			dApplying = false;
		}
	}

	// ── Tagline state ─────────────────────────────────────────────────────────────
	let tAC = makeBookAC();
	let tSlug = $state('');
	let tGenerating = $state(false);
	let tError = $state('');
	let tResult = $state('');
	let tEdited = $state('');
	let tUsedModel = $state('');
	let tCopied = $state(false);

	let tCanGenerate = $derived(tSlug.trim().length > 0 && !tGenerating);

	function selectTBook(b: BookSummary) { tSlug = b.slug; tAC.inputVal = b.slug; tAC.focused = false; }
	function onTSlugInput() { tSlug = tAC.inputVal; }

	async function generateTagline() {
		if (!tCanGenerate) return;
		tGenerating = true; tError = ''; tResult = ''; tEdited = ''; tCopied = false;
		try {
			const res = await fetch('/api/admin/text-gen/tagline', {
				method: 'POST', headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ slug: tSlug.trim(), model: selectedModel })
			});
			const body = await res.json().catch(() => ({}));
			if (!res.ok) { tError = body.error ?? `Error ${res.status}`; return; }
			tResult = body.new_tagline ?? '';
			tEdited = tResult;
			tUsedModel = body.model ?? '';
		} catch { tError = 'Network error.'; } finally { tGenerating = false; }
	}

	async function copyTagline() {
		await navigator.clipboard.writeText(tEdited || tResult);
		tCopied = true;
		setTimeout(() => { tCopied = false; }, 2000);
	}

	// ── Genres state ──────────────────────────────────────────────────────────────
	let gAC = makeBookAC();
	let gSlug = $state('');
	let gGenerating = $state(false);
	let gError = $state('');
	let gCurrent = $state<string[]>([]);
	let gProposed = $state<string[]>([]);
	let gEdited = $state<string[]>([]);
	let gUsedModel = $state('');
	let gApplying = $state(false);
	let gApplyError = $state('');
	let gApplySuccess = $state(false);

	let gCanGenerate = $derived(gSlug.trim().length > 0 && !gGenerating);
	let gCanApply = $derived(gEdited.length > 0 && !gApplying);

	function selectGBook(b: BookSummary) { gSlug = b.slug; gAC.inputVal = b.slug; gAC.focused = false; }
	function onGSlugInput() { gSlug = gAC.inputVal; }

	async function generateGenres() {
		if (!gCanGenerate) return;
		gGenerating = true; gError = ''; gCurrent = []; gProposed = []; gEdited = []; gApplySuccess = false;
		try {
			const res = await fetch('/api/admin/text-gen/genres', {
				method: 'POST', headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ slug: gSlug.trim(), model: selectedModel })
			});
			const body = await res.json().catch(() => ({}));
			if (!res.ok) { gError = body.error ?? `Error ${res.status}`; return; }
			gCurrent = body.current_genres ?? [];
			gProposed = body.proposed_genres ?? [];
			gEdited = [...gProposed];
			gUsedModel = body.model ?? '';
		} catch { gError = 'Network error.'; } finally { gGenerating = false; }
	}

	async function applyGenres() {
		if (!gCanApply) return;
		gApplying = true; gApplyError = ''; gApplySuccess = false;
		try {
			const res = await fetch('/api/admin/text-gen/genres/apply', {
				method: 'POST', headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ slug: gSlug.trim(), genres: gEdited.filter(Boolean) })
			});
			const body = await res.json().catch(() => ({}));
			if (!res.ok) { gApplyError = body.error ?? `Error ${res.status}`; return; }
			gApplySuccess = true;
		} catch { gApplyError = 'Network error.'; } finally { gApplying = false; }
	}

	// ── Content warnings state ────────────────────────────────────────────────────
	let wAC = makeBookAC();
	let wSlug = $state('');
	let wGenerating = $state(false);
	let wError = $state('');
	let wWarnings = $state<string[]>([]);
	let wUsedModel = $state('');
	let wCopied = $state(false);

	let wCanGenerate = $derived(wSlug.trim().length > 0 && !wGenerating);

	function selectWBook(b: BookSummary) { wSlug = b.slug; wAC.inputVal = b.slug; wAC.focused = false; }
	function onWSlugInput() { wSlug = wAC.inputVal; }

	async function generateWarnings() {
		if (!wCanGenerate) return;
		wGenerating = true; wError = ''; wWarnings = []; wCopied = false;
		try {
			const res = await fetch('/api/admin/text-gen/content-warnings', {
				method: 'POST', headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ slug: wSlug.trim(), model: selectedModel })
			});
			const body = await res.json().catch(() => ({}));
			if (!res.ok) { wError = body.error ?? `Error ${res.status}`; return; }
			wWarnings = body.warnings ?? [];
			wUsedModel = body.model ?? '';
		} catch { wError = 'Network error.'; } finally { wGenerating = false; }
	}

	async function copyWarnings() {
		await navigator.clipboard.writeText(wWarnings.join(', '));
		wCopied = true;
		setTimeout(() => { wCopied = false; }, 2000);
	}

	// ── Quality score state ───────────────────────────────────────────────────────
	let qAC = makeBookAC();
	let qSlug = $state('');
	let qGenerating = $state(false);
	let qError = $state('');
	let qScore = $state(0);
	let qFeedback = $state('');
	let qUsedModel = $state('');

	let qCanGenerate = $derived(qSlug.trim().length > 0 && !qGenerating);

	function selectQBook(b: BookSummary) { qSlug = b.slug; qAC.inputVal = b.slug; qAC.focused = false; }
	function onQSlugInput() { qSlug = qAC.inputVal; }

	async function generateQualityScore() {
		if (!qCanGenerate) return;
		qGenerating = true; qError = ''; qScore = 0; qFeedback = '';
		try {
			const res = await fetch('/api/admin/text-gen/quality-score', {
				method: 'POST', headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ slug: qSlug.trim(), model: selectedModel })
			});
			const body = await res.json().catch(() => ({}));
			if (!res.ok) { qError = body.error ?? `Error ${res.status}`; return; }
			qScore = body.score ?? 0;
			qFeedback = body.feedback ?? '';
			qUsedModel = body.model ?? '';
		} catch { qError = 'Network error.'; } finally { qGenerating = false; }
	}
</script>

<svelte:head>
	<title>{m.admin_text_gen_page_title()}</title>
</svelte:head>

<div class="space-y-6 max-w-5xl">
	<!-- Header -->
	<div>
		<h1 class="text-2xl font-bold text-(--color-text)">{m.admin_text_gen_heading()}</h1>
		<p class="text-(--color-muted) text-sm mt-1">
			Generate chapter titles and book descriptions using Cloudflare Workers AI.
		</p>
	</div>

	<!-- Model selector (shared) -->
	<div class="space-y-1 max-w-md">
		<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="model-select">
			Model
		</label>
		<select
			id="model-select"
			bind:value={selectedModel}
			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 models as m}
				<option value={m.id}>{m.label} — {m.provider} ({fmtCtx(m.context_size)})</option>
			{/each}
		</select>
		{#if selectedModelInfo}
			<p class="text-xs text-(--color-muted)">{selectedModelInfo.description}</p>
		{/if}
	</div>

	<!-- Tab toggle -->
	<div class="flex flex-wrap gap-1 bg-(--color-surface-2) rounded-lg p-1 w-fit border border-(--color-border)">
		{#each ([
			['chapters', 'Chapter Names'],
			['description', 'Description'],
			['tagline', 'Tagline'],
			['genres', 'Genres'],
			['warnings', 'Warnings'],
			['quality', 'Quality'],
		] as const) as [t, label]}
			<button
				onclick={() => (activeTab = t)}
				class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors
					{activeTab === t
						? 'bg-(--color-surface-3) text-(--color-text)'
						: 'text-(--color-muted) hover:text-(--color-text)'}"
			>
				{label}
			</button>
		{/each}
	</div>

	<!-- ── Chapter names panel ────────────────────────────────────────────────── -->
	{#if activeTab === 'chapters'}
		<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
			<!-- Left: form -->
			<div class="space-y-4">
				<div class="space-y-1">
					<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="ch-slug">
						Book slug
					</label>
					<div class="relative">
						<input
							id="ch-slug"
							type="text"
							bind:value={chAC.inputVal}
							oninput={onChSlugInput}
							onfocus={() => (chAC.focused = true)}
							onblur={() => setTimeout(() => { chAC.focused = false; }, 150)}
							placeholder="e.g. shadow-slave"
							autocomplete="off"
							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)"
						/>
						{#if chAC.focused && chAC.suggestions.length > 0}
							<ul class="absolute z-50 top-full left-0 right-0 mt-1 bg-(--color-surface-2) border border-(--color-border) rounded-lg shadow-xl overflow-hidden max-h-56 overflow-y-auto">
								{#each chAC.suggestions as b}
									<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
									<li
										role="option"
										aria-selected={chSlug === b.slug}
										onmousedown={() => selectChBook(b)}
										class="flex items-center gap-3 px-3 py-2 cursor-pointer hover:bg-(--color-surface-3) transition-colors"
									>
										<div class="min-w-0">
											<p class="text-sm text-(--color-text) truncate">{b.title}</p>
											<p class="text-xs text-(--color-muted) font-mono">{b.slug}</p>
										</div>
									</li>
								{/each}
							</ul>
						{/if}
					</div>
				</div>

				<div class="space-y-1">
					<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="ch-pattern">
						Naming pattern
					</label>
					<input
						id="ch-pattern"
						type="text"
						bind:value={chPattern}
						placeholder="e.g. Chapter [n]: [brief scene description]"
						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)"
					/>
					<p class="text-xs text-(--color-muted)">
						Describe the desired chapter title format. The AI will rename every chapter to match.
					</p>
				</div>

				<!-- Range (optional) -->
				<div class="grid grid-cols-2 gap-3">
					<div class="space-y-1">
						<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="ch-from">
							From chapter <span class="font-normal">(0=all)</span>
						</label>
						<input id="ch-from" type="number" bind:value={chFromChapter} 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="ch-to">
							To chapter <span class="font-normal">(0=all)</span>
						</label>
						<input id="ch-to" type="number" bind:value={chToChapter} 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>
				{#if chJobID}
					<p class="text-xs text-(--color-muted)">Job: <span class="font-mono">{chJobID}</span></p>
				{/if}

				<button
					onclick={generateChapterNames}
					disabled={!chCanGenerate}
					class="w-full 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 justify-center gap-2"
				>
					{#if chGenerating}
						<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>
						Generating…
					{:else}
						Generate proposals
					{/if}
				</button>

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

			<!-- Right: proposals -->
			<div class="space-y-4">
				{#if chProposals.length > 0}
					<div class="space-y-2">
						<div class="flex items-center justify-between">
							<p class="text-xs font-semibold text-(--color-muted) uppercase tracking-widest">
								{chProposals.length} proposals
								{#if chUsedModel}
									<span class="normal-case font-normal">· {chUsedModel.split('/').pop()}</span>
								{/if}
							</p>
							{#if chBatchProgress}
								<span class="text-xs text-(--color-muted) flex items-center gap-1.5">
									{#if chGenerating}
										<svg class="w-3 h-3 animate-spin shrink-0" 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>
									{/if}
									{chBatchProgress}
								</span>
							{/if}
						</div>

						{#if chBatchWarnings.length > 0}
							<div class="space-y-1">
								{#each chBatchWarnings as w}
									<p class="text-xs text-amber-400 bg-amber-400/10 rounded-lg px-3 py-2">{w}</p>
								{/each}
							</div>
						{/if}

						<div class="max-h-[28rem] overflow-y-auto space-y-2 pr-1">
							{#each chProposals as proposal}
								<div class="bg-(--color-surface) border border-(--color-border) rounded-lg p-3 space-y-1.5">
									<div class="flex items-center gap-2">
										<span class="text-xs font-mono text-(--color-muted) w-8 shrink-0">#{proposal.number}</span>
										<p class="text-xs text-(--color-muted) line-through truncate flex-1" title={proposal.old_title}>
											{proposal.old_title}
										</p>
									</div>
									<input
										type="text"
										bind:value={proposal.edited}
										class="w-full bg-(--color-surface-2) border border-(--color-border) rounded-md px-2.5 py-1.5 text-(--color-text) text-sm focus:outline-none focus:ring-1 focus:ring-(--color-brand)"
									/>
								</div>
							{/each}
						</div>

						<div class="flex gap-2 pt-1">
							<button
								onclick={applyChapterNames}
								disabled={!chCanApply}
								class="flex-1 py-2 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"
							>
								{chApplying ? 'Saving…' : chApplySuccess ? 'Saved ✓' : `Apply ${chProposals.length} titles`}
							</button>
							<button
								onclick={() => { chProposals = []; chBatchProgress = ''; chBatchWarnings = []; chApplySuccess = false; }}
								class="px-4 py-2 rounded-lg bg-(--color-surface-2) text-(--color-muted) text-sm
									hover:text-(--color-text) transition-colors border border-(--color-border)"
							>
								Clear
							</button>
						</div>

						{#if chApplyError}
							<p class="text-sm text-(--color-danger) bg-(--color-danger)/10 rounded-lg px-3 py-2">{chApplyError}</p>
						{/if}
						{#if chApplySuccess}
							<p class="text-sm text-green-400 bg-green-400/10 rounded-lg px-3 py-2">
								{chProposals.length} chapter titles saved successfully.
							</p>
						{/if}
					</div>
				{:else if chGenerating}
					<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) rounded-xl h-48">
						<div class="text-center space-y-3">
							<svg class="w-8 h-8 animate-spin mx-auto text-(--color-brand)" 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>
							<p class="text-sm text-(--color-muted)">
								{chBatchProgress || 'Generating chapter titles…'}
							</p>
						</div>
					</div>
				{:else}
					<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) border-dashed rounded-xl h-48">
						<p class="text-sm text-(--color-muted)">Proposed titles will appear here</p>
					</div>
				{/if}
			</div>
		</div>
	{/if}

	<!-- ── Description panel ──────────────────────────────────────────────────── -->
	{#if activeTab === 'description'}
		<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
			<!-- Left: form -->
			<div class="space-y-4">
				<div class="space-y-1">
					<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="d-slug">
						Book slug
					</label>
					<div class="relative">
						<input
							id="d-slug"
							type="text"
							bind:value={dAC.inputVal}
							oninput={onDSlugInput}
							onfocus={() => (dAC.focused = true)}
							onblur={() => setTimeout(() => { dAC.focused = false; }, 150)}
							placeholder="e.g. shadow-slave"
							autocomplete="off"
							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)"
						/>
						{#if dAC.focused && dAC.suggestions.length > 0}
							<ul class="absolute z-50 top-full left-0 right-0 mt-1 bg-(--color-surface-2) border border-(--color-border) rounded-lg shadow-xl overflow-hidden max-h-56 overflow-y-auto">
								{#each dAC.suggestions as b}
									<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
									<li
										role="option"
										aria-selected={dSlug === b.slug}
										onmousedown={() => selectDBook(b)}
										class="flex items-center gap-3 px-3 py-2 cursor-pointer hover:bg-(--color-surface-3) transition-colors"
									>
										<div class="min-w-0">
											<p class="text-sm text-(--color-text) truncate">{b.title}</p>
											<p class="text-xs text-(--color-muted) font-mono">{b.slug}</p>
										</div>
									</li>
								{/each}
							</ul>
						{/if}
					</div>
				</div>

				<div class="space-y-1">
					<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="d-instructions">
						Instructions <span class="normal-case font-normal text-(--color-muted)">(optional)</span>
					</label>
					<textarea
						id="d-instructions"
						bind:value={dInstructions}
						rows="3"
						placeholder="e.g. Write a 3-sentence blurb, avoid spoilers, dramatic tone."
						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) resize-y"
					></textarea>
				</div>

				<button
					onclick={generateDescription}
					disabled={!dCanGenerate}
					class="w-full 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 justify-center gap-2"
				>
					{#if dGenerating}
						<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>
						Generating…
					{:else}
						Generate description
					{/if}
				</button>

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

			<!-- Right: result -->
			<div class="space-y-4">
				{#if dNewDesc || dOldDesc}
					<div class="space-y-4">
						{#if dOldDesc}
							<div class="space-y-1">
								<p class="text-xs font-semibold text-(--color-muted) uppercase tracking-widest">Current description</p>
								<div class="bg-(--color-surface) border border-(--color-border) rounded-lg p-3">
									<p class="text-sm text-(--color-muted) whitespace-pre-wrap">{dOldDesc}</p>
								</div>
							</div>
						{/if}

						{#if dNewDesc}
							<div class="space-y-1">
								<div class="flex items-center justify-between">
									<p class="text-xs font-semibold text-(--color-muted) uppercase tracking-widest">
										Proposed description
										{#if dUsedModel}
											<span class="normal-case font-normal">· {dUsedModel.split('/').pop()}</span>
										{/if}
									</p>
								</div>
								<textarea
									bind:value={dNewDesc}
									rows="6"
									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) resize-y"
								></textarea>
							</div>

							<div class="flex gap-2">
								<button
									onclick={applyDescription}
									disabled={!dCanApply}
									class="flex-1 py-2 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"
								>
									{dApplying ? 'Saving…' : dApplySuccess ? 'Saved ✓' : 'Apply description'}
								</button>
								<button
									onclick={() => { dNewDesc = ''; dOldDesc = ''; dApplySuccess = false; }}
									class="px-4 py-2 rounded-lg bg-(--color-surface-2) text-(--color-muted) text-sm
										hover:text-(--color-text) transition-colors border border-(--color-border)"
								>
									Clear
								</button>
							</div>

							{#if dApplyError}
								<p class="text-sm text-(--color-danger) bg-(--color-danger)/10 rounded-lg px-3 py-2">{dApplyError}</p>
							{/if}
							{#if dApplySuccess}
								<p class="text-sm text-green-400 bg-green-400/10 rounded-lg px-3 py-2">Description saved successfully.</p>
							{/if}
						{/if}
					</div>
				{:else if dGenerating}
					<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) rounded-xl h-48">
						<div class="text-center space-y-3">
							<svg class="w-8 h-8 animate-spin mx-auto text-(--color-brand)" 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>
							<p class="text-sm text-(--color-muted)">Generating description…</p>
						</div>
					</div>
				{:else}
					<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) border-dashed rounded-xl h-48">
						<p class="text-sm text-(--color-muted)">Proposed description will appear here</p>
					</div>
				{/if}
			</div>
		</div>
	{/if}

	<!-- ── Tagline panel ──────────────────────────────────────────────────────── -->
	{#if activeTab === 'tagline'}
		<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
			<div class="space-y-4">
				<div class="space-y-1">
					<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="t-slug">Book slug</label>
					<div class="relative">
						<input id="t-slug" type="text" bind:value={tAC.inputVal} oninput={onTSlugInput}
							onfocus={() => (tAC.focused = true)} onblur={() => setTimeout(() => { tAC.focused = false; }, 150)}
							placeholder="e.g. shadow-slave" autocomplete="off"
							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)" />
						{#if tAC.focused && tAC.suggestions.length > 0}
							<ul class="absolute z-50 top-full left-0 right-0 mt-1 bg-(--color-surface-2) border border-(--color-border) rounded-lg shadow-xl overflow-hidden max-h-56 overflow-y-auto">
								{#each tAC.suggestions as b}
									<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
									<li role="option" aria-selected={tSlug === b.slug} onmousedown={() => selectTBook(b)}
										class="px-3 py-2 cursor-pointer hover:bg-(--color-surface-3) transition-colors">
										<p class="text-sm text-(--color-text) truncate">{b.title}</p>
										<p class="text-xs text-(--color-muted) font-mono">{b.slug}</p>
									</li>
								{/each}
							</ul>
						{/if}
					</div>
				</div>
				<button onclick={generateTagline} disabled={!tCanGenerate}
					class="w-full 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 justify-center gap-2">
					{#if tGenerating}
						<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>Generating…
					{:else}Generate tagline{/if}
				</button>
				{#if tError}<p class="text-sm text-(--color-danger) bg-(--color-danger)/10 rounded-lg px-3 py-2">{tError}</p>{/if}
			</div>
		<div>
			{#if tResult}
				<div class="space-y-2">
					<p class="text-xs font-semibold text-(--color-muted) uppercase tracking-widest">
						Tagline{#if tUsedModel}<span class="normal-case font-normal"> · {tUsedModel.split('/').pop()}</span>{/if}
					</p>
					<input
						type="text"
						bind:value={tEdited}
						class="w-full bg-(--color-surface) border border-(--color-brand)/40 rounded-xl px-4 py-3 text-base italic text-(--color-text) focus:outline-none focus:ring-1 focus:ring-(--color-brand)"
					/>
					<p class="text-xs text-(--color-muted)">Note: tagline field not yet in the data model — copy and use manually.</p>
					<div class="flex gap-2">
						<button
							onclick={copyTagline}
							class="px-3 py-1.5 rounded-md text-xs font-medium transition-colors
								{tCopied ? 'bg-green-400/10 text-green-400 border border-green-400/30' : 'bg-(--color-surface-2) text-(--color-muted) hover:text-(--color-text) border border-(--color-border)'}"
						>
							{tCopied ? 'Copied ✓' : 'Copy to clipboard'}
						</button>
						<button onclick={() => { tResult = ''; tEdited = ''; }} class="text-xs text-(--color-muted) hover:text-(--color-text) transition-colors">Clear</button>
					</div>
				</div>
			{:else if tGenerating}
				<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) rounded-xl h-28">
					<svg class="w-6 h-6 animate-spin text-(--color-brand)" 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>
				</div>
			{:else}
				<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) border-dashed rounded-xl h-28">
					<p class="text-sm text-(--color-muted)">Tagline will appear here</p>
				</div>
			{/if}
		</div>
	</div>
{/if}

	<!-- ── Genres panel ────────────────────────────────────────────────────────── -->
	{#if activeTab === 'genres'}
		<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
			<div class="space-y-4">
				<div class="space-y-1">
					<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="g-slug">Book slug</label>
					<div class="relative">
						<input id="g-slug" type="text" bind:value={gAC.inputVal} oninput={onGSlugInput}
							onfocus={() => (gAC.focused = true)} onblur={() => setTimeout(() => { gAC.focused = false; }, 150)}
							placeholder="e.g. shadow-slave" autocomplete="off"
							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)" />
						{#if gAC.focused && gAC.suggestions.length > 0}
							<ul class="absolute z-50 top-full left-0 right-0 mt-1 bg-(--color-surface-2) border border-(--color-border) rounded-lg shadow-xl overflow-hidden max-h-56 overflow-y-auto">
								{#each gAC.suggestions as b}
									<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
									<li role="option" aria-selected={gSlug === b.slug} onmousedown={() => selectGBook(b)}
										class="px-3 py-2 cursor-pointer hover:bg-(--color-surface-3) transition-colors">
										<p class="text-sm text-(--color-text) truncate">{b.title}</p>
										<p class="text-xs text-(--color-muted) font-mono">{b.slug}</p>
									</li>
								{/each}
							</ul>
						{/if}
					</div>
				</div>
				<button onclick={generateGenres} disabled={!gCanGenerate}
					class="w-full 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 justify-center gap-2">
					{#if gGenerating}
						<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>Generating…
					{:else}Suggest genres{/if}
				</button>
				{#if gError}<p class="text-sm text-(--color-danger) bg-(--color-danger)/10 rounded-lg px-3 py-2">{gError}</p>{/if}
			</div>
			<div class="space-y-4">
				{#if gProposed.length > 0}
					<div class="space-y-3">
						{#if gCurrent.length > 0}
							<div>
								<p class="text-xs font-semibold text-(--color-muted) uppercase tracking-widest mb-1.5">Current genres</p>
								<div class="flex flex-wrap gap-1.5">
									{#each gCurrent as g}
										<span class="px-2.5 py-0.5 rounded-full text-xs bg-(--color-surface-2) text-(--color-muted) border border-(--color-border)">{g}</span>
									{/each}
								</div>
							</div>
						{/if}
						<div>
							<p class="text-xs font-semibold text-(--color-muted) uppercase tracking-widest mb-1.5">
								Proposed{#if gUsedModel}<span class="normal-case font-normal"> · {gUsedModel.split('/').pop()}</span>{/if}
							</p>
							<div class="flex flex-wrap gap-1.5">
								{#each gEdited as g, i}
									<input type="text" bind:value={gEdited[i]}
										class="px-2.5 py-0.5 rounded-full text-xs bg-(--color-brand)/10 border border-(--color-brand)/30 text-(--color-text) focus:outline-none focus:ring-1 focus:ring-(--color-brand)" />
								{/each}
								<button onclick={() => { gEdited = [...gEdited, '']; }}
									class="px-2.5 py-0.5 rounded-full text-xs bg-(--color-surface-2) border border-(--color-border) text-(--color-muted) hover:text-(--color-text) transition-colors">+ add</button>
							</div>
						</div>
						<div class="flex gap-2 pt-1">
							<button onclick={applyGenres} disabled={!gCanApply}
								class="flex-1 py-2 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">
								{gApplying ? 'Saving…' : gApplySuccess ? 'Saved ✓' : 'Apply genres'}
							</button>
							<button onclick={() => { gProposed = []; gEdited = []; gApplySuccess = false; }}
								class="px-4 py-2 rounded-lg bg-(--color-surface-2) text-(--color-muted) text-sm hover:text-(--color-text) transition-colors border border-(--color-border)">
								Clear
							</button>
						</div>
						{#if gApplyError}<p class="text-sm text-(--color-danger) bg-(--color-danger)/10 rounded-lg px-3 py-2">{gApplyError}</p>{/if}
						{#if gApplySuccess}<p class="text-sm text-green-400 bg-green-400/10 rounded-lg px-3 py-2">Genres saved successfully.</p>{/if}
					</div>
				{:else if gGenerating}
					<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) rounded-xl h-32">
						<svg class="w-6 h-6 animate-spin text-(--color-brand)" 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>
					</div>
				{:else}
					<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) border-dashed rounded-xl h-32">
						<p class="text-sm text-(--color-muted)">Genre suggestions will appear here</p>
					</div>
				{/if}
			</div>
		</div>
	{/if}

	<!-- ── Content warnings panel ─────────────────────────────────────────────── -->
	{#if activeTab === 'warnings'}
		<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
			<div class="space-y-4">
				<div class="space-y-1">
					<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="w-slug">Book slug</label>
					<div class="relative">
						<input id="w-slug" type="text" bind:value={wAC.inputVal} oninput={onWSlugInput}
							onfocus={() => (wAC.focused = true)} onblur={() => setTimeout(() => { wAC.focused = false; }, 150)}
							placeholder="e.g. shadow-slave" autocomplete="off"
							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)" />
						{#if wAC.focused && wAC.suggestions.length > 0}
							<ul class="absolute z-50 top-full left-0 right-0 mt-1 bg-(--color-surface-2) border border-(--color-border) rounded-lg shadow-xl overflow-hidden max-h-56 overflow-y-auto">
								{#each wAC.suggestions as b}
									<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
									<li role="option" aria-selected={wSlug === b.slug} onmousedown={() => selectWBook(b)}
										class="px-3 py-2 cursor-pointer hover:bg-(--color-surface-3) transition-colors">
										<p class="text-sm text-(--color-text) truncate">{b.title}</p>
										<p class="text-xs text-(--color-muted) font-mono">{b.slug}</p>
									</li>
								{/each}
							</ul>
						{/if}
					</div>
				</div>
				<button onclick={generateWarnings} disabled={!wCanGenerate}
					class="w-full 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 justify-center gap-2">
					{#if wGenerating}
						<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>Detecting…
					{:else}Detect content warnings{/if}
				</button>
				{#if wError}<p class="text-sm text-(--color-danger) bg-(--color-danger)/10 rounded-lg px-3 py-2">{wError}</p>{/if}
			</div>
		<div>
			{#if wWarnings.length > 0}
				<div class="space-y-2">
					<p class="text-xs font-semibold text-(--color-muted) uppercase tracking-widest">
						Detected warnings{#if wUsedModel}<span class="normal-case font-normal"> · {wUsedModel.split('/').pop()}</span>{/if}
					</p>
					<div class="flex flex-wrap gap-2">
						{#each wWarnings as w}
							<span class="px-3 py-1 rounded-full text-sm bg-amber-400/10 text-amber-400 border border-amber-400/30">{w}</span>
						{/each}
					</div>
					<p class="text-xs text-(--color-muted)">Note: content warnings field not yet in the data model — copy and use manually.</p>
					<div class="flex gap-2">
						<button
							onclick={copyWarnings}
							class="px-3 py-1.5 rounded-md text-xs font-medium transition-colors
								{wCopied ? 'bg-green-400/10 text-green-400 border border-green-400/30' : 'bg-(--color-surface-2) text-(--color-muted) hover:text-(--color-text) border border-(--color-border)'}"
						>
							{wCopied ? 'Copied ✓' : 'Copy to clipboard'}
						</button>
						<button onclick={() => { wWarnings = []; wUsedModel = ''; }} class="text-xs text-(--color-muted) hover:text-(--color-text) transition-colors">Clear</button>
					</div>
				</div>
			{:else if wWarnings.length === 0 && wUsedModel}
				<div class="bg-green-400/10 border border-green-400/30 rounded-xl p-4">
					<p class="text-sm text-green-400">No content warnings detected.</p>
				</div>
			{:else if wGenerating}
				<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) rounded-xl h-28">
					<svg class="w-6 h-6 animate-spin text-(--color-brand)" 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>
				</div>
			{:else}
				<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) border-dashed rounded-xl h-28">
					<p class="text-sm text-(--color-muted)">Warnings will appear here</p>
				</div>
			{/if}
		</div>
	</div>
{/if}

	<!-- ── Quality score panel ────────────────────────────────────────────────── -->
	{#if activeTab === 'quality'}
		<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
			<div class="space-y-4">
				<div class="space-y-1">
					<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="q-slug">Book slug</label>
					<div class="relative">
						<input id="q-slug" type="text" bind:value={qAC.inputVal} oninput={onQSlugInput}
							onfocus={() => (qAC.focused = true)} onblur={() => setTimeout(() => { qAC.focused = false; }, 150)}
							placeholder="e.g. shadow-slave" autocomplete="off"
							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)" />
						{#if qAC.focused && qAC.suggestions.length > 0}
							<ul class="absolute z-50 top-full left-0 right-0 mt-1 bg-(--color-surface-2) border border-(--color-border) rounded-lg shadow-xl overflow-hidden max-h-56 overflow-y-auto">
								{#each qAC.suggestions as b}
									<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
									<li role="option" aria-selected={qSlug === b.slug} onmousedown={() => selectQBook(b)}
										class="px-3 py-2 cursor-pointer hover:bg-(--color-surface-3) transition-colors">
										<p class="text-sm text-(--color-text) truncate">{b.title}</p>
										<p class="text-xs text-(--color-muted) font-mono">{b.slug}</p>
									</li>
								{/each}
							</ul>
						{/if}
					</div>
				</div>
				<button onclick={generateQualityScore} disabled={!qCanGenerate}
					class="w-full 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 justify-center gap-2">
					{#if qGenerating}
						<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>Scoring…
					{:else}Score description quality{/if}
				</button>
				{#if qError}<p class="text-sm text-(--color-danger) bg-(--color-danger)/10 rounded-lg px-3 py-2">{qError}</p>{/if}
			</div>
			<div>
				{#if qScore > 0}
					<div class="space-y-3">
						<p class="text-xs font-semibold text-(--color-muted) uppercase tracking-widest">
							Quality score{#if qUsedModel}<span class="normal-case font-normal"> · {qUsedModel.split('/').pop()}</span>{/if}
						</p>
						<div class="bg-(--color-surface) border border-(--color-border) rounded-xl p-4 space-y-3">
							<div class="flex items-center gap-3">
								<span class="text-4xl font-bold text-(--color-brand)">{qScore}</span>
								<div class="flex gap-1">
									{#each [1,2,3,4,5] as star}
										<span class="text-xl {star <= qScore ? 'text-amber-400' : 'text-(--color-border)'}">★</span>
									{/each}
								</div>
							</div>
							{#if qFeedback}
								<p class="text-sm text-(--color-muted)">{qFeedback}</p>
							{/if}
						</div>
						<button onclick={() => { qScore = 0; qFeedback = ''; qUsedModel = ''; }} class="text-xs text-(--color-muted) hover:text-(--color-text) transition-colors">Clear</button>
					</div>
				{:else if qGenerating}
					<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) rounded-xl h-28">
						<svg class="w-6 h-6 animate-spin text-(--color-brand)" 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>
					</div>
				{:else}
					<div class="flex items-center justify-center bg-(--color-surface) border border-(--color-border) border-dashed rounded-xl h-28">
						<p class="text-sm text-(--color-muted)">Quality score will appear here</p>
					</div>
				{/if}
			</div>
		</div>
	{/if}
</div>
