/**
 * folk-applet — Generic rApplet shape for the canvas.
 *
 * Compact mode (default): 300×200 card with module-provided HTML body + port indicators.
 * Expanded mode: 600×400 with applet-circuit-canvas sub-graph or iframe fallback.
 *
 * Persisted fields: moduleId, appletId, instanceConfig, mode.
 * Live data arrives via updateLiveData() — no direct module imports.
 */

import { FolkShape } from "./folk-shape";
import { css, html } from "./tags";
import { dataTypeColor } from "./data-types";
import type { PortDescriptor } from "./data-types";
import type { AppletDefinition, AppletLiveData, AppletContext } from "../shared/applet-types";

// ── Applet registry (populated by modules at init) ──

const appletDefs = new Map<string, AppletDefinition>();

/** Register an applet definition. Key = "moduleId:appletId". */
export function registerAppletDef(moduleId: string, def: AppletDefinition): void {
	appletDefs.set(`${moduleId}:${def.id}`, def);
}

/** Look up a registered applet definition. */
export function getAppletDef(moduleId: string, appletId: string): AppletDefinition | undefined {
	return appletDefs.get(`${moduleId}:${appletId}`);
}

/** List all registered applet definitions. */
export function listAppletDefs(): Array<{ moduleId: string; def: AppletDefinition }> {
	const result: Array<{ moduleId: string; def: AppletDefinition }> = [];
	for (const [key, def] of appletDefs) {
		const moduleId = key.split(":")[0];
		result.push({ moduleId, def });
	}
	return result;
}

// ── Styles ──

const COMPACT_W = 300;
const COMPACT_H = 200;
const EXPANDED_W = 600;
const EXPANDED_H = 400;

const styles = css`
	:host {
		background: var(--rs-bg-surface, #1e293b);
		border-radius: 10px;
		box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
		overflow: visible;
	}

	.applet-wrapper {
		width: 100%;
		height: 100%;
		display: flex;
		flex-direction: column;
		position: relative;
	}

	.header {
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: 8px 12px;
		color: white;
		font-size: 12px;
		font-weight: 600;
		cursor: move;
		border-radius: 10px 10px 0 0;
		min-height: 32px;
	}

	.header-title {
		display: flex;
		align-items: center;
		gap: 6px;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}

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

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

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

	.body {
		flex: 1;
		padding: 12px;
		overflow: hidden;
		font-size: 12px;
		color: var(--rs-text-primary, #e2e8f0);
		border-radius: 0 0 10px 10px;
	}

	.body-empty {
		display: flex;
		align-items: center;
		justify-content: center;
		color: var(--rs-text-muted, #64748b);
		font-style: italic;
	}

	/* Port chips on edges */
	.port-chip {
		position: absolute;
		display: flex;
		align-items: center;
		gap: 4px;
		padding: 1px 6px;
		border-radius: 8px;
		border: 1.5px solid;
		background: var(--rs-bg-surface, #1e293b);
		font-size: 9px;
		color: var(--rs-text-muted, #94a3b8);
		white-space: nowrap;
		cursor: crosshair;
		z-index: 2;
		transform: translateY(-50%);
		transition: filter 0.15s;
	}

	.port-chip:hover {
		filter: brightness(1.3);
	}

	.port-chip.input {
		left: -2px;
	}

	.port-chip.output {
		right: -2px;
		flex-direction: row-reverse;
	}

	.chip-dot {
		width: 6px;
		height: 6px;
		border-radius: 50%;
		flex-shrink: 0;
	}

	/* Expanded mode circuit container */
	.circuit-container {
		flex: 1;
		border-radius: 0 0 10px 10px;
		overflow: hidden;
	}

	.circuit-container applet-circuit-canvas {
		width: 100%;
		height: 100%;
	}
`;

declare global {
	interface HTMLElementTagNameMap {
		"folk-applet": FolkApplet;
	}
}

export class FolkApplet extends FolkShape {
	static override tagName = "folk-applet";

	// Dynamic port descriptors set from the applet definition
	static override portDescriptors: PortDescriptor[] = [];

	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;
	}

	#moduleId = "";
	#appletId = "";
	#mode: "compact" | "expanded" = "compact";
	#instanceConfig: Record<string, unknown> = {};
	#liveData: AppletLiveData | null = null;

	// DOM refs
	#bodyEl!: HTMLElement;
	#wrapper!: HTMLElement;

	// Instance-level port descriptors (override static)
	#instancePorts: PortDescriptor[] = [];

	// Live data polling timer
	#liveDataTimer: ReturnType<typeof setInterval> | null = null;

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

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

	get mode() { return this.#mode; }
	set mode(v: "compact" | "expanded") {
		if (this.#mode === v) return;
		this.#mode = v;
		this.#updateMode();
	}

	get instanceConfig() { return this.#instanceConfig; }
	set instanceConfig(v: Record<string, unknown>) { this.#instanceConfig = v; }

	/** Sync port descriptors from the applet definition. */
	#syncDefPorts(): void {
		const def = getAppletDef(this.#moduleId, this.#appletId);
		if (def) {
			this.#instancePorts = def.ports;
		}
	}

	/** Override: use instance ports instead of static. */
	override getInputPorts(): PortDescriptor[] {
		return this.#instancePorts.filter(p => p.direction === "input");
	}

	override getOutputPorts(): PortDescriptor[] {
		return this.#instancePorts.filter(p => p.direction === "output");
	}

	override getPort(name: string): PortDescriptor | undefined {
		return this.#instancePorts.find(p => p.name === name);
	}

	/** Bridge FolkArrow piping → applet def's onInputReceived. */
	override setPortValue(name: string, value: unknown): void {
		super.setPortValue(name, value);

		const port = this.getPort(name);
		if (port?.direction !== "input") return;

		const def = getAppletDef(this.#moduleId, this.#appletId);
		if (!def?.onInputReceived) return;

		const ctx: AppletContext = {
			space: (this.closest("[space]") as any)?.getAttribute("space") || "",
			shapeId: this.id,
			emitOutput: (portName, val) => super.setPortValue(portName, val),
		};

		def.onInputReceived(name, value, ctx);
		this.#renderBody();
	}

	/** Update live data and re-render compact body. */
	updateLiveData(snapshot: Record<string, unknown>): void {
		this.#liveData = {
			space: (this.closest("[space]") as any)?.getAttribute("space") || "",
			moduleId: this.#moduleId,
			appletId: this.#appletId,
			snapshot,
			outputValues: {},
		};
		this.#renderBody();
	}

	override createRenderRoot() {
		const root = super.createRenderRoot();
		this.#syncDefPorts();
		this.initPorts();

		const def = getAppletDef(this.#moduleId, this.#appletId);
		const accentColor = def?.accentColor || "#475569";
		const icon = def?.icon || "📦";
		const label = def?.label || this.#appletId;

		this.#wrapper = document.createElement("div");
		this.#wrapper.className = "applet-wrapper";
		this.#wrapper.innerHTML = html`
			<div class="header" data-drag style="background: ${accentColor}">
				<span class="header-title">${icon} ${label}</span>
				<span class="header-actions">
					<button class="expand-btn" title="Toggle expanded">⊞</button>
					<button class="close-btn" title="Close">&times;</button>
				</span>
			</div>
			<div class="body body-empty">Loading...</div>
		`;

		const slot = root.querySelector("slot");
		const container = slot?.parentElement as HTMLElement;
		if (container) container.replaceWith(this.#wrapper);

		this.#bodyEl = this.#wrapper.querySelector(".body") as HTMLElement;

		// Wire events
		this.#wrapper.querySelector(".expand-btn")!.addEventListener("click", (e) => {
			e.stopPropagation();
			this.mode = this.#mode === "compact" ? "expanded" : "compact";
			this.dispatchEvent(new CustomEvent("content-change"));
		});

		this.#wrapper.querySelector(".close-btn")!.addEventListener("click", (e) => {
			e.stopPropagation();
			this.dispatchEvent(new CustomEvent("close"));
		});

		// Render port indicators
		this.#renderPorts();

		// Render initial body
		this.#renderBody();

		// Notify canvas we want live data
		this.dispatchEvent(new CustomEvent("applet-subscribe", {
			bubbles: true,
			detail: { moduleId: this.#moduleId, appletId: this.#appletId, shapeId: this.id },
		}));

		// Start self-fetch polling if the applet defines fetchLiveData
		this.#startLiveDataPolling();

		return root;
	}

	disconnectedCallback() {
		if (this.#liveDataTimer) {
			clearInterval(this.#liveDataTimer);
			this.#liveDataTimer = null;
		}
	}

	#startLiveDataPolling(): void {
		const def = getAppletDef(this.#moduleId, this.#appletId);
		if (!def?.fetchLiveData) return;

		const space = (this.closest("[space]") as any)?.getAttribute("space") || "";
		const doFetch = () => {
			def.fetchLiveData!(space).then(snapshot => {
				this.updateLiveData(snapshot);
			}).catch(() => {});
		};

		// Fetch immediately, then every 30s
		doFetch();
		this.#liveDataTimer = setInterval(doFetch, 30_000);
	}

	#renderPorts(): void {
		this.#wrapper.querySelectorAll(".port-chip").forEach(el => el.remove());

		const renderChips = (ports: PortDescriptor[], dir: "input" | "output") => {
			ports.forEach((port, i) => {
				const yPct = ((i + 1) / (ports.length + 1)) * 100;
				const color = dataTypeColor(port.type);

				const chip = document.createElement("div");
				chip.className = `port-chip ${dir}`;
				chip.style.top = `${yPct}%`;
				chip.style.borderColor = color;
				chip.dataset.portName = port.name;
				chip.dataset.portDir = dir;
				chip.title = `${port.name} (${port.type})`;

				const dot = document.createElement("span");
				dot.className = "chip-dot";
				dot.style.background = color;

				const label = document.createTextNode(port.name);

				chip.appendChild(dot);
				chip.appendChild(label);
				this.#wrapper.appendChild(chip);
			});
		};

		renderChips(this.getInputPorts(), "input");
		renderChips(this.getOutputPorts(), "output");
	}

	#renderBody(): void {
		if (!this.#bodyEl) return;

		const def = getAppletDef(this.#moduleId, this.#appletId);
		if (!def) {
			this.#bodyEl.className = "body body-empty";
			this.#bodyEl.textContent = `Unknown applet: ${this.#moduleId}:${this.#appletId}`;
			return;
		}

		if (this.#mode === "expanded" && def.getCircuit) {
			this.#renderExpanded(def);
			return;
		}

		// Compact mode — module-provided HTML
		const data: AppletLiveData = this.#liveData || {
			space: "",
			moduleId: this.#moduleId,
			appletId: this.#appletId,
			snapshot: {},
			outputValues: {},
		};

		try {
			const bodyHtml = def.renderCompact(data);
			this.#bodyEl.className = "body";
			this.#bodyEl.innerHTML = bodyHtml;
		} catch (err) {
			this.#bodyEl.className = "body body-empty";
			this.#bodyEl.textContent = `Render error: ${err}`;
		}
	}

	#renderExpanded(def: AppletDefinition): void {
		if (!def.getCircuit) return;

		const space = (this.closest("[space]") as any)?.getAttribute("space") || "";
		const { nodes, edges } = def.getCircuit(space);

		this.#bodyEl.className = "body circuit-container";
		this.#bodyEl.innerHTML = "";

		const canvas = document.createElement("applet-circuit-canvas") as any;
		canvas.nodes = nodes;
		canvas.edges = edges;
		this.#bodyEl.appendChild(canvas);
	}

	#updateMode(): void {
		if (!this.#wrapper) return;

		if (this.#mode === "expanded") {
			this.width = EXPANDED_W;
			this.height = EXPANDED_H;
		} else {
			this.width = COMPACT_W;
			this.height = COMPACT_H;
		}

		this.#renderBody();

		// Update expand button icon
		const btn = this.#wrapper.querySelector(".expand-btn");
		if (btn) btn.textContent = this.#mode === "expanded" ? "⊟" : "⊞";
	}

	// ── Serialization ──

	override toJSON() {
		return {
			...super.toJSON(),
			type: "folk-applet",
			moduleId: this.#moduleId,
			appletId: this.#appletId,
			mode: this.#mode,
			instanceConfig: this.#instanceConfig,
		};
	}

	static override fromData(data: Record<string, any>): FolkApplet {
		const shape = FolkShape.fromData.call(this, data) as FolkApplet;
		if (data.moduleId) shape.moduleId = data.moduleId;
		if (data.appletId) shape.appletId = data.appletId;
		if (data.mode) shape.mode = data.mode;
		if (data.instanceConfig) shape.instanceConfig = data.instanceConfig;
		return shape;
	}

	override applyData(data: Record<string, any>): void {
		super.applyData(data);
		if (data.moduleId !== undefined && data.moduleId !== this.#moduleId) this.moduleId = data.moduleId;
		if (data.appletId !== undefined && data.appletId !== this.#appletId) this.appletId = data.appletId;
		if (data.mode !== undefined && data.mode !== this.#mode) this.mode = data.mode;
		if (data.instanceConfig !== undefined) this.instanceConfig = data.instanceConfig;
	}
}
