<script lang="ts">
	import { untrack } from 'svelte';
	import { goto } from '$app/navigation';
	import { uploadSheetOpen, uploadBadgeCount } from '$lib/ui-store';
	import { pendingFiles } from '$lib/pending-upload-store';
	import { scrollLock } from '$lib/actions/scroll-lock';
	import CameraCapture from '$lib/components/CameraCapture.svelte';
	import type { PendingFile } from '$lib/pending-upload-store';
	import { eventState, uploadsClosed } from '$lib/event-state-store';
	import { commentsEnabled } from '$lib/event-config-store';
	import { isBanned } from '$lib/ban-store';

	// A ban closes uploads just as hard as an event lock does — the backend refuses every
	// write from a banned user. Without this the read-only banner said uploading was off while
	// the FAB still opened the sheet, the camera still opened, files still staged, and only the
	// final POST 403'd: the guest burns their photo and their upload allowance on a rejection
	// nobody is around to explain.
	let banned = $derived($isBanned);
	// Uploads closed (event locked or gallery released) — show a lock notice instead of
	// the capture options, so a guest can't stage a photo that would just be rejected.
	let closed = $derived(banned || uploadsClosed($eventState));

	let showCamera = $state(false);
	let fileInput: HTMLInputElement;
	let sheet = $state<HTMLDivElement | null>(null);
	let returnFocus: HTMLElement | null = null;

	// Keep the sheet and backdrop always in the DOM for smooth CSS transitions.
	let open = $derived($uploadSheetOpen);

	function close() {
		uploadSheetOpen.set(false);
	}

	// Focus-trap + Escape, wired manually because the sheet stays mounted for its
	// translate-y animation (so use:focusTrap, which activates on mount, won't do).
	// Mirrors ContextSheet. Suspended while the camera overlay owns the screen.
	function onKeyDown(e: KeyboardEvent) {
		if (showCamera) return;
		if (e.key === 'Escape') {
			e.preventDefault();
			close();
			return;
		}
		if (e.key !== 'Tab' || !sheet) return;
		const list = Array.from(sheet.querySelectorAll<HTMLElement>('button:not([disabled])'));
		if (list.length === 0) return;
		const first = list[0];
		const last = list[list.length - 1];
		const active = document.activeElement as HTMLElement | null;
		if (e.shiftKey && (active === first || !sheet.contains(active))) {
			e.preventDefault();
			last.focus();
		} else if (!e.shiftKey && active === last) {
			e.preventDefault();
			first.focus();
		}
	}

	function restoreFocus() {
		if (!returnFocus) return;
		try {
			returnFocus.focus({ preventScroll: true });
		} catch {
			/* element gone */
		}
		returnFocus = null;
	}

	$effect(() => {
		if (open) {
			returnFocus = (document.activeElement as HTMLElement | null) ?? null;
			requestAnimationFrame(() => {
				if (showCamera) return;
				const first = sheet?.querySelector<HTMLButtonElement>('button:not([disabled])');
				first?.focus({ preventScroll: true });
			});
			window.addEventListener('keydown', onKeyDown);
			return () => window.removeEventListener('keydown', onKeyDown);
		} else if (untrack(() => showCamera)) {
			// The camera closed the sheet from under us. Restoring focus now would put it on the
			// FAB *behind* a full-screen overlay, where a Tab could then walk the page underneath;
			// `handleCameraClose` does it once the overlay is gone. `untrack` because this branch
			// must not resubscribe the effect to `showCamera` — that would re-run the open branch
			// when the camera mounts and re-capture `returnFocus` as the sheet's own Kamera button.
		} else {
			restoreFocus();
		}
	});

	function openGallery() {
		fileInput?.click();
	}

	function openCamera() {
		showCamera = true;
	}

	// Close the sheet before navigating: it stays mounted for its translate-y animation,
	// so leaving it open would keep the backdrop and scroll lock over /upload.
	function openQueue() {
		close();
		void goto('/upload');
	}

	async function handleFiles() {
		const files = fileInput?.files;
		if (!files || files.length === 0) return;

		const staged: PendingFile[] = [];
		for (const file of files) {
			staged.push({ file, previewUrl: URL.createObjectURL(file) });
		}
		pendingFiles.set(staged);
		fileInput.value = '';
		close();
		await goto('/upload');
	}

	async function handleCapture(blob: Blob, type: 'photo' | 'video') {
		const ext = type === 'photo' ? 'jpg' : blob.type.includes('mp4') ? 'mp4' : 'webm';
		const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
		const fileName = `${type}_${timestamp}.${ext}`;
		const file = new File([blob], fileName, { type: blob.type });
		pendingFiles.set([{ file, previewUrl: URL.createObjectURL(file) }]);
		showCamera = false;
		close();
		await goto('/upload');
	}

	// The camera preview is up, so the sheet has done its job — dismiss it. Leaving it open put
	// the Galerie/Kamera panel over the shutter button, and since it is `fixed` it could not be
	// scrolled away: the only escape was the phone's back button.
	//
	// Deliberately driven by the camera's own ready signal rather than by the tap on "Kamera".
	// Closing on the tap would dismiss the sheet before we know the camera will work at all, so a
	// denied permission — or an insecure context, where `getUserMedia` does not exist — would
	// leave the guest looking at an error panel with nothing behind it. This way the sheet is
	// still there if the camera never opens, and `handleCameraClose` returns them to it.
	function handleCameraReady() {
		close();
	}

	function handleCameraClose() {
		showCamera = false;
		// `close()` normally restores focus to whatever opened the sheet, but when the camera
		// dismissed it that element was behind a full-screen overlay and the restore was skipped.
		// Do it now that the overlay is gone, so focus never ends up on <body>.
		restoreFocus();
	}
</script>

<!-- Camera (rendered outside sheet so it gets full viewport) -->
{#if showCamera}
	<CameraCapture
		oncapture={handleCapture}
		onclose={handleCameraClose}
		onready={handleCameraReady}
		onpickfile={() => {
			// Close the camera first, then open the OS picker — the sheet is still mounted
			// behind it, so this returns the guest to a working path instead of a dead end.
			showCamera = false;
			openGallery();
		}}
	/>
{/if}

<!-- Hidden file input -->
<input
	bind:this={fileInput}
	type="file"
	accept="image/*,video/*"
	multiple
	class="hidden"
	onchange={handleFiles}
/>

<!-- Lock body scroll only while open (sheet stays mounted for its animation). -->
{#if open && !showCamera}
	<div use:scrollLock class="hidden"></div>
{/if}

<!-- Backdrop — real <button> so keyboard / switch-control users get parity. -->
<button
	type="button"
	class="fixed inset-0 z-40 bg-black/50 transition-opacity duration-300"
	class:opacity-0={!open}
	class:pointer-events-none={!open}
	class:opacity-100={open}
	onclick={close}
	tabindex="-1"
	aria-label="Schließen"
></button>

<!-- Sheet -->
<div
	bind:this={sheet}
	class="fixed inset-x-0 bottom-0 z-50 rounded-t-2xl bg-white transition-transform duration-300 dark:bg-gray-900"
	class:translate-y-full={!open}
	class:translate-y-0={open}
	style="padding-bottom: env(safe-area-inset-bottom)"
	role={open ? 'dialog' : undefined}
	aria-modal={open ? 'true' : undefined}
	aria-label="Hochladen"
	aria-hidden={!open}
	inert={!open}
	tabindex="-1"
>
	<!-- Drag handle -->
	<div class="flex justify-center pt-3 pb-1">
		<div class="h-1 w-10 rounded-full bg-gray-300 dark:bg-gray-600"></div>
	</div>

	<div class="space-y-3 px-4 pb-4 pt-2">
		{#if closed}
			<!-- Uploads closed: no capture options, just an explanation + dismiss. A ban and an
			     event lock both land here, but they need different copy — a banned guest keeps
			     read access only, so promising them likes and comments would just set up the
			     next 403. -->
			<div class="rounded-xl bg-amber-50 px-5 py-4 text-center dark:bg-amber-950/30">
				{#if banned}
					<p class="font-semibold text-amber-800 dark:text-amber-300">Nur-Lese-Modus</p>
					<p class="mt-1 text-sm text-amber-700 dark:text-amber-400">
						Hochladen ist für dich deaktiviert. Du kannst weiterhin alle Fotos ansehen und die
						Galerie später herunterladen.
					</p>
				{:else}
					<p class="font-semibold text-amber-800 dark:text-amber-300">Uploads geschlossen</p>
					<p class="mt-1 text-sm text-amber-700 dark:text-amber-400">
						Der Host hat die Uploads für dieses Event beendet. Du kannst weiterhin Fotos ansehen{$commentsEnabled
							? ', liken und kommentieren'
							: ' und liken'}.
					</p>
				{/if}
			</div>
		{:else}
			<!-- Gallery option -->
			<button
				onclick={openGallery}
				class="flex w-full items-center gap-4 rounded-xl bg-gray-50 px-5 py-4 text-left transition hover:bg-gray-100 active:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 dark:active:bg-gray-600"
			>
				<span
					class="flex h-11 w-11 items-center justify-center rounded-full bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300"
				>
					<svg
						class="h-6 w-6"
						fill="none"
						viewBox="0 0 24 24"
						stroke="currentColor"
						stroke-width="1.5"
					>
						<path
							stroke-linecap="round"
							stroke-linejoin="round"
							d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
						/>
					</svg>
				</span>
				<div>
					<p class="font-semibold text-gray-900 dark:text-gray-100">Galerie</p>
					<p class="text-sm text-gray-500 dark:text-gray-400">Foto oder Video wählen</p>
				</div>
			</button>

			<!-- Camera option -->
			<button
				onclick={openCamera}
				class="flex w-full items-center gap-4 rounded-xl bg-gray-50 px-5 py-4 text-left transition hover:bg-gray-100 active:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 dark:active:bg-gray-600"
			>
				<span
					class="flex h-11 w-11 items-center justify-center rounded-full bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300"
				>
					<svg
						class="h-6 w-6"
						fill="none"
						viewBox="0 0 24 24"
						stroke="currentColor"
						stroke-width="1.5"
					>
						<path
							stroke-linecap="round"
							stroke-linejoin="round"
							d="M6.827 6.175A2.31 2.31 0 0 1 5.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 0 0-1.134-.175 2.31 2.31 0 0 1-1.64-1.055l-.822-1.316a2.192 2.192 0 0 0-1.736-1.039 48.774 48.774 0 0 0-5.232 0 2.192 2.192 0 0 0-1.736 1.039l-.821 1.316Z"
						/>
						<path
							stroke-linecap="round"
							stroke-linejoin="round"
							d="M16.5 12.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0ZM18.75 10.5h.008v.008h-.008V10.5Z"
						/>
					</svg>
				</span>
				<div>
					<p class="font-semibold text-gray-900 dark:text-gray-100">Kamera</p>
					<p class="text-sm text-gray-500 dark:text-gray-400">Jetzt aufnehmen</p>
				</div>
			</button>

			<!-- The in-app-browser escape hatch.
			     The join link travels through WhatsApp groups, and a link tapped inside one opens
			     in that app's own browser rather than Safari or Chrome. There, the file picker and
			     `getUserMedia` both depend on the host app having wired them up, and when they are
			     not wired up the buttons above simply do nothing — no error, nothing to act on,
			     with no operator to ask. Deliberately NOT user-agent sniffing: a sniff list is
			     wrong for browsers it has never heard of, whereas a quiet standing hint costs a
			     line of text and is never wrong. It lives here rather than in the root layout
			     because both layout banners are gated on `$showBottomNav`, which `/upload` turns
			     off — a banner there would never render on the composer. -->
			<p class="px-1 pt-1 text-center text-xs text-gray-500 dark:text-gray-400">
				Nichts passiert beim Tippen? Öffne den Link in Safari oder Chrome.
			</p>
		{/if}

		<!-- Queue access. The FAB badge is the ONLY signal a guest gets that an upload is
		     pending or failed, and tapping it opens this sheet — not the queue. Without this
		     entry the queue view (and with it the only retry button in the app) is reachable
		     only by staging a NEW file, so a guest with a failed upload sees a red badge,
		     three capture options, and no way to act on it. Shown in the closed/banned state
		     too: that guest is the MOST likely to have items parked in the queue, and it is
		     the only place they can clear the badge. -->
		{#if $uploadBadgeCount > 0}
			<button
				onclick={openQueue}
				class="flex w-full items-center gap-4 rounded-xl bg-gray-50 px-5 py-4 text-left transition hover:bg-gray-100 active:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 dark:active:bg-gray-600"
			>
				<span
					class="flex h-11 w-11 items-center justify-center rounded-full bg-amber-100 text-amber-600 dark:bg-amber-900/40 dark:text-amber-300"
				>
					<svg
						class="h-6 w-6"
						fill="none"
						viewBox="0 0 24 24"
						stroke="currentColor"
						stroke-width="1.5"
					>
						<path
							stroke-linecap="round"
							stroke-linejoin="round"
							d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
						/>
					</svg>
				</span>
				<div>
					<p class="font-semibold text-gray-900 dark:text-gray-100">Warteschlange</p>
					<p class="text-sm text-gray-500 dark:text-gray-400">
						{$uploadBadgeCount}
						{$uploadBadgeCount === 1 ? 'Foto wartet' : 'Fotos warten'}
					</p>
				</div>
			</button>
		{/if}

		<button onclick={close} class="btn btn-secondary btn-block">
			{closed ? 'Schließen' : 'Abbrechen'}
		</button>
	</div>
</div>
