<!--
	Multi-select metadata panel. Mirrors mule-image's RightSidebar bulk mode:
	apply the same Note / Date / Keyword to every selected photo.

	Apply-button-driven (not blur-on-edit) so the user controls when the
	mutation fans out — accidental focus loss won't rewrite N photos.
-->
<script lang="ts">
	import { useQueryClient } from '@tanstack/svelte-query';
	import { toast } from 'svelte-sonner';
	import { Calendar, Star, Tag } from 'lucide-svelte';
	import {
		buildTakenAtPatch,
		bulkSetMarks,
		type PhotoMark,
		type PhotoMarksMap,
		type UpdatePhotoBody
	} from '$lib/services/photoprism';
	import { patchTargets } from '$lib/services/bulk';

	const qc = useQueryClient();

	interface Props {
		ids: string[];
	}
	let { ids }: Props = $props();

	let noteDraft = $state('');
	let dateDraft = $state('');
	let keywordDraft = $state('');
	// `null` = nothing picked yet; `0` / `''` = explicit clear.
	let ratingDraft = $state<number | null>(null);
	let colorDraft = $state<string | null>(null);
	let busy = $state(false);

	async function withBusy<T>(fn: () => Promise<T>): Promise<T> {
		busy = true;
		try {
			return await fn();
		} finally {
			busy = false;
		}
	}

	async function applyNote() {
		if (busy) return;
		const value = noteDraft;
		await withBusy(() =>
			patchTargets(
				ids,
				{ Caption: value, CaptionSrc: 'manual' },
				value ? `Note → ${ids.length}` : `Cleared note on ${ids.length}`,
				(p) => ({ Caption: p.Caption ?? '', CaptionSrc: 'manual' })
			)
		);
		noteDraft = '';
	}

	async function applyDate() {
		if (busy || !dateDraft) return;
		// datetime-local omits the timezone; treat the input as UTC (same
		// convention as the single-photo sidebar) and let PhotoPrism's
		// backwrite stamp the local timezone field downstream.
		const iso = `${dateDraft}:00Z`;
		await withBusy(() =>
			patchTargets(
				ids,
				buildTakenAtPatch(iso),
				`Date → ${ids.length}`,
				(p) =>
					p.TakenAt
						? buildTakenAtPatch(p.TakenAt)
						: ({ TakenSrc: '' } as UpdatePhotoBody)
			)
		);
		dateDraft = '';
	}

	async function applyMarks(patch: PhotoMark, label: string) {
		if (busy) return;
		await withBusy(async () => {
			// Optimistic: patch every selected photo's mark in the local
			// cache before round-tripping. Sidecar bulk endpoint is
			// authoritative; on failure we just invalidate so the next
			// list query overrides.
			qc.setQueryData<PhotoMarksMap>(['marks'], (prev) => {
				const map = { ...(prev ?? {}) };
				for (const id of ids) {
					const merged: PhotoMark = { ...(map[id] ?? {}), ...patch };
					if (!merged.rating) delete merged.rating;
					if (!merged.color) delete merged.color;
					if (merged.rating == null && !merged.color) delete map[id];
					else map[id] = merged;
				}
				return map;
			});
			try {
				await bulkSetMarks(ids, patch);
				toast.success(`${label} · ${ids.length}`);
			} catch (err) {
				toast.error(err instanceof Error ? err.message : 'Save failed');
				void qc.invalidateQueries({ queryKey: ['marks'] });
			}
		});
	}

	async function applyRating() {
		if (ratingDraft === null) return;
		const value = ratingDraft;
		await applyMarks({ rating: value }, value === 0 ? 'Cleared score' : `★ ${value}`);
		ratingDraft = null;
	}

	async function applyColor() {
		if (colorDraft === null) return;
		const value = colorDraft;
		await applyMarks({ color: value }, value ? `Color ${value}` : 'Cleared color');
		colorDraft = null;
	}

	const COLOR_SWATCHES: { key: string; bg: string; title: string }[] = [
		{ key: 'red', bg: 'bg-red-500', title: 'Red — reject' },
		{ key: 'orange', bg: 'bg-orange-500', title: 'Orange — review' },
		{ key: 'yellow', bg: 'bg-yellow-400', title: 'Yellow — pick' },
		{ key: 'green', bg: 'bg-green-500', title: 'Green — keep' }
	];

	async function applyKeyword() {
		if (busy) return;
		const kw = keywordDraft.trim().replace(/,/g, '');
		if (!kw) return;
		keywordDraft = '';
		await withBusy(() =>
			patchTargets(
				ids,
				(p) => {
					const cur = (p.Details?.Keywords ?? '')
						.split(',')
						.map((k) => k.trim())
						.filter(Boolean);
					if (cur.includes(kw)) return {};
					const next = [...cur, kw].join(', ');
					return { Details: { Keywords: next, KeywordsSrc: 'manual' } };
				},
				`Tagged "${kw}" → ${ids.length}`,
				(p) => ({
					Details: { Keywords: p.Details?.Keywords ?? '', KeywordsSrc: 'manual' }
				})
			)
		);
	}

	function onKeywordKeydown(e: KeyboardEvent) {
		if (e.key === 'Enter' || e.key === ',') {
			e.preventDefault();
			void applyKeyword();
		}
	}
</script>

<aside class="space-y-4 p-3 text-xs">
	<header class="border-b border-border pb-2">
		<div class="text-sm font-medium text-foreground">{ids.length} selected</div>
		<p class="mt-0.5 text-[10px] text-muted-foreground">
			Edits apply to every selected photo.
		</p>
	</header>

	<!-- Note (Caption) -->
	<section class="space-y-1">
		<div
			class="flex items-center justify-between text-[10px] uppercase tracking-wide text-muted-foreground"
		>
			<span>Note</span>
			<span class="font-normal normal-case text-muted-foreground/70">
				Overwrites each photo
			</span>
		</div>
		<textarea
			rows="3"
			placeholder="Add a note for all selected…"
			class="w-full resize-y rounded border border-input bg-background px-1.5 py-1 text-xs shadow-sm focus:outline-none focus:ring-1 focus:ring-ring"
			bind:value={noteDraft}
			disabled={busy}
		></textarea>
		<button
			class="w-full rounded-md border border-border px-2 py-1 text-xs hover:bg-accent disabled:opacity-50"
			disabled={busy}
			onclick={applyNote}
		>
			Apply note to {ids.length}
		</button>
	</section>

	<!-- Date (TakenAt) -->
	<section class="space-y-1">
		<div
			class="flex items-center gap-1 text-[10px] uppercase tracking-wide text-muted-foreground"
		>
			<Calendar class="h-3 w-3" /> Date taken
		</div>
		<input
			type="datetime-local"
			class="w-full rounded border border-input bg-background px-1.5 py-1 text-xs shadow-sm focus:outline-none focus:ring-1 focus:ring-ring"
			bind:value={dateDraft}
			disabled={busy}
		/>
		<button
			class="w-full rounded-md border border-border px-2 py-1 text-xs hover:bg-accent disabled:opacity-50"
			disabled={busy || !dateDraft}
			onclick={applyDate}
		>
			Apply date to {ids.length}
		</button>
	</section>

	<!-- Score (rating) — pick a value with the stars, then Apply. The "Clear"
	     button picks `0` so the Apply step explicitly wipes the score across
	     the selection. Stored on the mule-sidecar; PhotoPrism's PUT can't
	     persist these. -->
	<section class="space-y-1">
		<div class="text-[10px] uppercase tracking-wide text-muted-foreground">Score</div>
		<div class="flex items-center gap-0.5" role="group" aria-label="Score">
			{#each [1, 2, 3, 4, 5] as n (n)}
				<button
					type="button"
					class="p-0.5 transition-colors disabled:opacity-50"
					class:text-yellow-400={ratingDraft !== null && ratingDraft >= n}
					class:text-muted-foreground={!(ratingDraft !== null && ratingDraft >= n)}
					disabled={busy}
					onclick={() => (ratingDraft = n)}
					title={`Pick ★ ${n}`}
					aria-label={`Score ${n}`}
				>
					<Star
						class="h-4 w-4"
						fill={ratingDraft !== null && ratingDraft >= n ? 'currentColor' : 'none'}
					/>
				</button>
			{/each}
			<button
				type="button"
				class="ml-1 rounded px-1 text-[10px] text-muted-foreground hover:bg-accent disabled:opacity-50"
				class:bg-accent={ratingDraft === 0}
				class:text-foreground={ratingDraft === 0}
				disabled={busy}
				onclick={() => (ratingDraft = 0)}
				title="Pick: clear score"
			>
				✕
			</button>
		</div>
		<button
			class="w-full rounded-md border border-border px-2 py-1 text-xs hover:bg-accent disabled:opacity-50"
			disabled={busy || ratingDraft === null}
			onclick={applyRating}
		>
			{#if ratingDraft === null}
				Pick a score
			{:else if ratingDraft === 0}
				Clear score on {ids.length}
			{:else}
				Apply ★ {ratingDraft} to {ids.length}
			{/if}
		</button>
	</section>

	<!-- Color label — same pattern as Score. -->
	<section class="space-y-1">
		<div class="text-[10px] uppercase tracking-wide text-muted-foreground">Color label</div>
		<div class="flex items-center gap-1" role="group" aria-label="Color label">
			{#each COLOR_SWATCHES as c (c.key)}
				<button
					type="button"
					class="h-4 w-4 rounded-full ring-2 transition-all disabled:opacity-50 {c.bg}"
					class:ring-foreground={colorDraft === c.key}
					class:ring-transparent={colorDraft !== c.key}
					disabled={busy}
					onclick={() => (colorDraft = c.key)}
					title={`Pick ${c.title}`}
					aria-label={`Color ${c.key}`}
				></button>
			{/each}
			<button
				type="button"
				class="ml-0.5 rounded px-1 text-[10px] text-muted-foreground hover:bg-accent disabled:opacity-50"
				class:bg-accent={colorDraft === ''}
				class:text-foreground={colorDraft === ''}
				disabled={busy}
				onclick={() => (colorDraft = '')}
				title="Pick: clear color"
			>
				✕
			</button>
		</div>
		<button
			class="w-full rounded-md border border-border px-2 py-1 text-xs hover:bg-accent disabled:opacity-50"
			disabled={busy || colorDraft === null}
			onclick={applyColor}
		>
			{#if colorDraft === null}
				Pick a color
			{:else if colorDraft === ''}
				Clear color on {ids.length}
			{:else}
				Apply {colorDraft} to {ids.length}
			{/if}
		</button>
	</section>

	<!-- Keywords (additive — merge into each photo's existing list) -->
	<section class="space-y-1">
		<div
			class="flex items-center gap-1 text-[10px] uppercase tracking-wide text-muted-foreground"
		>
			<Tag class="h-3 w-3" /> Add keyword
		</div>
		<input
			type="text"
			placeholder="tag name + Enter"
			class="w-full rounded border border-input bg-background px-1.5 py-1 text-xs shadow-sm focus:outline-none focus:ring-1 focus:ring-ring"
			bind:value={keywordDraft}
			disabled={busy}
			onkeydown={onKeywordKeydown}
		/>
		<button
			class="w-full rounded-md border border-border px-2 py-1 text-xs hover:bg-accent disabled:opacity-50"
			disabled={busy || !keywordDraft.trim()}
			onclick={applyKeyword}
		>
			Add to {ids.length}
		</button>
		<p class="text-[10px] text-muted-foreground/80">
			Adds to existing keywords; doesn't replace them.
		</p>
	</section>

	{#if busy}
		<div class="text-[10px] text-muted-foreground">Applying…</div>
	{/if}
</aside>
