/**
 * folk-gov-conviction — Conviction Accumulator
 *
 * Dual-mode GovMod: Gate mode accumulates conviction over time and emits
 * satisfied when score >= threshold. Tuner mode continuously emits the
 * current conviction score as a dynamic value for downstream wiring.
 */

import { FolkShape } from "./folk-shape";
import { css, html } from "./tags";
import type { PortDescriptor } from "./data-types";
import { convictionScore, convictionVelocity } from "./folk-choice-conviction";
import type { ConvictionStake } from "./folk-choice-conviction";

const HEADER_COLOR = "#d97706";

type ConvictionMode = "gate" | "tuner";

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: 240px;
		min-height: 160px;
		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;
	}

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

	.config-row {
		display: flex;
		align-items: center;
		gap: 6px;
		font-size: 11px;
		color: var(--rs-text-muted, #94a3b8);
	}

	.mode-select, .threshold-input {
		background: rgba(255, 255, 255, 0.06);
		border: 1px solid rgba(255, 255, 255, 0.1);
		border-radius: 4px;
		color: var(--rs-text-primary, #e2e8f0);
		font-size: 11px;
		padding: 2px 6px;
		outline: none;
	}

	.threshold-input {
		width: 60px;
		text-align: right;
	}

	.progress-wrap {
		position: relative;
		height: 20px;
		background: rgba(255, 255, 255, 0.08);
		border-radius: 10px;
		overflow: hidden;
	}

	.progress-bar {
		height: 100%;
		border-radius: 10px;
		transition: width 0.3s, background 0.3s;
		background: ${HEADER_COLOR};
	}

	.progress-bar.complete {
		background: #22c55e;
		box-shadow: 0 0 8px rgba(34, 197, 94, 0.4);
	}

	.progress-label {
		position: absolute;
		inset: 0;
		display: flex;
		align-items: center;
		justify-content: center;
		font-size: 10px;
		font-weight: 600;
		color: white;
		text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
	}

	.score-display {
		font-size: 20px;
		font-weight: 700;
		color: ${HEADER_COLOR};
		text-align: center;
		font-variant-numeric: tabular-nums;
	}

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

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

	.stakes-list {
		max-height: 80px;
		overflow-y: auto;
		font-size: 10px;
		color: var(--rs-text-muted, #94a3b8);
	}

	.stake-item {
		display: flex;
		justify-content: space-between;
		padding: 2px 0;
		border-bottom: 1px solid rgba(255, 255, 255, 0.05);
	}

	.status-label {
		font-size: 10px;
		font-weight: 500;
		text-transform: uppercase;
		letter-spacing: 0.5px;
		text-align: center;
	}

	.status-label.satisfied {
		color: #22c55e;
	}

	.status-label.waiting {
		color: #f59e0b;
	}

	.status-label.tuner {
		color: ${HEADER_COLOR};
	}
`;

declare global {
	interface HTMLElementTagNameMap {
		"folk-gov-conviction": FolkGovConviction;
	}
}

export class FolkGovConviction extends FolkShape {
	static override tagName = "folk-gov-conviction";

	static override portDescriptors: PortDescriptor[] = [
		{ name: "stake-in", type: "json", direction: "input" },
		{ name: "threshold-in", type: "number", direction: "input" },
		{ name: "conviction-out", type: "json", direction: "output" },
		{ name: "gate-out", type: "json", direction: "output" },
	];

	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 = "Conviction Gate";
	#convictionMode: ConvictionMode = "gate";
	#threshold = 10;
	#stakes: ConvictionStake[] = [];
	#tickInterval: ReturnType<typeof setInterval> | null = null;

	// DOM refs
	#titleEl!: HTMLInputElement;
	#modeEl!: HTMLSelectElement;
	#thresholdEl!: HTMLInputElement;
	#thresholdRow!: HTMLElement;
	#progressWrap!: HTMLElement;
	#progressBar!: HTMLElement;
	#progressLabel!: HTMLElement;
	#scoreDisplay!: HTMLElement;
	#velocityLabel!: HTMLElement;
	#chartEl!: HTMLElement;
	#stakesList!: HTMLElement;
	#statusEl!: HTMLElement;

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

	get convictionMode() { return this.#convictionMode; }
	set convictionMode(v: ConvictionMode) {
		this.#convictionMode = v;
		if (this.#modeEl) this.#modeEl.value = v;
		this.#updateLayout();
		this.#updateVisuals();
		this.#emitPorts();
	}

	get threshold() { return this.#threshold; }
	set threshold(v: number) {
		this.#threshold = v;
		if (this.#thresholdEl) this.#thresholdEl.value = String(v);
		this.#updateVisuals();
		this.#emitPorts();
	}

	get stakes(): ConvictionStake[] { return [...this.#stakes]; }
	set stakes(v: ConvictionStake[]) {
		this.#stakes = v;
		this.#updateVisuals();
		this.#emitPorts();
	}

	#getTotalScore(): number {
		// Aggregate conviction across all stakes (single "option" = this gate)
		const now = Date.now();
		let total = 0;
		for (const s of this.#stakes) {
			total += s.weight * Math.max(0, now - s.since) / 3600000;
		}
		return total;
	}

	#getTotalVelocity(): number {
		return this.#stakes.reduce((sum, s) => sum + s.weight, 0);
	}

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

		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">&#x23F3; Conviction</span>
				<span class="header-actions">
					<button class="close-btn">&times;</button>
				</span>
			</div>
			<div class="body">
				<input class="title-input" type="text" placeholder="Conviction gate title..." />
				<div class="config-row">
					<span>Mode:</span>
					<select class="mode-select">
						<option value="gate">Gate</option>
						<option value="tuner">Tuner</option>
					</select>
					<span class="threshold-row">
						<span>Threshold:</span>
						<input class="threshold-input" type="number" min="0" step="1" />
					</span>
				</div>
				<div class="progress-wrap">
					<div class="progress-bar" style="width: 0%"></div>
					<div class="progress-label">0 / 10</div>
				</div>
				<div class="score-display" style="display:none">0.00</div>
				<div class="velocity-label"></div>
				<div class="chart-area"></div>
				<div class="stakes-list"></div>
				<span class="status-label waiting">WAITING</span>
			</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.#modeEl = wrapper.querySelector(".mode-select") as HTMLSelectElement;
		this.#thresholdEl = wrapper.querySelector(".threshold-input") as HTMLInputElement;
		this.#thresholdRow = wrapper.querySelector(".threshold-row") as HTMLElement;
		this.#progressWrap = wrapper.querySelector(".progress-wrap") as HTMLElement;
		this.#progressBar = wrapper.querySelector(".progress-bar") as HTMLElement;
		this.#progressLabel = wrapper.querySelector(".progress-label") as HTMLElement;
		this.#scoreDisplay = wrapper.querySelector(".score-display") as HTMLElement;
		this.#velocityLabel = wrapper.querySelector(".velocity-label") as HTMLElement;
		this.#chartEl = wrapper.querySelector(".chart-area") as HTMLElement;
		this.#stakesList = wrapper.querySelector(".stakes-list") as HTMLElement;
		this.#statusEl = wrapper.querySelector(".status-label") as HTMLElement;

		// Set initial values
		this.#titleEl.value = this.#title;
		this.#modeEl.value = this.#convictionMode;
		this.#thresholdEl.value = String(this.#threshold);
		this.#updateLayout();
		this.#updateVisuals();

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

		this.#modeEl.addEventListener("change", (e) => {
			e.stopPropagation();
			this.#convictionMode = this.#modeEl.value as ConvictionMode;
			this.#updateLayout();
			this.#updateVisuals();
			this.#emitPorts();
			this.dispatchEvent(new CustomEvent("content-change"));
		});

		this.#thresholdEl.addEventListener("input", (e) => {
			e.stopPropagation();
			this.#threshold = parseFloat(this.#thresholdEl.value) || 0;
			this.#updateVisuals();
			this.#emitPorts();
			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, select, button")) {
			el.addEventListener("pointerdown", (e) => e.stopPropagation());
		}

		// Handle input ports
		this.addEventListener("port-value-changed", ((e: CustomEvent) => {
			const { name, value } = e.detail;
			if (name === "stake-in" && value && typeof value === "object") {
				const v = value as any;
				const stake: ConvictionStake = {
					userId: v.userId || v.who || crypto.randomUUID().slice(0, 8),
					userName: v.userName || v.who || "anonymous",
					optionId: "gate",
					weight: v.weight || v.amount || 1,
					since: v.since || Date.now(),
				};
				// Update existing or add
				const idx = this.#stakes.findIndex(s => s.userId === stake.userId);
				if (idx >= 0) {
					this.#stakes[idx] = stake;
				} else {
					this.#stakes.push(stake);
				}
				this.#updateVisuals();
				this.#emitPorts();
				this.dispatchEvent(new CustomEvent("content-change"));
			}
			if (name === "threshold-in" && typeof value === "number") {
				this.#threshold = value;
				if (this.#thresholdEl) this.#thresholdEl.value = String(value);
				this.#updateVisuals();
				this.#emitPorts();
				this.dispatchEvent(new CustomEvent("content-change"));
			}
		}) as EventListener);

		// Tick timer for live conviction updates
		this.#tickInterval = setInterval(() => {
			this.#updateVisuals();
			this.#emitPorts();
		}, 10000);

		return root;
	}

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

	#updateLayout() {
		if (!this.#thresholdRow) return;
		const isGate = this.#convictionMode === "gate";
		this.#thresholdRow.style.display = isGate ? "" : "none";
		if (this.#progressWrap) this.#progressWrap.style.display = isGate ? "" : "none";
		if (this.#scoreDisplay) this.#scoreDisplay.style.display = isGate ? "none" : "";
	}

	#updateVisuals() {
		const score = this.#getTotalScore();
		const velocity = this.#getTotalVelocity();

		if (this.#convictionMode === "gate") {
			// Gate mode: progress bar
			const pct = this.#threshold > 0 ? Math.min(100, (score / this.#threshold) * 100) : 0;
			const satisfied = score >= this.#threshold;

			if (this.#progressBar) {
				this.#progressBar.style.width = `${pct}%`;
				this.#progressBar.classList.toggle("complete", satisfied);
			}
			if (this.#progressLabel) {
				this.#progressLabel.textContent = `${this.#fmtScore(score)} / ${this.#threshold}`;
			}
			if (this.#statusEl) {
				this.#statusEl.textContent = satisfied ? "SATISFIED" : "WAITING";
				this.#statusEl.className = `status-label ${satisfied ? "satisfied" : "waiting"}`;
			}
		} else {
			// Tuner mode: score display
			if (this.#scoreDisplay) {
				this.#scoreDisplay.textContent = this.#fmtScore(score);
			}
			if (this.#statusEl) {
				this.#statusEl.textContent = "EMITTING";
				this.#statusEl.className = "status-label tuner";
			}
		}

		if (this.#velocityLabel) {
			this.#velocityLabel.textContent = `velocity: ${velocity.toFixed(1)} wt/hr`;
		}

		this.#renderChart();
		this.#renderStakes();
	}

	#renderChart() {
		if (!this.#chartEl || this.#stakes.length === 0) {
			if (this.#chartEl) this.#chartEl.innerHTML = "";
			return;
		}

		const now = Date.now();
		const W = 220;
		const H = 60;
		const PAD = { top: 6, right: 8, bottom: 12, left: 28 };
		const plotW = W - PAD.left - PAD.right;
		const plotH = H - PAD.top - PAD.bottom;

		const earliest = Math.min(...this.#stakes.map(s => s.since));
		const timeRange = Math.max(now - earliest, 60000);

		// Sample conviction curve at 20 points
		const SAMPLES = 20;
		const points: { t: number; v: number }[] = [];
		let maxV = 0;
		for (let i = 0; i <= SAMPLES; i++) {
			const t = earliest + (timeRange * i) / SAMPLES;
			let v = 0;
			for (const s of this.#stakes) {
				if (s.since <= t) v += s.weight * Math.max(0, t - s.since) / 3600000;
			}
			points.push({ t, v });
			maxV = Math.max(maxV, v);
		}
		if (maxV === 0) maxV = 1;

		const x = (t: number) => PAD.left + ((t - earliest) / timeRange) * plotW;
		const y = (v: number) => PAD.top + (1 - v / maxV) * plotH;

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

		// Threshold line in gate mode
		if (this.#convictionMode === "gate" && this.#threshold > 0 && this.#threshold <= maxV) {
			const ty = y(this.#threshold);
			svg += `<line x1="${PAD.left}" y1="${ty}" x2="${W - PAD.right}" y2="${ty}" stroke="#22c55e" stroke-width="0.5" stroke-dasharray="3,2"/>`;
		}

		// Area
		const areaD = `M${x(points[0].t)},${y(0)} ` +
			points.map(p => `L${x(p.t)},${y(p.v)}`).join(" ") +
			` L${x(points[points.length - 1].t)},${y(0)} Z`;
		svg += `<path d="${areaD}" fill="${HEADER_COLOR}" opacity="0.15"/>`;

		// Line
		const lineD = points.map((p, i) => `${i === 0 ? "M" : "L"}${x(p.t)},${y(p.v)}`).join(" ");
		svg += `<path d="${lineD}" fill="none" stroke="${HEADER_COLOR}" stroke-width="1.5" stroke-linejoin="round"/>`;

		// End dot
		const last = points[points.length - 1];
		svg += `<circle cx="${x(last.t)}" cy="${y(last.v)}" r="2.5" fill="${HEADER_COLOR}"/>`;

		// Y axis
		svg += `<text x="${PAD.left - 3}" y="${PAD.top + 4}" text-anchor="end" font-size="7" fill="#94a3b8" font-family="system-ui">${this.#fmtScore(maxV)}</text>`;
		svg += `<text x="${PAD.left - 3}" y="${y(0)}" text-anchor="end" font-size="7" fill="#94a3b8" font-family="system-ui">0</text>`;

		svg += "</svg>";
		this.#chartEl.innerHTML = svg;
	}

	#renderStakes() {
		if (!this.#stakesList) return;
		const now = Date.now();
		this.#stakesList.innerHTML = this.#stakes.map(s => {
			const dur = this.#fmtDuration(now - s.since);
			return `<div class="stake-item"><span>${s.userName} (wt:${s.weight})</span><span>${dur}</span></div>`;
		}).join("");
	}

	#emitPorts() {
		const score = this.#getTotalScore();
		const velocity = this.#getTotalVelocity();
		const satisfied = this.#convictionMode === "gate" ? score >= this.#threshold : true;

		this.setPortValue("conviction-out", {
			score,
			velocity,
			stakeCount: this.#stakes.length,
			mode: this.#convictionMode,
		});

		this.setPortValue("gate-out", {
			satisfied,
			score,
			threshold: this.#threshold,
			mode: this.#convictionMode,
		});
	}

	#fmtScore(v: number): string {
		if (v < 1) return v.toFixed(2);
		if (v < 100) return v.toFixed(1);
		return Math.round(v).toString();
	}

	#fmtDuration(ms: number): string {
		if (ms < 60000) return "<1m";
		if (ms < 3600000) return `${Math.floor(ms / 60000)}m`;
		if (ms < 86400000) return `${Math.floor(ms / 3600000)}h`;
		return `${Math.floor(ms / 86400000)}d`;
	}

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

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

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