<script lang="ts">
	import { resolve } from '$app/paths';
	import { onMount, untrack } from 'svelte';

	import {
		ChevronLeft,
		ChevronRight,
		PanelLeft,
		RotateCcw,
		Settings2,
		TriangleAlert
	} from '@lucide/svelte';

	import type { FoliateRelocateDetail, FoliateTocItem } from '$foliate/view.js';
	import { ProgressReporter } from '$lib/reader/progress';
	import { purgeLegacyLocationCache } from '$lib/reader/legacy-cache';
	import { setReaderSettingsState } from '$lib/state/reader-settings.svelte';

	import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
	import { Spinner } from '$lib/components/ui/spinner/index';
	import * as Sidebar from '$lib/components/ui/sidebar/index';
	import * as Tooltip from '$lib/components/ui/tooltip/index';
	import ThemeToggle from '$lib/components/layout/theme-toggle.svelte';
	import FoliateView from './foliate-view.svelte';
	import ReaderSettings from './reader-settings.svelte';
	import ReaderToc from './reader-toc.svelte';

	let {
		fileUrl,
		bookId,
		filename,
		title = '',
		initialProgress = 0,
		initialEpubLoc = null
	}: {
		fileUrl: string;
		bookId: string | number;
		filename: string;
		title?: string;
		initialProgress?: number;
		initialEpubLoc?: string | null;
	} = $props();

	const settingsState = setReaderSettingsState();

	// Read once: the reader is remounted per file, so these are fixed for its
	// lifetime, and tracking them would restart the load mid-read.
	const reporter = new ProgressReporter(untrack(() => bookId));
	const initialCfi = untrack(() => initialEpubLoc);
	const initialFraction = untrack(() => initialProgress);

	let file = $state<File>();
	let loadError = $state<string | null>(null);
	let isReady = $state(false);

	let toc = $state<FoliateTocItem[]>([]);
	let activeTocId = $state<number | null>(null);
	let isSidebarOpen = $state(false);
	let isSettingsOpen = $state(false);
	let isFixedLayout = $state(false);

	let progress = $state(initialFraction);
	let viewer = $state<ReturnType<typeof FoliateView>>();

	const percent = $derived(Math.round(progress * 100));

	/**
	 * Fetches the book ourselves rather than handing foliate the URL.
	 *
	 * view.open(url) would route through foliate's fetchFile, which names the File
	 * after the URL path — "34", with no extension — and makeBook's CBZ/FB2 checks
	 * are filename-based. Doing it here also keeps the response.ok check: the proxy
	 * answers a failure with a SvelteKit error page, and fetch resolves on a 404,
	 * so without it a missing file surfaced only as an opaque parse error.
	 */
	async function loadFile() {
		loadError = null;
		isReady = false;
		file = undefined;

		try {
			const response = await fetch(fileUrl);
			if (!response.ok) throw new Error(`The server returned ${response.status} for this file.`);

			file = new File([await response.blob()], filename, { type: 'application/epub+zip' });
		} catch (error) {
			console.error('Could not download the book', error);
			loadError = error instanceof Error ? error.message : 'The file could not be downloaded.';
		}
	}

	function handleRelocate(detail: FoliateRelocateDetail) {
		progress = detail.fraction;
		activeTocId = detail.tocItem?.id ?? null;
		reporter.record(detail.fraction, detail.cfi);
	}

	function navigateToChapter(href: string) {
		// Deliberately leaves the sidebar open: picking a chapter is usually part of
		// browsing several, and closing it each time made that tedious.
		void viewer?.goTo(href);
	}

	onMount(() => {
		purgeLegacyLocationCache();
		reporter.listen();
		void loadFile();

		return () => reporter.dispose();
	});
</script>

<svelte:head>
	<title>{title ? `${title} — Chitai` : 'Reader — Chitai'}</title>
</svelte:head>

<Sidebar.Provider bind:open={isSidebarOpen} class="min-h-0">
	{#if isReady}
		<ReaderToc {toc} activeId={activeTocId} onnavigate={navigateToChapter} />
	{/if}

	<main class="flex h-screen w-full flex-col overflow-hidden bg-background">
		<!-- Reader chrome: somewhere to go back to, what you are reading, how far in -->
		<header class="flex h-12 shrink-0 items-center gap-2 border-b px-3">
			<Tooltip.Provider>
				<Tooltip.Root ignoreNonKeyboardFocus>
					<Tooltip.Trigger
						class="{buttonVariants({ variant: 'ghost', size: 'icon' })} size-8"
						onclick={() => (isSidebarOpen = !isSidebarOpen)}
						disabled={!isReady || toc.length === 0}
					>
						<PanelLeft class="size-4" />
					</Tooltip.Trigger>
					<Tooltip.Content side="bottom"><p>Chapters</p></Tooltip.Content>
				</Tooltip.Root>
			</Tooltip.Provider>

			<a
				href={resolve('/(root)/(library)/book/[bookId]', { bookId: String(bookId) })}
				class="min-w-0 flex-1 truncate font-serif text-sm hover:underline"
				{title}
			>
				{title || 'Reader'}
			</a>

			{#if isReady}
				<span class="hidden items-center gap-2 sm:flex">
					<span class="h-1 w-24 overflow-hidden rounded-full bg-muted-foreground/25">
						<span class="block h-full bg-flag" style="width: {percent}%;"></span>
					</span>
					<span class="font-mono text-xs text-muted-foreground tabular-nums">{percent}%</span>
				</span>
			{/if}

			<Tooltip.Provider>
				<Tooltip.Root ignoreNonKeyboardFocus>
					<Tooltip.Trigger
						class="{buttonVariants({ variant: 'ghost', size: 'icon' })} size-8"
						onclick={() => (isSettingsOpen = true)}
						disabled={!isReady}
					>
						<Settings2 class="size-4" />
					</Tooltip.Trigger>
					<Tooltip.Content side="bottom"><p>Reading settings</p></Tooltip.Content>
				</Tooltip.Root>
			</Tooltip.Provider>

			<ThemeToggle />
		</header>

		{#if loadError}
			<div class="flex flex-1 items-center justify-center p-6">
				<div class="flex max-w-sm flex-col items-center gap-3 text-center">
					<TriangleAlert class="size-8 text-destructive" />
					<h2 class="font-serif text-lg">This book wouldn't open</h2>
					<p class="text-sm text-muted-foreground">{loadError}</p>
					<div class="mt-2 flex gap-2">
						<Button onclick={loadFile}>
							<RotateCcw class="size-4" />
							Try again
						</Button>
						<a
							href={resolve('/(root)/(library)/book/[bookId]', { bookId: String(bookId) })}
							class={buttonVariants({ variant: 'outline' })}
						>
							Back to book
						</a>
					</div>
				</div>
			</div>
		{:else}
			{#if !isReady}
				<div class="flex flex-1 items-center justify-center gap-3">
					<Spinner />
					<span class="text-sm text-muted-foreground">Opening…</span>
				</div>
			{/if}

			<div class="min-h-0 flex-1 p-4 {isReady ? '' : 'hidden'}">
				<!-- The page itself: same colour the book is themed to, so the two
				     meet seamlessly and the border reads as the edge of the sheet. -->
				<div class="relative h-full overflow-hidden rounded-lg border bg-card shadow-sm">
					{#if file}
						<FoliateView
							bind:this={viewer}
							{file}
							{initialCfi}
							{initialFraction}
							settings={settingsState.settings}
							onrelocate={handleRelocate}
							onerror={(message) => (loadError = message)}
							onready={(detail) => {
								toc = detail.toc;
								isFixedLayout = detail.isFixedLayout;
								isReady = true;
							}}
						/>
					{/if}

					<!-- Overlaid rather than placed beside the page, so they stay within
					     reach of the text. They sit in foliate's own page margin. -->
					<Button
						variant="ghost"
						size="icon"
						class="absolute top-1/2 left-1 size-10 -translate-y-1/2 opacity-40 transition-opacity hover:opacity-100"
						onclick={() => viewer?.goBack()}
						aria-label="Previous page"
					>
						<ChevronLeft class="size-5" />
					</Button>

					<Button
						variant="ghost"
						size="icon"
						class="absolute top-1/2 right-1 size-10 -translate-y-1/2 opacity-40 transition-opacity hover:opacity-100"
						onclick={() => viewer?.goForward()}
						aria-label="Next page"
					>
						<ChevronRight class="size-5" />
					</Button>
				</div>
			</div>
		{/if}
	</main>
</Sidebar.Provider>

<ReaderSettings bind:open={isSettingsOpen} {isFixedLayout} />
