/**
 * <folk-book-shelf> — Book grid with search, tags, and upload.
 *
 * Displays community books in a responsive grid. Clicking a book
 * navigates to the flipbook reader. Authenticated users can upload.
 */

import { booksCatalogSchema, booksCatalogDocId, type BooksCatalogDoc } from "../schemas";
import { makeDraggableAll } from "../../../shared/draggable";
import type { DocumentId } from "../../../shared/local-first/document";
import { TourEngine } from "../../../shared/tour-engine";
import { startPresenceHeartbeat } from '../../../shared/collab-presence';

interface BookData {
	id: string;
	slug: string;
	title: string;
	author: string | null;
	description: string | null;
	pdf_size_bytes: number;
	page_count: number;
	tags: string[];
	cover_color: string;
	contributor_name: string | null;
	featured: boolean;
	view_count: number;
	created_at: string;
}

export class FolkBookShelf extends HTMLElement {
	private _books: BookData[] = [];
	private _filtered: BookData[] = [];
	private _spaceSlug = "personal";
	private get _basePath(): string {
		if (window.location.hostname.endsWith('.rspace.online')) return '/rbooks';
		return `${this._basePath}`;
	}
	private _searchTerm = "";
	private _selectedTag: string | null = null;
	private _offlineUnsub: (() => void) | null = null;
	private _subscribedDocIds: string[] = [];
	private _stopPresence: (() => void) | null = null;
	private _tour!: TourEngine;
	private static readonly TOUR_STEPS = [
		{ target: '.search-input', title: "Search", message: "Search books by title, author, or description.", advanceOnClick: false },
		{ target: '.tag', title: "Filter by Tag", message: "Click a tag to filter the library by category.", advanceOnClick: true },
		{ target: '.upload-btn', title: "Add Book", message: "Upload a new book as a PDF to share with the community.", advanceOnClick: false },
		{ target: '.book-card', title: "Read a Book", message: "Click any book to open it in the flipbook reader.", advanceOnClick: false },
	];

	static get observedAttributes() {
		return ["space-slug"];
	}

	set books(val: BookData[]) {
		this._books = val;
		this._filtered = val;
		this.render();
	}

	get books() {
		return this._books;
	}

	set spaceSlug(val: string) {
		this._spaceSlug = val;
	}

	connectedCallback() {
		this.attachShadow({ mode: "open" });
		this._tour = new TourEngine(
			this.shadowRoot!,
			FolkBookShelf.TOUR_STEPS,
			"rbooks_tour_done",
			() => this.shadowRoot!.host as HTMLElement,
		);
		this._spaceSlug = this.getAttribute("space-slug") || this._spaceSlug;
		this.render();
		if (this._spaceSlug === "demo" || this.getAttribute("space") === "demo") {
			this.loadDemoBooks();
		} else {
			this.subscribeOffline();
		}
		if (!localStorage.getItem("rbooks_tour_done")) {
			setTimeout(() => this._tour.start(), 1200);
		}
		this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rbooks', context: 'Library' }));
	}

	disconnectedCallback() {
		this._stopPresence?.();
		this._offlineUnsub?.();
		this._offlineUnsub = null;
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (runtime) {
			for (const id of this._subscribedDocIds) runtime.unsubscribe(id);
		}
		this._subscribedDocIds = [];
	}

	private async subscribeOffline() {
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;

		try {
			const docId = booksCatalogDocId(this._spaceSlug) as DocumentId;
			const doc = await runtime.subscribe(docId, booksCatalogSchema);
			this._subscribedDocIds.push(docId);
			this.renderFromDoc(doc);

			this._offlineUnsub = runtime.onChange(docId, (updated: any) => {
				this.renderFromDoc(updated);
			});
		} catch {
			// Runtime unavailable — server-side hydration handles data
		}
	}

	private renderFromDoc(doc: BooksCatalogDoc) {
		if (!doc?.items || Object.keys(doc.items).length === 0) return;
		// Only populate from doc if not already hydrated by server
		if (this._books.length > 0) return;
		this.books = Object.values(doc.items).map(b => ({
			id: b.id, slug: b.slug, title: b.title, author: b.author,
			description: b.description, pdf_size_bytes: b.pdfSizeBytes,
			page_count: b.pageCount, tags: b.tags || [],
			cover_color: b.coverColor || '#333',
			contributor_name: b.contributorName, featured: b.featured,
			view_count: b.viewCount, created_at: new Date(b.createdAt).toISOString(),
		}));
	}

	private loadDemoBooks() {
		this.books = [
			{ id: "b1", slug: "amusing-ourselves-to-death", title: "Amusing Ourselves to Death", author: "Neil Postman", description: "A prophetic look at what happens when politics, journalism, education, and even religion become subject to the demands of entertainment. Originally published in 1985, this book remains incredibly relevant in the age of social media and constant digital distraction.", pdf_size_bytes: 2457600, page_count: 231, tags: ["media criticism", "society", "technology", "culture"], cover_color: "#1e3a5f", contributor_name: null, featured: true, view_count: 523, created_at: "2025-11-10" },
			{ id: "b2", slug: "interference", title: "Interference: A Grand Scientific Musical Theory", author: "Richard Merrick", description: "A groundbreaking exploration of Harmonic Interference Theory - a set of principles explaining music perception using the physics of harmonic standing waves, connecting music theory to cymatics, Fibonacci sequences, and the fundamental patterns found throughout nature.", pdf_size_bytes: 8388608, page_count: 524, tags: ["music theory", "harmonics", "cymatics", "science", "perception"], cover_color: "#7c3aed", contributor_name: null, featured: true, view_count: 312, created_at: "2025-12-05" },
			{ id: "b3", slug: "governing-the-commons", title: "Governing the Commons", author: "Elinor Ostrom", description: "Analysis of collective action and the governance of common-pool resources", pdf_size_bytes: 2457600, page_count: 280, tags: ["economics", "governance"], cover_color: "#2563eb", contributor_name: "Community Library", featured: true, view_count: 342, created_at: "2026-01-15" },
			{ id: "b4", slug: "the-mushroom-at-the-end-of-the-world", title: "The Mushroom at the End of the World", author: "Anna Lowenhaupt Tsing", description: "On the possibility of life in capitalist ruins", pdf_size_bytes: 3145728, page_count: 352, tags: ["ecology", "anthropology"], cover_color: "#059669", contributor_name: null, featured: false, view_count: 187, created_at: "2026-01-20" },
			{ id: "b5", slug: "doughnut-economics", title: "Doughnut Economics", author: "Kate Raworth", description: "Seven ways to think like a 21st-century economist", pdf_size_bytes: 1887436, page_count: 320, tags: ["economics"], cover_color: "#d97706", contributor_name: "Reading Circle", featured: false, view_count: 256, created_at: "2026-02-01" },
			{ id: "b6", slug: "entangled-life", title: "Entangled Life", author: "Merlin Sheldrake", description: "How fungi make our worlds, change our minds, and shape our futures", pdf_size_bytes: 2621440, page_count: 368, tags: ["ecology", "science"], cover_color: "#0891b2", contributor_name: "Mycofi Lab", featured: false, view_count: 431, created_at: "2026-02-10" },
		];
	}

	attributeChangedCallback(name: string, _old: string, val: string) {
		if (name === "space-slug") this._spaceSlug = val;
	}

	private get allTags(): string[] {
		const tags = new Set<string>();
		for (const b of this._books) {
			for (const t of b.tags || []) tags.add(t);
		}
		return Array.from(tags).sort();
	}

	private applyFilters() {
		let result = this._books;

		if (this._searchTerm) {
			const term = this._searchTerm.toLowerCase();
			result = result.filter(
				(b) =>
					b.title.toLowerCase().includes(term) ||
					(b.author && b.author.toLowerCase().includes(term)) ||
					(b.description && b.description.toLowerCase().includes(term))
			);
		}

		if (this._selectedTag) {
			const tag = this._selectedTag;
			result = result.filter((b) => b.tags?.includes(tag));
		}

		this._filtered = result;
	}

	private formatSize(bytes: number): string {
		if (bytes < 1024) return `${bytes} B`;
		if (bytes < 1048576) return `${(bytes / 1024).toFixed(0)} KB`;
		return `${(bytes / 1048576).toFixed(1)} MB`;
	}

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

		const tags = this.allTags;
		const books = this._filtered;

		this.shadowRoot.innerHTML = `
			<style>
				:host {
					display: block;
					padding: 1.5rem;
					max-width: 1200px;
					margin: 0 auto;
				}

				.rapp-nav { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; min-height: 36px; }
				.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__actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
				.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: var(--rs-primary); color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
				.rapp-nav__btn:hover { background: var(--rs-primary-hover); }

				.controls {
					display: flex;
					gap: 0.75rem;
					flex-wrap: wrap;
					align-items: center;
					margin-bottom: 1rem;
				}

				.search-input {
					flex: 1;
					min-width: 200px;
					padding: 0.5rem 0.75rem;
					border: 1px solid var(--rs-input-border);
					border-radius: 0.5rem;
					background: var(--rs-input-bg);
					color: var(--rs-input-text);
					font-size: 0.9rem;
				}
				.search-input::placeholder { color: var(--rs-text-muted); }
				.search-input:focus { outline: none; border-color: #60a5fa; }

				.tags {
					display: flex;
					gap: 0.375rem;
					flex-wrap: wrap;
				}

				.tag {
					padding: 0.25rem 0.625rem;
					border-radius: 999px;
					border: 1px solid var(--rs-border);
					background: var(--rs-bg-surface);
					color: var(--rs-text-secondary);
					font-size: 0.75rem;
					cursor: pointer;
					transition: all 0.15s;
				}
				.tag:hover { border-color: #60a5fa; color: var(--rs-text-primary); }
				.tag.active { background: #1e3a5f; border-color: #60a5fa; color: #60a5fa; }

				.upload-btn {
					padding: 0.5rem 1rem;
					border: none;
					border-radius: 0.5rem;
					background: var(--rs-primary);
					color: #fff;
					font-size: 0.875rem;
					cursor: pointer;
					font-weight: 500;
					transition: background 0.15s;
				}
				.upload-btn:hover { background: var(--rs-primary-hover); }

				.grid {
					display: grid;
					grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
					gap: 1.25rem;
				}

				.book-card {
					display: flex;
					flex-direction: column;
					border-radius: 0.75rem;
					overflow: hidden;
					background: var(--rs-bg-surface);
					border: 1px solid var(--rs-border);
					cursor: pointer;
					transition: transform 0.15s, border-color 0.15s;
					text-decoration: none;
					color: inherit;
				}
				.book-card:hover {
					transform: translateY(-2px);
					border-color: #60a5fa;
				}

				.book-cover {
					aspect-ratio: 3/4;
					display: flex;
					align-items: center;
					justify-content: center;
					padding: 1rem;
					position: relative;
				}

				.book-cover-title {
					font-size: 0.875rem;
					font-weight: 600;
					color: #fff;
					text-align: center;
					line-height: 1.3;
					text-shadow: 0 1px 3px rgba(0,0,0,0.4);
					display: -webkit-box;
					-webkit-line-clamp: 4;
					-webkit-box-orient: vertical;
					overflow: hidden;
				}

				.featured-badge {
					position: absolute;
					top: 0.5rem;
					right: 0.5rem;
					font-size: 0.7rem;
					background: rgba(250, 204, 21, 0.9);
					color: #1e293b;
					padding: 0.125rem 0.375rem;
					border-radius: 4px;
					font-weight: 600;
				}

				.book-info {
					padding: 0.75rem;
					flex: 1;
					display: flex;
					flex-direction: column;
					gap: 0.25rem;
				}

				.book-title {
					font-size: 0.8rem;
					font-weight: 600;
					color: var(--rs-text-primary);
					display: -webkit-box;
					-webkit-line-clamp: 2;
					-webkit-box-orient: vertical;
					overflow: hidden;
				}

				.book-author {
					font-size: 0.75rem;
					color: var(--rs-text-secondary);
					display: -webkit-box;
					-webkit-line-clamp: 1;
					-webkit-box-orient: vertical;
					overflow: hidden;
				}

				.book-meta {
					display: flex;
					justify-content: space-between;
					font-size: 0.7rem;
					color: var(--rs-text-muted);
					margin-top: auto;
					padding-top: 0.375rem;
				}

				.empty {
					text-align: center;
					padding: 3rem;
					color: var(--rs-text-muted);
				}
				.empty h3 { margin: 0 0 0.5rem; color: var(--rs-text-secondary); }

				/* Upload modal */
				.modal-overlay {
					position: fixed;
					inset: 0;
					background: rgba(0,0,0,0.6);
					z-index: 1000;
					display: flex;
					align-items: center;
					justify-content: center;
				}
				.modal-overlay[hidden] { display: none; }

				.modal {
					background: var(--rs-bg-surface);
					border: 1px solid var(--rs-border);
					border-radius: 1rem;
					padding: 1.5rem;
					width: 90%;
					max-width: 480px;
					max-height: 90vh;
					overflow-y: auto;
				}

				.modal h3 {
					margin: 0 0 1rem;
					color: var(--rs-text-primary);
					font-size: 1.1rem;
				}

				.modal label {
					display: block;
					margin-bottom: 0.25rem;
					font-size: 0.8rem;
					color: var(--rs-text-secondary);
				}

				.modal input,
				.modal textarea {
					width: 100%;
					padding: 0.5rem 0.75rem;
					margin-bottom: 0.75rem;
					border: 1px solid var(--rs-input-border);
					border-radius: 0.5rem;
					background: var(--rs-input-bg);
					color: var(--rs-input-text);
					font-size: 0.875rem;
					box-sizing: border-box;
				}

				.modal textarea { min-height: 80px; resize: vertical; }
				.modal input:focus, .modal textarea:focus { outline: none; border-color: #60a5fa; }

				.modal-actions {
					display: flex;
					gap: 0.75rem;
					justify-content: flex-end;
					margin-top: 1rem;
				}

				.btn-cancel {
					padding: 0.5rem 1rem;
					border: 1px solid var(--rs-border);
					border-radius: 0.5rem;
					background: transparent;
					color: var(--rs-text-secondary);
					cursor: pointer;
				}
				.btn-cancel:hover { border-color: var(--rs-text-muted); }

				.btn-submit {
					padding: 0.5rem 1rem;
					border: none;
					border-radius: 0.5rem;
					background: var(--rs-primary);
					color: #fff;
					cursor: pointer;
					font-weight: 500;
				}
				.btn-submit:hover { background: var(--rs-primary-hover); }
				.btn-submit:disabled { opacity: 0.5; cursor: not-allowed; }

				.drop-zone {
					border: 2px dashed var(--rs-border);
					border-radius: 0.75rem;
					padding: 1.5rem;
					text-align: center;
					color: var(--rs-text-muted);
					margin-bottom: 0.75rem;
					cursor: pointer;
					transition: border-color 0.15s;
				}
				.drop-zone:hover, .drop-zone.dragover { border-color: #60a5fa; color: var(--rs-text-secondary); }
				.drop-zone .selected { color: #60a5fa; font-weight: 500; }

				.error-msg {
					color: var(--rs-error);
					font-size: 0.8rem;
					margin-bottom: 0.5rem;
				}
			</style>

			<div class="rapp-nav">
				<span class="rapp-nav__title">Library</span>
				<div class="rapp-nav__actions">
					<button class="rapp-nav__btn upload-btn">+ Add Book</button>
					<button class="rapp-nav__btn" id="btn-tour" style="font-size:0.78rem;padding:4px 10px">Tour</button>
				</div>
			</div>

			<div class="controls">
				<input class="search-input" type="text" placeholder="Search books..." />
			</div>

			${tags.length > 0 ? `
				<div class="tags">
					${tags.map((t) => `<span class="tag" data-tag="${t}">${t}</span>`).join("")}
				</div>
			` : ""}

			${books.length === 0
				? `<div class="empty">
						<h3>No books yet</h3>
						<p>Upload a PDF to share with the community</p>
					</div>`
				: `<div class="grid">
						${books.map((b) => `
							<a class="book-card" data-collab-id="book:${b.id}" href="${this._basePath}/read/${b.slug}">
								<div class="book-cover" style="background:${b.cover_color}">
									<span class="book-cover-title">${this.escapeHtml(b.title)}</span>
									${b.featured ? '<span class="featured-badge">Featured</span>' : ""}
								</div>
								<div class="book-info">
									<div class="book-title">${this.escapeHtml(b.title)}</div>
									${b.author ? `<div class="book-author">${this.escapeHtml(b.author)}</div>` : ""}
									<div class="book-meta">
										<span>${this.formatSize(b.pdf_size_bytes)}</span>
										<span>${b.view_count} views</span>
									</div>
								</div>
							</a>
						`).join("")}
					</div>`
			}

			<div class="modal-overlay" hidden>
				<div class="modal">
					<h3>Share a Book</h3>
					<div class="error-msg" hidden></div>

					<div class="drop-zone">
						<input type="file" accept="application/pdf" style="display:none" />
						Drop a PDF here or click to browse
					</div>

					<label>Title *</label>
					<input type="text" name="title" required />

					<label>Author</label>
					<input type="text" name="author" />

					<label>Description</label>
					<textarea name="description"></textarea>

					<label>Tags (comma-separated)</label>
					<input type="text" name="tags" placeholder="e.g. science, philosophy" />

					<label>License</label>
					<input type="text" name="license" value="CC BY-SA 4.0" />

					<div class="modal-actions">
						<button class="btn-cancel">Cancel</button>
						<button class="btn-submit" disabled>Upload</button>
					</div>
				</div>
			</div>
		`;

		this.bindEvents();

		// Make book cards draggable for calendar reminders
		makeDraggableAll(this.shadowRoot!, ".book-card[data-collab-id]", (el) => {
			const title = el.querySelector(".book-title")?.textContent || "";
			const id = el.dataset.collabId?.replace("book:", "") || "";
			return title ? { title, module: "rbooks", entityId: id, label: "Book", color: "#f97316" } : null;
		});

		this._tour.renderOverlay();
	}

	startTour() {
		this._tour.start();
	}

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

		this.shadowRoot.querySelector("#btn-tour")?.addEventListener("click", () => this.startTour());

		// Search
		const searchInput = this.shadowRoot.querySelector(".search-input") as HTMLInputElement;
		searchInput?.addEventListener("input", () => {
			this._searchTerm = searchInput.value;
			this.applyFilters();
			this.updateGrid();
		});

		// Tags
		this.shadowRoot.querySelectorAll(".tag").forEach((el) => {
			el.addEventListener("click", () => {
				const tag = (el as HTMLElement).dataset.tag!;
				if (this._selectedTag === tag) {
					this._selectedTag = null;
					el.classList.remove("active");
				} else {
					this.shadowRoot!.querySelectorAll(".tag").forEach((t) => t.classList.remove("active"));
					this._selectedTag = tag;
					el.classList.add("active");
				}
				this.applyFilters();
				this.updateGrid();
			});
		});

		// Upload modal
		const uploadBtn = this.shadowRoot.querySelector(".upload-btn");
		const overlay = this.shadowRoot.querySelector(".modal-overlay") as HTMLElement;
		const cancelBtn = this.shadowRoot.querySelector(".btn-cancel");
		const submitBtn = this.shadowRoot.querySelector(".btn-submit") as HTMLButtonElement;
		const dropZone = this.shadowRoot.querySelector(".drop-zone") as HTMLElement;
		const fileInput = this.shadowRoot.querySelector('input[type="file"]') as HTMLInputElement;
		const titleInput = this.shadowRoot.querySelector('input[name="title"]') as HTMLInputElement;
		const errorEl = this.shadowRoot.querySelector(".error-msg") as HTMLElement;

		let selectedFile: File | null = null;

		uploadBtn?.addEventListener("click", () => {
			overlay.hidden = false;
		});

		cancelBtn?.addEventListener("click", () => {
			overlay.hidden = true;
			selectedFile = null;
		});

		overlay?.addEventListener("click", (e) => {
			if (e.target === overlay) overlay.hidden = true;
		});

		dropZone?.addEventListener("click", () => fileInput?.click());
		dropZone?.addEventListener("dragover", (e) => { e.preventDefault(); dropZone.classList.add("dragover"); });
		dropZone?.addEventListener("dragleave", () => dropZone.classList.remove("dragover"));
		dropZone?.addEventListener("drop", (e) => {
			e.preventDefault();
			dropZone.classList.remove("dragover");
			const file = (e as DragEvent).dataTransfer?.files[0];
			if (file?.type === "application/pdf") {
				selectedFile = file;
				dropZone.innerHTML = `<span class="selected">${file.name}</span>`;
				if (titleInput.value) submitBtn.disabled = false;
			}
		});

		fileInput?.addEventListener("change", () => {
			if (fileInput.files?.[0]) {
				selectedFile = fileInput.files[0];
				dropZone.innerHTML = `<span class="selected">${selectedFile.name}</span>`;
				if (titleInput.value) submitBtn.disabled = false;
			}
		});

		titleInput?.addEventListener("input", () => {
			submitBtn.disabled = !titleInput.value.trim() || !selectedFile;
		});

		submitBtn?.addEventListener("click", async () => {
			if (!selectedFile || !titleInput.value.trim()) return;
			submitBtn.disabled = true;
			submitBtn.textContent = "Uploading...";
			errorEl.hidden = true;

			const formData = new FormData();
			formData.append("pdf", selectedFile);
			formData.append("title", titleInput.value.trim());

			const authorInput = this.shadowRoot!.querySelector('input[name="author"]') as HTMLInputElement;
			const descInput = this.shadowRoot!.querySelector('textarea[name="description"]') as HTMLTextAreaElement;
			const tagsInput = this.shadowRoot!.querySelector('input[name="tags"]') as HTMLInputElement;
			const licenseInput = this.shadowRoot!.querySelector('input[name="license"]') as HTMLInputElement;

			if (authorInput.value) formData.append("author", authorInput.value);
			if (descInput.value) formData.append("description", descInput.value);
			if (tagsInput.value) formData.append("tags", tagsInput.value);
			if (licenseInput.value) formData.append("license", licenseInput.value);

			// Get auth token
			const token = localStorage.getItem("encryptid_token");
			if (!token) {
				errorEl.textContent = "Please sign in first (use the identity button in the header)";
				errorEl.hidden = false;
				submitBtn.disabled = false;
				submitBtn.textContent = "Upload";
				return;
			}

			try {
				const res = await fetch(`${this._basePath}/api/books`, {
					method: "POST",
					headers: { Authorization: `Bearer ${token}` },
					body: formData,
				});
				const data = await res.json();

				if (!res.ok) {
					throw new Error(data.error || "Upload failed");
				}

				// Navigate to the new book
				window.location.href = `${this._basePath}/read/${data.slug}`;
			} catch (e: any) {
				errorEl.textContent = e.message;
				errorEl.hidden = false;
				submitBtn.disabled = false;
				submitBtn.textContent = "Upload";
			}
		});
	}

	private updateGrid() {
		// Re-render just the grid portion (lightweight update)
		this.render();
	}

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

customElements.define("folk-book-shelf", FolkBookShelf);
