/**
 * Books module — community PDF library with flipbook reader.
 *
 * Ported from rbooks-online (Next.js) to Hono routes.
 * Routes are relative to mount point (/:space/books in unified, / in standalone).
 *
 * Storage: Automerge documents via SyncServer (one doc per space).
 * PDF files stay on the filesystem — only metadata lives in Automerge.
 */

import { Hono } from "hono";
import { resolve } from "node:path";
import { mkdir } from "node:fs/promises";
import { randomUUID } from "node:crypto";
import * as Automerge from "@automerge/automerge";
import { renderShell } from "../../server/shell";
import { getModuleInfoList } from "../../shared/module";
import type { RSpaceModule, SpaceLifecycleContext } from "../../shared/module";
import { renderLanding } from "./landing";
import { verifyToken, extractToken } from "../../server/auth";
import type { SyncServer } from '../../server/local-first/sync-server';
import {
	booksCatalogSchema,
	booksCatalogDocId,
	type BooksCatalogDoc,
	type BookItem,
} from './schemas';

let _syncServer: SyncServer | null = null;

const BOOKS_DIR = process.env.BOOKS_DIR || "/data/books";

// ── Helpers ──

function ensureDoc(space: string): BooksCatalogDoc {
	const docId = booksCatalogDocId(space);
	let doc = _syncServer!.getDoc<BooksCatalogDoc>(docId);
	if (!doc) {
		doc = Automerge.change(Automerge.init<BooksCatalogDoc>(), 'init', (d) => {
			const init = booksCatalogSchema.init();
			d.meta = init.meta;
			d.meta.spaceSlug = space;
			d.items = {};
		});
		_syncServer!.setDoc(docId, doc);
	}
	return doc;
}

/** Find a book by slug or id across the items map. */
function findBook(doc: BooksCatalogDoc, idOrSlug: string): BookItem | undefined {
	// Direct key lookup first (by id)
	if (doc.items[idOrSlug]) return doc.items[idOrSlug];
	// Then scan by slug
	return Object.values(doc.items).find(
		(b) => b.slug === idOrSlug || b.id === idOrSlug
	);
}

/** Convert a BookItem to the JSON shape the API has always returned. */
function bookToRow(b: BookItem) {
	return {
		id: b.id,
		slug: b.slug,
		title: b.title,
		author: b.author,
		description: b.description,
		pdf_path: b.pdfPath,
		pdf_size_bytes: b.pdfSizeBytes,
		page_count: b.pageCount,
		tags: b.tags,
		license: b.license,
		cover_color: b.coverColor,
		contributor_id: b.contributorId,
		contributor_name: b.contributorName,
		status: b.status,
		featured: b.featured,
		view_count: b.viewCount,
		download_count: b.downloadCount,
		created_at: new Date(b.createdAt).toISOString(),
		updated_at: new Date(b.updatedAt).toISOString(),
	};
}

function slugify(text: string): string {
	return text
		.toLowerCase()
		.replace(/[^a-z0-9]+/g, "-")
		.replace(/^-|-$/g, "")
		.slice(0, 80);
}

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

// ── Routes ──

const routes = new Hono();

// ── API: List books ──
routes.get("/api/books", async (c) => {
	const space = c.req.param("space") || "global";
	const dataSpace = c.get("effectiveSpace") || space;
	const search = c.req.query("search")?.toLowerCase();
	const tag = c.req.query("tag");
	const limit = Math.min(parseInt(c.req.query("limit") || "50"), 100);
	const offset = parseInt(c.req.query("offset") || "0");

	const doc = ensureDoc(dataSpace);
	let books = Object.values(doc.items).filter((b) => b.status === "published");

	if (search) {
		books = books.filter(
			(b) =>
				b.title.toLowerCase().includes(search) ||
				b.author.toLowerCase().includes(search) ||
				b.description.toLowerCase().includes(search)
		);
	}
	if (tag) {
		books = books.filter((b) => b.tags.includes(tag));
	}

	// Sort: featured first, then newest
	books.sort((a, b) => {
		if (a.featured !== b.featured) return a.featured ? -1 : 1;
		return b.createdAt - a.createdAt;
	});

	// Paginate
	const paged = books.slice(offset, offset + limit);

	// Return the subset of fields the old query returned
	const rows = paged.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,
		contributor_name: b.contributorName,
		featured: b.featured,
		view_count: b.viewCount,
		created_at: new Date(b.createdAt).toISOString(),
	}));

	return c.json({ books: rows });
});

// ── API: Upload book ──
routes.post("/api/books", async (c) => {
	const space = c.req.param("space") || "global";
	const dataSpace = c.get("effectiveSpace") || space;
	const token = extractToken(c.req.raw.headers);
	if (!token) return c.json({ error: "Authentication required" }, 401);

	let claims;
	try {
		claims = await verifyToken(token);
	} catch {
		return c.json({ error: "Invalid token" }, 401);
	}

	const formData = await c.req.formData();
	const file = formData.get("pdf") as File | null;
	const title = (formData.get("title") as string || "").trim();
	const author = (formData.get("author") as string || "").trim() || null;
	const description = (formData.get("description") as string || "").trim() || null;
	const tagsRaw = (formData.get("tags") as string || "").trim();
	const license = (formData.get("license") as string || "CC BY-SA 4.0").trim();

	if (!file || file.type !== "application/pdf") {
		return c.json({ error: "PDF file required" }, 400);
	}
	if (!title) {
		return c.json({ error: "Title required" }, 400);
	}

	const tags = tagsRaw ? tagsRaw.split(",").map((t) => t.trim()).filter(Boolean) : [];
	const shortId = randomUUID().slice(0, 8);
	const id = randomUUID();
	let slug = slugify(title);

	// Check slug collision
	const doc = ensureDoc(dataSpace);
	const slugExists = Object.values(doc.items).some((b) => b.slug === slug);
	if (slugExists) {
		slug = `${slug}-${shortId}`;
	}

	// Save PDF to disk
	await mkdir(BOOKS_DIR, { recursive: true });
	const filename = `${slug}.pdf`;
	const filepath = resolve(BOOKS_DIR, filename);
	const buffer = Buffer.from(await file.arrayBuffer());
	await Bun.write(filepath, buffer);

	const now = Date.now();

	// Insert into Automerge doc
	const docId = booksCatalogDocId(dataSpace);
	_syncServer!.changeDoc<BooksCatalogDoc>(docId, `add book: ${slug}`, (d) => {
		d.items[id] = {
			id,
			slug,
			title,
			author: author || "",
			description: description || "",
			pdfPath: filename,
			pdfSizeBytes: buffer.length,
			pageCount: 0,
			tags,
			license,
			coverColor: null,
			contributorId: claims.sub,
			contributorName: claims.username || null,
			status: "published",
			featured: false,
			viewCount: 0,
			downloadCount: 0,
			createdAt: now,
			updatedAt: now,
		};
	});

	return c.json({
		id,
		slug,
		title,
		author,
		description,
		tags,
		created_at: new Date(now).toISOString(),
	}, 201);
});

// ── API: Get book details ──
routes.get("/api/books/:id", async (c) => {
	const space = c.req.param("space") || "global";
	const dataSpace = c.get("effectiveSpace") || space;
	const id = c.req.param("id");

	const doc = ensureDoc(dataSpace);
	const book = findBook(doc, id);

	if (!book || book.status !== "published") {
		return c.json({ error: "Book not found" }, 404);
	}

	// Increment view count
	const docId = booksCatalogDocId(dataSpace);
	_syncServer!.changeDoc<BooksCatalogDoc>(docId, `view: ${book.slug}`, (d) => {
		if (d.items[book.id]) {
			d.items[book.id].viewCount += 1;
			d.items[book.id].updatedAt = Date.now();
		}
	});

	return c.json(bookToRow(book));
});

// ── API: Serve PDF ──
routes.get("/api/books/:id/pdf", async (c) => {
	const space = c.req.param("space") || "global";
	const dataSpace = c.get("effectiveSpace") || space;
	const id = c.req.param("id");

	const doc = ensureDoc(dataSpace);
	const book = findBook(doc, id);

	if (!book || book.status !== "published") {
		return c.json({ error: "Book not found" }, 404);
	}

	const filepath = resolve(BOOKS_DIR, book.pdfPath);
	const file = Bun.file(filepath);

	if (!(await file.exists())) {
		return c.json({ error: "PDF file not found" }, 404);
	}

	// Increment download count
	const docId = booksCatalogDocId(dataSpace);
	_syncServer!.changeDoc<BooksCatalogDoc>(docId, `download: ${book.slug}`, (d) => {
		if (d.items[book.id]) {
			d.items[book.id].downloadCount += 1;
			d.items[book.id].updatedAt = Date.now();
		}
	});

	return new Response(file, {
		headers: {
			"Content-Type": "application/pdf",
			"Content-Disposition": `inline; filename="${book.slug}.pdf"`,
			"Content-Length": String(file.size),
		},
	});
});

// ── Page: Library ──
routes.get("/", (c) => {
	const spaceSlug = c.req.param("space") || "personal";
	const dataSpace = c.get("effectiveSpace") || spaceSlug;
	return c.html(renderShell({
		title: `${spaceSlug} — Library | rSpace`,
		moduleId: "rbooks",
		spaceSlug,
		modules: getModuleInfoList(),
		theme: "dark",
		body: `<folk-book-shelf space-slug="${spaceSlug}"></folk-book-shelf>`,
		scripts: `<script type="module" src="/modules/rbooks/folk-book-shelf.js?v=2"></script>`,
		styles: `<link rel="stylesheet" href="/modules/rbooks/books.css">`,
	}));
});

// ── Page: Book reader ──
routes.get("/read/:id", async (c) => {
	const spaceSlug = c.req.param("space") || "personal";
	const dataSpace = c.get("effectiveSpace") || spaceSlug;
	const id = c.req.param("id");

	const doc = ensureDoc(dataSpace);
	const book = findBook(doc, id);

	if (!book || book.status !== "published") {
		const html = renderShell({
			title: "Book not found | rSpace",
			moduleId: "rbooks",
			spaceSlug,
			body: `<div style="padding:3rem;text-align:center;color:#94a3b8;"><h2>Book not found</h2><p><a href="/rbooks" style="color:#60a5fa;">Back to library</a></p></div>`,
			modules: getModuleInfoList(),
		});
		return c.html(html, 404);
	}

	// Increment view count
	const docId = booksCatalogDocId(dataSpace);
	_syncServer!.changeDoc<BooksCatalogDoc>(docId, `view: ${book.slug}`, (d) => {
		if (d.items[book.id]) {
			d.items[book.id].viewCount += 1;
			d.items[book.id].updatedAt = Date.now();
		}
	});

	// Build the PDF URL relative to this module's mount point
	const pdfUrl = c.get("isSubdomain") ? `/rbooks/api/books/${book.slug}/pdf` : `/${spaceSlug}/rbooks/api/books/${book.slug}/pdf`;

	const html = renderShell({
		title: `${book.title} | rSpace`,
		moduleId: "rbooks",
		spaceSlug,
		body: `
			<folk-book-reader
				id="reader"
				pdf-url="${pdfUrl}"
				book-id="${book.slug}"
				title="${escapeAttr(book.title)}"
				author="${escapeAttr(book.author || '')}"
			></folk-book-reader>
		`,
		modules: getModuleInfoList(),
		theme: "dark",
		head: `<link rel="stylesheet" href="/modules/rbooks/books.css">`,
		scripts: `
			<script type="module">
				import { FolkBookReader } from '/modules/rbooks/folk-book-reader.js';
			</script>
		`,
	});

	return c.html(html);
});

// ── Seed template data ──

function seedTemplateBooks(space: string) {
	if (!_syncServer) return;
	const docId = booksCatalogDocId(space);
	const doc = ensureDoc(space);
	if (Object.keys(doc.items).length > 0) return;

	const now = Date.now();
	const books: Array<{ title: string; author: string; desc: string; tags: string[]; color: string }> = [
		{
			title: 'Governing the Commons', author: 'Elinor Ostrom',
			desc: 'Nobel Prize-winning analysis of how communities self-organize to manage shared resources without privatization or state control.',
			tags: ['commons', 'governance', 'economics'], color: '#6366f1',
		},
		{
			title: 'Doughnut Economics', author: 'Kate Raworth',
			desc: 'A framework for 21st-century economics that meets the needs of all within the means of the planet.',
			tags: ['economics', 'sustainability', 'systems-thinking'], color: '#10b981',
		},
		{
			title: 'Sacred Economics', author: 'Charles Eisenstein',
			desc: 'Tracing the history of money from ancient gift economies to modern capitalism, and envisioning a more connected economy.',
			tags: ['economics', 'gift-economy', 'philosophy'], color: '#f59e0b',
		},
	];

	_syncServer.changeDoc<BooksCatalogDoc>(docId, 'seed template books', (d) => {
		for (const b of books) {
			const id = randomUUID();
			const slug = b.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
			d.items[id] = {
				id, slug, title: b.title, author: b.author, description: b.desc,
				pdfPath: '', pdfSizeBytes: 0, pageCount: 0, tags: b.tags,
				license: 'CC BY-SA 4.0', coverColor: b.color,
				contributorId: 'did:demo:seed', contributorName: 'Community Library',
				status: 'published', featured: false, viewCount: 0, downloadCount: 0,
				createdAt: now, updatedAt: now,
			};
		}
	});

	console.log(`[Books] Template seeded for "${space}": 3 book entries`);
}

// ── Module export ──

export function getRecentBooksForMI(space: string, limit = 5): { id: string; title: string; author: string; pageCount: number; createdAt: number }[] {
	if (!_syncServer) return [];
	const docId = booksCatalogDocId(space);
	const doc = _syncServer.getDoc<BooksCatalogDoc>(docId);
	if (!doc) return [];
	return Object.values(doc.items)
		.sort((a, b) => b.createdAt - a.createdAt)
		.slice(0, limit)
		.map((b) => ({ id: b.id, title: b.title, author: b.author, pageCount: b.pageCount || 0, createdAt: b.createdAt }));
}

export const booksModule: RSpaceModule = {
	id: "rbooks",
	name: "rBooks",
	icon: "📚",
	description: "Community PDF library with flipbook reader",
	scoping: { defaultScope: 'global', userConfigurable: true },
	docSchemas: [{ pattern: '{space}:books:catalog', description: 'Book catalog metadata', init: booksCatalogSchema.init }],
	routes,
	standaloneDomain: "rbooks.online",
	landingPage: renderLanding,
	seedTemplate: seedTemplateBooks,
	async onInit(ctx) {
		_syncServer = ctx.syncServer;
		console.log("[Books] Module initialized (Automerge storage)");
	},
	feeds: [
		{
			id: "reading-list",
			name: "Reading List",
			kind: "data",
			description: "Books in the community library — titles, authors, tags",
			filterable: true,
		},
		{
			id: "annotations",
			name: "Annotations",
			kind: "data",
			description: "Reader highlights, bookmarks, and notes on books",
		},
	],
	acceptsFeeds: ["data", "resource"],
	outputPaths: [
		{ path: "books", name: "Books", icon: "📚", description: "Community PDF library" },
		{ path: "collections", name: "Collections", icon: "📑", description: "Curated book collections" },
	],

	subPageInfos: [
		{
			path: "read",
			title: "Flipbook Reader",
			icon: "📖",
			tagline: "rBooks Tool",
			description: "Read PDFs in a beautiful flipbook layout with page-turn animations. Bookmark pages, adjust display, and read collaboratively.",
			features: [
				{ icon: "📄", title: "Flipbook View", text: "Page-turning animations bring PDFs to life in a book-like reading experience." },
				{ icon: "🔖", title: "Bookmarks & Notes", text: "Mark your place and add annotations that sync across devices." },
				{ icon: "👥", title: "Shared Library", text: "Upload PDFs to your community library for everyone to discover and read." },
			],
		},
	],
	onboardingActions: [
		{ label: "Upload a PDF", icon: "📕", description: "Add a book or document to the library", type: 'upload' as const, upload: { accept: '.pdf', endpoint: '/{space}/rbooks/api/books' } },
		{ label: "Browse the Library", icon: "📚", description: "Explore the shared book collection", type: 'create' as const, href: '/{space}/rbooks' },
	],
	async onSpaceCreate(ctx: SpaceLifecycleContext) {
		// Books are global, not space-scoped (for now). No-op.
	},
};
