<!--
	Bottom filmstrip for the full-screen PreviewModal. Walks the same
	`selection.order` list the host view uses, so the user steps through
	their current context (timeline, drill-in, etc.) without surprises.

	Windowed: only renders a slice of ±WINDOW around the focused index, so
	a 10k-photo timeline doesn't paint 10k thumbs. The slice shifts as
	focus moves; the strip re-scrolls the focused tile into view on every
	change.

	Thumbnails are resolved from TanStack's cache so this surface stays
	side-effect-free (no extra fetches just to draw a strip).
-->
<script lang="ts">
	import { useQueryClient } from '@tanstack/svelte-query';
	import { thumbUrl } from '$lib/stores/session.svelte';
	import {
		selectOnly,
		selectRange,
		selection,
		setAnchor,
		setFocused,
		toggle
	} from '$lib/stores/selection.svelte';
	import { primaryFile, type PpPhoto } from '$lib/types/photoprism';

	const qc = useQueryClient();

	/** Tiles either side of focus to render. ±50 = ~100 thumbs in DOM at
	 *  any time, plenty to cover normal arrow-skim without bloating. */
	const WINDOW = 50;

	function lookup(uid: string): string | null {
		const direct = qc.getQueryData<PpPhoto>(['photo', uid]);
		if (direct) return primaryFile(direct).Hash ?? null;
		const lists = qc.getQueriesData({ queryKey: ['photos'] });
		for (const [, data] of lists) {
			if (!data) continue;
			if (Array.isArray(data)) {
				const hit = (data as PpPhoto[]).find((p) => p.UID === uid);
				if (hit) return primaryFile(hit).Hash ?? null;
				continue;
			}
			const pages = (data as { pages?: PpPhoto[][] }).pages;
			if (!Array.isArray(pages)) continue;
			for (const page of pages) {
				const hit = page?.find?.((p) => p.UID === uid);
				if (hit) return primaryFile(hit).Hash ?? null;
			}
		}
		return null;
	}

	const order = $derived(selection.order);
	const focused = $derived(selection.focused);
	const focusedIdx = $derived(focused ? order.indexOf(focused) : -1);

	interface Tile {
		uid: string;
		hash: string | null;
		idx: number;
	}
	const slice = $derived.by<Tile[]>(() => {
		if (focusedIdx < 0) return [];
		const lo = Math.max(0, focusedIdx - WINDOW);
		const hi = Math.min(order.length, focusedIdx + WINDOW + 1);
		const out: Tile[] = [];
		for (let i = lo; i < hi; i++) {
			out.push({ uid: order[i], hash: lookup(order[i]), idx: i });
		}
		return out;
	});

	// Scroll the focused tile into the centre of the strip whenever
	// `focused` changes. We key the scroll on `data-uid` so the lookup
	// survives slice re-renders.
	let stripEl: HTMLDivElement | null = $state(null);
	$effect(() => {
		const uid = focused;
		if (!uid || !stripEl) return;
		// Defer until after the slice re-renders — without this, the
		// querySelector on the freshly added tile element returns null.
		queueMicrotask(() => {
			const el = stripEl?.querySelector<HTMLElement>(`[data-strip-uid="${uid}"]`);
			el?.scrollIntoView({ inline: 'center', block: 'nearest', behavior: 'smooth' });
		});
	});

	/**
	 * Modifier-aware click: shift extends the range from the anchor, ⌘/Ctrl
	 * toggles in/out of the multi-selection, plain click reduces to this
	 * tile. Same semantics as the grid's PhotoTile + gridKeyNav click
	 * handler, so the carousel reads as a continuation of the grid rather
	 * than a separate surface.
	 */
	function onTileClick(e: MouseEvent, uid: string) {
		if (e.shiftKey) {
			e.preventDefault();
			selectRange(uid);
			setFocused(uid);
			return;
		}
		if (e.metaKey || e.ctrlKey) {
			e.preventDefault();
			toggle(uid);
			setFocused(uid);
			return;
		}
		selectOnly(uid);
		setFocused(uid);
		setAnchor(uid);
	}
</script>

<div
	bind:this={stripEl}
	class="flex h-20 shrink-0 items-center gap-1 overflow-x-auto overflow-y-hidden border-t border-border bg-background/80 px-2 py-1.5 backdrop-blur"
>
	{#each slice as tile (tile.uid)}
		{@const isFocused = tile.uid === focused}
		{@const isSelected = isFocused || selection.ids.has(tile.uid)}
		<!--
			Mirror PhotoTile's selected styling so the focused tile in the
			strip reads identically to a selected tile in the grid: the
			springy scale-90, the blue ring with background offset, and
			the blue tint overlay. Keeps the visual language consistent
			as the user moves between grid and modal.
		-->
		<button
			type="button"
			data-strip-uid={tile.uid}
			onclick={(e) => onTileClick(e, tile.uid)}
			aria-label={`Photo ${tile.idx + 1} of ${order.length}`}
			aria-current={isFocused ? 'true' : undefined}
			class:scale-90={isSelected}
			class:ring-2={isSelected}
			class:ring-blue-500={isSelected}
			class:ring-offset-2={isSelected}
			class:ring-offset-background={isSelected}
			class:transition-[transform,box-shadow]={isSelected}
			class:duration-300={isSelected}
			class:ease-[cubic-bezier(0.34,1.3,0.64,1)]={isSelected}
			class="relative h-full aspect-square shrink-0 overflow-hidden rounded-md border border-border bg-secondary p-0 outline-none focus:outline-none"
		>
			{#if tile.hash}
				<img
					src={thumbUrl(tile.hash, 'tile_100')}
					alt=""
					loading="lazy"
					decoding="async"
					class="h-full w-full object-cover"
				/>
			{/if}
			{#if isSelected}
				<div class="pointer-events-none absolute inset-0 bg-blue-500/40"></div>
			{/if}
		</button>
	{/each}
</div>
