/**
 * folk-gov-sankey — Governance Flow Visualizer
 *
 * Auto-discovers all connected governance shapes via arrow graph traversal,
 * renders an SVG Sankey diagram with animated flow curves, tooltips, and
 * a color-coded legend. Purely visual — no ports.
 */

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

const HEADER_COLOR = "#7c3aed";

// Gov shape tag names recognized by the visualizer
const GOV_TAG_NAMES = new Set([
	"FOLK-GOV-BINARY",
	"FOLK-GOV-THRESHOLD",
	"FOLK-GOV-KNOB",
	"FOLK-GOV-PROJECT",
	"FOLK-GOV-AMENDMENT",
	"FOLK-GOV-QUADRATIC",
	"FOLK-GOV-CONVICTION",
	"FOLK-GOV-MULTISIG",
]);

const TYPE_COLORS: Record<string, string> = {
	"FOLK-GOV-BINARY": "#7c3aed",
	"FOLK-GOV-THRESHOLD": "#0891b2",
	"FOLK-GOV-KNOB": "#b45309",
	"FOLK-GOV-PROJECT": "#1d4ed8",
	"FOLK-GOV-AMENDMENT": "#be185d",
	"FOLK-GOV-QUADRATIC": "#14b8a6",
	"FOLK-GOV-CONVICTION": "#d97706",
	"FOLK-GOV-MULTISIG": "#6366f1",
};

const TYPE_LABELS: Record<string, string> = {
	"FOLK-GOV-BINARY": "Binary",
	"FOLK-GOV-THRESHOLD": "Threshold",
	"FOLK-GOV-KNOB": "Knob",
	"FOLK-GOV-PROJECT": "Project",
	"FOLK-GOV-AMENDMENT": "Amendment",
	"FOLK-GOV-QUADRATIC": "Quadratic",
	"FOLK-GOV-CONVICTION": "Conviction",
	"FOLK-GOV-MULTISIG": "Multisig",
};

interface SankeyNode {
	id: string;
	tagName: string;
	title: string;
	satisfied: boolean;
	column: number; // 0 = leftmost
	row: number;
}

interface SankeyFlow {
	sourceId: string;
	targetId: string;
}

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

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

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

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

	.body {
		display: flex;
		flex-direction: column;
		padding: 12px;
		gap: 8px;
		overflow: auto;
		max-height: calc(100% - 36px);
	}

	.title-input {
		background: transparent;
		border: none;
		color: var(--rs-text-primary, #e2e8f0);
		font-size: 13px;
		font-weight: 600;
		width: 100%;
		outline: none;
	}

	.title-input::placeholder {
		color: var(--rs-text-muted, #64748b);
	}

	.summary {
		font-size: 11px;
		color: var(--rs-text-muted, #94a3b8);
		text-align: center;
	}

	.sankey-area svg {
		width: 100%;
		display: block;
	}

	.legend {
		display: flex;
		flex-wrap: wrap;
		gap: 8px;
		justify-content: center;
	}

	.legend-item {
		display: flex;
		align-items: center;
		gap: 4px;
		font-size: 9px;
		color: var(--rs-text-muted, #94a3b8);
	}

	.legend-dot {
		width: 8px;
		height: 8px;
		border-radius: 2px;
		flex-shrink: 0;
	}

	.no-data {
		font-size: 11px;
		color: var(--rs-text-muted, #475569);
		font-style: italic;
		text-align: center;
		padding: 24px 0;
	}

	@keyframes flow-dash {
		to { stroke-dashoffset: -20; }
	}
`;

declare global {
	interface HTMLElementTagNameMap {
		"folk-gov-sankey": FolkGovSankey;
	}
}

export class FolkGovSankey extends FolkShape {
	static override tagName = "folk-gov-sankey";

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

	#title = "Governance Flow";
	#pollInterval: ReturnType<typeof setInterval> | null = null;
	#lastHash = "";

	// DOM refs
	#titleEl!: HTMLInputElement;
	#summaryEl!: HTMLElement;
	#sankeyEl!: HTMLElement;
	#legendEl!: HTMLElement;

	get title() { return this.#title; }
	set title(v: string) {
		this.#title = v;
		if (this.#titleEl) this.#titleEl.value = v;
	}

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

		const wrapper = document.createElement("div");
		wrapper.style.cssText = "width:100%;height:100%;display:flex;flex-direction:column;";
		wrapper.innerHTML = html`
			<div class="header" data-drag>
				<span class="header-title">&#x1F4CA; Sankey</span>
				<span class="header-actions">
					<button class="close-btn">&times;</button>
				</span>
			</div>
			<div class="body">
				<input class="title-input" type="text" placeholder="Flow visualizer title..." />
				<div class="summary"></div>
				<div class="sankey-area"></div>
				<div class="legend"></div>
			</div>
		`;

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

		// Cache refs
		this.#titleEl = wrapper.querySelector(".title-input") as HTMLInputElement;
		this.#summaryEl = wrapper.querySelector(".summary") as HTMLElement;
		this.#sankeyEl = wrapper.querySelector(".sankey-area") as HTMLElement;
		this.#legendEl = wrapper.querySelector(".legend") as HTMLElement;

		// Set initial values
		this.#titleEl.value = this.#title;

		// Wire events
		this.#titleEl.addEventListener("input", (e) => {
			e.stopPropagation();
			this.#title = this.#titleEl.value;
			this.dispatchEvent(new CustomEvent("content-change"));
		});

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

		// Prevent drag on inputs
		for (const el of wrapper.querySelectorAll("input, button")) {
			el.addEventListener("pointerdown", (e) => e.stopPropagation());
		}

		// Poll every 3 seconds
		this.#pollInterval = setInterval(() => this.#discover(), 3000);
		requestAnimationFrame(() => this.#discover());

		return root;
	}

	override disconnectedCallback() {
		super.disconnectedCallback();
		if (this.#pollInterval) {
			clearInterval(this.#pollInterval);
			this.#pollInterval = null;
		}
	}

	#discover() {
		const arrows = document.querySelectorAll("folk-arrow");
		const nodes = new Map<string, SankeyNode>();
		const flows: SankeyFlow[] = [];

		// Collect all gov shapes connected by arrows
		for (const arrow of arrows) {
			const a = arrow as any;
			const sourceId = a.sourceId;
			const targetId = a.targetId;
			if (!sourceId || !targetId) continue;

			// Skip self
			if (sourceId === this.id || targetId === this.id) continue;

			const sourceEl = document.getElementById(sourceId) as any;
			const targetEl = document.getElementById(targetId) as any;
			if (!sourceEl || !targetEl) continue;

			const srcTag = sourceEl.tagName?.toUpperCase();
			const tgtTag = targetEl.tagName?.toUpperCase();

			const srcIsGov = GOV_TAG_NAMES.has(srcTag);
			const tgtIsGov = GOV_TAG_NAMES.has(tgtTag);

			if (!srcIsGov && !tgtIsGov) continue;

			if (srcIsGov && !nodes.has(sourceId)) {
				const portVal = sourceEl.getPortValue?.("gate-out");
				nodes.set(sourceId, {
					id: sourceId,
					tagName: srcTag,
					title: sourceEl.title || srcTag,
					satisfied: portVal?.satisfied === true,
					column: 0,
					row: 0,
				});
			}

			if (tgtIsGov && !nodes.has(targetId)) {
				const portVal = targetEl.getPortValue?.("gate-out") || targetEl.getPortValue?.("circuit-out");
				nodes.set(targetId, {
					id: targetId,
					tagName: tgtTag,
					title: targetEl.title || tgtTag,
					satisfied: portVal?.satisfied === true || portVal?.status === "completed",
					column: 0,
					row: 0,
				});
			}

			if (srcIsGov && tgtIsGov) {
				flows.push({ sourceId, targetId });
			}
		}

		// Hash-based skip
		const hash = [...nodes.keys()].sort().join(",") + "|" +
			flows.map(f => `${f.sourceId}->${f.targetId}`).sort().join(",") +
			"|" + [...nodes.values()].map(n => n.satisfied ? "1" : "0").join("");
		if (hash === this.#lastHash) return;
		this.#lastHash = hash;

		this.#layout(nodes, flows);
		this.#renderSankey(nodes, flows);
	}

	#layout(nodes: Map<string, SankeyNode>, flows: SankeyFlow[]) {
		if (nodes.size === 0) return;

		// Build adjacency for topological column assignment
		const outEdges = new Map<string, string[]>();
		const inDegree = new Map<string, number>();
		for (const n of nodes.keys()) {
			outEdges.set(n, []);
			inDegree.set(n, 0);
		}
		for (const f of flows) {
			if (nodes.has(f.sourceId) && nodes.has(f.targetId)) {
				outEdges.get(f.sourceId)!.push(f.targetId);
				inDegree.set(f.targetId, (inDegree.get(f.targetId) || 0) + 1);
			}
		}

		// BFS topological layering
		const queue: string[] = [];
		for (const [id, deg] of inDegree) {
			if (deg === 0) queue.push(id);
		}

		const visited = new Set<string>();
		while (queue.length > 0) {
			const id = queue.shift()!;
			if (visited.has(id)) continue;
			visited.add(id);

			for (const next of outEdges.get(id) || []) {
				const parentCol = nodes.get(id)!.column;
				const node = nodes.get(next)!;
				node.column = Math.max(node.column, parentCol + 1);
				const newDeg = (inDegree.get(next) || 1) - 1;
				inDegree.set(next, newDeg);
				if (newDeg <= 0) queue.push(next);
			}
		}

		// Assign rows within each column
		const columns = new Map<number, string[]>();
		for (const [id, node] of nodes) {
			const col = node.column;
			if (!columns.has(col)) columns.set(col, []);
			columns.get(col)!.push(id);
		}
		for (const [, ids] of columns) {
			ids.forEach((id, i) => {
				nodes.get(id)!.row = i;
			});
		}
	}

	#renderSankey(nodes: Map<string, SankeyNode>, flows: SankeyFlow[]) {
		if (nodes.size === 0) {
			if (this.#summaryEl) this.#summaryEl.textContent = "";
			if (this.#sankeyEl) this.#sankeyEl.innerHTML = `<div class="no-data">Drop near gov shapes to visualize flows</div>`;
			if (this.#legendEl) this.#legendEl.innerHTML = "";
			return;
		}

		// Summary
		if (this.#summaryEl) {
			this.#summaryEl.textContent = `${nodes.size} shapes, ${flows.length} flows`;
		}

		// Calculate dimensions
		const maxCol = Math.max(...[...nodes.values()].map(n => n.column));
		const columns = new Map<number, SankeyNode[]>();
		for (const n of nodes.values()) {
			if (!columns.has(n.column)) columns.set(n.column, []);
			columns.get(n.column)!.push(n);
		}
		const maxRows = Math.max(...[...columns.values()].map(c => c.length));

		const NODE_W = 80;
		const NODE_H = 28;
		const COL_GAP = 60;
		const ROW_GAP = 12;
		const PAD = 16;

		const W = PAD * 2 + (maxCol + 1) * NODE_W + maxCol * COL_GAP;
		const H = PAD * 2 + maxRows * NODE_H + (maxRows - 1) * ROW_GAP;

		const nodeX = (col: number) => PAD + col * (NODE_W + COL_GAP);
		const nodeY = (col: number, row: number) => {
			const colNodes = columns.get(col) || [];
			const totalH = colNodes.length * NODE_H + (colNodes.length - 1) * ROW_GAP;
			const offsetY = (H - totalH) / 2;
			return offsetY + row * (NODE_H + ROW_GAP);
		};

		let svg = `<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet">`;

		// Flows (Bezier curves)
		for (const f of flows) {
			const src = nodes.get(f.sourceId);
			const tgt = nodes.get(f.targetId);
			if (!src || !tgt) continue;

			const sx = nodeX(src.column) + NODE_W;
			const sy = nodeY(src.column, src.row) + NODE_H / 2;
			const tx = nodeX(tgt.column);
			const ty = nodeY(tgt.column, tgt.row) + NODE_H / 2;
			const cx1 = sx + (tx - sx) * 0.4;
			const cx2 = tx - (tx - sx) * 0.4;

			const color = TYPE_COLORS[src.tagName] || "#94a3b8";

			// Background curve
			svg += `<path d="M${sx},${sy} C${cx1},${sy} ${cx2},${ty} ${tx},${ty}" fill="none" stroke="${color}" stroke-width="3" opacity="0.15"/>`;
			// Animated dash curve
			svg += `<path d="M${sx},${sy} C${cx1},${sy} ${cx2},${ty} ${tx},${ty}" fill="none" stroke="${color}" stroke-width="2" stroke-dasharray="6,4" opacity="0.6" style="animation:flow-dash 1.5s linear infinite"/>`;
		}

		// Nodes
		for (const n of nodes.values()) {
			const x = nodeX(n.column);
			const y = nodeY(n.column, n.row);
			const color = TYPE_COLORS[n.tagName] || "#94a3b8";
			const fillOpacity = n.satisfied ? "0.25" : "0.1";

			svg += `<rect x="${x}" y="${y}" width="${NODE_W}" height="${NODE_H}" rx="6" fill="${color}" fill-opacity="${fillOpacity}" stroke="${color}" stroke-width="1.5"/>`;

			// Satisfied glow
			if (n.satisfied) {
				svg += `<rect x="${x}" y="${y}" width="${NODE_W}" height="${NODE_H}" rx="6" fill="none" stroke="#22c55e" stroke-width="1" opacity="0.5"/>`;
			}

			// Label (truncated)
			const label = n.title.length > 12 ? n.title.slice(0, 11) + "..." : n.title;
			svg += `<text x="${x + NODE_W / 2}" y="${y + NODE_H / 2 + 3}" text-anchor="middle" font-size="8" fill="${color}" font-weight="600" font-family="system-ui">${this.#escapeXml(label)}</text>`;

			// Tooltip title
			svg += `<title>${this.#escapeXml(n.title)} (${TYPE_LABELS[n.tagName] || n.tagName}) - ${n.satisfied ? "Satisfied" : "Waiting"}</title>`;
		}

		svg += "</svg>";
		if (this.#sankeyEl) this.#sankeyEl.innerHTML = svg;

		// Legend
		if (this.#legendEl) {
			const usedTypes = new Set([...nodes.values()].map(n => n.tagName));
			this.#legendEl.innerHTML = [...usedTypes].map(t => {
				const color = TYPE_COLORS[t] || "#94a3b8";
				const label = TYPE_LABELS[t] || t;
				return `<div class="legend-item"><span class="legend-dot" style="background:${color}"></span>${label}</div>`;
			}).join("");
		}
	}

	#escapeXml(text: string): string {
		return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
	}

	override toJSON() {
		return {
			...super.toJSON(),
			type: "folk-gov-sankey",
			title: this.#title,
		};
	}

	static override fromData(data: Record<string, any>): FolkGovSankey {
		const shape = FolkShape.fromData.call(this, data) as FolkGovSankey;
		if (data.title !== undefined) shape.title = data.title;
		return shape;
	}

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