<script lang="ts">
	import { enhance } from '$app/forms';
	import type { PageData, ActionData } from './$types';
	import type { TranslationJob } from '$lib/server/pocketbase';
	import * as m from '$lib/paraglide/messages.js';

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

	let jobs = $state<TranslationJob[]>([]);

	// Resolve streamed promise; re-runs on server reloads (invalidateAll)
	$effect(() => {
		Promise.resolve(data.jobs).then((resolved) => { jobs = resolved; });
	});

	// ── Live-poll while any job is in-flight ─────────────────────────────────────
	let hasInFlight = $derived(jobs.some((j) => j.status === 'pending' || j.status === 'running'));

	$effect(() => {
		if (!hasInFlight) return;
		const id = setInterval(async () => {
			const res = await fetch('/api/admin/translation-jobs').catch(() => null);
			if (res?.ok) {
				const body = await res.json().catch(() => null);
				if (body?.jobs) jobs = body.jobs;
			}
		}, 3000);
		return () => clearInterval(id);
	});

	// ── Tabs ─────────────────────────────────────────────────────────────────────
	type Tab = 'enqueue' | 'jobs';
	let activeTab = $state<Tab>('enqueue');

	// ── Bulk enqueue form ─────────────────────────────────────────────────────────
	let slugInput = $state('');
	let langInput = $state('ru');
	let fromInput = $state(1);
	let toInput = $state(1);
	let submitting = $state(false);

	const langs = [
		{ value: 'ru', label: 'Russian (ru)' },
		{ value: 'id', label: 'Indonesian (id)' },
		{ value: 'pt', label: 'Portuguese (pt)' },
		{ value: 'fr', label: 'French (fr)' }
	];

	// ── Jobs helpers ──────────────────────────────────────────────────────────────
	function jobStatusColor(status: string) {
		if (status === 'done') return 'text-green-400';
		if (status === 'running') return 'text-(--color-brand) animate-pulse';
		if (status === 'pending') return 'text-sky-400 animate-pulse';
		if (status === 'failed') return 'text-(--color-danger)';
		if (status === 'cancelled') return 'text-(--color-muted)';
		return 'text-(--color-text)';
	}

	function fmtDate(s: string) {
		if (!s || s.startsWith('0001')) return '—';
		return new Date(s).toLocaleString(undefined, {
			month: 'short',
			day: 'numeric',
			hour: '2-digit',
			minute: '2-digit'
		});
	}

	function duration(started: string, finished: string) {
		if (!started || !finished || started.startsWith('0001') || finished.startsWith('0001'))
			return '—';
		const ms = new Date(finished).getTime() - new Date(started).getTime();
		if (ms < 0) return '—';
		const s = Math.floor(ms / 1000);
		if (s < 60) return `${s}s`;
		const m = Math.floor(s / 60);
		return `${m}m ${s % 60}s`;
	}

	let jobsQ = $state('');
	let jobsStatusFilter = $state('all');
	const JOB_STATUS_OPTIONS = ['all', 'running', 'pending', 'done', 'failed', 'cancelled'] as const;

	let filteredJobs = $derived(
		jobs.filter((j: TranslationJob) => {
			const qLower = jobsQ.trim().toLowerCase();
			const matchesQ =
				!qLower ||
				j.slug.toLowerCase().includes(qLower) ||
				j.lang.toLowerCase().includes(qLower) ||
				j.status.toLowerCase().includes(qLower);
			const matchesStatus = jobsStatusFilter === 'all' || j.status === jobsStatusFilter;
			return matchesQ && matchesStatus;
		})
	);

	let stats = $derived({
		total: jobs.length,
		done: jobs.filter((j: TranslationJob) => j.status === 'done').length,
		failed: jobs.filter((j: TranslationJob) => j.status === 'failed').length,
		running: jobs.filter((j: TranslationJob) => j.status === 'running').length,
		pending: jobs.filter((j: TranslationJob) => j.status === 'pending').length,
		cancelled: jobs.filter((j: TranslationJob) => j.status === 'cancelled').length,
		inFlight: jobs.filter((j: TranslationJob) => j.status === 'pending' || j.status === 'running').length
	});

	// ── Cancel single job ────────────────────────────────────────────────────────
	let cancellingJobIds = $state(new Set<string>());
	let cancelJobErrors: Record<string, string> = $state({});

	async function cancelJob(id: string) {
		if (cancellingJobIds.has(id)) return;
		cancellingJobIds = new Set([...cancellingJobIds, id]);
		delete cancelJobErrors[id];
		try {
			const res = await fetch(`/api/admin/ai-jobs/${encodeURIComponent(id)}/cancel`, { method: 'POST' });
			if (!res.ok) {
				const body = await res.json().catch(() => ({}));
				cancelJobErrors = { ...cancelJobErrors, [id]: body.error ?? `Error ${res.status}` };
			} else {
				jobs = jobs.map((j: TranslationJob) => j.id === id ? { ...j, status: 'cancelled' } : j);
			}
		} catch {
			cancelJobErrors = { ...cancelJobErrors, [id]: 'Network error.' };
		} finally {
			cancellingJobIds = new Set([...cancellingJobIds].filter((x) => x !== id));
		}
	}

	// ── Retry failed job ─────────────────────────────────────────────────────────
	let retryingJobIds = $state(new Set<string>());
	let retryJobErrors: Record<string, string> = $state({});

	async function retryJob(job: TranslationJob) {
		if (retryingJobIds.has(job.id)) return;
		retryingJobIds = new Set([...retryingJobIds, job.id]);
		delete retryJobErrors[job.id];
		try {
			const res = await fetch('/api/admin/translation/bulk', {
				method: 'POST',
				headers: { 'content-type': 'application/json' },
				body: JSON.stringify({ slug: job.slug, lang: job.lang, from: job.chapter, to: job.chapter })
			});
			if (!res.ok) {
				const body = await res.json().catch(() => ({}));
				retryJobErrors = { ...retryJobErrors, [job.id]: body.error ?? `Error ${res.status}` };
			} else {
				jobs = jobs.map((j: TranslationJob) => j.id === job.id ? { ...j, status: 'pending', error_message: '' } : j);
			}
		} catch {
			retryJobErrors = { ...retryJobErrors, [job.id]: 'Network error.' };
		} finally {
			retryingJobIds = new Set([...retryingJobIds].filter((x) => x !== job.id));
		}
	}
</script>

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

<div class="space-y-6">
	<!-- Header -->
	<div>
		<h1 class="text-2xl font-bold text-(--color-text)">{m.admin_translation_heading()}</h1>
		<p class="text-(--color-muted) text-sm mt-1">
			{stats.total} job{stats.total !== 1 ? 's' : ''} &middot;
			<span class="text-green-400">{stats.done} done</span>
			{#if stats.failed > 0}
				&middot; <span class="text-(--color-danger)">{stats.failed} failed</span>
			{/if}
			{#if stats.inFlight > 0}
				&middot; <span class="text-(--color-brand) animate-pulse">{stats.inFlight} in-flight</span>
			{/if}
		</p>
	</div>

	<!-- Tabs -->
	<div class="flex gap-1 bg-(--color-surface-2) rounded-lg p-1 w-fit border border-(--color-border)">
		<button
			onclick={() => (activeTab = 'enqueue')}
			class="px-4 py-1.5 rounded-md text-sm font-medium transition-colors
				{activeTab === 'enqueue' ? 'bg-(--color-surface-3) text-(--color-text)' : 'text-(--color-muted) hover:text-(--color-text)'}"
		>
			{m.admin_translation_tab_enqueue()}
		</button>
		<button
			onclick={() => (activeTab = 'jobs')}
			class="px-4 py-1.5 rounded-md text-sm font-medium transition-colors
				{activeTab === 'jobs' ? 'bg-(--color-surface-3) text-(--color-text)' : 'text-(--color-muted) hover:text-(--color-text)'}"
		>
			{m.admin_translation_tab_jobs()}
			{#if stats.inFlight > 0}
				<span
					class="ml-1.5 inline-flex items-center justify-center w-4 h-4 rounded-full bg-(--color-brand) text-(--color-surface) text-[10px] font-bold"
				>
					{stats.inFlight}
				</span>
			{/if}
		</button>
	</div>

	<!-- ── Enqueue tab ─────────────────────────────────────────────────────────── -->
	{#if activeTab === 'enqueue'}
		<div class="max-w-lg space-y-5">
			<!-- Result banner -->
			{#if form?.success}
				<div class="rounded-lg border border-green-500/40 bg-green-500/10 px-4 py-3 text-sm text-green-400">
					Enqueued {form.enqueued} translation job{form.enqueued !== 1 ? 's' : ''} successfully.
				</div>
			{:else if form?.error}
				<div class="rounded-lg border border-(--color-danger)/40 bg-(--color-danger)/10 px-4 py-3 text-sm text-(--color-danger)">
					{form.error}
				</div>
			{/if}

			<form
				method="POST"
				action="?/bulk"
				use:enhance={() => {
					submitting = true;
					return async ({ update }) => {
						await update();
						submitting = false;
						activeTab = 'jobs';
					};
				}}
				class="space-y-4"
			>
				<!-- Book slug -->
				<div class="space-y-1">
					<label for="slug" class="block text-sm font-medium text-(--color-text)">Book slug</label>
					<input
						id="slug"
						name="slug"
						type="text"
						required
						list="book-slugs"
						bind:value={slugInput}
						placeholder="e.g. the-beginning-after-the-end"
						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)"
					/>
					<datalist id="book-slugs">
						{#each data.books as book}
							<option value={book.slug}>{book.title}</option>
						{/each}
					</datalist>
				</div>

				<!-- Language -->
				<div class="space-y-1">
					<label for="lang" class="block text-sm font-medium text-(--color-text)">Target language</label>
					<select
						id="lang"
						name="lang"
						bind:value={langInput}
						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 langs as l}
							<option value={l.value}>{l.label}</option>
						{/each}
					</select>
				</div>

				<!-- Chapter range -->
				<div class="grid grid-cols-2 gap-3">
					<div class="space-y-1">
						<label for="from" class="block text-sm font-medium text-(--color-text)">From chapter</label>
						<input
							id="from"
							name="from"
							type="number"
							min="1"
							required
							bind:value={fromInput}
							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 for="to" class="block text-sm font-medium text-(--color-text)">To chapter</label>
						<input
							id="to"
							name="to"
							type="number"
							min="1"
							required
							bind:value={toInput}
							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>

				<p class="text-xs text-(--color-muted)">
					Enqueues {Math.max(0, toInput - fromInput + 1)} task{toInput - fromInput + 1 !== 1 ? 's' : ''} — one per chapter. Max 1000 at a time.
				</p>

				<button
					type="submit"
					disabled={submitting}
					class="w-full sm:w-auto px-6 py-2 rounded-lg bg-(--color-brand) text-(--color-surface) text-sm font-semibold hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
				>
					{submitting ? 'Enqueueing…' : 'Enqueue translations'}
				</button>
			</form>
		</div>
	{/if}

	<!-- ── Jobs tab ───────────────────────────────────────────────────────────── -->
	{#if activeTab === 'jobs'}
		<div class="flex flex-wrap gap-3 items-center">
			<input
				type="search"
				bind:value={jobsQ}
				placeholder={m.admin_translation_filter_placeholder()}
				class="flex-1 min-w-48 max-w-sm 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 class="flex gap-1 flex-wrap">
			{#each JOB_STATUS_OPTIONS as s}
				{@const count = s === 'all' ? stats.total : (stats as Record<string, number>)[s] ?? 0}
					<button
						onclick={() => (jobsStatusFilter = s)}
						class="px-2.5 py-1 rounded-md text-xs font-medium transition-colors capitalize
							{jobsStatusFilter === s
								? 'bg-(--color-brand) text-black'
								: 'bg-(--color-surface-2) text-(--color-muted) hover:text-(--color-text)'}"
					>
						{s}{count > 0 ? ` ${count}` : ''}
					</button>
				{/each}
			</div>
		</div>

		{#if filteredJobs.length === 0}
			<p class="text-(--color-muted) text-sm py-8 text-center">
				{jobsQ.trim() ? m.admin_translation_no_matching() : m.admin_translation_no_jobs()}
			</p>
		{:else}
			<!-- Desktop table -->
			<div class="hidden sm:block overflow-x-auto rounded-xl border border-(--color-border)">
				<table class="w-full text-sm">
				<thead class="bg-(--color-surface-2) text-(--color-muted) text-xs uppercase tracking-wide">
					<tr>
						<th class="px-4 py-3 text-left">Book</th>
						<th class="px-4 py-3 text-right">Ch.</th>
						<th class="px-4 py-3 text-left">Lang</th>
						<th class="px-4 py-3 text-left">Status</th>
						<th class="px-4 py-3 text-left">Started</th>
						<th class="px-4 py-3 text-left">Duration</th>
						<th class="px-4 py-3 text-left">Actions</th>
					</tr>
				</thead>
				<tbody class="divide-y divide-(--color-border)/50">
					{#each filteredJobs as job}
						<tr class="bg-(--color-surface) hover:bg-(--color-surface-2)/50 transition-colors">
							<td class="px-4 py-3 text-(--color-text) font-medium">
								<a href="/books/{job.slug}" class="hover:text-(--color-brand) transition-colors"
									>{job.slug}</a
								>
							</td>
							<td class="px-4 py-3 text-right text-(--color-muted)">{job.chapter}</td>
							<td class="px-4 py-3 text-(--color-muted) font-mono text-xs uppercase">{job.lang}</td>
							<td class="px-4 py-3">
								<span class="font-medium {jobStatusColor(job.status)}">{job.status}</span>
							</td>
							<td class="px-4 py-3 text-(--color-muted) whitespace-nowrap">{fmtDate(job.started)}</td>
							<td class="px-4 py-3 text-(--color-muted) whitespace-nowrap"
								>{duration(job.started, job.finished)}</td
							>
						<td class="px-4 py-3">
							{#if job.status === 'pending' || job.status === 'running'}
								<button
									onclick={() => cancelJob(job.id)}
									disabled={cancellingJobIds.has(job.id)}
									class="px-2 py-1 rounded text-xs font-medium bg-(--color-danger)/10 text-(--color-danger) hover:bg-(--color-danger)/20 disabled:opacity-50 transition-colors"
								>
									{cancellingJobIds.has(job.id) ? 'Cancelling…' : 'Cancel'}
								</button>
							{/if}
							{#if job.status === 'failed'}
								<button
									onclick={() => retryJob(job)}
									disabled={retryingJobIds.has(job.id)}
									class="px-2 py-1 rounded text-xs font-medium bg-sky-400/10 text-sky-400 hover:bg-sky-400/20 disabled:opacity-50 transition-colors"
								>
									{retryingJobIds.has(job.id) ? 'Retrying…' : 'Retry ↺'}
								</button>
							{/if}
							{#if cancelJobErrors[job.id]}
								<p class="text-xs text-(--color-danger) mt-1">{cancelJobErrors[job.id]}</p>
							{/if}
							{#if retryJobErrors[job.id]}
								<p class="text-xs text-(--color-danger) mt-1">{retryJobErrors[job.id]}</p>
							{/if}
						</td>
						</tr>
						{#if job.error_message}
							<tr class="bg-(--color-danger)/10">
								<td colspan="7" class="px-4 py-2 text-xs text-(--color-danger) font-mono"
									>{job.error_message}</td
								>
							</tr>
						{/if}
					{/each}
				</tbody>
				</table>
			</div>

			<!-- Mobile cards -->
			<div class="sm:hidden space-y-3">
				{#each filteredJobs as job}
					<div class="bg-(--color-surface) rounded-xl border border-(--color-border) p-4 space-y-2">
						<div class="flex items-start justify-between gap-2">
							<a
								href="/books/{job.slug}"
								class="text-(--color-text) font-medium hover:text-(--color-brand) transition-colors truncate"
							>
								{job.slug}
							</a>
							<span class="shrink-0 text-xs font-semibold {jobStatusColor(job.status)}"
								>{job.status}</span
							>
						</div>
						<div class="grid grid-cols-2 gap-1 text-xs">
							<span class="text-(--color-muted)">Chapter</span><span
								class="text-(--color-muted) text-right">{job.chapter}</span
							>
							<span class="text-(--color-muted)">Lang</span><span
								class="text-(--color-muted) font-mono text-right uppercase">{job.lang}</span
							>
							<span class="text-(--color-muted)">Started</span><span
								class="text-(--color-muted) text-right">{fmtDate(job.started)}</span
							>
							<span class="text-(--color-muted)">Duration</span><span
								class="text-(--color-muted) text-right">{duration(job.started, job.finished)}</span
							>
						</div>
					{#if job.error_message}
						<p class="text-xs text-(--color-danger) font-mono break-all">{job.error_message}</p>
					{/if}
					{#if job.status === 'pending' || job.status === 'running'}
						<button
							onclick={() => cancelJob(job.id)}
							disabled={cancellingJobIds.has(job.id)}
							class="w-full px-3 py-1.5 rounded-lg text-xs font-medium bg-(--color-danger)/10 text-(--color-danger) hover:bg-(--color-danger)/20 disabled:opacity-50 transition-colors"
						>
							{cancellingJobIds.has(job.id) ? 'Cancelling…' : 'Cancel'}
						</button>
					{/if}
					{#if job.status === 'failed'}
						<button
							onclick={() => retryJob(job)}
							disabled={retryingJobIds.has(job.id)}
							class="w-full px-3 py-1.5 rounded-lg text-xs font-medium bg-sky-400/10 text-sky-400 hover:bg-sky-400/20 disabled:opacity-50 transition-colors"
						>
							{retryingJobIds.has(job.id) ? 'Retrying…' : 'Retry ↺'}
						</button>
					{/if}
					{#if cancelJobErrors[job.id]}
						<p class="text-xs text-(--color-danger)">{cancelJobErrors[job.id]}</p>
					{/if}
					{#if retryJobErrors[job.id]}
						<p class="text-xs text-(--color-danger)">{retryJobErrors[job.id]}</p>
					{/if}
					</div>
				{/each}
			</div>
		{/if}
	{/if}
</div>
