<script lang="ts">
	import { resolve } from '$app/paths';
	import * as Table from '$lib/components/ui/table/index';
	import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index';
	import { Checkbox } from '$lib/components/ui/checkbox/index';
	import { Badge } from '$lib/components/ui/badge/index';
	import { buttonVariants } from '$lib/components/ui/button/index.js';
	import BookCover from './book-cover.svelte';
	import BookActionsMenu from './book-actions-menu.svelte';
	import { getBookSelectionState } from '$lib/state/bookSelection.svelte';
	import { getBookCollectionState } from '$lib/state/bookCollection.svelte';
	import { getLibraryState } from '$lib/state/library.svelte';
	import { formatFileSize, getFileType } from '$lib/utils';
	import { ArrowDown, ArrowUp, Columns3 } from '@lucide/svelte';
	import type { Book } from '$lib/schema';

	let { books }: { books: Book[] } = $props();

	const selectionState = getBookSelectionState();
	const bookCollection = getBookCollectionState();
	const libraryState = getLibraryState();

	/**
	 * Column definitions.
	 *
	 * `sort` names the backend orderBy field. Only the fields the API can
	 * actually order by carry one — the rest are not clickable, rather than
	 * offering a header that silently does nothing.
	 */
	type Column = {
		key: string;
		label: string;
		sort?: string;
		numeric?: boolean;
		on: boolean;
		fixed?: boolean;
	};

	let columns = $state<Column[]>([
		{ key: 'title', label: 'Title', sort: 'title', on: true, fixed: true },
		{ key: 'authors', label: 'Authors', on: true },
		{ key: 'series', label: 'Series', on: false },
		{ key: 'publisher', label: 'Publisher', on: false },
		{ key: 'published', label: 'Year', sort: 'published_date', numeric: true, on: true },
		{ key: 'pages', label: 'Pages', sort: 'pages', numeric: true, on: true },
		{ key: 'format', label: 'Format', on: true },
		{ key: 'size', label: 'Size', numeric: true, on: true },
		{ key: 'added', label: 'Added', sort: 'created_at', on: false },
		{ key: 'progress', label: 'Progress', sort: 'last_accessed', on: true },
		{ key: 'missing', label: 'Missing', on: true }
	]);

	const visible = $derived(columns.filter((c) => c.on));

	const allSelected = $derived(
		books.length > 0 && books.every((b) => selectionState.isSelected(b.id))
	);

	/** What a record is lacking — the reason this view exists. */
	function missing(book: Book) {
		const gaps: string[] = [];
		if (!book.cover_image) gaps.push('cover');
		if (!book.description) gaps.push('description');
		if (!book.identifiers || Object.keys(book.identifiers).length === 0) gaps.push('isbn');
		if (!book.publisher) gaps.push('publisher');
		return gaps;
	}

	function totalSize(book: Book) {
		return book.files.reduce((n, f) => n + f.size, 0);
	}

	function formats(book: Book) {
		return [...new Set(book.files.map((f) => getFileType(f.filename)))].join(' + ');
	}

	/**
	 * Once a selection exists, selecting is the primary interaction — the whole
	 * row toggles. preventDefault also stops the title and author links inside
	 * the row from navigating, since the click bubbles through them to here.
	 */
	function handleRowClick(event: MouseEvent, book: Book) {
		if (!selectionState.selectionModeActive) return;
		if ((event.target as HTMLElement).closest('[data-row-control]')) return;

		event.preventDefault();
		selectionState.toggleSelection(book);
	}

	/**
	 * BookRead does not expose created_at yet, though the column exists in the
	 * database and the API can already order by it — so the header sorts today
	 * and the cell fills in by itself once the schema catches up. See TODO.md.
	 */
	function addedOn(book: Book) {
		const value = (book as Book & { created_at?: string }).created_at;
		return value ? new Date(value).toLocaleDateString() : '—';
	}
</script>

<div class="flex flex-col gap-3">
	<!--
		Column picker. Sorting lives on the headers, so this is all the toolbar needs.

		sticky left-0 + w-fit keeps it against the viewport's left edge while the
		table scrolls sideways. Without it the toolbar is as wide as the table — it
		is a sibling inside the ScrollArea's fit-content wrapper — so a right
		aligned button ends up off-screen once enough columns are on. This works
		where the sticky columns could not, because the toolbar sits outside the
		table's own overflow-x-auto container.
	-->
	<div class="sticky left-0 flex w-fit items-center gap-3">
		<span class="font-mono text-xs text-muted-foreground tabular-nums">
			{books.length} shown
		</span>
		<div>
			<DropdownMenu.Root>
				<DropdownMenu.Trigger class={buttonVariants({ variant: 'outline', size: 'sm' })}>
					<Columns3 class="size-4" />
					Columns
				</DropdownMenu.Trigger>
				<DropdownMenu.Content align="end" class="w-48">
					<!-- GroupHeading reads the group context, so it has to sit inside a Group -->
					<DropdownMenu.Group>
						<DropdownMenu.GroupHeading>Show columns</DropdownMenu.GroupHeading>
						<DropdownMenu.Separator />
						{#each columns as column (column.key)}
							<!-- closeOnSelect={false} so several columns can be toggled in one pass -->
							<DropdownMenu.CheckboxItem
								checked={column.on}
								disabled={column.fixed}
								closeOnSelect={false}
								onCheckedChange={(value) => {
									if (!column.fixed) column.on = value;
								}}
							>
								{column.label}
							</DropdownMenu.CheckboxItem>
						{/each}
					</DropdownMenu.Group>
				</DropdownMenu.Content>
			</DropdownMenu.Root>
		</div>
	</div>

	<!--
		The table is w-full by default, so with many columns on it compresses every
		cell rather than growing. w-max lets it take the width its content needs and
		scroll horizontally in the browser's ScrollArea; min-w-full keeps it filling
		the container when only a few columns are on.
	-->
	<Table.Root class="w-max min-w-full">
		<Table.Header>
			<Table.Row>
				<Table.Head class="w-10">
					<Checkbox
						checked={allSelected}
						aria-label="Select all loaded books"
						onCheckedChange={() => {
							if (allSelected) selectionState.deselectAll();
							else selectionState.selectAll(books);
						}}
					/>
				</Table.Head>
				<Table.Head class="w-12"></Table.Head>

				{#each visible as column (column.key)}
					<Table.Head class={column.numeric ? 'text-right' : ''}>
						{#if column.sort}
							<!-- Sorting is server-side: the header drives the same orderBy /
							     sortOrder state the sort menu uses, so it orders the whole
							     library rather than the pages loaded so far. -->
							<button
								type="button"
								class="inline-flex items-center gap-1 hover:text-foreground {bookCollection.orderBy ===
								column.sort
									? 'font-semibold text-foreground'
									: ''}"
								onclick={() => bookCollection.updateSort(column.sort!)}
							>
								{column.label}
								{#if bookCollection.orderBy === column.sort}
									{#if bookCollection.sortOrder === 'asc'}
										<ArrowUp class="size-3" />
									{:else}
										<ArrowDown class="size-3" />
									{/if}
								{/if}
							</button>
						{:else}
							{column.label}
						{/if}
					</Table.Head>
				{/each}
			</Table.Row>
		</Table.Header>

		<Table.Body>
			{#each books as book (book.id)}
				<Table.Row
					data-state={selectionState.isSelected(book.id) ? 'selected' : undefined}
					onclick={(event: MouseEvent) => handleRowClick(event, book)}
					class={selectionState.selectionModeActive ? 'cursor-pointer' : ''}
				>
					<Table.Cell data-row-control>
						<Checkbox
							checked={selectionState.isSelected(book.id)}
							aria-label="Select {book.title}"
							onCheckedChange={() => selectionState.toggleSelection(book)}
						/>
					</Table.Cell>
					<Table.Cell>
						<a href={resolve('/(root)/(library)/book/[bookId]', { bookId: String(book.id) })}
							><BookCover {book} height={36} /></a
						>
					</Table.Cell>

					{#each visible as column (column.key)}
						{#if column.key === 'title'}
							<Table.Cell class="max-w-[280px] truncate font-serif">
								<a
									href={resolve('/(root)/(library)/book/[bookId]', { bookId: String(book.id) })}
									class="hover:underline">{book.title}</a
								>
							</Table.Cell>
						{:else if column.key === 'authors'}
							<Table.Cell class="max-w-[180px] truncate">
								{#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}
							</Table.Cell>
						{:else if column.key === 'series'}
							<Table.Cell class="text-muted-foreground">
								{book.series ? book.series.title : '—'}
							</Table.Cell>
						{:else if column.key === 'publisher'}
							<Table.Cell class="max-w-[160px] truncate text-muted-foreground">
								{book.publisher ? book.publisher.name : '—'}
							</Table.Cell>
						{:else if column.key === 'published'}
							<Table.Cell class="text-right font-mono text-xs tabular-nums">
								{book.published_date ? new Date(book.published_date).getFullYear() : '—'}
							</Table.Cell>
						{:else if column.key === 'pages'}
							<Table.Cell class="text-right font-mono text-xs tabular-nums">
								{book.pages ?? '—'}
							</Table.Cell>
						{:else if column.key === 'format'}
							<Table.Cell><span class="font-mono text-xs">{formats(book)}</span></Table.Cell>
						{:else if column.key === 'size'}
							<Table.Cell class="text-right font-mono text-xs tabular-nums">
								{formatFileSize(totalSize(book))}
							</Table.Cell>
						{:else if column.key === 'added'}
							<Table.Cell class="font-mono text-xs text-muted-foreground tabular-nums">
								{addedOn(book)}
							</Table.Cell>
						{:else if column.key === 'progress'}
							<Table.Cell>
								{#if book.progress?.completed}
									<span class="font-mono text-xs font-semibold text-success">Finished</span>
								{:else if book.progress?.percentage}
									<span class="flex items-center gap-2">
										<span class="h-1 w-16 overflow-hidden rounded-full bg-muted-foreground/25">
											<span
												class="block h-full bg-flag"
												style="width: {Math.round(book.progress.percentage * 100)}%;"
											></span>
										</span>
										<span class="font-mono text-xs text-muted-foreground tabular-nums">
											{Math.round(book.progress.percentage * 100)}%
										</span>
									</span>
								{:else}
									<span class="font-mono text-xs text-muted-foreground">Unread</span>
								{/if}
							</Table.Cell>
						{:else if column.key === 'missing'}
							<Table.Cell>
								{#each missing(book) as gap (gap)}
									<Badge variant="outline" class="mr-1 border-flag font-mono text-[10px] text-flag">
										{gap}
									</Badge>
								{:else}
									<span class="font-mono text-xs text-success">complete</span>
								{/each}
							</Table.Cell>
						{/if}
					{/each}

					<Table.Cell class="text-right">
						<BookActionsMenu {book} class="size-8" />
					</Table.Cell>
				</Table.Row>
			{/each}
		</Table.Body>
	</Table.Root>
</div>
