<script lang="ts">
	import { browser } from '$app/environment';
	import { goto } from '$app/navigation';
	import type { PageData } from './$types';
	import type { ImageModelInfo, BookSummary } from './+page.server';

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

	// ── Form state ───────────────────────────────────────────────────────────────
	type ImageType = 'cover' | 'chapter';

	const CONFIG_KEY = 'admin_image_gen_config_v1';

	interface SavedConfig {
		selectedModel: string;
		numSteps: number;
		guidance: number;
		strength: number;
		width: number;
		height: number;
		showAdvanced: boolean;
	}

	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, numSteps, guidance, strength, width, height, showAdvanced };
		localStorage.setItem(CONFIG_KEY, JSON.stringify(cfg));
	}

	const saved = loadConfig();

	let imageType = $state<ImageType>('cover');
	let slug = $state('');
	let chapter = $state<number>(1);
	let selectedModel = $state(saved.selectedModel ?? '');
	let prompt = $state('');
	let referenceFile = $state<File | null>(null);
	let referencePreviewUrl = $state('');
	let useCoverAsRef = $state(false);

	// Advanced
	let showAdvanced = $state(saved.showAdvanced ?? false);
	let numSteps = $state(saved.numSteps ?? 20);
	let guidance = $state(saved.guidance ?? 7.5);
	let strength = $state(saved.strength ?? 0.75);
	let width = $state(saved.width ?? 1024);
	let height = $state(saved.height ?? 1024);

	// Persist config on change
	$effect(() => {
		void selectedModel; void numSteps; void guidance; void strength;
		void width; void height; void showAdvanced;
		saveConfig();
	});

	// ── Book autocomplete ────────────────────────────────────────────────────────
	// Books arrive as a streamed promise — start empty and populate on resolve.
	let books = $state<BookSummary[]>([]);
	$effect(() => {
		data.books.then((resolved) => { books = resolved; });
	});
	let slugInput = $state('');
	let slugFocused = $state(false);
	let selectedBook = $state<BookSummary | null>(null);

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

	function selectBook(b: BookSummary) {
		selectedBook = b;
		slug = b.slug;
		slugInput = b.slug;
		slugFocused = false;
		// Reset cover-as-ref if no cover
		if (!b.cover) useCoverAsRef = false;
	}

	function onSlugInput() {
		slug = slugInput;
		// If user edits away from selected book slug, deselect
		if (selectedBook && slugInput !== selectedBook.slug) {
			selectedBook = null;
			useCoverAsRef = false;
		}
	}

	// When useCoverAsRef toggled on, load the book cover as reference
	$effect(() => {
		if (!browser) return;
		if (!useCoverAsRef || !selectedBook?.cover) {
			if (useCoverAsRef) useCoverAsRef = false;
			return;
		}
		// Fetch the cover image and set as referenceFile
		(async () => {
			try {
				const res = await fetch(selectedBook!.cover);
				const blob = await res.blob();
				const ext = blob.type === 'image/jpeg' ? 'jpg' : blob.type === 'image/webp' ? 'webp' : 'png';
				const file = new File([blob], `${selectedBook!.slug}-cover.${ext}`, { type: blob.type });
				handleReferenceFile(file);
			} catch {
				useCoverAsRef = false;
			}
		})();
	});

	// ── Generation state ─────────────────────────────────────────────────────────
	let generating = $state(false);
	let genError = $state('');

	// ── Generate (async: fire-and-forget → redirect to ai-jobs) ─────────────────
	let canGenerate = $derived(prompt.trim().length > 0 && slug.trim().length > 0 && !generating);

	async function generate() {
		if (!canGenerate) return;
		generating = true;
		genError = '';

		try {
			const payload = {
				prompt: prompt.trim(),
				model: selectedModel,
				type: imageType,
				slug: slug.trim(),
				chapter: imageType === 'chapter' ? chapter : 0,
				num_steps: numSteps,
				guidance,
				strength,
				width,
				height
			};

			let res: Response;
			if (referenceFile && selectedModelInfo?.supports_ref) {
				const fd = new FormData();
				fd.append('json', JSON.stringify(payload));
				fd.append('reference', referenceFile);
				res = await fetch('/api/admin/image-gen/async', { method: 'POST', body: fd });
			} else {
				res = await fetch('/api/admin/image-gen/async', {
					method: 'POST',
					headers: { 'Content-Type': 'application/json' },
					body: JSON.stringify(payload)
				});
			}

			const body = await res.json().catch(() => ({}));
			if (!res.ok) {
				genError = body.error ?? body.message ?? `Error ${res.status}`;
				return;
			}

		// Navigate to ai-jobs so the admin can monitor progress and review.
		await goto('/admin/ai-jobs');
		} catch {
			genError = 'Network error.';
		} finally {
			generating = false;
		}
	}

	// ── Model helpers ────────────────────────────────────────────────────────────
	// svelte-ignore state_referenced_locally
	const models: ImageModelInfo[] = data.models ?? [];

	let coverModels = $derived(models.filter((m) => m.recommended_for.includes('cover')));
	let chapterModels = $derived(models.filter((m) => m.recommended_for.includes('chapter')));
	let otherModels = $derived(
		models.filter(
			(m) => !m.recommended_for.includes('cover') && !m.recommended_for.includes('chapter')
		)
	);

	// ── Auto-select default model when type changes ──────────────────────────────
	$effect(() => {
		const preferred = imageType === 'cover' ? coverModels : chapterModels;
		if (!selectedModel && preferred.length > 0) {
			selectedModel = preferred[0].id;
		}
	});

	$effect(() => {
		void imageType;
		const preferred = imageType === 'cover' ? coverModels : chapterModels;
		if (preferred.length > 0) {
			const current = models.find((m) => m.id === selectedModel);
			if (!current || !current.recommended_for.includes(imageType)) {
				selectedModel = preferred[0].id;
			}
		}
	});

	// ── Prompt templates ────────────────────────────────────────────────────────
	let promptTemplate = $derived(
		imageType === 'cover'
			? `Book cover for "${slugInput || 'untitled novel'}", a fantasy adventure novel. Epic scene with dramatic lighting, professional book cover art, cinematic composition, highly detailed, 4K.`
			: `Illustration for chapter ${chapter} of "${slugInput || 'untitled novel'}". Dramatic moment, vivid colors, anime-inspired style, detailed background, cinematic lighting.`
	);

	function applyTemplate() {
		prompt = promptTemplate;
	}

	function injectDescription() {
		const desc = selectedBook?.summary?.trim();
		if (!desc) return;
		const snippet = desc.length > 300 ? desc.slice(0, 300) + '…' : desc;
		prompt = prompt ? `${prompt}\n\nBook description: ${snippet}` : `Book description: ${snippet}`;
	}

	// ── Auto-prompt ──────────────────────────────────────────────────────────────
	let autoPrompting = $state(false);
	let autoPromptError = $state('');

	async function autoGeneratePrompt() {
		if (!slug.trim() || autoPrompting) return;
		autoPrompting = true;
		autoPromptError = '';
		try {
			const res = await fetch('/api/admin/image-gen/auto-prompt', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ slug: slug.trim(), type: imageType, chapter: imageType === 'chapter' ? chapter : 0 })
			});
			const body = await res.json().catch(() => ({}));
			if (!res.ok) {
				autoPromptError = body.error ?? `Error ${res.status}`;
				return;
			}
			prompt = body.prompt ?? '';
		} catch {
			autoPromptError = 'Network error.';
		} finally {
			autoPrompting = false;
		}
	}

	// ── Style presets ────────────────────────────────────────────────────────────
	const PRESETS_KEY = 'admin_image_gen_presets_v1';

	interface StylePreset {
		name: string;
		prompt: string;
	}

	function loadPresets(): StylePreset[] {
		if (!browser) return [];
		try {
			const raw = localStorage.getItem(PRESETS_KEY);
			return raw ? (JSON.parse(raw) as StylePreset[]) : [];
		} catch { return []; }
	}

	function savePresets(p: StylePreset[]) {
		if (!browser) return;
		localStorage.setItem(PRESETS_KEY, JSON.stringify(p));
	}

	let presets = $state<StylePreset[]>(loadPresets());
	let newPresetName = $state('');
	let showPresets = $state(false);

	function saveCurrentAsPreset() {
		const name = newPresetName.trim();
		if (!name || !prompt.trim()) return;
		const existing = presets.findIndex((p) => p.name === name);
		const updated = [...presets];
		if (existing >= 0) updated[existing] = { name, prompt };
		else updated.push({ name, prompt });
		presets = updated;
		savePresets(updated);
		newPresetName = '';
	}

	function applyPreset(p: StylePreset) {
		prompt = p.prompt;
		showPresets = false;
	}

	function deletePreset(name: string) {
		const updated = presets.filter((p) => p.name !== name);
		presets = updated;
		savePresets(updated);
	}

	// ── Reference image handling ─────────────────────────────────────────────────
	let dragOver = $state(false);

	function handleReferenceFile(file: File | null) {
		referenceFile = file;
		if (referencePreviewUrl) URL.revokeObjectURL(referencePreviewUrl);
		referencePreviewUrl = file ? URL.createObjectURL(file) : '';
	}

	function onFileInput(e: Event) {
		const input = e.target as HTMLInputElement;
		handleReferenceFile(input.files?.[0] ?? null);
		useCoverAsRef = false;
	}

	function onDrop(e: DragEvent) {
		e.preventDefault();
		dragOver = false;
		const file = e.dataTransfer?.files[0];
		if (file && file.type.startsWith('image/')) {
			handleReferenceFile(file);
			useCoverAsRef = false;
		}
	}

	function clearReference() {
		handleReferenceFile(null);
		useCoverAsRef = false;
		const input = document.getElementById('ref-file-input') as HTMLInputElement | null;
		if (input) input.value = '';
	}

	function fmtBytes(bytes: number): string {
		if (bytes < 1024) return `${bytes} B`;
		if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
		return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
	}

	// ── Selected model info ──────────────────────────────────────────────────────
	let selectedModelInfo = $derived(models.find((m) => m.id === selectedModel) ?? null);
	let refWarning = $derived(
		referenceFile && selectedModelInfo && !selectedModelInfo.supports_ref
			? `${selectedModelInfo.label} does not support reference images. The reference will be ignored.`
			: ''
	);
</script>

<svelte:head>
	<title>Image Gen — Admin</title>
</svelte:head>

<div class="space-y-6 max-w-6xl">
	<!-- Header -->
	<div>
		<h1 class="text-2xl font-bold text-(--color-text)">Image Generation</h1>
		<p class="text-(--color-muted) text-sm mt-1">
			Generate book covers and chapter images using Cloudflare Workers AI.
		</p>
	</div>

	<!-- Layout: form + result side by side on large screens -->
	<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
		<!-- ── Left: Form panel ──────────────────────────────────────────────────── -->
		<div class="space-y-4">
			<!-- Type toggle -->
			<div class="flex gap-1 bg-(--color-surface-2) rounded-lg p-1 w-fit border border-(--color-border)">
				{#each (['cover', 'chapter'] as const) as t}
					<button
						onclick={() => (imageType = t)}
						class="px-4 py-1.5 rounded-md text-sm font-medium transition-colors
							{imageType === t
								? 'bg-(--color-surface-3) text-(--color-text)'
								: 'text-(--color-muted) hover:text-(--color-text)'}"
					>
						{t === 'cover' ? 'Cover' : 'Chapter Image'}
					</button>
				{/each}
			</div>

			<!-- Slug + chapter -->
			<div class="flex gap-3">
				<div class="flex-1 min-w-0 space-y-1">
					<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="slug-input">
						Book slug
					</label>
					<!-- Autocomplete wrapper -->
					<div class="relative">
						<input
							id="slug-input"
							type="text"
							bind:value={slugInput}
							oninput={onSlugInput}
							onfocus={() => (slugFocused = true)}
							onblur={() => setTimeout(() => { slugFocused = 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 slugFocused && bookSuggestions.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 bookSuggestions as b}
									<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
									<li
										role="option"
										aria-selected={selectedBook?.slug === b.slug}
										onmousedown={() => selectBook(b)}
										class="flex items-center gap-3 px-3 py-2 cursor-pointer hover:bg-(--color-surface-3) transition-colors"
									>
										{#if b.cover}
											<img src={b.cover} alt="" class="w-8 h-10 object-cover rounded shrink-0" />
										{:else}
											<div class="w-8 h-10 rounded bg-(--color-surface-3) shrink-0"></div>
										{/if}
										<div class="min-w-0">
											<p class="text-sm text-(--color-text) truncate">{b.title}</p>
											<p class="text-xs text-(--color-muted) truncate font-mono">{b.slug}</p>
										</div>
									</li>
								{/each}
							</ul>
						{/if}
					</div>
					<!-- Book info pill when a book is selected -->
					{#if selectedBook}
						<div class="flex items-center gap-2 mt-1">
							<span class="text-xs text-(--color-success) flex items-center gap-1">
								<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
									<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
								</svg>
								{selectedBook.title}
							</span>
							{#if selectedBook.summary}
								<button
									onclick={injectDescription}
									class="text-xs text-(--color-brand) hover:text-(--color-brand-dim) transition-colors"
									title="Append book description to prompt"
								>
									+ inject description
								</button>
							{/if}
						</div>
					{/if}
				</div>

				{#if imageType === 'chapter'}
					<div class="w-24 space-y-1 shrink-0">
						<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="chapter-input">
							Chapter
						</label>
						<input
							id="chapter-input"
							type="number"
							bind:value={chapter}
							min="1"
							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>
				{/if}
			</div>

			<!-- Model selector -->
			<div class="space-y-1">
				<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)"
				>
					{#if coverModels.length > 0}
						<optgroup label="Recommended for covers">
							{#each coverModels as m}
								<option value={m.id}>
									{m.label} — {m.provider}{m.supports_ref ? ' ★ref' : ''}
								</option>
							{/each}
						</optgroup>
					{/if}
					{#if chapterModels.length > 0}
						<optgroup label="Recommended for chapters">
							{#each chapterModels as m}
								<option value={m.id}>
									{m.label} — {m.provider}{m.supports_ref ? ' ★ref' : ''}
								</option>
							{/each}
						</optgroup>
					{/if}
					{#if otherModels.length > 0}
						<optgroup label="All models">
							{#each otherModels as m}
								<option value={m.id}>
									{m.label} — {m.provider}{m.supports_ref ? ' ★ref' : ''}
								</option>
							{/each}
						</optgroup>
					{/if}
				</select>
				{#if selectedModelInfo}
					<p class="text-xs text-(--color-muted)">{selectedModelInfo.description}</p>
				{/if}
				{#if refWarning}
					<p class="text-xs text-amber-400">{refWarning}</p>
				{/if}
			</div>

			<!-- Prompt -->
			<div class="space-y-2">
				<div class="flex items-center justify-between">
					<label class="text-xs font-medium text-(--color-muted) uppercase tracking-wide" for="prompt-input">
						Prompt
					</label>
					<div class="flex flex-wrap items-center gap-3">
						{#if slug.trim()}
							<button onclick={autoGeneratePrompt} disabled={autoPrompting}
								class="text-xs text-(--color-brand) hover:text-(--color-brand-dim) transition-colors disabled:opacity-50">
								{autoPrompting ? 'Generating…' : 'Auto-prompt'}
							</button>
						{/if}
						{#if selectedBook?.summary}
							<button onclick={injectDescription} class="text-xs text-(--color-muted) hover:text-(--color-text) transition-colors">
								Inject description
							</button>
						{/if}
						<button onclick={applyTemplate} class="text-xs text-(--color-muted) hover:text-(--color-text) transition-colors">
							Use template
						</button>
						{#if presets.length > 0}
							<button onclick={() => (showPresets = !showPresets)} class="text-xs text-(--color-muted) hover:text-(--color-text) transition-colors">
								Presets ({presets.length})
							</button>
						{/if}
					</div>
				</div>

				{#if showPresets && presets.length > 0}
					<div class="bg-(--color-surface) border border-(--color-border) rounded-lg p-3 space-y-1.5">
						{#each presets as p}
							<div class="flex items-center gap-2 group">
								<button onclick={() => applyPreset(p)} class="flex-1 text-left text-sm text-(--color-text) hover:text-(--color-brand) transition-colors truncate" title={p.prompt}>{p.name}</button>
								<button onclick={() => deletePreset(p.name)} class="text-xs text-(--color-muted) hover:text-(--color-danger) transition-colors opacity-0 group-hover:opacity-100">delete</button>
							</div>
						{/each}
					</div>
				{/if}

				<textarea
					id="prompt-input"
					bind:value={prompt}
					rows="5"
					placeholder="Describe the image to generate…"
					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>
				{#if autoPromptError}
					<p class="text-xs text-(--color-danger)">{autoPromptError}</p>
				{/if}

				<div class="flex gap-2">
					<input type="text" bind:value={newPresetName} placeholder="Preset name…"
						class="flex-1 bg-(--color-surface-2) border border-(--color-border) rounded-lg px-3 py-1.5 text-(--color-text) text-xs placeholder-zinc-500 focus:outline-none focus:ring-1 focus:ring-(--color-brand)" />
					<button onclick={saveCurrentAsPreset} disabled={!newPresetName.trim() || !prompt.trim()}
						class="px-3 py-1.5 rounded-lg bg-(--color-surface-2) border border-(--color-border) text-xs text-(--color-muted) hover:text-(--color-text) transition-colors disabled:opacity-40 disabled:cursor-not-allowed">
						Save preset
					</button>
				</div>
			</div>

			<!-- Reference image drop zone -->
			<div class="space-y-1">
				<div class="flex items-center justify-between">
					<p class="text-xs font-medium text-(--color-muted) uppercase tracking-wide">
						Reference image <span class="normal-case font-normal text-(--color-muted)">(optional, img2img)</span>
					</p>
					{#if selectedBook?.cover && selectedModelInfo?.supports_ref}
						<div class="flex items-center gap-1.5 cursor-pointer select-none">
							<button
								type="button"
								role="switch"
								aria-checked={useCoverAsRef}
								aria-label="Use book cover as reference"
								onclick={() => (useCoverAsRef = !useCoverAsRef)}
								class="w-8 h-4 rounded-full transition-colors relative focus:outline-none focus:ring-1 focus:ring-(--color-brand) {useCoverAsRef ? 'bg-(--color-brand)' : 'bg-(--color-surface-3)'}"
							>
								<span class="absolute top-0.5 left-0.5 w-3 h-3 rounded-full bg-white transition-transform {useCoverAsRef ? 'translate-x-4' : ''}"></span>
							</button>
							<span class="text-xs text-(--color-muted)">Use book cover</span>
						</div>
					{/if}
				</div>
				{#if referenceFile && referencePreviewUrl}
					<div class="flex items-start gap-3 p-3 bg-(--color-surface-2) rounded-lg border border-(--color-border)">
						<img
							src={referencePreviewUrl}
							alt="Reference"
							class="w-16 h-16 object-cover rounded-md shrink-0 border border-(--color-border)"
						/>
						<div class="min-w-0 flex-1 space-y-0.5">
							<p class="text-sm text-(--color-text) truncate">{referenceFile.name}</p>
							<p class="text-xs text-(--color-muted)">{fmtBytes(referenceFile.size)}</p>
							{#if useCoverAsRef}
								<p class="text-xs text-(--color-brand)">Current book cover</p>
							{/if}
						</div>
						<button
							onclick={clearReference}
							class="text-(--color-muted) hover:text-(--color-text) transition-colors shrink-0"
							aria-label="Remove reference image"
						>
							<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
								<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
							</svg>
						</button>
					</div>
				{:else}
					<!-- Drop zone -->
					<label
						class="flex flex-col items-center justify-center gap-2 p-4 border-2 border-dashed rounded-lg cursor-pointer transition-colors
							{dragOver
								? 'border-(--color-brand) bg-(--color-brand)/5'
								: 'border-(--color-border) hover:border-(--color-brand)/50 hover:bg-(--color-surface-2)'}"
						ondragover={(e) => { e.preventDefault(); dragOver = true; }}
						ondragleave={() => { dragOver = false; }}
						ondrop={onDrop}
					>
						<svg class="w-6 h-6 text-(--color-muted)" fill="none" stroke="currentColor" viewBox="0 0 24 24">
							<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
								d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
						</svg>
						<span class="text-xs text-(--color-muted)">Drop image or <span class="text-(--color-brand)">click to browse</span></span>
						<input
							id="ref-file-input"
							type="file"
							accept="image/png,image/jpeg,image/webp"
							onchange={onFileInput}
							class="sr-only"
						/>
					</label>
				{/if}
			</div>

			<!-- Advanced collapsible -->
			<div class="border border-(--color-border) rounded-lg overflow-hidden">
				<button
					onclick={() => (showAdvanced = !showAdvanced)}
					class="w-full flex items-center justify-between px-4 py-2.5 bg-(--color-surface-2) text-sm font-medium text-(--color-muted) hover:text-(--color-text) transition-colors"
				>
					Advanced options
					<svg
						class="w-4 h-4 transition-transform {showAdvanced ? 'rotate-180' : ''}"
						fill="none" stroke="currentColor" viewBox="0 0 24 24"
					>
						<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
					</svg>
				</button>

				{#if showAdvanced}
					<div class="px-4 py-4 bg-(--color-surface) space-y-4">
						<!-- Cloudflare AI timeout warning -->
						{#if selectedModelInfo?.provider === 'cloudflare' || selectedModelInfo?.id.toLowerCase().includes('flux')}
							<p class="text-xs text-amber-400/80 bg-amber-400/10 rounded px-2.5 py-1.5">
								Cloudflare Workers AI has a ~100 s timeout. High step counts on FLUX models may result in a 502 error. Keep steps ≤ 20 to stay within limits.
							</p>
						{/if}

						<!-- num_steps -->
						<div class="space-y-1">
							<div class="flex justify-between">
								<label for="img-steps" class="text-xs text-(--color-muted)">Steps</label>
								<span class="text-xs text-(--color-text) font-mono">{numSteps}</span>
							</div>
							<input id="img-steps" type="range" min="1" max="20" step="1" bind:value={numSteps}
								class="w-full accent-(--color-brand)" />
						</div>

						<!-- guidance -->
						<div class="space-y-1">
							<div class="flex justify-between">
								<label for="img-guidance" class="text-xs text-(--color-muted)">Guidance</label>
								<span class="text-xs text-(--color-text) font-mono">{guidance.toFixed(1)}</span>
							</div>
							<input id="img-guidance" type="range" min="1" max="20" step="0.5" bind:value={guidance}
								class="w-full accent-(--color-brand)" />
						</div>

						<!-- strength (only when reference present) -->
						{#if referenceFile}
							<div class="space-y-1">
								<div class="flex justify-between">
									<label for="img-strength" class="text-xs text-(--color-muted)">Strength</label>
									<span class="text-xs text-(--color-text) font-mono">{strength.toFixed(2)}</span>
								</div>
								<input id="img-strength" type="range" min="0" max="1" step="0.05" bind:value={strength}
									class="w-full accent-(--color-brand)" />
								<p class="text-xs text-(--color-muted)">0 = copy reference · 1 = ignore reference</p>
							</div>
						{/if}

						<!-- width × height -->
						<div class="grid grid-cols-2 gap-3">
							<div class="space-y-1">
								<label class="text-xs text-(--color-muted)" for="width-input">Width</label>
								<input id="width-input" type="number" min="256" max="2048" step="64" bind:value={width}
									class="w-full bg-(--color-surface-2) border border-(--color-border) rounded-md px-3 py-1.5 text-(--color-text) text-sm focus:outline-none focus:ring-1 focus:ring-(--color-brand)" />
							</div>
							<div class="space-y-1">
								<label class="text-xs text-(--color-muted)" for="height-input">Height</label>
								<input id="height-input" type="number" min="256" max="2048" step="64" bind:value={height}
									class="w-full bg-(--color-surface-2) border border-(--color-border) rounded-md px-3 py-1.5 text-(--color-text) text-sm focus:outline-none focus:ring-1 focus:ring-(--color-brand)" />
							</div>
						</div>
					</div>
				{/if}
			</div>

			<!-- Generate button -->
			<button
				onclick={generate}
				disabled={!canGenerate}
				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 generating}
					<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>
					Queuing…
				{:else}
					Generate (async)
				{/if}
			</button>

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

		<!-- ── Right: Info panel ──────────────────────────────────────────────────── -->
		<div class="space-y-4">
			<div class="bg-(--color-surface) border border-(--color-border) rounded-xl p-5 space-y-3">
				<h2 class="text-sm font-semibold text-(--color-text)">How it works</h2>
				<ol class="space-y-2 text-sm text-(--color-muted) list-decimal list-inside">
					<li>Fill in the form and click <strong class="text-(--color-text)">Generate (async)</strong>.</li>
					<li>The job is queued in the background — no waiting on this page.</li>
					<li>You'll be taken to <strong class="text-(--color-text)">AI Jobs</strong> to monitor progress.</li>
					<li>When done, click <strong class="text-(--color-text)">Review</strong> to see the image and approve or discard it.</li>
				</ol>
				<a
					href="/admin/ai-jobs"
					class="mt-3 flex items-center gap-1.5 text-sm text-(--color-brand) hover:underline"
				>
					<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
						<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
							d="M9 5l7 7-7 7" />
					</svg>
					Go to AI Jobs
				</a>
			</div>

			<div class="bg-(--color-surface) border border-(--color-border) rounded-xl p-5 space-y-2">
				<p class="text-xs font-semibold text-(--color-muted) uppercase tracking-wide">Tips</p>
				<ul class="space-y-1.5 text-xs text-(--color-muted)">
					<li>• Use <strong class="text-(--color-text)">Auto-prompt</strong> to generate a prompt from the book's description.</li>
					<li>• FLUX models produce high-quality covers but take 60–120 s — the async path prevents timeouts.</li>
					<li>• Keep steps ≤ 20 on Cloudflare Workers AI to stay within the ~100 s limit.</li>
					<li>• Reference images (img2img) only work with models that show ★ref.</li>
				</ul>
			</div>
		</div>
	</div>
</div>
