<script lang="ts">
	import '../app.css';
	import { onMount } from 'svelte';
	import { browser } from '$app/environment';
	import { goto } from '$app/navigation';
	import { page } from '$app/state';
	import type { Component } from 'svelte';
	import { QueryClientProvider } from '@tanstack/svelte-query';
	import { ModeWatcher } from 'mode-watcher';
	import { Toaster } from 'svelte-sonner';
	import { isAuthenticated } from '$lib/stores/session.svelte';
	import { bootstrapSessionFromPhotoPrism } from '$lib/services/photoprism';
	import { setLeftSidebarWidth, view } from '$lib/stores/view.svelte';
	import { resizable } from '$lib/actions/resizable';
	import { queryClient } from '$lib/queryClient';
	import { preview } from '$lib/stores/preview.svelte';
	import { startIndexerWatch, stopIndexerWatch } from '$lib/stores/indexer.svelte';
	import IndexerStatusPill from '$lib/components/layout/IndexerStatusPill.svelte';
	import LeftSidebar from '$lib/components/layout/LeftSidebar.svelte';
	import AnimatedMule from '$lib/components/mule/AnimatedMule.svelte';

	let { children } = $props();

	// Bootstrap state: the OIDC return drops the user back on `/` with
	// PhotoPrism's session info written to localStorage under
	// `pp:<storageNamespace>:session.*`, but the SPA store is empty. We
	// try to adopt that session on first mount before the auth guard
	// can punt to /login.
	let bootstrapped = $state(false);

	onMount(async () => {
		if (!isAuthenticated()) {
			await bootstrapSessionFromPhotoPrism();
		}
		bootstrapped = true;
	});

	// Auth guard. Anything outside /login requires a session; otherwise
	// punt to the login page (which itself redirects authenticated users
	// back to /). Held until the bootstrap pass has had a chance to run.
	$effect(() => {
		if (!browser || !bootstrapped) return;
		const onLogin = page.url.pathname === '/login';
		if (!isAuthenticated() && !onLogin) {
			void goto('/login', { replaceState: true });
		}
	});

	// Indexer status feed. The store opens a WS to PhotoPrism's /api/v1/ws
	// channel once we're signed in and tears it down on logout. Kept here
	// (not in the store's top-level) so SvelteKit's SSR pass never touches
	// `WebSocket` and so logout can short-circuit reconnect attempts.
	$effect(() => {
		if (!browser || !bootstrapped) return;
		if (isAuthenticated()) startIndexerWatch();
		else stopIndexerWatch();
	});

	// PreviewOverlay is the full-screen lightbox — keyboard nav, map
	// pane, exif sidebar. Users who never click into a photo never need
	// it, so we lazy-import the first time `preview.uid` flips non-null
	// and keep the loaded module around for the rest of the session
	// (re-opens skip the network round-trip). Closing the overlay leaves
	// the component mounted but renders nothing — its internal
	// `{#if preview.uid !== null}` guard collapses the DOM tree.
	let PreviewOverlay = $state<Component | null>(null);
	$effect(() => {
		if (!browser) return;
		if (preview.uid !== null && PreviewOverlay === null) {
			void import('$lib/components/preview/PreviewOverlay.svelte').then((m) => {
				PreviewOverlay = m.default as Component;
			});
		}
	});
</script>

<svelte:head>
	<title>Mulimage</title>
</svelte:head>

<ModeWatcher />
<Toaster richColors position="bottom-right" />

<QueryClientProvider client={queryClient}>
	{#if isAuthenticated() && page.url.pathname !== '/login'}
		<!-- App shell locks to viewport height; only the main thumbnail
		     region inside each page scrolls. The mule header, toolbar, and
		     sidebars stay fixed regardless of how far you scroll the grid. -->
		<div class="flex h-screen flex-col overflow-hidden">
			<AnimatedMule>
				<IndexerStatusPill />
			</AnimatedMule>
			<div class="flex min-h-0 flex-1">
				{#if !view.leftSidebarCollapsed}
					<aside
						class="relative hidden h-full shrink-0 border-r border-border bg-card/30 md:block"
						style="width: {view.leftSidebarWidth}px;"
					>
						<!-- LeftSidebar owns its own flex-col layout so its footer
						     row (user/theme/settings/logout) can pin to the bottom
						     while the nav above scrolls. -->
						<LeftSidebar />
						<div
							class="group absolute -right-1.5 top-0 z-20 hidden h-full w-3 cursor-col-resize md:block"
							use:resizable={{
								edge: 'right',
								getWidth: () => view.leftSidebarWidth,
								setWidth: setLeftSidebarWidth
							}}
							role="separator"
							aria-orientation="vertical"
							aria-label="Resize left panel"
						>
							<div
								class="ml-1 h-full w-0.5 bg-transparent transition-colors group-hover:bg-primary/40"
							></div>
						</div>
					</aside>
				{/if}
				<!-- Right column hosts the route's content. Pages render as
				     a flex column whose first child is the Toolbar (shrink-0)
				     and whose remaining content takes the rest, so each route
				     can decide which of its panes is the scrollable one. -->
				<div class="flex min-w-0 flex-1 flex-col overflow-hidden">
					{@render children?.()}
				</div>
			</div>
		</div>
	{:else}
		{@render children?.()}
	{/if}
	{#if PreviewOverlay}
		<PreviewOverlay />
	{/if}
</QueryClientProvider>
