/**
 * Client-side tab cache for instant tab switching.
 *
 * Instead of full page navigations, previously loaded tabs are kept in the DOM
 * and shown/hidden via CSS. New tabs are fetched via fetch() + DOMParser on
 * first visit, then stay in the DOM forever.
 *
 * Hiding preserves ALL component state: Shadow DOM, event listeners,
 * WebSocket connections, timers.
 */

export class TabCache {
	private currentModuleId: string;
	private spaceSlug: string;
	private panes = new Map<string, HTMLElement>();

	constructor(spaceSlug: string, moduleId: string) {
		this.spaceSlug = spaceSlug;
		this.currentModuleId = moduleId;
	}

	/** Wrap the initial #app content in a pane div and set up popstate */
	init(): boolean {
		const app = document.getElementById("app");
		if (!app) return false;

		// Wrap existing content in a pane
		const pane = document.createElement("div");
		pane.className = "rspace-tab-pane rspace-tab-pane--active";
		pane.dataset.moduleId = this.currentModuleId;
		pane.dataset.pageTitle = document.title;

		while (app.firstChild) {
			pane.appendChild(app.firstChild);
		}
		app.appendChild(pane);
		this.panes.set(this.currentModuleId, pane);

		// Push initial state into history
		history.replaceState(
			{ moduleId: this.currentModuleId, spaceSlug: this.spaceSlug },
			"",
			window.location.href,
		);

		// Handle browser back/forward
		window.addEventListener("popstate", (e) => {
			const state = e.state;
			if (state?.moduleId && this.panes.has(state.moduleId)) {
				this.showPane(state.moduleId);
			} else {
				window.location.reload();
			}
		});

		return true;
	}

	/** Switch to a module tab. Returns true if handled client-side. */
	async switchTo(moduleId: string): Promise<boolean> {
		if (moduleId === this.currentModuleId) return true;

		if (this.panes.has(moduleId)) {
			this.showPane(moduleId);
			this.updateUrl(moduleId);
			return true;
		}

		return this.fetchAndInject(moduleId);
	}

	/** Check if a pane exists in the DOM cache */
	has(moduleId: string): boolean {
		return this.panes.has(moduleId);
	}

	/** Remove a cached pane from the DOM */
	removePane(moduleId: string): void {
		const pane = this.panes.get(moduleId);
		if (pane) {
			pane.remove();
			this.panes.delete(moduleId);
		}
	}

	/** Fetch a module page, extract content, and inject into a new pane */
	private async fetchAndInject(moduleId: string): Promise<boolean> {
		const navUrl = (window as any).__rspaceNavUrl;
		if (!navUrl) return false;
		const url: string = navUrl(this.spaceSlug, moduleId);

		// Cross-origin URLs → fall back to full navigation
		try {
			const resolved = new URL(url, window.location.href);
			if (resolved.origin !== window.location.origin) return false;
		} catch {
			return false;
		}

		const app = document.getElementById("app");
		if (!app) return false;

		// Show loading spinner
		const loadingPane = document.createElement("div");
		loadingPane.className =
			"rspace-tab-pane rspace-tab-pane--active rspace-tab-loading";
		loadingPane.innerHTML = '<div class="rspace-tab-spinner"></div>';
		this.hideAllPanes();
		app.appendChild(loadingPane);

		try {
			const resp = await fetch(url);
			if (!resp.ok) {
				loadingPane.remove();
				return false;
			}

			const html = await resp.text();
			const content = this.extractContent(html);
			if (!content) {
				loadingPane.remove();
				return false;
			}

			// Remove loading spinner
			loadingPane.remove();

			// Create the real pane
			const pane = document.createElement("div");
			pane.className = "rspace-tab-pane rspace-tab-pane--active";
			pane.dataset.moduleId = moduleId;
			pane.dataset.pageTitle = content.title;
			pane.innerHTML = content.body;
			app.appendChild(pane);
			this.panes.set(moduleId, pane);

			// Load module-specific assets
			this.loadAssets(content.scripts, content.styles);

			// Update shell state
			this.currentModuleId = moduleId;
			document.title = content.title;
			this.updateShellState(moduleId);
			this.updateUrl(moduleId);
			this.updateCanvasLayout(moduleId);

			return true;
		} catch {
			loadingPane.remove();
			return false;
		}
	}

	/** Parse full HTML response and extract module content */
	private extractContent(
		html: string,
	): {
		body: string;
		title: string;
		scripts: string[];
		styles: string[];
	} | null {
		const parser = new DOMParser();
		const doc = parser.parseFromString(html, "text/html");

		// Extract #app content or .rspace-iframe-wrap (for external app modules)
		const appEl = doc.getElementById("app");
		const iframeWrap = doc.querySelector(".rspace-iframe-wrap");

		let body: string;
		if (appEl) {
			body = appEl.innerHTML;
		} else if (iframeWrap) {
			body = iframeWrap.outerHTML;
		} else {
			return null;
		}

		const title = doc.title || "";

		// Extract module-specific scripts (exclude shell.js and inline shell bootstrap)
		const scripts: string[] = [];
		doc.querySelectorAll('script[type="module"]').forEach((s) => {
			const src = s.getAttribute("src") || "";
			if (src.includes("shell.js")) return;
			const text = s.textContent || "";
			if (text.includes("import '/shell.js")) return;
			if (text.includes('import "/shell.js')) return;
			if (src) scripts.push(src);
		});

		// Extract module-specific styles (exclude shell.css)
		const styles: string[] = [];
		doc.querySelectorAll('link[rel="stylesheet"]').forEach((l) => {
			const href = l.getAttribute("href") || "";
			if (href.includes("shell.css")) return;
			styles.push(href);
		});

		return { body, title, scripts, styles };
	}

	/** Load script and style assets (idempotent — browsers deduplicate module scripts) */
	private loadAssets(scripts: string[], styles: string[]): void {
		for (const src of scripts) {
			const el = document.createElement("script");
			el.type = "module";
			el.src = src;
			document.body.appendChild(el);
		}

		for (const href of styles) {
			if (document.querySelector(`link[href="${CSS.escape(href)}"]`))
				continue;
			const el = document.createElement("link");
			el.rel = "stylesheet";
			el.href = href;
			document.head.appendChild(el);
		}
	}

	/** Show a specific pane and update shell state */
	private showPane(moduleId: string): void {
		this.hideAllPanes();
		const pane = this.panes.get(moduleId);
		if (pane) {
			pane.classList.add("rspace-tab-pane--active");
			const storedTitle = pane.dataset.pageTitle;
			if (storedTitle) document.title = storedTitle;
		}
		this.currentModuleId = moduleId;
		this.updateShellState(moduleId);
		this.updateCanvasLayout(moduleId);
	}

	/** Hide all panes */
	private hideAllPanes(): void {
		const app = document.getElementById("app");
		if (!app) return;
		app.querySelectorAll(".rspace-tab-pane").forEach((p) => {
			p.classList.remove("rspace-tab-pane--active");
		});
	}

	/** Update app-switcher and tab-bar to reflect current module */
	private updateShellState(moduleId: string): void {
		const appSwitcher = document.querySelector("rstack-app-switcher");
		if (appSwitcher) appSwitcher.setAttribute("current", moduleId);

		const tabBar = (window as any).__rspaceTabBar;
		if (tabBar) tabBar.setAttribute("active", "layer-" + moduleId);
	}

	/** Toggle canvas-layout class on #app */
	private updateCanvasLayout(moduleId: string): void {
		const app = document.getElementById("app");
		if (!app) return;
		if (moduleId === "rspace") {
			app.classList.add("canvas-layout");
		} else {
			app.classList.remove("canvas-layout");
		}
	}

	/** Push new URL to browser history */
	private updateUrl(moduleId: string): void {
		const navUrl = (window as any).__rspaceNavUrl;
		if (!navUrl) return;
		const url: string = navUrl(this.spaceSlug, moduleId);
		history.pushState(
			{ moduleId, spaceSlug: this.spaceSlug },
			"",
			url,
		);
	}
}
