<!--
	/review — PhotoPrism's quality-flagged photo queue *plus* the
	duplicate-resolution panels (stacks & cross-folder). Cause tabs and
	duplicate tabs share the same pill row so the user has a single
	"things to clean up" surface instead of two routes.

	Cause tabs use the shared timeline machinery (PhotoGrid, gridKeyNav,
	BulkActionBar, BulkMetadataSidebar); duplicate tabs are a different
	flow (per-group card with its own action buttons) so they render in
	a stripped-down layout with no BulkActionBar / right sidebar.

	The route flips `filters.section = 'review'` while it's mounted —
	that's what swings the shared action surface into review semantics
	(BulkActionBar shows Dismiss/Archive, gridKeyNav's S maps to
	approve). The previous section is restored on unmount.
-->
<script lang="ts">
	import { page } from '$app/state';
	import { createQuery } from '@tanstack/svelte-query';
	import {
		listReviewGroups,
		type CauseKey,
		type ReviewGroup
	} from '$lib/services/adapters/review';
	import {
		listDuplicateGroups,
		type DuplicateGroup
	} from '$lib/services/adapters/duplicates';
	import {
		scanCrossFolderDuplicates,
		type CrossFolderScanResult
	} from '$lib/services/photoprism';
	import { isAuthenticated } from '$lib/stores/session.svelte';
	import { clearSelection, selection } from '$lib/stores/selection.svelte';
	import { filters, setSection, type Section } from '$lib/stores/filters.svelte';
	import {
		setRightSidebarWidth,
		setThumbnailSize,
		THUMBNAIL_SIZE_LABELS,
		THUMBNAIL_SIZE_PRESETS,
		view
	} from '$lib/stores/view.svelte';
	import { getPhoto } from '$lib/services/photoprism';
	import { gridKeyNav } from '$lib/actions/gridKeyNav';
	import { resizable } from '$lib/actions/resizable';
	import type { PpPhoto } from '$lib/types/photoprism';
	import Toolbar from '$lib/components/layout/Toolbar.svelte';
	import BulkActionBar from '$lib/components/timeline/BulkActionBar.svelte';
	import RightSidebar from '$lib/components/sidebar/RightSidebar.svelte';
	import BulkMetadataSidebar from '$lib/components/sidebar/BulkMetadataSidebar.svelte';
	import CauseGroupCard from '$lib/components/review/CauseGroupCard.svelte';
	import DuplicatesView from '$lib/components/duplicates/DuplicatesView.svelte';
	import { EmptyState, InlineLoader } from '$lib/components/feedback';
	import { AlertCircle, Sparkles } from 'lucide-svelte';

	type DupTab = 'stacks' | 'cross-folder';
	type Tab = CauseKey | DupTab;

	function isDupTab(t: Tab | null): t is DupTab {
		return t === 'stacks' || t === 'cross-folder';
	}

	// Stash the section that was active when the user arrived; restore
	// on unmount so navigating away doesn't leak `section=review` to
	// the timeline (which would silently re-filter it).
	const prevSection: Section = filters.section;
	$effect(() => {
		setSection('review');
		return () => {
			setSection(prevSection);
			clearSelection();
		};
	});

	const reviewQuery = createQuery<ReviewGroup[]>(() => ({
		queryKey: ['review-groups'],
		queryFn: listReviewGroups,
		enabled: isAuthenticated(),
		staleTime: 30_000
	}));

	// Stacks is a cheap PhotoPrism query so we run it eagerly — the
	// "Stacks" tab badge needs the count even while the user is on a
	// cause tab. Cross-folder is the expensive disk scan; the page
	// observes its cache (enabled:false) and DuplicatesView is what
	// triggers the actual scan when its tab is active.
	const stacksQuery = createQuery<DuplicateGroup[]>(() => ({
		queryKey: ['duplicates'],
		queryFn: listDuplicateGroups,
		enabled: isAuthenticated(),
		staleTime: 30_000
	}));
	const crossFolderQuery = createQuery<CrossFolderScanResult>(() => ({
		queryKey: ['duplicates-cross-folder'],
		queryFn: scanCrossFolderDuplicates,
		enabled: false,
		staleTime: 5 * 60_000
	}));

	const focusedPhotoQuery = createQuery<PpPhoto | null>(() => ({
		queryKey: ['photo', selection.focused ?? ''],
		queryFn: () => (selection.focused ? getPhoto(selection.focused) : Promise.resolve(null)),
		enabled: isAuthenticated() && Boolean(selection.focused)
	}));

	const groups = $derived(reviewQuery.data ?? []);
	const stacksCount = $derived(stacksQuery.data?.length);
	const crossFolderCount = $derived(crossFolderQuery.data?.groups.length);

	type TabSpec = { id: Tab; label: string; count: number | undefined };
	const tabs = $derived<TabSpec[]>([
		...groups.map((g) => ({
			id: g.cause as Tab,
			label: g.meta.title,
			count: g.photos.length as number | undefined
		})),
		{ id: 'stacks', label: 'Stacks', count: stacksCount },
		{ id: 'cross-folder', label: 'Duplicates', count: crossFolderCount }
	]);

	const requestedTab = $derived(page.url.searchParams.get('tab'));
	const activeTab: Tab = $derived.by(() => {
		const want = tabs.find((t) => t.id === requestedTab);
		return (want ?? tabs[0]).id;
	});
	const activeIsDup = $derived(isDupTab(activeTab));

	// Selection is global — without this effect, a user who multi-
	// selected in `low_resolution` then switched to `stripped_exif`
	// would carry the previous tab's UIDs into the new tab's
	// BulkActionBar verbs and accidentally act on the wrong photos.
	// Also fires when switching into a duplicates tab (where selection
	// is meaningless anyway).
	$effect(() => {
		void activeTab;
		clearSelection();
	});

	const activeGroup = $derived(groups.find((g) => g.cause === activeTab));
	const activeTabSpec = $derived(tabs.find((t) => t.id === activeTab));
</script>

<Toolbar>
	<span class="rounded-md border border-border px-2 py-0.5 text-[11px] text-muted-foreground">
		Review
	</span>
	{#if activeTabSpec}
		<span class="text-[11px] font-medium">{activeTabSpec.label}</span>
		{#if activeTabSpec.count !== undefined}
			<span class="text-[11px] text-muted-foreground">{activeTabSpec.count}</span>
		{/if}
	{/if}
	{#snippet trailing()}
		<div
			class="flex items-center overflow-hidden rounded border border-border"
			role="group"
			aria-label="Thumbnail size"
		>
			{#each THUMBNAIL_SIZE_PRESETS as size, i (size)}
				<button
					type="button"
					class="px-1.5 py-0.5 text-[10px] font-medium hover:bg-accent"
					class:bg-accent={view.thumbnailSize === size}
					class:text-foreground={view.thumbnailSize === size}
					class:text-muted-foreground={view.thumbnailSize !== size}
					onclick={() => setThumbnailSize(size)}
					title={`${THUMBNAIL_SIZE_LABELS[i]} · ${size}px`}
				>
					{THUMBNAIL_SIZE_LABELS[i]}
				</button>
			{/each}
		</div>
	{/snippet}
</Toolbar>

<!--
	Two layout branches:
	  • Cause tabs use the timeline-style layout (grid key nav, bulk
	    action bar, right metadata sidebar) since the user is acting on
	    individual photos.
	  • Duplicate tabs use a stripped layout — DuplicatesView renders
	    its own per-group cards with built-in actions, so the bulk bar
	    and right sidebar would just clutter.
-->
{#if activeIsDup}
	<main class="min-h-0 flex-1 overflow-y-auto">
		<DuplicatesView
			activeTab={activeTab as DupTab}
			groups={stacksQuery.data ?? []}
			pending={stacksQuery.isPending}
			error={stacksQuery.error}
		/>
	</main>
{:else}
	<div class="flex min-h-0 flex-1">
		<div class="flex min-w-0 flex-1 flex-col">
			<main class="min-h-0 flex-1 overflow-y-auto px-6 py-4 pb-6" use:gridKeyNav={{}}>
				{#if reviewQuery.isPending}
					<InlineLoader label="Loading review queue…" />
				{:else if reviewQuery.error}
					<EmptyState
						tone="destructive"
						icon={AlertCircle}
						title="Could not load review queue"
						description={reviewQuery.error instanceof Error
							? reviewQuery.error.message
							: 'unknown error'}
					/>
				{:else if groups.length === 0}
					<EmptyState icon={Sparkles} title="Nothing to review">
						{#snippet descriptionSnippet()}
							<p>
								The indexer flags photos with a low quality score for human review. New
								arrivals with missing EXIF, low resolution, or unknown cameras will land
								here. The Stacks and Duplicates tabs above stay available for
								duplicate cleanup.
							</p>
						{/snippet}
					</EmptyState>
				{:else if activeGroup}
					{#key activeGroup.cause}
						<CauseGroupCard group={activeGroup} />
					{/key}
				{/if}
			</main>
			<BulkActionBar />
		</div>

		{#if !view.rightSidebarCollapsed && (selection.focused || selection.ids.size >= 2)}
			<aside
				class="relative h-full shrink-0 border-l border-border bg-card/30"
				style="width: {view.rightSidebarWidth}px;"
			>
				<div class="h-full overflow-y-auto">
					{#if selection.ids.size >= 2}
						<BulkMetadataSidebar ids={Array.from(selection.ids)} />
					{:else if focusedPhotoQuery.data}
						<RightSidebar photo={focusedPhotoQuery.data} />
					{:else if focusedPhotoQuery.isFetching}
						<InlineLoader size="sm" label="Loading metadata…" />
					{/if}
				</div>
				<div
					class="group absolute -left-1.5 top-0 z-20 h-full w-3 cursor-col-resize"
					use:resizable={{
						edge: 'left',
						getWidth: () => view.rightSidebarWidth,
						setWidth: setRightSidebarWidth
					}}
					role="separator"
					aria-orientation="vertical"
					aria-label="Resize info panel"
				></div>
			</aside>
		{/if}
	</div>
{/if}
