/**
 * <folk-pubs-flipbook> — Interactive page-flip PDF preview using pdf.js + StPageFlip.
 *
 * Ephemeral preview for generated PDFs — no IndexedDB caching needed.
 * Follows the pattern from folk-book-reader.ts.
 *
 * Attributes:
 *   pdf-url — Blob URL or HTTP URL to the PDF
 */

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";

export class FolkPubsFlipbook extends HTMLElement {
	private _pdfUrl = "";
	private _pageImages: string[] = [];
	private _numPages = 0;
	private _currentPage = 0;
	private _aspectRatio = 1.414;
	private _isLoading = true;
	private _loadingProgress = 0;
	private _loadingStatus = "Preparing...";
	private _error: string | null = null;
	private _flipBook: any = null;
	private _keyHandler: ((e: KeyboardEvent) => void) | null = null;
	private _resizeTimer: ReturnType<typeof setTimeout> | null = null;
	private _resizeHandler: (() => void) | null = null;

	static get observedAttributes() {
		return ["pdf-url"];
	}

	attributeChangedCallback(name: string, _old: string, val: string) {
		if (name === "pdf-url" && val !== _old) {
			this._pdfUrl = val;
			if (this.shadowRoot) this.loadPDF();
		}
	}

	connectedCallback() {
		this._pdfUrl = this.getAttribute("pdf-url") || "";
		if (!this.shadowRoot) this.attachShadow({ mode: "open" });
		this.renderLoading();
		if (this._pdfUrl) this.loadPDF();
	}

	disconnectedCallback() {
		this._flipBook?.destroy();
		if (this._keyHandler) { document.removeEventListener("keydown", this._keyHandler); this._keyHandler = null; }
		if (this._resizeTimer) { clearTimeout(this._resizeTimer); this._resizeTimer = null; }
		if (this._resizeHandler) { window.removeEventListener("resize", this._resizeHandler); this._resizeHandler = null; }
	}

	private async loadPDF() {
		this._isLoading = true;
		this._error = null;
		this._pageImages = [];
		this.renderLoading();

		try {
			this._loadingStatus = "Loading PDF.js...";
			this.updateLoadingUI();

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

			this._loadingStatus = "Rendering pages...";
			this.updateLoadingUI();

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

			const firstPage = await pdf.getPage(1);
			const viewport = firstPage.getViewport({ scale: 1 });
			this._aspectRatio = viewport.width / viewport.height;

			const scale = 2;
			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));
			}

			this._isLoading = false;
			this._currentPage = 0;
			this.renderReader();
		} catch (e: any) {
			this._error = e.message || "Failed to render PDF";
			this._isLoading = false;
			this.renderError();
		}
	}

	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">
				<p>Failed to render preview: ${this._error || "Unknown error"}</p>
			</div>
		`;
	}

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

		const maxW = Math.min((this.parentElement?.clientWidth || window.innerWidth) - 40, 700);
		const maxH = 500;
		let pageW = Math.max(maxW / 2, 100);
		let pageH = pageW / this._aspectRatio;
		if (pageH > maxH) {
			pageH = maxH;
			pageW = pageH * this._aspectRatio;
		}

		this.shadowRoot.innerHTML = `
			${this.getStyles()}
			<div class="reader">
				<div class="flipbook-row">
					<button class="nav-btn" data-dir="prev" title="Previous page">&#8249;</button>
					<div class="flipbook-container" style="width:${Math.round(pageW * 2)}px; height:${Math.round(pageH)}px;"></div>
					<button class="nav-btn" data-dir="next" title="Next page">&#8250;</button>
				</div>
				<div class="page-info">
					Page <span class="cur">${this._currentPage + 1}</span> of ${this._numPages}
				</div>
			</div>
		`;

		this.initFlipbook(Math.round(pageW), Math.round(pageH)).catch((e) => {
			console.warn('[folk-pubs-flipbook] Flipbook init failed, using fallback:', e);
			this.renderFallback();
		});
		this.bindEvents();
	}

	private async initFlipbook(pageW: number, pageH: number) {
		if (!this.shadowRoot) return;
		const container = this.shadowRoot.querySelector(".flipbook-container") as HTMLElement;
		if (!container) return;

		try {
			await this.loadStPageFlip();
		} catch (e) {
			console.warn('[folk-pubs-flipbook] StPageFlip failed to load, using fallback:', e);
			this.renderFallback();
			return;
		}
		const PageFlip = (window as any).St?.PageFlip;
		if (!PageFlip) {
			console.warn('[folk-pubs-flipbook] StPageFlip not available, using fallback');
			this.renderFallback();
			return;
		}

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

			const pages: HTMLElement[] = [];
			for (let i = 0; i < this._pageImages.length; i++) {
				const page = document.createElement("div");
				page.className = "flipbook-page";
				page.style.cssText = `width:${pageW}px; height:${pageH}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.updatePageInfo();
			});
		} catch (e) {
			console.warn('[folk-pubs-flipbook] StPageFlip render failed, using fallback:', e);
			this._flipBook = null;
			this.renderFallback();
			return;
		}

		// Verify pages actually rendered — if not, fall back after a short delay
		setTimeout(() => {
			if (!this.shadowRoot) return;
			const items = this.shadowRoot.querySelectorAll('.stf__item');
			// If StPageFlip created items but none are visible, fall back
			if (items.length > 0) {
				const anyVisible = Array.from(items).some(
					(el) => (el as HTMLElement).style.display !== 'none'
				);
				if (!anyVisible) {
					console.warn('[folk-pubs-flipbook] No visible pages after init, using fallback');
					this._flipBook?.destroy();
					this._flipBook = null;
					this.renderFallback();
				}
			} else if (!this.shadowRoot.querySelector('.stf__parent')) {
				// StPageFlip didn't create its structure at all
				console.warn('[folk-pubs-flipbook] StPageFlip structure missing, using fallback');
				this._flipBook = null;
				this.renderFallback();
			}
		}, 500);

		// Initial page info update for spread display
		this.updatePageInfo();
	}

	private updatePageInfo() {
		const cur = this.shadowRoot?.querySelector(".page-info");
		if (!cur) return;
		const p = this._currentPage;
		const n = this._numPages;
		// First page (cover) and last page shown solo; middle pages as spreads
		if (p === 0 || p >= n - 1) {
			cur.textContent = `Page ${p + 1} of ${n}`;
		} else {
			const right = Math.min(p + 1, n);
			cur.textContent = `Pages ${p}–${right} of ${n}`;
		}
	}

	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 bindEvents() {
		if (!this.shadowRoot) return;

		this.shadowRoot.querySelectorAll(".nav-btn").forEach((btn) => {
			btn.addEventListener("click", () => {
				const dir = (btn as HTMLElement).dataset.dir;
				if (dir === "prev") this._flipBook?.flipPrev();
				else this._flipBook?.flipNext();
			});
		});

		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);

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

	private renderFallback() {
		if (!this.shadowRoot) return;
		this.shadowRoot.innerHTML = `
			${this.getStyles()}
			<div class="reader">
				<div class="fallback-pages">
					${this._pageImages.map((src, i) => `
						<div class="fallback-page-wrap">
							<span class="fallback-page-num">${i + 1}</span>
							<img src="${src}" alt="Page ${i + 1}" />
						</div>
					`).join('')}
				</div>
				<div class="page-info">${this._numPages} pages (scroll view)</div>
			</div>
		`;
	}

	private getStyles(): string {
		return `<style>
			:host {
				display: block;
				position: relative;
				z-index: 0;
			}

			/* StPageFlip injects these into document.head, but they don't
			   penetrate shadow DOM — replicate complete CSS here. */
			.stf__parent {
				position: relative;
				display: block;
				box-sizing: border-box;
				transform: translateZ(0);
				-ms-touch-action: pan-y;
				touch-action: pan-y;
				-webkit-touch-callout: none;
				-webkit-user-select: none;
				-moz-user-select: none;
				user-select: none;
			}
			.stf__wrapper {
				position: relative;
				width: 100%;
				height: 100%;
				box-sizing: border-box;
			}
			.stf__parent canvas {
				position: absolute;
				width: 100%;
				height: 100%;
				left: 0;
				top: 0;
				z-index: 2;
			}
			.stf__block {
				position: absolute;
				width: 100%;
				height: 100%;
				box-sizing: border-box;
				perspective: 2000px;
				z-index: 1;
			}
			.stf__item {
				display: none;
				position: absolute;
				box-sizing: border-box;
				transform-style: preserve-3d;
				top: 0;
				left: 0;
				overflow: hidden;
				backface-visibility: hidden;
			}
			.stf__outerShadow,
			.stf__innerShadow,
			.stf__hardShadow,
			.stf__hardInnerShadow {
				position: absolute;
				left: 0;
				top: 0;
				pointer-events: none;
			}
			/* Ensure page content fills the flipbook page */
			.stf__item {
				background: #fff;
			}
			.flipbook-page {
				box-sizing: border-box;
				background: #fff;
			}
			.flipbook-page img {
				pointer-events: none;
				user-select: none;
			}

			.loading {
				display: flex; flex-direction: column; align-items: center;
				justify-content: center; padding: 2rem; gap: 0.75rem;
			}
			.loading-spinner {
				width: 32px; height: 32px;
				border: 3px solid var(--rs-border-strong, #444);
				border-top-color: var(--rs-accent, #14b8a6); border-radius: 50%;
				animation: spin 0.8s linear infinite;
			}
			@keyframes spin { to { transform: rotate(360deg); } }
			.loading-status { color: var(--rs-text-secondary, #aaa); font-size: 0.8rem; }
			.loading-bar { width: 160px; height: 3px; background: var(--rs-bg-surface, #333); border-radius: 2px; overflow: hidden; }
			.loading-fill { height: 100%; background: var(--rs-accent, #14b8a6); transition: width 0.3s; border-radius: 2px; }

			.error { padding: 1rem; color: #f87171; font-size: 0.8rem; text-align: center; }

			.reader { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; }
			.flipbook-row { display: flex; align-items: center; gap: 0.375rem; }
			.flipbook-container {
				position: relative;
				overflow: hidden; border-radius: 3px;
				box-shadow: 0 4px 20px rgba(0,0,0,0.35);
				background: #fff;
				flex-shrink: 0;
			}
			/* Fallback scroll view when StPageFlip fails to load */
			.fallback-pages {
				display: flex; flex-direction: column; align-items: center; gap: 1rem;
				padding: 1rem; max-height: 500px; overflow-y: auto;
			}
			.fallback-page-wrap {
				position: relative;
				max-width: 100%;
			}
			.fallback-page-num {
				position: absolute;
				top: 4px;
				right: 8px;
				font-size: 0.6rem;
				padding: 0.15rem 0.4rem;
				border-radius: 0.25rem;
				background: rgba(0,0,0,0.5);
				color: #fff;
			}
			.fallback-pages img {
				max-width: 100%; border-radius: 2px;
				box-shadow: 0 2px 8px rgba(0,0,0,0.2);
			}
			.nav-btn {
				width: 32px; height: 60px;
				border: 1px solid var(--rs-border-strong, #555);
				border-radius: 0.375rem;
				background: var(--rs-bg-surface, #2a2a2a);
				color: var(--rs-text-primary, #eee);
				font-size: 1.25rem; cursor: pointer;
				display: flex; align-items: center; justify-content: center;
			}
			.nav-btn:hover { background: var(--rs-border-strong, #555); }
			.page-info {
				font-size: 0.75rem; color: var(--rs-text-secondary, #aaa);
			}
		</style>`;
	}
}

customElements.define("folk-pubs-flipbook", FolkPubsFlipbook);
