<script lang="ts">
	import { createQuery, useQueryClient } from '@tanstack/svelte-query';
	import { toast } from 'svelte-sonner';
	import {
		addToHeap,
		approvePhoto,
		batchArchive,
		batchDelete,
		batchRestore,
		likePhoto,
		listHeaps,
		removeFromHeap,
		unlikePhoto,
		type PpAlbum
	} from '$lib/services/photoprism';
	import { batchEdit } from '$lib/services/batch';
	import {
		clearSelection,
		focusAfter,
		selection,
		setFocused
	} from '$lib/stores/selection.svelte';
	import { filters } from '$lib/stores/filters.svelte';
	import { popAndRun, push as pushUndo, undoStack } from '$lib/stores/undo.svelte';
	import { isAuthenticated } from '$lib/stores/session.svelte';

	const qc = useQueryClient();
	let busy = $state(false);
	let heapPickerOpen = $state(false);

	const heapsQuery = createQuery<PpAlbum[]>(() => ({
		queryKey: ['heaps'],
		queryFn: listHeaps,
		enabled: isAuthenticated()
	}));

	/**
	 * Targets of an action: the multi-selected set when one exists, else the
	 * focused tile alone. Mule-image's design treats focus as "implicit single
	 * selection" so the bar's actions always have something to operate on.
	 */
	function snapshotIds(): string[] {
		if (selection.ids.size > 0) return Array.from(selection.ids);
		if (selection.focused) return [selection.focused];
		return [];
	}

	const targetCount = $derived(
		selection.ids.size > 0 ? selection.ids.size : selection.focused ? 1 : 0
	);
	const isBulk = $derived(selection.ids.size > 0);
	// Review section uses a two-button decision flow (Keep / Archive) —
	// every other action is hidden so the choice can't be confused with
	// favoriting / heap-adding / restoring. The S keybinding is rerouted
	// to approve from gridKeyNav for the same reason.
	const isReview = $derived(filters.section === 'review');
	// Archive section is the parallel two-button flow: Keep (restore back
	// to the timeline) or Delete (permanent, no undo). X is repurposed
	// from "archive" to "delete" since the photo is already archived;
	// gridKeyNav mirrors the rerouting.
	const isArchive = $derived(filters.section === 'archive');

	function clearAll() {
		clearSelection();
		setFocused(null);
	}

	async function withBusy<T>(fn: () => Promise<T>): Promise<T> {
		busy = true;
		try {
			return await fn();
		} finally {
			busy = false;
			void qc.invalidateQueries({ queryKey: ['photos'] });
		}
	}

	async function onApprove() {
		const ids = snapshotIds();
		if (ids.length === 0) return;
		await withBusy(async () => {
			// PhotoPrism's approve is one-way (Quality jumps to 3+); there's
			// no /unapprove route. We fan out per-photo because there's no
			// batch endpoint either. Errors are tallied rather than aborting
			// the loop so a single bad UID doesn't block the rest.
			const { updated, errors } = await batchEdit(ids, (id) => approvePhoto(id));
			if (errors.length) {
				toast.error(`Kept ${updated.length}; ${errors.length} failed`);
			} else {
				toast.success(`Kept ${ids.length}`);
			}
			clearSelection();
		});
	}

	async function onArchive() {
		const ids = snapshotIds();
		if (ids.length === 0) return;
		await withBusy(async () => {
			try {
				await batchArchive(ids);
				pushUndo(`Archived ${ids.length}`, async () => {
					await batchRestore(ids);
					void qc.invalidateQueries({ queryKey: ['photos'] });
				});
				// Advance focus to the photo immediately after the archived
				// set before the multi-selection is dropped — lets the user
				// keep stepping through the timeline with X.
				focusAfter(ids);
				clearSelection();
				toast.success(`Archived ${ids.length}`);
			} catch (err) {
				toast.error(err instanceof Error ? err.message : 'Archive failed');
			}
		});
	}

	async function onDelete() {
		const ids = snapshotIds();
		if (ids.length === 0) return;
		const msg =
			ids.length === 1
				? 'Permanently delete this photo? This cannot be undone.'
				: `Permanently delete ${ids.length} photos? This cannot be undone.`;
		if (!confirm(msg)) return;
		await withBusy(async () => {
			try {
				await batchDelete(ids);
				clearSelection();
				toast.success(`Deleted ${ids.length}`);
			} catch (err) {
				toast.error(err instanceof Error ? err.message : 'Delete failed');
			}
		});
	}

	async function onRestore() {
		const ids = snapshotIds();
		if (ids.length === 0) return;
		await withBusy(async () => {
			try {
				await batchRestore(ids);
				pushUndo(`Restored ${ids.length}`, async () => {
					await batchArchive(ids);
					void qc.invalidateQueries({ queryKey: ['photos'] });
				});
				clearSelection();
				toast.success(`Restored ${ids.length}`);
			} catch (err) {
				toast.error(err instanceof Error ? err.message : 'Restore failed');
			}
		});
	}

	async function onFavorite() {
		const ids = snapshotIds();
		if (ids.length === 0) return;
		await withBusy(async () => {
			const { updated, errors } = await batchEdit(ids, (id) => likePhoto(id));
			if (errors.length) {
				toast.error(`Favorited ${updated.length}; ${errors.length} failed`);
			} else {
				toast.success(`Favorited ${ids.length}`);
			}
			pushUndo(`Favorited ${ids.length}`, async () => {
				await batchEdit(ids, (id) => unlikePhoto(id));
				void qc.invalidateQueries({ queryKey: ['photos'] });
			});
			clearSelection();
		});
	}

	async function onUndo() {
		const entry = await popAndRun();
		if (entry) toast.success(`Undone: ${entry.label}`);
		else toast.message('Nothing to undo');
	}

	async function onAddToHeap(heap: PpAlbum) {
		const ids = snapshotIds();
		if (!ids.length) return;
		heapPickerOpen = false;
		await withBusy(async () => {
			try {
				const { added } = await addToHeap(heap.UID, ids);
				qc.invalidateQueries({ queryKey: ['heaps'] });
				// PhotoPrism returns 200 even when nothing was added (UIDs
				// already present or unknown to the index) — surface the
				// real delta so the user isn't fooled by a green toast over
				// a no-op.
				if (added.length === 0) {
					toast.error(`Nothing added to ${heap.Title}`, {
						description: `PhotoPrism rejected all ${ids.length} UIDs (already in heap, or not indexed).`
					});
					return;
				}
				if (added.length < ids.length) {
					toast.success(`Added ${added.length}/${ids.length} → ${heap.Title}`, {
						description: 'The rest were already in this heap.'
					});
				} else {
					toast.success(`Added ${added.length} → ${heap.Title}`);
				}
				pushUndo(`Added ${added.length} to ${heap.Title}`, async () => {
					await removeFromHeap(heap.UID, added);
					qc.invalidateQueries({ queryKey: ['heaps'] });
				});
				clearSelection();
			} catch (err) {
				toast.error(err instanceof Error ? err.message : 'Add-to-heap failed');
			}
		});
	}
</script>

{#if targetCount > 0}
	<!--
		Inline row at the bottom of the main content column (NOT fixed) so
		the sidebars stay reachable. Matches the Toolbar's h-9 / px-3 /
		bg-background/80 backdrop-blur visual so it reads as the timeline's
		own footer.
	-->
	<div
		class="flex min-h-9 shrink-0 items-center gap-2 border-t border-border bg-background px-3 py-1"
	>
		<span class="shrink-0 text-[11px] font-medium text-foreground">
			{#if isBulk}
				{targetCount} selected
			{:else}
				Focused photo
			{/if}
		</span>

		<!--
			`overflow-x-auto` would clip the heap-picker dropdown — CSS
			forces `overflow-y: auto` whenever `overflow-x` is non-visible,
			so the dropdown's `bottom-full` placement is clipped to zero
			pixels above the 36px bar (it renders but is invisible). We
			use `flex-wrap` instead so very narrow viewports get a second
			row rather than a horizontal scroll, and the dropdown stays
			free to escape upward.
		-->
		<div class="ml-auto flex min-w-0 flex-wrap items-center justify-end gap-1">
			{#if isReview}
				<!-- Review pile = binary decision. Keep approves (Quality →
				     3+, lands in the main timeline); Archive batches into
				     the archive section. Everything else (heap, favorite,
				     restore) is hidden so the choice reads as decisive. -->
				<button
					class="inline-flex items-center gap-1 rounded border border-primary/40 bg-primary/10 px-2 py-0.5 text-[11px] text-primary hover:bg-primary/20 disabled:opacity-50"
					disabled={busy}
					onclick={onApprove}
					title="Keep — accept into timeline"
				>
					✓ Keep
					<kbd class="rounded bg-muted px-1 text-[9px] font-medium text-muted-foreground">S</kbd>
				</button>
				<button
					class="inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[11px] hover:bg-accent disabled:opacity-50"
					disabled={busy}
					onclick={onArchive}
					title="Archive"
				>
					Archive
					<kbd class="rounded bg-muted px-1 text-[9px] font-medium text-muted-foreground">X</kbd>
				</button>
			{:else if isArchive}
				<!-- Archive section = mirror of review: Keep restores back
				     to the timeline; Delete is permanent and can't be
				     undone. X is repurposed from archive→delete since the
				     photo is already archived; the destructive styling
				     reinforces the irreversibility. -->
				<button
					class="inline-flex items-center gap-1 rounded border border-primary/40 bg-primary/10 px-2 py-0.5 text-[11px] text-primary hover:bg-primary/20 disabled:opacity-50"
					disabled={busy}
					onclick={onRestore}
					title="Keep — restore to timeline"
				>
					✓ Keep
					<kbd class="rounded bg-muted px-1 text-[9px] font-medium text-muted-foreground">S</kbd>
				</button>
				<button
					class="inline-flex items-center gap-1 rounded border border-destructive/40 bg-destructive/5 px-2 py-0.5 text-[11px] text-destructive hover:bg-destructive/10 disabled:opacity-50"
					disabled={busy}
					onclick={onDelete}
					title="Permanently delete (no undo)"
				>
					Delete
					<kbd class="rounded bg-muted px-1 text-[9px] font-medium text-muted-foreground">X</kbd>
				</button>
			{:else}
				<div class="relative">
					<button
						class="inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[11px] hover:bg-accent disabled:opacity-50"
						disabled={busy}
						onclick={() => (heapPickerOpen = !heapPickerOpen)}
						title="Add to heap (S then 1–9 picks a heap)"
					>
						＋ Add to heap
						<kbd class="rounded bg-muted px-1 text-[9px] font-medium text-muted-foreground"
							>S&nbsp;N</kbd
						>
					</button>
					{#if heapPickerOpen}
						<div
							class="absolute bottom-full right-0 mb-2 max-h-72 w-56 overflow-y-auto rounded-md border border-border bg-background p-1 text-xs shadow-lg"
						>
							{#if heapsQuery.isPending}
								<p class="px-2 py-1 text-muted-foreground">Loading…</p>
							{:else if (heapsQuery.data ?? []).length === 0}
								<p class="px-2 py-1 text-muted-foreground">No heaps yet</p>
							{:else}
								{#each heapsQuery.data ?? [] as heap, i (heap.UID)}
									<button
										class="flex w-full items-center gap-2 rounded px-2 py-1 text-left hover:bg-accent"
										onclick={() => onAddToHeap(heap)}
									>
										{#if i < 9}
											<kbd
												class="shrink-0 rounded bg-muted px-1 py-0.5 text-[9px] font-medium text-muted-foreground"
												title={`S ${i + 1}`}
											>
												{i + 1}
											</kbd>
										{:else}
											<span class="w-3 shrink-0"></span>
										{/if}
										<span class="flex-1 truncate">{heap.Title}</span>
										<span class="shrink-0 text-muted-foreground">
											({heap.PhotoCount ?? 0})
										</span>
									</button>
								{/each}
							{/if}
						</div>
					{/if}
				</div>
				<button
					class="inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[11px] hover:bg-accent disabled:opacity-50"
					disabled={busy}
					onclick={onFavorite}
					title="Favorite"
				>
					♥ Favorite
					<kbd class="rounded bg-muted px-1 text-[9px] font-medium text-muted-foreground">F</kbd>
				</button>
				<button
					class="inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[11px] hover:bg-accent disabled:opacity-50"
					disabled={busy}
					onclick={onArchive}
					title="Archive"
				>
					Archive
					<kbd class="rounded bg-muted px-1 text-[9px] font-medium text-muted-foreground">X</kbd>
				</button>
				<button
					class="inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[11px] hover:bg-accent disabled:opacity-50"
					disabled={busy}
					onclick={onRestore}
					title="Restore"
				>
					Restore
					<kbd class="rounded bg-muted px-1 text-[9px] font-medium text-muted-foreground">U</kbd>
				</button>
			{/if}
			<button
				class="inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[11px] hover:bg-accent disabled:opacity-50"
				disabled={busy || undoStack.entries.length === 0}
				onclick={onUndo}
				title="Undo last action"
			>
				Undo
				<kbd class="rounded bg-muted px-1 text-[9px] font-medium text-muted-foreground">⌘Z</kbd>
			</button>
			<button
				class="inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[11px] hover:bg-accent"
				onclick={clearAll}
				title={isBulk ? 'Clear selection' : 'Clear focus'}
			>
				{isBulk ? 'Clear' : 'Dismiss'}
				<kbd class="rounded bg-muted px-1 text-[9px] font-medium text-muted-foreground">Esc</kbd>
			</button>
		</div>
	</div>
{/if}
