/**
 * Canvas module — the collaborative infinite canvas.
 *
 * This is the original rSpace canvas restructured as an rSpace module.
 * Routes are relative to the mount point (/:space/rspace in unified mode,
 * / in standalone mode).
 */

import { Hono } from "hono";
import { resolve } from "node:path";
import { renderShell } from "../../server/shell";
import { getModuleInfoList } from "../../shared/module";
import type { RSpaceModule } from "../../shared/module";
import { loadCommunity, getDocumentData } from "../../server/community-store";
import { renderLanding } from "./landing";

const DIST_DIR = resolve(import.meta.dir, "../../dist");

const routes = new Hono();

// GET /api/meta — space metadata (owner, members) for space-settings fallback
routes.get("/api/meta", async (c) => {
	const space = c.req.param("space") || "demo";
	try {
		await loadCommunity(space);
		const doc = getDocumentData(space);
		if (!doc) return c.json({ meta: {} });
		return c.json({ meta: { ownerDID: doc.meta?.ownerDID, members: doc.members } });
	} catch {
		return c.json({ meta: {} });
	}
});

/**
 * Extract body content and scripts from the full canvas.html page.
 * Strips the shell chrome (header, tab-bar, welcome overlay) that renderShell provides,
 * and returns just the canvas-specific DOM + inline styles + module scripts.
 */
/** Strip a <div class="className"> and all its nested children by counting open/close tags. */
function stripNestedDiv(html: string, className: string): string {
	const re = new RegExp(`<div\\s[^>]*class="[^"]*${className}[^"]*"[^>]*>`);
	const match = html.match(re);
	if (!match || match.index === undefined) return html;

	let depth = 1;
	let pos = match.index + match[0].length;
	while (depth > 0 && pos < html.length) {
		const nextOpen = html.indexOf("<div", pos);
		const nextClose = html.indexOf("</div>", pos);
		if (nextClose === -1) break;
		if (nextOpen !== -1 && nextOpen < nextClose) {
			depth++;
			pos = nextOpen + 4;
		} else {
			depth--;
			pos = nextClose + 6;
		}
	}
	return html.substring(0, match.index) + html.substring(pos);
}

function extractCanvasContent(html: string): { body: string; styles: string; scripts: string } {
	// Extract inline <style> blocks from <head> (canvas CSS)
	const headMatch = html.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
	const headContent = headMatch?.[1] || "";
	const styleBlocks: string[] = [];
	headContent.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (match) => {
		styleBlocks.push(match);
		return "";
	});

	// Extract module script src from <head>
	const scriptSrcs: string[] = [];
	headContent.replace(/<script[^>]*\bsrc="([^"]+)"[^>]*>/gi, (_m, src: string) => {
		if (!src.includes("shell.js")) scriptSrcs.push(src);
		return "";
	});

	// Extract <body> content
	const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
	let bodyContent = bodyMatch?.[1] || "";

	// Strip shell chrome that renderShell already provides:
	// header, tab-row (including all children like people badge/panel),
	// space-settings, history-panel, and welcome overlay.
	bodyContent = bodyContent
		.replace(/<header[\s\S]*?<\/header>/i, "")
		.replace(/<rstack-history-panel[^>]*><\/rstack-history-panel>/gi, "")
		.replace(/<!--\s*Welcome overlay[\s\S]*?<\/div>\s*<\/div>\s*<\/div>/i, "");
	// Strip the rstack-tab-row div (nested divs make simple regex unreliable)
	bodyContent = stripNestedDiv(bodyContent, "rstack-tab-row");

	const styles = styleBlocks.join("\n");
	const scripts = scriptSrcs.map(s => `<script type="module" crossorigin src="${s}"></script>`).join("\n");

	return { body: bodyContent.trim(), styles, scripts };
}

// Cache the extracted canvas content (parsed once at startup)
let canvasContentCache: { body: string; styles: string; scripts: string } | null = null;

async function getCanvasContent(): Promise<{ body: string; styles: string; scripts: string }> {
	if (canvasContentCache) return canvasContentCache;

	// Prefer canvas-module.html (pre-extracted body partial)
	const moduleFile = Bun.file(resolve(DIST_DIR, "canvas-module.html"));
	if (await moduleFile.exists()) {
		canvasContentCache = {
			body: await moduleFile.text(),
			styles: "",
			scripts: `<script type="module" src="/canvas-module.js"></script>`,
		};
		return canvasContentCache;
	}

	// Fall back to extracting from full canvas.html
	const fullFile = Bun.file(resolve(DIST_DIR, "canvas.html"));
	if (await fullFile.exists()) {
		canvasContentCache = extractCanvasContent(await fullFile.text());
		return canvasContentCache;
	}

	return {
		body: `<div style="padding:2rem;text-align:center;color:#64748b;">Canvas loading...</div>`,
		styles: "",
		scripts: "",
	};
}

// GET / — serve the canvas page wrapped in shell
routes.get("/", async (c) => {
	const spaceSlug = c.req.param("space") || c.req.query("space") || "demo";
	const canvas = await getCanvasContent();

	const html = renderShell({
		title: `${spaceSlug} — Canvas | rSpace`,
		moduleId: "rspace",
		spaceSlug,
		body: canvas.body,
		modules: getModuleInfoList(),
		theme: "dark",
		styles: canvas.styles,
		scripts: canvas.scripts,
	});

	return c.html(html);
});

// ── MI export ──

export function getCanvasSummaryForMI(space: string, _limit = 5): { totalShapes: number; typeBreakdown: { type: string; count: number }[] }[] {
	const doc = getDocumentData(space);
	if (!doc?.shapes) return [];
	const counts: Record<string, number> = {};
	for (const shape of Object.values(doc.shapes as Record<string, any>)) {
		const tag = shape.type || "unknown";
		counts[tag] = (counts[tag] || 0) + 1;
	}
	const typeBreakdown = Object.entries(counts)
		.sort(([, a], [, b]) => b - a)
		.slice(0, 10)
		.map(([type, count]) => ({ type, count }));
	return [{ totalShapes: Object.keys(doc.shapes).length, typeBreakdown }];
}

export const canvasModule: RSpaceModule = {
	id: "rspace",
	name: "rSpace",
	icon: "🎨",
	description: "Real-time collaborative canvas",
	scoping: { defaultScope: 'space', userConfigurable: false },
	landingPage: renderLanding,
	routes,
	feeds: [
		{
			id: "shapes",
			name: "Canvas Shapes",
			kind: "data",
			description: "All shapes on this canvas layer — notes, embeds, arrows, etc.",
			filterable: true,
		},
		{
			id: "connections",
			name: "Shape Connections",
			kind: "data",
			description: "Arrow connections between shapes — the canvas graph",
		},
	],
	acceptsFeeds: ["economic", "trust", "data", "attention", "governance", "resource"],
	outputPaths: [
		{ path: "canvases", name: "Canvases", icon: "🎨", description: "Collaborative infinite canvases" },
	],
};
