<script lang="ts">
	import { buttonVariants } from '$lib/components/ui/button/button.svelte';
	import * as Tooltip from '$lib/components/ui/tooltip/index';
	import {
		BookOpenCheck,
		Download,
		GitMerge,
		Trash2,
		SquareCheckBig,
		SlidersHorizontal,
		X,
		Album,
		PlusIcon
	} from '@lucide/svelte';
	import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index';
	import { getBookOperationsState } from '$lib/state/bookOperations.svelte';
	import { getBookSelectionState } from '$lib/state/bookSelection.svelte';
	import { getBookCollectionState } from '$lib/state/bookCollection.svelte';
	import { getLibraryState } from '$lib/state/library.svelte';
	import { getBookshelfState } from '$lib/state/bookshelf.svelte';
	import ShelfCreateDialog from '../forms/shelf-create-dialog.svelte';
	import MergeBooks from '../forms/merge-books/merge-books.svelte';
	import { mergeInto } from '../forms/merge-books/merge';
	import type { Book } from '$lib/schema';

	const libraryState = getLibraryState();
	const bookshelfState = getBookshelfState();
	const selectionState = getBookSelectionState();
	const bookOps = getBookOperationsState();
	const collectionState = getBookCollectionState();

	let selectedBooks = $derived(selectionState.getSelectedBooks());

	let createShelfDialogOpen = $state(false);

	// The books the merge dialog opened on. Snapshotted rather than read live from
	// the selection, so clearing the selection on success cannot empty the dialog
	// underneath itself.
	let mergingBooks = $state<Book[] | null>(null);

	// A quick merge has no dialog to disable, so the flag is what stops a slow
	// round trip being started twice.
	let quickMerging = $state(false);

	/** Put the library back in step with a merge that went through. */
	function afterMerge(folded: Book[]) {
		// Only the folded records are gone; the survivor is still in the library.
		libraryState.activeLibrary!.total! -= folded.length;
		bookshelfState.deletedBooks(folded);
		selectionState.deselectAll();
	}

	/**
	 * Merge without opening the workbench, keeping the book selected first.
	 *
	 * Selection is held in insertion order, so the first record is the one the
	 * reader started from — the one they were looking at when they decided the
	 * rest were copies of it.
	 */
	async function quickMerge() {
		if (quickMerging) return;

		// Read once: the merge clears the selection, and the bookkeeping afterwards
		// still needs the records that went away.
		const [survivor, ...folded] = selectedBooks;
		if (!survivor || folded.length === 0) return;

		quickMerging = true;

		const merged = await mergeInto(libraryState.activeLibrary!.id, survivor, folded);

		quickMerging = false;

		if (merged) afterMerge(folded);
	}
</script>

<!-- Mark selected as finished button -->
<Tooltip.Provider>
	<Tooltip.Root ignoreNonKeyboardFocus>
		<Tooltip.Trigger
			onclick={async () => {
				await bookOps.markBooksAsComplete(selectionState.getSelectedIds());
				selectionState.deselectAll();
			}}
			class={buttonVariants({ variant: 'ghost', size: 'icon' })}
		>
			<BookOpenCheck />
		</Tooltip.Trigger>
		<Tooltip.Content>
			<p>Mark as finished</p>
		</Tooltip.Content>
	</Tooltip.Root>
</Tooltip.Provider>

<!-- Add to shelf button -->
<Tooltip.Provider>
	<Tooltip.Root ignoreNonKeyboardFocus>
		<Tooltip.Trigger>
			{#snippet child({ props })}
				<DropdownMenu.Root>
					<DropdownMenu.Trigger
						{...props}
						class={buttonVariants({
							variant: 'ghost',
							size: 'icon'
						})}
					>
						<Album />
					</DropdownMenu.Trigger>
					<DropdownMenu.Content>
						<DropdownMenu.Group>
							<DropdownMenu.GroupHeading>Shelves</DropdownMenu.GroupHeading>
							{#each bookshelfState.getBookshelves(libraryState.activeLibrary!.id) ?? [] as shelf (shelf.id)}
								<DropdownMenu.CheckboxItem
									checked={selectedBooks.every(
										(book) => book.lists.findIndex((sh) => sh.id === shelf.id) !== -1
									)}
									onclick={async () => {
										// Check if all books are in the shelf
										const allBooksInShelf = selectedBooks.every((book) =>
											book.lists.find((sh) => sh.id === shelf.id)
										);

										if (allBooksInShelf) {
											// Remove all books from the shelf
											const bookIds = selectedBooks.map((book) => book.id);
											await bookshelfState.removeBooksFromShelf(shelf.id, bookIds);
											selectedBooks.forEach((book) => {
												book.lists = book.lists.filter((sh) => sh.id !== shelf.id);
											});
										} else {
											// Add all books to the shelf
											const bookIds = selectedBooks.map((book) => book.id);
											await bookshelfState.addBooksToShelf(shelf.id, bookIds);
											selectedBooks.forEach((book) => {
												if (!book.lists.find((sh) => sh.id === shelf.id)) {
													book.lists.push(shelf);
												}
											});
										}
										selectionState.deselectAll();
									}}
								>
									{shelf.title}
								</DropdownMenu.CheckboxItem>
							{/each}
						</DropdownMenu.Group>
						<DropdownMenu.Separator />
						<DropdownMenu.Item
							onclick={() => (createShelfDialogOpen = true)}
							class="text-muted-foreground "
						>
							<PlusIcon class="size-4" />
							New Shelf
						</DropdownMenu.Item>
					</DropdownMenu.Content>
				</DropdownMenu.Root>
			{/snippet}
		</Tooltip.Trigger>
		<Tooltip.Content side="bottom">
			<p>Add/Remove from shelf</p>
		</Tooltip.Content>
	</Tooltip.Root>
</Tooltip.Provider>

<!-- Download selected button -->
<Tooltip.Provider>
	<Tooltip.Root ignoreNonKeyboardFocus>
		<Tooltip.Trigger
			onclick={() => {
				bookOps.downloadBooks(selectionState.getSelectedIds());
			}}
			class={buttonVariants({ variant: 'ghost', size: 'icon' })}
		>
			<Download />
		</Tooltip.Trigger>
		<Tooltip.Content>
			<p>Download selected</p>
		</Tooltip.Content>
	</Tooltip.Root>
</Tooltip.Provider>

<!-- Merge selected button. Two books is the smallest thing a merge can mean, so
     it appears only once there are two. -->
{#if selectedBooks.length > 1}
	<Tooltip.Provider>
		<Tooltip.Root ignoreNonKeyboardFocus>
			<Tooltip.Trigger>
				{#snippet child({ props })}
					<!-- A choice rather than a jump straight into the workbench: two
					     copies of one book have nothing to resolve, and the dialog is a
					     step in the way of saying so. -->
					<DropdownMenu.Root>
						<DropdownMenu.Trigger
							{...props}
							disabled={quickMerging}
							class={buttonVariants({ variant: 'ghost', size: 'icon' })}
						>
							<GitMerge />
						</DropdownMenu.Trigger>
						<DropdownMenu.Content>
							<DropdownMenu.Item onclick={quickMerge}>
								<GitMerge class="size-4 shrink-0" />
								Merge into first selected
							</DropdownMenu.Item>
							<DropdownMenu.Item onclick={() => (mergingBooks = selectedBooks)}>
								<SlidersHorizontal class="size-4 shrink-0" />
								Manual merge…
							</DropdownMenu.Item>
						</DropdownMenu.Content>
					</DropdownMenu.Root>
				{/snippet}
			</Tooltip.Trigger>
			<Tooltip.Content>
				<p>Merge {selectedBooks.length} books</p>
			</Tooltip.Content>
		</Tooltip.Root>
	</Tooltip.Provider>
{/if}

<!-- Delete selected button -->
<Tooltip.Provider>
	<Tooltip.Root ignoreNonKeyboardFocus>
		<Tooltip.Trigger
			onclick={() => {
				bookOps.deleteDialogTitle = `Delete ${selectionState.getSelectedIds().length} books?`;
				bookOps.deleteFn = async (deleteFiles: boolean) => {
					await bookOps.deleteBooks(selectionState.getSelectedIds(), deleteFiles);
					libraryState.activeLibrary!.total! -= selectedBooks.length;
					bookshelfState.deletedBooks(selectedBooks);
					selectionState.deselectAll();
				};
				bookOps.deleteDialogOpen = true;
			}}
			class={buttonVariants({ variant: 'ghost', size: 'icon' })}
		>
			<Trash2 />
		</Tooltip.Trigger>
		<Tooltip.Content>
			<p>Delete selected</p>
		</Tooltip.Content>
	</Tooltip.Root>
</Tooltip.Provider>

<!-- Select all button -->
<Tooltip.Provider>
	<Tooltip.Root ignoreNonKeyboardFocus>
		<Tooltip.Trigger
			onclick={() => {
				selectionState.selectAll(collectionState.books);
			}}
			class={buttonVariants({ variant: 'ghost', size: 'icon' })}
		>
			<SquareCheckBig />
		</Tooltip.Trigger>
		<Tooltip.Content>
			<p>Select all</p>
		</Tooltip.Content>
	</Tooltip.Root>
</Tooltip.Provider>

<!-- Deselect all button -->
<Tooltip.Provider>
	<Tooltip.Root ignoreNonKeyboardFocus>
		<Tooltip.Trigger
			onclick={() => {
				selectionState.deselectAll();
			}}
			class={buttonVariants({ variant: 'ghost', size: 'icon' })}
		>
			<X />
		</Tooltip.Trigger>
		<Tooltip.Content>
			<p>Deselect all</p>
		</Tooltip.Content>
	</Tooltip.Root>
</Tooltip.Provider>

<ShelfCreateDialog
	bind:open={createShelfDialogOpen}
	onSubmit={async (name: string) => {
		const bookshelf = await bookshelfState.addBookshelf(
			name,
			libraryState.activeLibrary!.id,
			selectionState.getSelectedIds()
		);
		selectedBooks.forEach((book) => book.lists.push(bookshelf));
		selectionState.deselectAll();
		createShelfDialogOpen = false;
	}}
/>

<!--
	Keyed on the selection so opening merge on a different pair starts from those
	records rather than the previous dialog's draft.
-->
{#if mergingBooks}
	{#key mergingBooks.map((book) => book.id).join()}
		<MergeBooks
			books={mergingBooks}
			libraryId={libraryState.activeLibrary!.id}
			bind:open={
				() => mergingBooks !== null,
				(value) => {
					// Bound, not passed as a bare `true`: the dialog closes itself on an
					// outside click or Escape, and if that never reaches `mergingBooks`
					// the snapshot stays set — the menu item then re-assigns the same
					// selection, nothing changes, and the dialog never reopens.
					if (!value) mergingBooks = null;
				}
			}
			onmerged={(_survivor, folded) => {
				afterMerge(folded);
				mergingBooks = null;
			}}
		/>
	{/key}
{/if}
