/**
 * <folk-book-reader> — Flipbook PDF reader using pdf.js + StPageFlip.
 *
 * Renders each PDF page to canvas, converts to images, then displays
 * in a realistic page-flip animation. Caches rendered pages in IndexedDB.
 * Saves reading position to localStorage.
 *
 * Attributes:
 *   pdf-url  — URL to the PDF file
 *   book-id  — Unique ID for caching/position tracking
 *   title    — Book title (for display)
 *   author   — Book author (for display)
 */

// pdf.js is loaded from CDN; StPageFlip is imported from npm
// (we'll load both dynamically to avoid bundling issues)

const PDFJS_CDN = "https://unpkg.com/pdfjs-dist@4.9.155/build/pdf.min.mjs";
const PDFJS_WORKER_CDN = "https://unpkg.com/pdfjs-dist@4.9.155/build/pdf.worker.min.mjs";
const STPAGEFLIP_CDN = "https://unpkg.com/page-flip@2.0.7/dist/js/page-flip.browser.js";

interface CachedBook {
	images: string[];
	numPages: number;
	aspectRatio: number;
}

export class FolkBookReader extends HTMLElement {
	private _pdfUrl = "";
	private _bookId = "";
	private _title = "";
	private _author = "";
	private _pageImages: string[] = [];
	private _numPages = 0;
	private _currentPage = 0;
	private _aspectRatio = 1.414; // A4 default
	private _isLoading = true;
	private _loadingProgress = 0;
	private _loadingStatus = "Preparing...";
	private _error: string | null = null;
	private _flipBook: any = null;
	private _db: IDBDatabase | null = null;
	private _keyHandler: ((e: KeyboardEvent) => void) | null = null;
	private _resizeHandler: (() => void) | null = null;
	private _resizeTimer: number | null = null;

	static get observedAttributes() {
		return ["pdf-url", "book-id", "title", "author"];
	}

	attributeChangedCallback(name: string, _old: string, val: string) {
		if (name === "pdf-url") this._pdfUrl = val;
		else if (name === "book-id") this._bookId = val;
		else if (name === "title") this._title = val;
		else if (name === "author") this._author = val;
	}

	async connectedCallback() {
		this._pdfUrl = this.getAttribute("pdf-url") || "";
		this._bookId = this.getAttribute("book-id") || "";
		this._title = this.getAttribute("title") || "";
		this._author = this.getAttribute("author") || "";

		this.attachShadow({ mode: "open" });
		this.renderLoading();

		// Restore reading position
		const savedPage = localStorage.getItem(`book-position-${this._bookId}`);
		if (savedPage) this._currentPage = parseInt(savedPage) || 0;

		try {
			await this.openDB();
			const cached = await this.loadFromCache();

			if (cached) {
				this._pageImages = cached.images;
				this._numPages = cached.numPages;
				this._aspectRatio = cached.aspectRatio;
				this._isLoading = false;
				this.renderReader();
			} else {
				await this.loadAndRenderPDF();
			}
		} catch (e: any) {
			this._error = e.message || "Failed to load book";
			this._isLoading = false;
			this.renderError();
		}
	}

	disconnectedCallback() {
		// Save position
		localStorage.setItem(`book-position-${this._bookId}`, String(this._currentPage));
		this._flipBook?.destroy();
		this._db?.close();
		if (this._keyHandler) { document.removeEventListener("keydown", this._keyHandler); this._keyHandler = null; }
		if (this._resizeHandler) { window.removeEventListener("resize", this._resizeHandler); this._resizeHandler = null; }
		if (this._resizeTimer !== null) { clearTimeout(this._resizeTimer); this._resizeTimer = null; }
	}

	// ── IndexedDB cache ──

	private openDB(): Promise<void> {
		return new Promise((resolve, reject) => {
			const req = indexedDB.open("rspace-books-cache", 1);
			req.onupgradeneeded = () => {
				const db = req.result;
				if (!db.objectStoreNames.contains("book-images")) {
					db.createObjectStore("book-images");
				}
			};
			req.onsuccess = () => { this._db = req.result; resolve(); };
			req.onerror = () => reject(req.error);
		});
	}

	private loadFromCache(): Promise<CachedBook | null> {
		return new Promise((resolve) => {
			if (!this._db) { resolve(null); return; }
			const tx = this._db.transaction("book-images", "readonly");
			const store = tx.objectStore("book-images");
			const req = store.get(this._bookId);
			req.onsuccess = () => resolve(req.result || null);
			req.onerror = () => resolve(null);
		});
	}

	private saveToCache(data: CachedBook): Promise<void> {
		return new Promise((resolve) => {
			if (!this._db) { resolve(); return; }
			const tx = this._db.transaction("book-images", "readwrite");
			const store = tx.objectStore("book-images");
			store.put(data, this._bookId);
			tx.oncomplete = () => resolve();
			tx.onerror = () => resolve();
		});
	}

	// ── PDF rendering ──

	private async loadAndRenderPDF() {
		this._loadingStatus = "Loading PDF.js...";
		this.updateLoadingUI();

		// Load pdf.js
		const pdfjsLib = await import(/* @vite-ignore */ PDFJS_CDN);
		pdfjsLib.GlobalWorkerOptions.workerSrc = PDFJS_WORKER_CDN;

		this._loadingStatus = "Downloading PDF...";
		this.updateLoadingUI();

		const pdf = await pdfjsLib.getDocument(this._pdfUrl).promise;
		this._numPages = pdf.numPages;
		this._pageImages = [];

		// Get aspect ratio from first page
		const firstPage = await pdf.getPage(1);
		const viewport = firstPage.getViewport({ scale: 1 });
		this._aspectRatio = viewport.width / viewport.height;

		const scale = 2; // 2x for quality
		const canvas = document.createElement("canvas");
		const ctx = canvas.getContext("2d")!;

		for (let i = 1; i <= pdf.numPages; i++) {
			this._loadingStatus = `Rendering page ${i} of ${pdf.numPages}...`;
			this._loadingProgress = Math.round((i / pdf.numPages) * 100);
			this.updateLoadingUI();

			const page = await pdf.getPage(i);
			const vp = page.getViewport({ scale });
			canvas.width = vp.width;
			canvas.height = vp.height;
			ctx.clearRect(0, 0, canvas.width, canvas.height);

			await page.render({ canvasContext: ctx, viewport: vp }).promise;
			this._pageImages.push(canvas.toDataURL("image/jpeg", 0.85));
		}

		// Cache
		await this.saveToCache({
			images: this._pageImages,
			numPages: this._numPages,
			aspectRatio: this._aspectRatio,
		});

		this._isLoading = false;
		this.renderReader();
	}

	// ── UI rendering ──

	private renderLoading() {
		if (!this.shadowRoot) return;
		this.shadowRoot.innerHTML = `
			${this.getStyles()}
			<div class="loading">
				<div class="loading-spinner"></div>
				<div class="loading-status">${this._loadingStatus}</div>
				<div class="loading-bar">
					<div class="loading-fill" style="width:${this._loadingProgress}%"></div>
				</div>
			</div>
		`;
	}

	private updateLoadingUI() {
		if (!this.shadowRoot) return;
		const status = this.shadowRoot.querySelector(".loading-status");
		const fill = this.shadowRoot.querySelector(".loading-fill") as HTMLElement;
		if (status) status.textContent = this._loadingStatus;
		if (fill) fill.style.width = `${this._loadingProgress}%`;
	}

	private renderError() {
		if (!this.shadowRoot) return;
		this.shadowRoot.innerHTML = `
			${this.getStyles()}
			<div class="error">
				<h3>Failed to load book</h3>
				<p>${this.escapeHtml(this._error || "Unknown error")}</p>
				<button onclick="location.reload()">Retry</button>
			</div>
		`;
	}

	private renderReader() {
		if (!this.shadowRoot) return;

		// Calculate dimensions
		const maxW = Math.min(window.innerWidth * 0.9, 800);
		const maxH = window.innerHeight - 160;
		let pageW = maxW / 2;
		let pageH = pageW / this._aspectRatio;
		if (pageH > maxH) {
			pageH = maxH;
			pageW = pageH * this._aspectRatio;
		}

		this.shadowRoot.innerHTML = `
			${this.getStyles()}
			<div class="reader-container">
				<div class="rapp-nav">
					<a class="rapp-nav__back" href="/${window.location.pathname.split('/')[1]}/rbooks">← Library</a>
					<span class="rapp-nav__title">${this.escapeHtml(this._title)}</span>
					${this._author ? `<span class="rapp-nav__subtitle">by ${this.escapeHtml(this._author)}</span>` : ""}
					<span class="rapp-nav__meta">
						Page <span class="current-page">${this._currentPage + 1}</span> of ${this._numPages}
					</span>
				</div>
				<div class="flipbook-wrapper">
					<button class="nav-btn nav-prev" title="Previous page">‹</button>
					<div class="flipbook-container" style="width:${pageW * 2}px; height:${pageH}px;"></div>
					<button class="nav-btn nav-next" title="Next page">›</button>
				</div>
				<div class="reader-footer">
					<button class="nav-text-btn" data-action="prev">← Previous</button>
					<button class="nav-text-btn" data-action="next">Next →</button>
				</div>
			</div>
		`;

		this.initFlipbook(pageW, pageH);
		this.bindReaderEvents();
	}

	private async initFlipbook(pageW: number, pageH: number) {
		if (!this.shadowRoot) return;

		const container = this.shadowRoot.querySelector(".flipbook-container") as HTMLElement;
		if (!container) return;

		// Load StPageFlip
		await this.loadStPageFlip();

		const PageFlip = (window as any).St?.PageFlip;
		if (!PageFlip) {
			console.error("StPageFlip not loaded");
			return;
		}

		this._flipBook = new PageFlip(container, {
			width: Math.round(pageW),
			height: Math.round(pageH),
			showCover: true,
			maxShadowOpacity: 0.5,
			mobileScrollSupport: false,
			useMouseEvents: true,
			swipeDistance: 30,
			clickEventForward: false,
			flippingTime: 600,
			startPage: this._currentPage,
		});

		// Create page elements
		const pages: HTMLElement[] = [];
		const pw = Math.round(pageW);
		const ph = Math.round(pageH);
		for (let i = 0; i < this._pageImages.length; i++) {
			const page = document.createElement("div");
			page.className = "page-content";
			page.style.cssText = `width:${pw}px; height:${ph}px; background:#fff; overflow:hidden;`;
			const img = document.createElement("img");
			img.src = this._pageImages[i];
			img.style.cssText = "width:100%; height:100%; object-fit:cover; display:block;";
			page.appendChild(img);
			pages.push(page);
		}

		this._flipBook.loadFromHTML(pages);

		this._flipBook.on("flip", (e: any) => {
			this._currentPage = e.data;
			this.updatePageCounter();
			localStorage.setItem(`book-position-${this._bookId}`, String(this._currentPage));
		});
	}

	private loadStPageFlip(): Promise<void> {
		return new Promise((resolve, reject) => {
			if ((window as any).St?.PageFlip) { resolve(); return; }
			const script = document.createElement("script");
			script.src = STPAGEFLIP_CDN;
			script.onload = () => resolve();
			script.onerror = () => reject(new Error("Failed to load StPageFlip"));
			document.head.appendChild(script);
		});
	}

	private bindReaderEvents() {
		if (!this.shadowRoot) return;

		// Nav buttons
		this.shadowRoot.querySelector(".nav-prev")?.addEventListener("click", () => {
			this._flipBook?.flipPrev();
		});
		this.shadowRoot.querySelector(".nav-next")?.addEventListener("click", () => {
			this._flipBook?.flipNext();
		});
		this.shadowRoot.querySelectorAll(".nav-text-btn").forEach((btn) => {
			btn.addEventListener("click", () => {
				const action = (btn as HTMLElement).dataset.action;
				if (action === "prev") this._flipBook?.flipPrev();
				else if (action === "next") this._flipBook?.flipNext();
			});
		});

		// Keyboard nav
		if (this._keyHandler) document.removeEventListener("keydown", this._keyHandler);
		this._keyHandler = (e: KeyboardEvent) => {
			if (e.key === "ArrowLeft") this._flipBook?.flipPrev();
			else if (e.key === "ArrowRight") this._flipBook?.flipNext();
		};
		document.addEventListener("keydown", this._keyHandler);

		// Resize handler
		if (this._resizeHandler) window.removeEventListener("resize", this._resizeHandler);
		this._resizeHandler = () => {
			if (this._resizeTimer !== null) clearTimeout(this._resizeTimer);
			this._resizeTimer = window.setTimeout(() => this.renderReader(), 250);
		};
		window.addEventListener("resize", this._resizeHandler);
	}

	private updatePageCounter() {
		if (!this.shadowRoot) return;
		const el = this.shadowRoot.querySelector(".current-page");
		if (el) el.textContent = String(this._currentPage + 1);
	}

	private getStyles(): string {
		return `<style>
			:host {
				display: block;
				width: 100%;
				height: 100%;
				background: var(--rs-bg-page);
			}

			/* StPageFlip CSS — injected to document.head but doesn't
			   penetrate shadow DOM, so replicated here. */
			.stf__parent {
				position: relative;
				display: block;
				box-sizing: border-box;
				transform: translateZ(0);
				-ms-touch-action: pan-y;
				touch-action: pan-y;
			}
			.stf__wrapper {
				position: relative;
				width: 100%;
				box-sizing: border-box;
			}
			.stf__parent canvas {
				position: absolute;
				width: 100%;
				height: 100%;
				left: 0;
				top: 0;
			}
			.stf__block {
				position: absolute;
				width: 100%;
				height: 100%;
				box-sizing: border-box;
				perspective: 2000px;
			}
			.stf__item {
				display: none;
				position: absolute;
				transform-style: preserve-3d;
			}
			.stf__outerShadow,
			.stf__innerShadow,
			.stf__hardShadow,
			.stf__hardInnerShadow {
				position: absolute;
				left: 0;
				top: 0;
				color: var(--rs-text-primary);
			}

			.loading {
				display: flex;
				flex-direction: column;
				align-items: center;
				justify-content: center;
				height: 100%;
				gap: 1rem;
			}

			.loading-spinner {
				width: 40px;
				height: 40px;
				border: 3px solid var(--rs-border-strong);
				border-top-color: #60a5fa;
				border-radius: 50%;
				animation: spin 0.8s linear infinite;
			}
			@keyframes spin { to { transform: rotate(360deg); } }

			.loading-status {
				color: var(--rs-text-secondary);
				font-size: 0.9rem;
			}

			.loading-bar {
				width: 200px;
				height: 4px;
				background: var(--rs-bg-surface);
				border-radius: 2px;
				overflow: hidden;
			}

			.loading-fill {
				height: 100%;
				background: #60a5fa;
				transition: width 0.3s;
				border-radius: 2px;
			}

			.error {
				display: flex;
				flex-direction: column;
				align-items: center;
				justify-content: center;
				height: 100%;
				gap: 0.5rem;
			}
			.error h3 { color: var(--rs-error); margin: 0; }
			.error p { color: var(--rs-text-secondary); margin: 0; }
			.error button {
				margin-top: 1rem;
				padding: 0.5rem 1.5rem;
				border: 1px solid var(--rs-border-strong);
				border-radius: 0.5rem;
				background: var(--rs-bg-surface);
				color: var(--rs-text-primary);
				cursor: pointer;
			}

			.reader-container {
				display: flex;
				flex-direction: column;
				align-items: center;
				padding: 1rem;
				gap: 0.75rem;
			}

			.rapp-nav {
				display: flex;
				align-items: center;
				gap: 8px;
				width: 100%;
				max-width: 900px;
				min-height: 36px;
			}
			.rapp-nav__back {
				padding: 4px 10px; border-radius: 6px; border: 1px solid var(--rs-border-subtle);
				background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 13px;
				text-decoration: none; transition: color 0.15s, border-color 0.15s;
			}
			.rapp-nav__back:hover { color: var(--rs-text-primary); border-color: var(--rs-border-strong); }
			.rapp-nav__title {
				font-size: 15px; font-weight: 600; color: var(--rs-text-primary);
				flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
			}
			.rapp-nav__subtitle {
				font-size: 0.8rem;
				color: var(--rs-text-secondary);
				margin-left: 4px;
			}
			.rapp-nav__meta {
				font-size: 0.85rem;
				color: var(--rs-text-secondary);
				white-space: nowrap;
			}

			.flipbook-wrapper {
				display: flex;
				align-items: center;
				gap: 0.5rem;
			}

			.flipbook-container {
				overflow: hidden;
				border-radius: 4px;
				box-shadow: 0 8px 32px rgba(0,0,0,0.4);
				flex-shrink: 0;
			}

			.nav-btn {
				width: 44px;
				height: 80px;
				border: 1px solid var(--rs-border-strong);
				border-radius: 0.5rem;
				background: var(--rs-bg-surface);
				color: var(--rs-text-primary);
				font-size: 1.5rem;
				cursor: pointer;
				display: flex;
				align-items: center;
				justify-content: center;
				transition: background 0.15s;
			}
			.nav-btn:hover { background: var(--rs-border-strong); }

			.reader-footer {
				display: flex;
				gap: 1rem;
			}

			.nav-text-btn {
				padding: 0.375rem 1rem;
				border: 1px solid var(--rs-border-strong);
				border-radius: 0.375rem;
				background: transparent;
				color: var(--rs-text-secondary);
				font-size: 0.8rem;
				cursor: pointer;
			}
			.nav-text-btn:hover { border-color: #60a5fa; color: var(--rs-text-primary); }
		</style>`;
	}

	private escapeHtml(s: string): string {
		return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
	}
}

customElements.define("folk-book-reader", FolkBookReader);
