<script lang="ts">
	import { fly } from 'svelte/transition';
	import { prefersReducedMotion } from 'svelte/motion';
	import { ChevronDown, CircleAlert, RotateCcw, X } from '@lucide/svelte';

	import { Button } from '$lib/components/ui/button/index.js';
	import { Spinner } from '$lib/components/ui/spinner/index';
	import { getUploadQueueState } from '$lib/state/upload-queue.svelte';
	import { formatFileSize } from '$lib/utils';

	const queue = getUploadQueueState();

	// A clean run clears itself after a few seconds, so it needs to leave rather
	// than blink out. Nothing to animate for anyone who asked not to see it.
	const motion = $derived(prefersReducedMotion.current ? 0 : 200);

	const percent = $derived(queue.total === 0 ? 0 : Math.round((queue.settled / queue.total) * 100));

	const heading = $derived.by(() => {
		const noun = queue.total === 1 ? 'book' : 'books';
		if (queue.active) return `Adding ${queue.settled} of ${queue.total} ${noun}`;
		if (queue.failed === 0) return `Added ${queue.done} ${noun}`;
		if (queue.done === 0) return `Couldn't add ${queue.failed} ${noun}`;
		return `Added ${queue.done}, ${queue.failed} failed`;
	});
</script>

{#if queue.total > 0}
	<!--
		Docked rather than a toast. This runs for minutes and carries a row per
		book, which a notification surface is not built to hold still for.
	-->
	<aside
		aria-label="Uploads"
		transition:fly={{ y: 12, duration: motion }}
		onmouseenter={() => queue.hold()}
		onmouseleave={() => queue.release()}
		onfocusin={() => queue.hold()}
		onfocusout={() => queue.release()}
		class="fixed right-4 bottom-4 z-50 w-80 max-w-[calc(100vw-2rem)] overflow-hidden rounded-lg border bg-card shadow-lg"
	>
		<div class="flex items-center gap-2 border-b px-3 py-2">
			{#if queue.active}
				<Spinner class="size-4 shrink-0" />
			{:else if queue.failed > 0}
				<CircleAlert class="size-4 shrink-0 text-destructive" />
			{/if}

			<span class="min-w-0 flex-1 truncate text-sm font-medium">{heading}</span>

			<Button
				variant="ghost"
				size="icon"
				class="size-7 shrink-0"
				onclick={() => (queue.collapsed = !queue.collapsed)}
			>
				<ChevronDown class="size-4 transition-transform {queue.collapsed ? '' : 'rotate-180'}" />
				<span class="sr-only">{queue.collapsed ? 'Show' : 'Hide'} the list</span>
			</Button>

			<!-- Only once nothing is in flight: dismissing mid-run would suggest it
			     stopped the upload, which it does not. -->
			{#if !queue.active}
				<Button variant="ghost" size="icon" class="size-7 shrink-0" onclick={() => queue.dismiss()}>
					<X class="size-4" />
					<span class="sr-only">Dismiss</span>
				</Button>
			{/if}
		</div>

		<div class="h-1 bg-muted">
			<div
				class="h-full bg-primary transition-[width] duration-300"
				style="width: {percent}%"
			></div>
		</div>

		{#if !queue.collapsed}
			<ul class="flex max-h-64 flex-col divide-y overflow-y-auto">
				{#each queue.jobs as job (job.id)}
					<li class="flex min-w-0 items-center gap-2 px-3 py-2">
						<span class="min-w-0 flex-1">
							<span class="block truncate text-sm" title={job.label}>{job.label}</span>
							<span class="block font-mono text-[10px] text-muted-foreground tabular-nums">
								{job.error ?? formatFileSize(job.size)}
							</span>
						</span>

						<span
							class="shrink-0 font-mono text-[10px] tracking-wider uppercase
								{job.status === 'done' ? 'text-primary' : ''}
								{job.status === 'failed' ? 'text-destructive' : ''}
								{job.status !== 'done' && job.status !== 'failed' ? 'text-muted-foreground' : ''}"
						>
							{#if job.status === 'uploading'}
								Adding
							{:else if job.status === 'done'}
								Done
							{:else if job.status === 'failed'}
								Failed
							{:else}
								Queued
							{/if}
						</span>
					</li>
				{/each}
			</ul>

			{#if !queue.active && queue.failed > 0}
				<div class="border-t p-2">
					<Button variant="outline" size="sm" class="w-full" onclick={() => queue.retryFailed()}>
						<RotateCcw class="size-4" />
						Retry {queue.failed} failed
					</Button>
				</div>
			{/if}
		{/if}
	</aside>
{/if}
