<script lang="ts">
	import { resolve } from '$app/paths';
	import { Badge, badgeVariants } from '$lib/components/ui/badge/index.js';
	import { CollapsibleText } from '$lib/components/ui/collapsible-text/index.js';
	import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index';
	import * as Tooltip from '$lib/components/ui/tooltip/index';
	import * as Field from '$lib/components/ui/field/index.js';
	import { Checkbox } from '$lib/components/ui/checkbox/index.js';
	import { buttonVariants } from '$lib/components/ui/button/index.js';
	import BookCover from '$lib/components/view/book-cover.svelte';
	import type { BookFile } from '$lib/schema';
	import {
		Album,
		BookOpenCheck,
		BookOpenText,
		Download,
		Pencil,
		PlusIcon,
		Trash2
	} from '@lucide/svelte';
	import {
		describeIdentifier,
		formatFileSize,
		getFileType,
		isReadable,
		sortIdentifiers
	} from '$lib/utils.js';
	import { getBookOperationsState } from '$lib/state/bookOperations.svelte.js';
	import { getBookshelfState } from '$lib/state/bookshelf.svelte.js';
	import { getLibraryState } from '$lib/state/library.svelte.js';
	import * as Accordion from '$lib/components/ui/accordion/index.js';
	import * as Table from '$lib/components/ui/table/index.js';
	import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
	import ShelfCreateDialog from '$lib/components/forms/shelf-create-dialog.svelte';

	let { data } = $props();

	// Follows the loaded book, and stays assignable so an edit can update it locally
	// until the next navigation supplies a fresh one.
	let book = $derived(data.book);

	const bookOps = getBookOperationsState();
	const libraryState = getLibraryState();
	const bookshelfState = getBookshelfState();

	let deleteFiles = $state(true);
	let fileDeleteDialogOpen = $state(false);
	let fileToDelete = $state<number>();
	let createShelfDialogOpen = $state(false);

	const percent = $derived(
		book.progress?.percentage ? Math.round(book.progress.percentage * 100) : 0
	);

	// Only the files there is a reader for. A book can be stored in a format Chitai
	// cannot open — a Calibre library is full of MOBI and AZW3 — and offering to read
	// one opened a window that did nothing at all.
	const readableFiles = $derived(book.files.filter((file) => isReadable(file.filename)));

	// The primary action states what it will actually do.
	const readLabel = $derived(
		book.progress?.completed
			? 'Read again'
			: percent > 0
				? `Continue reading · ${percent}%`
				: 'Read'
	);

	// window.open is outside the lint rule's reach, but the paths need resolving
	// just the same — they would break under a non-empty base path.
	function openBookInReader(file: BookFile) {
		const params = { bookId: String(book.id), fileId: String(file.id) };

		if (getFileType(file.filename) === 'EPUB')
			window.open(
				resolve('/(root)/(library)/book/[bookId]/read/epub/[fileId]', params),
				'_blank',
				'noopener,noreferrer'
			);

		if (getFileType(file.filename) === 'PDF')
			window.open(
				resolve('/(root)/(library)/book/[bookId]/read/pdf/[fileId]', params),
				'_blank',
				'noopener,noreferrer'
			);
	}

	// On the band, the accent is the ground — invert the buttons against it.
	const bandPrimary =
		'inline-flex items-center gap-2 rounded-lg bg-primary-foreground px-4 py-2 text-sm font-semibold text-primary tabular-nums transition-colors hover:bg-primary-foreground/90';
	const bandGhost =
		'inline-flex items-center gap-2 rounded-lg border border-primary-foreground/35 px-4 py-2 text-sm font-medium transition-colors hover:bg-primary-foreground/10';

	// With nothing to read, downloading is the only thing left to do — so it takes the
	// primary slot rather than leaving the band with no filled button on it.
	const bandDownload = $derived(readableFiles.length ? bandGhost : bandPrimary);
</script>

<div class="flex h-full flex-col overflow-y-auto">
	<!-- Jacket band: the accent carries the identity, the cover overlaps its edge -->
	<!--
		A contained card, not a full-bleed banner. Breaking out with -mx-4 left it
		inset on one side and bleeding off the other, and the negative top margin
		was clipped by the layout's overflow-hidden. rounded-lg is exactly
		var(--radius), so the Corners setting reaches it.
	-->
	<header class="rounded-lg bg-primary px-6 pt-10 pb-6 text-primary-foreground md:px-10">
		<!-- items-start so the title sits on the cover's top edge; the cover's
		     negative bottom margin still carries the overlap below the band -->
		<div class="mx-auto flex max-w-5xl items-start gap-6">
			<!-- BookCover draws its own progress bar, clipped to the cover's radius -->
			<BookCover {book} height={260} class="-mb-20 drop-shadow-2xl" />

			<div class="min-w-0 flex-1">
				<h1 class="font-serif text-3xl leading-tight tracking-tight text-balance">
					{book.title}
				</h1>

				{#if book.subtitle}
					<p class="mt-1 font-serif text-lg text-primary-foreground/75 italic">{book.subtitle}</p>
				{/if}

				{#if book.series}
					<p class="mt-1 font-mono text-xs tracking-wider text-primary-foreground/70 uppercase">
						{book.series.title}{book.series_position ? ` · #${book.series_position}` : ''}
					</p>
				{/if}

				{#if book.authors && book.authors.length > 0}
					<p class="mt-2 line-clamp-1 text-primary-foreground/85">
						By
						{#each book.authors as author (author.id)}
							<a
								href="{resolve('/(root)/(library)/library/[libraryId]/view', {
									libraryId: String(libraryState.activeLibrary!.id)
								})}?authors={author.id}"
								class="cs-list hover:underline">{author.name}</a
							> &thinsp;
						{/each}
					</p>
				{/if}

				<!--
					Primary actions live on the band, not in a side rail. Read and Download
					are counted separately: everything can be downloaded, only EPUB and PDF
					can be opened, so a book can have two files and one way to read it.
				-->
				<div class="mt-5 flex flex-wrap items-center gap-2">
					{#if readableFiles.length > 1}
						<DropdownMenu.Root>
							<DropdownMenu.Trigger class={bandPrimary}>
								<BookOpenText class="size-4" />
								{readLabel}
							</DropdownMenu.Trigger>
							<DropdownMenu.Content>
								<DropdownMenu.Group>
									<DropdownMenu.GroupHeading>File formats:</DropdownMenu.GroupHeading>
									<DropdownMenu.Separator />
									{#each readableFiles as file (file.id)}
										<DropdownMenu.Item onclick={() => openBookInReader(file)}>
											{getFileType(file.filename)}
										</DropdownMenu.Item>
									{/each}
								</DropdownMenu.Group>
							</DropdownMenu.Content>
						</DropdownMenu.Root>
					{:else if readableFiles.length === 1}
						<button
							type="button"
							class={bandPrimary}
							onclick={() => openBookInReader(readableFiles[0])}
						>
							<BookOpenText class="size-4" />
							{readLabel}
						</button>
					{/if}

					{#if book.files.length > 1}
						<DropdownMenu.Root>
							<DropdownMenu.Trigger class={bandDownload}>
								<Download class="size-4" />
								Download
							</DropdownMenu.Trigger>
							<DropdownMenu.Content>
								<DropdownMenu.Group>
									<DropdownMenu.GroupHeading>File formats:</DropdownMenu.GroupHeading>
									<DropdownMenu.Separator />
									{#each book.files as file (file.id)}
										<DropdownMenu.Item
											onclick={() => bookOps.downloadBookFile(book.id, file.id, file.filename)}
										>
											{getFileType(file.filename)}
										</DropdownMenu.Item>
									{/each}
									<DropdownMenu.Separator />
									<DropdownMenu.Item
										onclick={() => bookOps.downloadBooks([book.id], `${book.title}.zip`)}
									>
										All (ZIP)
									</DropdownMenu.Item>
								</DropdownMenu.Group>
							</DropdownMenu.Content>
						</DropdownMenu.Root>
					{:else if book.files.length === 1}
						<button
							type="button"
							class={bandDownload}
							onclick={() =>
								bookOps.downloadBookFile(book.id, book.files[0].id, book.files[0].filename)}
						>
							<Download class="size-4" />
							Download
						</button>
					{/if}
				</div>
			</div>
		</div>
	</header>

	<!--
		Body. Source order is description → details → files, which is the mobile
		reading order asked for. On md+ the explicit row/column placement puts the
		details rail on the right and files back under the description.
	-->
	<!--
		Same horizontal frame as the band — matching px and max-width, so the
		description lines up with the cover's left edge.
	-->
	<div class="px-6 md:px-10">
		<!--
		grid-rows matters here. The rail spans both rows, and with two auto rows the
		browser splits any surplus rail height evenly between them — so a one-line
		description got stretched to half the rail's height, leaving a large gap
		above the files card. Sizing row 1 to its content and letting row 2 take the
		free space sends the slack to the bottom instead, where it is invisible.
	-->
		<div
			class="mx-auto grid w-full max-w-5xl gap-8 pt-28 pb-10 md:grid-cols-[minmax(0,1fr)_260px] md:grid-rows-[auto_1fr] md:items-start"
		>
			<!-- Description -->
			<section class="min-w-0 md:col-start-1 md:row-start-1">
				{#if book.description}
					<CollapsibleText text={book.description} maxLength={500} />
				{:else}
					<p class="text-sm text-muted-foreground">No description for this book yet.</p>
				{/if}
			</section>

			<!-- Details rail -->
			<aside class="flex flex-col gap-6 md:col-start-2 md:row-span-2 md:row-start-1">
				<div>
					<h2 class="mb-3 font-mono text-[10px] tracking-widest text-muted-foreground uppercase">
						Details
					</h2>
					<!-- items-baseline: the 10px mono label and the 14px value sit on a
				     shared text baseline, so the pair reads as one line -->
					<dl
						class="grid grid-cols-[auto_minmax(0,1fr)] items-baseline gap-x-4 gap-y-2 rounded-lg border bg-card p-4 text-sm"
					>
						{#if book.publisher}
							<dt class="font-mono text-[10px] tracking-wider text-muted-foreground uppercase">
								Publisher
							</dt>
							<dd class="m-0">{book.publisher.name}</dd>
						{/if}
						{#if book.published_date}
							<dt class="font-mono text-[10px] tracking-wider text-muted-foreground uppercase">
								Published
							</dt>
							<dd class="m-0 font-mono tabular-nums">{book.published_date}</dd>
						{/if}
						{#if book.pages}
							<dt class="font-mono text-[10px] tracking-wider text-muted-foreground uppercase">
								Pages
							</dt>
							<dd class="m-0 font-mono tabular-nums">{book.pages}</dd>
						{/if}
						{#if book.language}
							<dt class="font-mono text-[10px] tracking-wider text-muted-foreground uppercase">
								Language
							</dt>
							<dd class="m-0">{book.language}</dd>
						{/if}
						{#if book.edition}
							<dt class="font-mono text-[10px] tracking-wider text-muted-foreground uppercase">
								Edition
							</dt>
							<dd class="m-0 font-mono tabular-nums">{book.edition}</dd>
						{/if}
						{#each sortIdentifiers(book.identifiers) as [name, value] (name)}
							{@const id = describeIdentifier(name, value)}
							<dt class="font-mono text-[10px] tracking-wider text-muted-foreground uppercase">
								{id.label}
							</dt>
							<dd class="m-0 font-mono break-all tabular-nums">
								{#if id.href}
									<!-- Always an absolute URL off-site: openlibrary, doi.org or amazon. -->
									<a
										href={id.href}
										target="_blank"
										rel="external noopener noreferrer"
										class="hover:text-primary hover:underline">{value}</a
									>
								{:else}
									{value}
								{/if}
							</dd>
						{/each}
					</dl>
				</div>

				{#if book.tags.length > 0}
					<div>
						<h2 class="mb-3 font-mono text-[10px] tracking-widest text-muted-foreground uppercase">
							Tags
						</h2>
						<div class="flex flex-wrap gap-1.5">
							{#each book.tags as tag (tag.id)}
								<!-- Was /tag/{id}, a route that has never existed — these badges 404'd.
							     Filter the library view, as the tag links in the list views do. -->
								<a
									href="{resolve('/(root)/(library)/library/[libraryId]/view', {
										libraryId: String(libraryState.activeLibrary!.id)
									})}?tags={tag.id}"
									class={badgeVariants({ variant: 'default' })}>{tag.name}</a
								>
							{/each}
						</div>
					</div>
				{/if}

				{#if book.lists.length > 0}
					<div>
						<h2 class="mb-3 font-mono text-[10px] tracking-widest text-muted-foreground uppercase">
							Shelves
						</h2>
						<div class="flex flex-wrap gap-1.5">
							{#each book.lists as shelf (shelf.id)}
								<a
									href="{resolve('/(root)/(library)/library/[libraryId]/view', {
										libraryId: String(libraryState.activeLibrary!.id)
									})}?shelves={shelf.id}"
									class={badgeVariants({ variant: 'outline' })}>{shelf.title}</a
								>
							{/each}
						</div>
					</div>
				{/if}

				<div>
					<h2 class="mb-3 font-mono text-[10px] tracking-widest text-muted-foreground uppercase">
						Manage
					</h2>
					<div class="flex flex-wrap gap-2">
						<!-- Mark as finished -->
						<Tooltip.Provider>
							<Tooltip.Root ignoreNonKeyboardFocus>
								<Tooltip.Trigger
									class="{buttonVariants({ variant: 'outline', size: 'icon' })} {book.progress
										?.completed
										? 'text-success'
										: ''}"
									onclick={async () => {
										if (book.progress?.completed) await bookOps.markBooksAsIncomplete([book.id]);
										else await bookOps.markBooksAsComplete([book.id]);
									}}
								>
									<BookOpenCheck />
								</Tooltip.Trigger>
								<Tooltip.Content side="bottom">
									<p>{book.progress?.completed ? 'Mark as not finished' : 'Mark as finished'}</p>
								</Tooltip.Content>
							</Tooltip.Root>
						</Tooltip.Provider>

						<!-- Add to shelf -->
						<Tooltip.Provider>
							<Tooltip.Root ignoreNonKeyboardFocus>
								<Tooltip.Trigger>
									{#snippet child({ props })}
										<DropdownMenu.Root>
											<DropdownMenu.Trigger
												{...props}
												class={buttonVariants({ variant: 'outline', 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={book.lists.findIndex((sh) => sh.id === shelf.id) !== -1}
															onclick={async () => {
																if (book.lists.find((sh) => sh.id === shelf.id)) {
																	await bookshelfState.removeBooksFromShelf(shelf.id, [book.id]);
																	book.lists = book.lists.filter((sh) => sh.id !== shelf.id);
																} else {
																	await bookshelfState.addBooksToShelf(shelf.id, [book.id]);
																	book.lists.push(shelf);
																}
															}}
														>
															{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 to shelf</p>
								</Tooltip.Content>
							</Tooltip.Root>
						</Tooltip.Provider>

						<!-- Edit -->
						<Tooltip.Provider>
							<Tooltip.Root ignoreNonKeyboardFocus>
								<Tooltip.Trigger
									onclick={() => {
										bookOps.bookToEdit = book;
										bookOps.editDialogOpen = true;
									}}
									class={buttonVariants({ variant: 'outline', size: 'icon' })}
								>
									<Pencil />
								</Tooltip.Trigger>
								<Tooltip.Content side="bottom">
									<p>Edit</p>
								</Tooltip.Content>
							</Tooltip.Root>
						</Tooltip.Provider>

						<!-- Delete -->
						<Tooltip.Provider>
							<Tooltip.Root ignoreNonKeyboardFocus>
								<Tooltip.Trigger
									class={buttonVariants({ variant: 'outline', size: 'icon' })}
									onclick={() => {
										bookOps.deleteDialogTitle = `Delete book?`;
										bookOps.deleteFn = async (deleteFiles: boolean) => {
											await bookOps.deleteBooks([book.id], deleteFiles, false);
											libraryState.activeLibrary!.total!--;
											bookshelfState.deletedBooks([book]);
											await history.back();
										};
										bookOps.deleteDialogOpen = true;
									}}
								>
									<Trash2 />
								</Tooltip.Trigger>
								<Tooltip.Content side="bottom">
									<p>Delete</p>
								</Tooltip.Content>
							</Tooltip.Root>
						</Tooltip.Provider>
					</div>
				</div>
			</aside>

			<!-- Library files -->
			<section class="min-w-0 md:col-start-1 md:row-start-2">
				<Accordion.Root type="single" class="w-full rounded-lg border bg-card px-4">
					<Accordion.Item value="files">
						<Accordion.Trigger>
							<div class="flex items-center gap-3">
								Library files
								<Badge>{book.files.length}</Badge>
							</div>
						</Accordion.Trigger>
						<Accordion.Content>
							<Table.Root>
								<Table.Header>
									<Table.Row>
										<Table.Head class="w-[300px]">Filename</Table.Head>
										<Table.Head>Size</Table.Head>
										<Table.Head>File type</Table.Head>
										<Table.Head class="text-right">Actions</Table.Head>
									</Table.Row>
								</Table.Header>
								<Table.Body>
									{#each book.files as file (file.id)}
										<Table.Row>
											<Table.Cell class="max-w-[300px] min-w-0 overflow-hidden">
												<div class="font-mono text-xs break-words whitespace-normal">
													{file.filename}
												</div>
											</Table.Cell>
											<Table.Cell class="font-mono text-xs tabular-nums">
												{formatFileSize(file.size)}
											</Table.Cell>
											<Table.Cell
												><span class="font-mono text-xs">{getFileType(file.filename)}</span
												></Table.Cell
											>
											<Table.Cell class="text-right">
												<div class="flex justify-end gap-1">
													{#if isReadable(file.filename)}
														<Tooltip.Provider>
															<Tooltip.Root ignoreNonKeyboardFocus>
																<Tooltip.Trigger
																	class="{buttonVariants({
																		variant: 'default',
																		size: 'icon'
																	})} scale-90"
																	onclick={() => openBookInReader(file)}
																>
																	<BookOpenText />
																</Tooltip.Trigger>
																<Tooltip.Content side="bottom">
																	<p>Read</p>
																</Tooltip.Content>
															</Tooltip.Root>
														</Tooltip.Provider>
													{/if}

													<Tooltip.Provider>
														<Tooltip.Root ignoreNonKeyboardFocus>
															<Tooltip.Trigger
																class="{buttonVariants({
																	variant: 'default',
																	size: 'icon'
																})} scale-90"
																onclick={async () => {
																	await bookOps.downloadBookFile(book.id, file.id, file.filename);
																}}
															>
																<Download />
															</Tooltip.Trigger>
															<Tooltip.Content side="bottom">
																<p>Download</p>
															</Tooltip.Content>
														</Tooltip.Root>
													</Tooltip.Provider>

													<Tooltip.Provider>
														<Tooltip.Root ignoreNonKeyboardFocus>
															<Tooltip.Trigger
																onclick={() => {
																	fileToDelete = file.id;
																	fileDeleteDialogOpen = true;
																}}
																class="{buttonVariants({
																	variant: 'destructive',
																	size: 'icon'
																})} scale-90"
															>
																<Trash2 />
															</Tooltip.Trigger>
															<Tooltip.Content side="bottom">Delete file</Tooltip.Content>
														</Tooltip.Root>
													</Tooltip.Provider>
												</div>
											</Table.Cell>
										</Table.Row>
									{/each}
								</Table.Body>
							</Table.Root>
						</Accordion.Content>
					</Accordion.Item>
				</Accordion.Root>
			</section>
		</div>
	</div>
</div>

<AlertDialog.Root bind:open={fileDeleteDialogOpen}>
	<AlertDialog.Content>
		<AlertDialog.Header>
			<AlertDialog.Title>Are you absolutely sure?</AlertDialog.Title>
			<AlertDialog.Description>
				This action cannot be undone. This will permanently delete the record from the database and
				the file from the filesystem.
			</AlertDialog.Description>
		</AlertDialog.Header>
		<div class="flex items-center gap-2">
			<Checkbox bind:checked={deleteFiles} />
			<Field.Label class="font-normal">Delete files from the filesystem</Field.Label>
		</div>
		<AlertDialog.Footer>
			<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
			<AlertDialog.Action
				onclick={async () => {
					await bookOps.deleteBookFiles(book.id, [fileToDelete!], deleteFiles);
					fileDeleteDialogOpen = false;
				}}
				class={buttonVariants({ variant: 'destructive' })}>Delete</AlertDialog.Action
			>
		</AlertDialog.Footer>
	</AlertDialog.Content>
</AlertDialog.Root>

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