<!--
	Flat photo grid for views that don't need infinite-scroll windowing or
	month headers — the drill-in screens in /tags. Wears the same tile look
	+ click semantics as the timeline via the shared PhotoTile so the user
	gets selection rings, single-click select, dblclick preview, hover-only
	open affordance, and arrow-key nav (via `gridKeyNav` on the scroll-root)
	without per-route plumbing.

	The grid carries `data-photo-grid` so gridKeyNav can measure its column
	count, and each tile (rendered by PhotoTile) carries `data-tile`+
	`data-uid` so the action's document-level click handler can pick up
	shift/cmd/ctrl modifiers and route them through the shared selection
	helpers.
-->
<script lang="ts">
	import { untrack } from 'svelte';
	import {
		isSelected,
		selection,
		setAnchor,
		setFocused,
		setOrder
	} from '$lib/stores/selection.svelte';
	import { openPreview, view } from '$lib/stores/view.svelte';
	import { type PpPhoto } from '$lib/types/photoprism';
	import PhotoTile from './PhotoTile.svelte';

	interface Props {
		photos: PpPhoto[];
		/** Override the column template. Defaults to the global
		 *  `view.thumbnailSize` so drill-in grids honour the same XS–XL
		 *  preset the timeline uses. */
		columns?: string;
		/** Forwarded to every PhotoTile. The Low Resolution review tab
		 *  opts in so users can spot pixel dimensions without opening
		 *  each tile. */
		dimensionBadge?: boolean;
	}
	let { photos, columns, dimensionBadge = false }: Props = $props();
	const tracks = $derived(
		columns ?? `repeat(auto-fill, minmax(${view.thumbnailSize}px, 1fr))`
	);

	const order = $derived(photos.map((p) => p.UID));
	$effect(() => {
		setOrder(order);
		// Re-anchor focus when the previously focused photo isn't part of
		// this grid — covers drilling into a /tags category from any
		// other view, where the selection.focused module state would
		// otherwise leak across surfaces and the new grid would render
		// with no tile highlighted. Crucially we only re-anchor when the
		// uid is *absent*, so refetches that keep the focused photo
		// around (e.g. after `focusAfter` set the next photo on archive)
		// don't snap focus back to photos[0].
		untrack(() => {
			if (order.length === 0) {
				setFocused(null);
				selection.ids.clear();
				return;
			}
			const cur = selection.focused;
			if (cur && order.includes(cur)) return;
			setFocused(order[0]);
			setAnchor(order[0]);
			selection.ids.clear();
		});
	});

	function onClick(e: MouseEvent, uid: string) {
		// Modifier clicks bubble to gridKeyNav's window handler (range +
		// toggle paths). Plain clicks reduce the selection to this tile,
		// matching the timeline's selectOnly semantics.
		if (e.shiftKey || e.metaKey || e.ctrlKey) return;
		selection.ids.clear();
		selection.ids.add(uid);
		setFocused(uid);
		setAnchor(uid);
	}

	function onDblclick(e: MouseEvent, uid: string) {
		if (e.shiftKey || e.metaKey || e.ctrlKey) return;
		e.preventDefault();
		selection.ids.clear();
		selection.ids.add(uid);
		setFocused(uid);
		setAnchor(uid);
		openPreview();
	}
</script>

<div data-photo-grid class="grid gap-2" style="grid-template-columns: {tracks};">
	{#each photos as photo (photo.UID)}
		{@const sel = isSelected(photo.UID) || selection.focused === photo.UID}
		<div class="aspect-square">
			<PhotoTile
				{photo}
				selected={sel}
				{dimensionBadge}
				onClick={(e) => onClick(e, photo.UID)}
				onDblclick={(e) => onDblclick(e, photo.UID)}
			/>
		</div>
	{/each}
</div>
