import { FolkShape } from "./folk-shape";
import { css, html } from "./tags";
import { getModuleApiBase } from "../shared/url-helpers";

const styles = css`
	:host {
		background: var(--rs-bg-surface, #0f172a);
		color: var(--rs-text-primary, #e2e8f0);
		border-radius: 8px;
		box-shadow: var(--rs-shadow-md, 0 2px 8px rgba(0, 0, 0, 0.3));
		min-width: 360px;
		min-height: 400px;
	}

	.header {
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: 8px 12px;
		background: linear-gradient(135deg, #7c3aed, #2563eb);
		color: white;
		border-radius: 8px 8px 0 0;
		font-size: 12px;
		font-weight: 600;
		cursor: move;
	}

	.header-title {
		display: flex;
		align-items: center;
		gap: 6px;
	}

	.header-actions {
		display: flex;
		gap: 4px;
	}

	.header-actions button {
		background: transparent;
		border: none;
		color: white;
		cursor: pointer;
		padding: 2px 6px;
		border-radius: 4px;
		font-size: 14px;
	}

	.header-actions button:hover {
		background: rgba(255, 255, 255, 0.2);
	}

	.content {
		display: flex;
		flex-direction: column;
		height: calc(100% - 36px);
		overflow: hidden;
	}

	.controls {
		padding: 10px 12px;
		border-bottom: 1px solid #1e293b;
		display: flex;
		gap: 8px;
		align-items: center;
	}

	.splat-url-input {
		flex: 1;
		padding: 8px 10px;
		border: 2px solid #334155;
		border-radius: 6px;
		background: #1e293b;
		color: #e2e8f0;
		font-size: 12px;
		outline: none;
	}

	.splat-url-input:focus {
		border-color: #7c3aed;
	}

	.load-btn {
		padding: 8px 14px;
		background: linear-gradient(135deg, #7c3aed, #2563eb);
		color: white;
		border: none;
		border-radius: 6px;
		font-size: 12px;
		font-weight: 600;
		cursor: pointer;
	}

	.load-btn:hover {
		opacity: 0.9;
	}

	.viewer-area {
		flex: 1;
		position: relative;
		background: #0a0a0a;
		overflow: hidden;
	}

	.viewer-area canvas {
		width: 100%;
		height: 100%;
		display: block;
	}

	.placeholder {
		flex: 1;
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		color: #64748b;
		text-align: center;
		gap: 8px;
	}

	.placeholder-icon {
		font-size: 48px;
		opacity: 0.5;
	}

	.gallery-list {
		display: grid;
		grid-template-columns: repeat(2, 1fr);
		gap: 8px;
		padding: 10px;
		overflow-y: auto;
		max-height: 200px;
	}

	.gallery-item {
		padding: 8px;
		background: #1e293b;
		border-radius: 6px;
		cursor: pointer;
		color: #cbd5e1;
		font-size: 11px;
		text-align: center;
		border: 2px solid transparent;
		transition: border-color 0.2s;
	}

	.gallery-item:hover {
		border-color: #7c3aed;
	}

	.gallery-item .title {
		font-weight: 600;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	.gallery-item .meta {
		font-size: 10px;
		color: #64748b;
		margin-top: 2px;
	}

	.loading {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		padding: 24px;
		gap: 12px;
		color: #94a3b8;
	}

	.spinner {
		width: 32px;
		height: 32px;
		border: 3px solid #334155;
		border-top-color: #7c3aed;
		border-radius: 50%;
		animation: spin 1s linear infinite;
	}

	@keyframes spin {
		to { transform: rotate(360deg); }
	}

	.error {
		color: #ef4444;
		padding: 12px;
		background: #1c1017;
		border-radius: 6px;
		font-size: 13px;
		margin: 10px;
	}

	.upload-btn {
		padding: 8px 14px;
		background: #334155;
		color: #e2e8f0;
		border: none;
		border-radius: 6px;
		font-size: 12px;
		cursor: pointer;
	}

	.upload-btn:hover {
		background: #475569;
	}
`;

declare global {
	interface HTMLElementTagNameMap {
		"folk-splat": FolkSplat;
	}
}

export class FolkSplat extends FolkShape {
	static override tagName = "folk-splat";

	static {
		const sheet = new CSSStyleSheet();
		const parentRules = Array.from(FolkShape.styles.cssRules)
			.map((r) => r.cssText)
			.join("\n");
		const childRules = Array.from(styles.cssRules)
			.map((r) => r.cssText)
			.join("\n");
		sheet.replaceSync(`${parentRules}\n${childRules}`);
		this.styles = sheet;
	}

	#splatUrl = "";
	#isLoading = false;
	#error: string | null = null;
	#viewer: any = null;
	#viewerCanvas: HTMLCanvasElement | null = null;
	#gallerySplats: any[] = [];
	#urlInput: HTMLInputElement | null = null;
	#rafId: number | null = null;

	get splatUrl() {
		return this.#splatUrl;
	}
	set splatUrl(v: string) {
		this.#splatUrl = v;
	}

	override createRenderRoot() {
		const root = super.createRenderRoot();

		const wrapper = document.createElement("div");
		wrapper.innerHTML = html`
			<div class="header">
				<span class="header-title">
					<span>🔮</span>
					<span>3D Splat</span>
				</span>
				<div class="header-actions">
					<button class="gallery-btn" title="Browse gallery">📂</button>
					<button class="close-btn" title="Close">×</button>
				</div>
			</div>
			<div class="content">
				<div class="controls">
					<input class="splat-url-input" type="text" placeholder="Paste .splat URL or pick from gallery..." />
					<button class="load-btn">Load</button>
				</div>
				<div class="gallery-list" style="display:none"></div>
				<div class="viewer-area">
					<div class="placeholder">
						<span class="placeholder-icon">🔮</span>
						<span>Enter a splat URL or browse the gallery</span>
					</div>
				</div>
			</div>
		`;

		const slot = root.querySelector("slot");
		const containerDiv = slot?.parentElement as HTMLElement;
		if (containerDiv) {
			containerDiv.replaceWith(wrapper);
		}

		this.#urlInput = wrapper.querySelector(".splat-url-input");
		const loadBtn = wrapper.querySelector(".load-btn") as HTMLButtonElement;
		const closeBtn = wrapper.querySelector(".close-btn") as HTMLButtonElement;
		const galleryBtn = wrapper.querySelector(".gallery-btn") as HTMLButtonElement;
		const galleryList = wrapper.querySelector(".gallery-list") as HTMLElement;
		const viewerArea = wrapper.querySelector(".viewer-area") as HTMLElement;

		loadBtn.addEventListener("click", (e) => {
			e.stopPropagation();
			const url = this.#urlInput?.value.trim();
			if (url) this.#loadSplat(url, viewerArea);
		});

		this.#urlInput?.addEventListener("keydown", (e) => {
			if (e.key === "Enter") {
				e.preventDefault();
				const url = this.#urlInput?.value.trim();
				if (url) this.#loadSplat(url, viewerArea);
			}
		});
		this.#urlInput?.addEventListener("pointerdown", (e) => e.stopPropagation());

		galleryBtn.addEventListener("click", (e) => {
			e.stopPropagation();
			const isVisible = galleryList.style.display !== "none";
			galleryList.style.display = isVisible ? "none" : "grid";
			if (!isVisible && this.#gallerySplats.length === 0) {
				this.#loadGallery(galleryList, viewerArea);
			}
		});

		closeBtn.addEventListener("click", (e) => {
			e.stopPropagation();
			this.dispatchEvent(new CustomEvent("close"));
		});

		// If splatUrl was set before render
		if (this.#splatUrl) {
			this.#loadSplat(this.#splatUrl, viewerArea);
		}

		return root;
	}

	async #loadGallery(container: HTMLElement, viewerArea: HTMLElement) {
		container.innerHTML = '<div class="loading"><div class="spinner"></div><span>Loading gallery...</span></div>';
		try {
			const res = await fetch(`${getModuleApiBase("rsplat")}/api/splats?limit=20`);
			if (!res.ok) throw new Error("Failed to load splats");
			const data = await res.json();
			this.#gallerySplats = data.splats || [];

			if (this.#gallerySplats.length === 0) {
				container.innerHTML = '<div class="placeholder" style="padding:16px"><span>No splats in this space yet</span></div>';
				return;
			}

			container.innerHTML = this.#gallerySplats.map((s) => `
				<div class="gallery-item" data-slug="${s.slug}">
					<div class="title">${this.#escapeHtml(s.title)}</div>
					<div class="meta">${s.file_format} • ${(s.file_size_bytes / 1024 / 1024).toFixed(1)}MB</div>
				</div>
			`).join("");

			container.querySelectorAll(".gallery-item").forEach((item) => {
				item.addEventListener("click", (e) => {
					e.stopPropagation();
					const slug = (item as HTMLElement).dataset.slug;
					const splat = this.#gallerySplats.find((s) => s.slug === slug);
					if (splat) {
						const url = `${getModuleApiBase("rsplat")}/api/splats/${splat.slug}/${splat.slug}.${splat.file_format}`;
						if (this.#urlInput) this.#urlInput.value = url;
						container.style.display = "none";
						this.#loadSplat(url, viewerArea);
					}
				});
			});
		} catch (err) {
			container.innerHTML = `<div class="error">Failed to load gallery</div>`;
		}
	}

	async #loadSplat(url: string, viewerArea: HTMLElement) {
		this.#splatUrl = url;
		this.#isLoading = true;
		this.#error = null;

		viewerArea.innerHTML = '<div class="loading"><div class="spinner"></div><span>Loading 3D splat...</span></div>';

		try {
			// Use Three.js + GaussianSplats3D via CDN importmap (not bundled)
			const threeId = "three";
			const gs3dId = "@mkkellogg/gaussian-splats-3d";
			const THREE = await (Function("id", "return import(id)")(threeId) as Promise<any>);
			const GS3D = await (Function("id", "return import(id)")(gs3dId) as Promise<any>);

			viewerArea.innerHTML = "";

			const canvas = document.createElement("canvas");
			canvas.style.width = "100%";
			canvas.style.height = "100%";
			viewerArea.appendChild(canvas);

			const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
			renderer.setSize(viewerArea.clientWidth, viewerArea.clientHeight);

			const viewer = new GS3D.Viewer({
				renderer,
				cameraUp: [0, -1, 0],
				initialCameraPosition: [0, 0, 5],
				initialCameraLookAt: [0, 0, 0],
			});

			await viewer.addSplatScene(url);
			this.#viewer = viewer;
			this.#viewerCanvas = canvas;

			// Simple animation loop
			const animate = () => {
				if (!this.#viewer) return;
				(viewer as any).update();
				(viewer as any).render();
				this.#rafId = requestAnimationFrame(animate);
			};
			animate();
		} catch (err) {
			// Fallback: show as iframe pointing to splat viewer page
			console.warn("[folk-splat] Three.js not available, using iframe fallback");
			const slug = url.split("/").filter(Boolean).pop()?.split(".")[0] || "";
			viewerArea.innerHTML = `<iframe src="${getModuleApiBase("rsplat")}/view/${slug}" style="width:100%;height:100%;border:none;border-radius:0 0 8px 8px"></iframe>`;
		} finally {
			this.#isLoading = false;
		}
	}

	#escapeHtml(text: string): string {
		const div = document.createElement("div");
		div.textContent = text;
		return div.innerHTML;
	}

	static override fromData(data: Record<string, any>): FolkSplat {
		const shape = FolkShape.fromData(data) as FolkSplat;
		if (data.splatUrl) shape.splatUrl = data.splatUrl;
		return shape;
	}

	override toJSON() {
		return {
			...super.toJSON(),
			type: "folk-splat",
			splatUrl: this.#splatUrl,
		};
	}

	override disconnectedCallback() {
		super.disconnectedCallback?.();
		if (this.#rafId !== null) {
			cancelAnimationFrame(this.#rafId);
			this.#rafId = null;
		}
		if (this.#viewer) {
			try { (this.#viewer as any).dispose?.(); } catch { /* ignore */ }
			this.#viewer = null;
		}
		this.#viewerCanvas = null;
	}

	override applyData(data: Record<string, any>): void {
		super.applyData(data);
		if ("splatUrl" in data && this.splatUrl !== data.splatUrl) {
			this.splatUrl = data.splatUrl;
		}
	}
}
