<script lang="ts">
	import * as Empty from '$lib/components/ui/empty/index.js';
	import { Button } from '$lib/components/ui/button/index.js';
	import { Spinner } from '$lib/components/ui/spinner/index.js';
	import { ScrollArea } from '$lib/components/ui/scroll-area/index';
	import { SearchX, FolderX, FunnelX, Upload } from '@lucide/svelte';

	import BookGrid from './book-grid.svelte';

	import * as Sidebar from '$lib/components/ui/sidebar/index.js';
	import FilterSidebar from '$lib/components/view/filter-sidebar.svelte';
	import FilterButton from './filter-button.svelte';
	import SortButton from './sort-button.svelte';
	import ViewToggle from './view-toggle.svelte';
	import PresetChips from './preset-chips.svelte';
	import BookTable from './book-table.svelte';
	import BookRows from './book-rows.svelte';
	import BatchOperationsToolbar from './batch-operations-toolbar.svelte';
	import { getBookCollectionState } from '$lib/state/bookCollection.svelte';
	import { getBookSelectionState } from '$lib/state/bookSelection.svelte';
	import { getBookOperationsState } from '$lib/state/bookOperations.svelte';

	const selectionState = getBookSelectionState();
	const bookCollection = getBookCollectionState();
	const bookOps = getBookOperationsState();

	let sentinel = $state<HTMLElement>();
	let scrollContainer = $state<HTMLElement | null>(null);

	$effect(() => {
		if (!sentinel || !bookCollection.moreBooks) return;
		const target = sentinel;

		const observer = new IntersectionObserver(
			async (entries) => {
				if (!entries[0].isIntersecting) return;

				await bookCollection.loadMoreBooks();

				// A page that does not push the sentinel back out of the prefetch zone
				// produces no further intersection change, so the observer would sit
				// idle until the next scroll. Re-observing asks for a fresh reading.
				if (bookCollection.moreBooks) {
					observer.unobserve(target);
					observer.observe(target);
				}
			},
			{
				root: scrollContainer,
				// One viewport of lead time: the next page is requested a full screen
				// before the reader reaches the end of the current one.
				rootMargin: '0px 0px 100% 0px'
			}
		);

		observer.observe(target);

		return () => {
			observer.disconnect();
		};
	});

	// TODO: Set initial sidebar state to the previous state (saved in cookies)
	// 		If there is is no previous state, it is opened if there are active filters o.w closed
	let initialFilterSidebarOpen = bookCollection.hasActiveFilters;
</script>

<div class="flex h-full w-full flex-col">
	<!-- Main Content Area -->
	<div class="flex h-0 w-full flex-1">
		<!-- Sidebar Layout -->
		<Sidebar.Provider
			contextKey="FILTER_SIDEBAR"
			open={initialFilterSidebarOpen}
			class="flex h-full w-full"
		>
			<!-- Sidebar Area -->
			<Sidebar.Inset class="flex min-w-0 flex-1">
				<div class="top-0 z-1 mb-4 flex h-12 w-full rounded-lg border bg-sidebar px-5">
					<div class="flex w-full items-center py-2">
						{#if !selectionState.selectionModeActive}
							<ViewToggle
								value={bookCollection.view}
								onValueChange={(next) => bookCollection.setView(next)}
							/>

							<div class="mx-4 min-w-0 flex-1">
								<PresetChips />
							</div>

							<div class="ml-auto">
								<SortButton />
								<FilterButton />
							</div>
						{:else}
							<p class="ml-2 font-medium">
								Selected {selectionState.numSelected()} book{selectionState.numSelected() > 1
									? 's'
									: ''}
							</p>

							<div class="ml-auto">
								<BatchOperationsToolbar />
							</div>
						{/if}
					</div>
				</div>

				{#if bookCollection.loading && bookCollection.books.length === 0}
					<div class="mb-[25vh] flex h-full w-full items-center justify-center">
						<Spinner />
					</div>
				{:else if bookCollection.books.length > 0}
					<ScrollArea
						bind:viewportRef={scrollContainer}
						orientation="both"
						class="h-[calc(100vh-11rem)] w-full px-5 pb-5"
					>
						{#if bookCollection.view === 'grid'}
							<BookGrid books={bookCollection.books} />
						{:else if bookCollection.view === 'list'}
							<BookRows books={bookCollection.books} />
						{:else}
							<BookTable books={bookCollection.books} />
						{/if}

						{#if bookCollection.moreBooks}
							<div bind:this={sentinel}>
								{#if bookCollection.loadingMore}
									<Spinner />
								{/if}
							</div>
						{/if}
					</ScrollArea>
				{:else if bookCollection.books.length === 0 && !bookCollection.hasActiveFilters}
					<!-- Show a CTA to upload books if the library is empty -->
					<Empty.Root class="mb-[20vh]">
						<Empty.Header>
							<Empty.Media variant="icon">
								<FolderX />
							</Empty.Media>
							<Empty.Title>Library Empty</Empty.Title>
							<Empty.Description>
								This library doesn't have any books yet. Get started by uploading your first books.
							</Empty.Description>
						</Empty.Header>
						<Empty.Content>
							<Button onclick={async () => (bookOps.uploadDialogOpen = true)}>
								<Upload />
								Upload Books
							</Button>
						</Empty.Content>
					</Empty.Root>
				{:else if bookCollection.books.length === 0 && bookCollection.hasActiveFilters}
					<!-- Show no results if the given filters result in no books -->
					<Empty.Root class="mb-[20vh]">
						<Empty.Header>
							<Empty.Media variant="icon">
								<SearchX />
							</Empty.Media>
							<Empty.Title>No Results</Empty.Title>
							<Empty.Description>
								No books exist with the applied filters. Adjust your filters.
							</Empty.Description>
						</Empty.Header>
						<Empty.Content>
							<Button variant="outline" onclick={() => bookCollection.clearFilters()}>
								<FunnelX />
								Clear Filters
							</Button>
						</Empty.Content>
					</Empty.Root>
				{/if}
			</Sidebar.Inset>

			<!-- Filter Sidebar (right-side) -->
			<FilterSidebar class="m-2 h-full pt-20 pb-5" />
		</Sidebar.Provider>
	</div>
</div>
