<!--
	Library admin dialog. Tabs map 1-to-1 to PhotoPrism's own Library page:
	general settings, manual index, manual import, server error log.

	Each tab owns its own query/mutation pair via TanStack Query so the data
	loads on first open and the rest of the app can read the same caches.
-->
<script lang="ts">
	import { Dialog, Tabs } from 'bits-ui';
	import { createMutation, createQuery, useQueryClient } from '@tanstack/svelte-query';
	import { toast } from 'svelte-sonner';
	import { Loader2, RefreshCw, Settings, X } from 'lucide-svelte';
	import {
		cancelImport,
		cancelIndex,
		getErrors,
		getSettings,
		saveSettings,
		startImport,
		startIndex,
		type ImportBody,
		type IndexBody,
		type PpLogEntry,
		type PpSettings
	} from '$lib/services/photoprism';

	interface Props {
		open: boolean;
		onClose: () => void;
	}
	let { open, onClose }: Props = $props();

	const qc = useQueryClient();

	let activeTab = $state<'library' | 'index' | 'import' | 'logs'>('library');

	// ── Library tab ───────────────────────────────────────────────────────
	// Pull settings only while the dialog is open so we don't keep them
	// warm in the background. Edits work on a local clone; Save POSTs it
	// back wholesale (PhotoPrism deep-merges server-side).
	const settingsQuery = createQuery<PpSettings>(() => ({
		queryKey: ['settings'],
		queryFn: getSettings,
		enabled: open
	}));

	let draft = $state<PpSettings | null>(null);
	$effect(() => {
		if (settingsQuery.data && draft === null) {
			draft = structuredClone(settingsQuery.data);
		}
	});
	// Reset the draft when the dialog closes so the next open re-reads.
	$effect(() => {
		if (!open) draft = null;
	});

	const saveMut = createMutation(() => ({
		mutationFn: (patch: PpSettings) => saveSettings(patch),
		onSuccess: (next) => {
			qc.setQueryData(['settings'], next);
			draft = structuredClone(next);
			toast.success('Settings saved');
		},
		onError: (err) =>
			toast.error(err instanceof Error ? err.message : 'Could not save settings')
	}));

	function resetDraft() {
		if (settingsQuery.data) draft = structuredClone(settingsQuery.data);
	}

	// ── Index tab ─────────────────────────────────────────────────────────
	let indexForm = $state<IndexBody>({ path: '/', rescan: false, cleanup: false });
	const startIndexMut = createMutation(() => ({
		mutationFn: (b: IndexBody) => startIndex(b),
		onSuccess: (r) => toast.success(r.message || 'Indexing complete'),
		onError: (err) =>
			toast.error(err instanceof Error ? err.message : 'Index failed')
	}));
	const cancelIndexMut = createMutation(() => ({
		mutationFn: () => cancelIndex(),
		onSuccess: () => toast.success('Indexing canceled'),
		onError: (err) =>
			toast.error(err instanceof Error ? err.message : 'Cancel failed')
	}));

	// ── Import tab ────────────────────────────────────────────────────────
	let importForm = $state<ImportBody>({ path: '/', move: false, dest: '' });
	const startImportMut = createMutation(() => ({
		mutationFn: (b: ImportBody) => startImport(b),
		onSuccess: (r) => toast.success(r.message || 'Import complete'),
		onError: (err) =>
			toast.error(err instanceof Error ? err.message : 'Import failed')
	}));
	const cancelImportMut = createMutation(() => ({
		mutationFn: () => cancelImport(),
		onSuccess: () => toast.success('Import canceled'),
		onError: (err) =>
			toast.error(err instanceof Error ? err.message : 'Cancel failed')
	}));

	// ── Logs tab ──────────────────────────────────────────────────────────
	// Poll while the Logs tab is showing; pause otherwise so the dialog
	// doesn't burn requests when the user is in another tab.
	const errorsQuery = createQuery<PpLogEntry[]>(() => ({
		queryKey: ['errors'],
		queryFn: () => getErrors({ limit: 200 }),
		enabled: open && activeTab === 'logs',
		refetchInterval: open && activeTab === 'logs' ? 5000 : false
	}));
</script>

<Dialog.Root
	{open}
	onOpenChange={(o) => {
		if (!o) onClose();
	}}
>
	<Dialog.Portal>
		<Dialog.Overlay
			class="fixed inset-0 z-40 bg-background/80 backdrop-blur-sm data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0"
		/>
		<Dialog.Content
			class="fixed left-1/2 top-1/2 z-50 grid w-full max-w-[640px] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border border-border bg-card p-5 text-card-foreground shadow-lg outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95"
		>
			<div class="flex items-start gap-2">
				<Settings class="mt-0.5 h-4 w-4 text-muted-foreground" />
				<div class="flex-1">
					<Dialog.Title class="text-sm font-semibold leading-tight">
						Library settings
					</Dialog.Title>
					<Dialog.Description class="mt-1 text-xs text-muted-foreground">
						Drive PhotoPrism's library, indexer, importer and server log.
					</Dialog.Description>
				</div>
				<Dialog.Close
					class="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
					aria-label="Close"
				>
					<X class="h-3.5 w-3.5" />
				</Dialog.Close>
			</div>

			<Tabs.Root bind:value={activeTab}>
				<Tabs.List
					class="mb-3 flex gap-1 border-b border-border"
				>
					{#each ['library', 'index', 'import', 'logs'] as const as t (t)}
						<Tabs.Trigger
							value={t}
							class="-mb-px border-b-2 border-transparent px-3 py-1.5 text-[12px] capitalize text-muted-foreground hover:text-foreground data-[state=active]:border-primary data-[state=active]:text-foreground"
						>
							{t}
						</Tabs.Trigger>
					{/each}
				</Tabs.List>

				<!-- Library — general settings -->
				<Tabs.Content value="library" class="outline-none">
					{#if settingsQuery.isPending}
						<p class="px-1 text-[12px] text-muted-foreground">Loading settings…</p>
					{:else if settingsQuery.isError}
						<p class="px-1 text-[12px] text-destructive">
							Could not load settings.
						</p>
					{:else if draft}
						<div class="max-h-[55vh] space-y-4 overflow-y-auto pr-1 text-[12px]">
							<section class="space-y-1.5">
								<h3 class="text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
									Indexer defaults
								</h3>
								<label class="flex items-center gap-2">
									<input
										type="checkbox"
										bind:checked={draft.index!.convert}
									/>
									Convert RAW / HEIC on index
								</label>
								<label class="flex items-center gap-2">
									<input
										type="checkbox"
										bind:checked={draft.index!.rescan}
									/>
									Rescan known files
								</label>
								<label class="flex items-center gap-2">
									<input
										type="checkbox"
										bind:checked={draft.index!.skipArchived}
									/>
									Skip archived photos
								</label>
							</section>

							<section class="space-y-1.5">
								<h3 class="text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
									Importer defaults
								</h3>
								<label class="flex items-center gap-2">
									<input type="checkbox" bind:checked={draft.import!.move} />
									Move (instead of copy) on import
								</label>
								<label class="flex flex-col gap-1">
									<span class="text-muted-foreground">Default destination subpath</span>
									<input
										type="text"
										placeholder="e.g. 2026/05"
										bind:value={draft.import!.dest}
										class="rounded border border-input bg-background px-2 py-1 focus:outline-none focus:ring-2 focus:ring-ring"
									/>
								</label>
							</section>

							<section class="space-y-1.5">
								<h3 class="text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
									Stacks
								</h3>
								<label class="flex items-center gap-2">
									<input type="checkbox" bind:checked={draft.stack!.uuid} />
									Stack files sharing a UUID
								</label>
								<label class="flex items-center gap-2">
									<input type="checkbox" bind:checked={draft.stack!.meta} />
									Stack files with matching metadata
								</label>
								<label class="flex items-center gap-2">
									<input type="checkbox" bind:checked={draft.stack!.name} />
									Stack files with matching names
								</label>
							</section>

							<section class="space-y-1.5">
								<h3 class="text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
									Downloads
								</h3>
								<label class="flex items-center gap-2">
									<input
										type="checkbox"
										bind:checked={draft.download!.originals}
									/>
									Originals
								</label>
								<label class="flex items-center gap-2">
									<input
										type="checkbox"
										bind:checked={draft.download!.mediaRaw}
									/>
									Include RAW
								</label>
								<label class="flex items-center gap-2">
									<input
										type="checkbox"
										bind:checked={draft.download!.mediaSidecar}
									/>
									Include sidecar files
								</label>
								<label class="flex items-center gap-2">
									<input
										type="checkbox"
										bind:checked={draft.download!.disabled}
									/>
									Disable downloads entirely
								</label>
							</section>

						</div>

						<div class="mt-4 flex items-center justify-end gap-2">
							<button
								type="button"
								class="rounded border border-border px-3 py-1 text-[12px] hover:bg-accent"
								onclick={resetDraft}
								disabled={saveMut.isPending}
							>
								Revert
							</button>
							<button
								type="button"
								class="flex items-center gap-1.5 rounded bg-primary px-3 py-1 text-[12px] text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
								onclick={() => draft && saveMut.mutate(draft)}
								disabled={saveMut.isPending}
							>
								{#if saveMut.isPending}
									<Loader2 class="h-3 w-3 animate-spin" />
								{/if}
								Save
							</button>
						</div>
					{/if}
				</Tabs.Content>

				<!-- Index — manual indexer run -->
				<Tabs.Content value="index" class="space-y-3 text-[12px] outline-none">
					<p class="text-muted-foreground">
						Rebuilds the search index by walking the originals folder. Safe to
						run while users are connected.
					</p>
					<label class="flex flex-col gap-1">
						<span class="text-muted-foreground">Path</span>
						<input
							type="text"
							bind:value={indexForm.path}
							placeholder="/"
							class="rounded border border-input bg-background px-2 py-1 focus:outline-none focus:ring-2 focus:ring-ring"
						/>
					</label>
					<label class="flex items-center gap-2">
						<input type="checkbox" bind:checked={indexForm.rescan} />
						Rescan files already in the index
					</label>
					<label class="flex items-center gap-2">
						<input type="checkbox" bind:checked={indexForm.cleanup} />
						Clean up missing files
					</label>
					<div class="flex items-center justify-end gap-2 pt-1">
						<button
							type="button"
							class="rounded border border-border px-3 py-1 hover:bg-accent disabled:opacity-50"
							onclick={() => cancelIndexMut.mutate()}
							disabled={cancelIndexMut.isPending || startIndexMut.isPending}
						>
							Cancel current
						</button>
						<button
							type="button"
							class="flex items-center gap-1.5 rounded bg-primary px-3 py-1 text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
							onclick={() => startIndexMut.mutate(indexForm)}
							disabled={startIndexMut.isPending}
						>
							{#if startIndexMut.isPending}
								<Loader2 class="h-3 w-3 animate-spin" />
							{/if}
							Start indexing
						</button>
					</div>
				</Tabs.Content>

				<!-- Import — manual import run -->
				<Tabs.Content value="import" class="space-y-3 text-[12px] outline-none">
					<p class="text-muted-foreground">
						Pulls files from the import folder into the library. With "move"
						enabled, files are deleted from the import folder after a
						successful import.
					</p>
					<label class="flex flex-col gap-1">
						<span class="text-muted-foreground">Source path</span>
						<input
							type="text"
							bind:value={importForm.path}
							placeholder="/"
							class="rounded border border-input bg-background px-2 py-1 focus:outline-none focus:ring-2 focus:ring-ring"
						/>
					</label>
					<label class="flex items-center gap-2">
						<input type="checkbox" bind:checked={importForm.move} />
						Move files (don't copy) after import
					</label>
					<label class="flex flex-col gap-1">
						<span class="text-muted-foreground">Destination subpath (optional)</span>
						<input
							type="text"
							bind:value={importForm.dest}
							placeholder="e.g. 2026/05"
							class="rounded border border-input bg-background px-2 py-1 focus:outline-none focus:ring-2 focus:ring-ring"
						/>
					</label>
					<div class="flex items-center justify-end gap-2 pt-1">
						<button
							type="button"
							class="rounded border border-border px-3 py-1 hover:bg-accent disabled:opacity-50"
							onclick={() => cancelImportMut.mutate()}
							disabled={cancelImportMut.isPending || startImportMut.isPending}
						>
							Cancel current
						</button>
						<button
							type="button"
							class="flex items-center gap-1.5 rounded bg-primary px-3 py-1 text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
							onclick={() => startImportMut.mutate(importForm)}
							disabled={startImportMut.isPending}
						>
							{#if startImportMut.isPending}
								<Loader2 class="h-3 w-3 animate-spin" />
							{/if}
							Start import
						</button>
					</div>
				</Tabs.Content>

				<!-- Logs — recent server errors -->
				<Tabs.Content value="logs" class="space-y-2 text-[12px] outline-none">
					<div class="flex items-center justify-between">
						<p class="text-muted-foreground">
							Most recent PhotoPrism errors and warnings. Auto-refreshes
							every 5 seconds.
						</p>
						<button
							type="button"
							class="flex items-center gap-1 rounded border border-border px-2 py-1 text-[11px] hover:bg-accent"
							onclick={() => qc.invalidateQueries({ queryKey: ['errors'] })}
							disabled={errorsQuery.isFetching}
						>
							<RefreshCw
								class="h-3 w-3 {errorsQuery.isFetching ? 'animate-spin' : ''}"
							/>
							Refresh
						</button>
					</div>
					{#if errorsQuery.isPending}
						<p class="px-1 text-muted-foreground">Loading…</p>
					{:else if errorsQuery.isError}
						<p class="px-1 text-destructive">Could not load error log.</p>
					{:else if (errorsQuery.data ?? []).length === 0}
						<p class="px-1 text-muted-foreground">No errors logged.</p>
					{:else}
						<ul
							class="max-h-[55vh] space-y-1 overflow-y-auto rounded border border-border bg-background p-2 font-mono text-[11px]"
						>
							{#each errorsQuery.data ?? [] as entry, i (i)}
								<li class="leading-snug">
									<span class="text-muted-foreground">{entry.Time}</span>
									<span
										class:text-destructive={entry.Level === 'error'}
										class:text-yellow-500={entry.Level === 'warn' ||
											entry.Level === 'warning'}
									>
										[{entry.Level}]
									</span>
									{entry.Message}
								</li>
							{/each}
						</ul>
					{/if}
				</Tabs.Content>
			</Tabs.Root>
		</Dialog.Content>
	</Dialog.Portal>
</Dialog.Root>
