/**
 * folk-exchange-node — Canvas shape rendering a P2P exchange order board.
 *
 * Buy orbs (green, left) ↔ sell orbs (amber, right) with connecting arcs
 * for matched trades. Status badges on orbs. Polls /api/exchange/intents every 30s.
 */

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

// ── Constants ──

const BUY_COLOR = '#10b981';
const SELL_COLOR = '#f59e0b';
const LP_COLOR_LEFT = '#10b981';
const LP_COLOR_RIGHT = '#f59e0b';
const MATCH_ARC_COLOR = '#60a5fa';
const BG_COLOR = '#0f172a';
const TEXT_COLOR = '#e2e8f0';
const MUTED_COLOR = '#64748b';

const TOKEN_ICONS: Record<string, string> = {
	cusdc: '💵', myco: '🌱', fusdc: '🎮',
};

// ── Types ──

interface OrderIntent {
	id: string;
	creatorName: string;
	side: 'buy' | 'sell';
	tokenId: string;
	fiatCurrency: string;
	tokenAmountMin: number;
	tokenAmountMax: number;
	rateType: string;
	rateFixed?: number;
	rateMarketBps?: number;
	paymentMethods: string[];
	isStandingOrder: boolean;
	status: string;
}

interface LpPosition {
	id: string;
	creatorName: string;
	tokenId: string;
	fiatCurrency: string;
	tokenRemaining: number;
	fiatRemaining: number;
	spreadBps: number;
	feesEarnedToken: number;
	feesEarnedFiat: number;
	tradesMatched: number;
	status: string;
}

interface LpOrb {
	position: LpPosition;
	x: number;
	y: number;
	radius: number;
	vx: number;
	vy: number;
	phase: number;
	opacity: number;
	hoverT: number;
}

interface Orb {
	intent: OrderIntent;
	x: number;
	y: number;
	radius: number;
	vx: number;
	vy: number;
	phase: number;
	opacity: number;
	hoverT: number;
	color: string;
}

// ── Shape ──

export class FolkExchangeNode extends FolkShape {
	static override tagName = 'folk-exchange-node';

	private _canvas: HTMLCanvasElement | null = null;
	private _ctx: CanvasRenderingContext2D | null = null;
	private _orbs: Orb[] = [];
	private _lpOrbs: LpOrb[] = [];
	private _hovered: Orb | null = null;
	private _hoveredLp: LpOrb | null = null;
	private _animFrame = 0;
	private _pollTimer = 0;
	private _spaceSlug = 'demo';
	private _intents: OrderIntent[] = [];
	private _lpPositions: LpPosition[] = [];

	override connectedCallback() {
		super.connectedCallback();
		this._spaceSlug = this.getAttribute('space') || this.getAttribute('spaceSlug') || 'demo';
		this._setup();
		this._fetchIntents();
		this._pollTimer = window.setInterval(() => this._fetchIntents(), 30000);
	}

	override disconnectedCallback() {
		super.disconnectedCallback();
		if (this._animFrame) cancelAnimationFrame(this._animFrame);
		if (this._pollTimer) clearInterval(this._pollTimer);
	}

	private _setup() {
		const shadow = this.shadowRoot!;
		const wrapper = document.createElement('div');
		wrapper.style.cssText = 'width:100%;height:100%;position:relative;overflow:hidden;border-radius:12px;background:' + BG_COLOR;

		// Header
		const header = document.createElement('div');
		header.style.cssText = 'position:absolute;top:0;left:0;right:0;padding:12px 16px;z-index:1;display:flex;justify-content:space-between;align-items:center';
		header.innerHTML = `
			<span style="font-size:0.85rem;font-weight:600;color:${TEXT_COLOR}">💱 rExchange</span>
			<span style="font-size:0.7rem;color:${MUTED_COLOR}" id="intent-count">0 intents</span>
		`;
		wrapper.appendChild(header);

		// Legend
		const legend = document.createElement('div');
		legend.style.cssText = 'position:absolute;bottom:8px;left:0;right:0;display:flex;justify-content:center;gap:16px;z-index:1';
		legend.innerHTML = `
			<span style="font-size:0.7rem;color:${BUY_COLOR};display:flex;align-items:center;gap:4px">
				<span style="width:8px;height:8px;border-radius:50%;background:${BUY_COLOR};display:inline-block"></span> Buy
			</span>
			<span style="font-size:0.7rem;color:${SELL_COLOR};display:flex;align-items:center;gap:4px">
				<span style="width:8px;height:8px;border-radius:50%;background:${SELL_COLOR};display:inline-block"></span> Sell
			</span>
			<span style="font-size:0.7rem;color:#a78bfa;display:flex;align-items:center;gap:4px">
				<span style="width:8px;height:8px;border-radius:50%;background:linear-gradient(to right,${LP_COLOR_LEFT},${LP_COLOR_RIGHT});display:inline-block"></span> LP
			</span>
			<span style="font-size:0.7rem;color:${MATCH_ARC_COLOR};display:flex;align-items:center;gap:4px">
				<span style="width:8px;height:1px;background:${MATCH_ARC_COLOR};display:inline-block"></span> Matched
			</span>
		`;
		wrapper.appendChild(legend);

		// Canvas
		this._canvas = document.createElement('canvas');
		this._canvas.style.cssText = 'width:100%;height:100%';
		wrapper.appendChild(this._canvas);

		shadow.appendChild(wrapper);

		this._canvas.addEventListener('mousemove', (e) => this._onMouseMove(e));
		this._canvas.addEventListener('mouseleave', () => { this._hovered = null; this._hoveredLp = null; });

		this._resizeCanvas();
		new ResizeObserver(() => this._resizeCanvas()).observe(wrapper);
		this._animate();
	}

	private _resizeCanvas() {
		if (!this._canvas) return;
		const rect = this._canvas.getBoundingClientRect();
		const dpr = window.devicePixelRatio || 1;
		this._canvas.width = rect.width * dpr;
		this._canvas.height = rect.height * dpr;
		this._ctx = this._canvas.getContext('2d');
		if (this._ctx) this._ctx.scale(dpr, dpr);
	}

	private async _fetchIntents() {
		try {
			const base = getModuleApiBase('rexchange');
			const [intentsRes, poolsRes] = await Promise.all([
				fetch(`${base}/api/exchange/intents`),
				fetch(`${base}/api/exchange/pools`),
			]);
			if (intentsRes.ok) {
				const data = await intentsRes.json() as { intents: OrderIntent[] };
				this._intents = data.intents || [];
				this._syncOrbs();
			}
			if (poolsRes.ok) {
				const data = await poolsRes.json() as { positions: LpPosition[] };
				this._lpPositions = data.positions || [];
				this._syncLpOrbs();
			}

			const counter = this.shadowRoot?.querySelector('#intent-count');
			if (counter) counter.textContent = `${this._intents.length} intents, ${this._lpPositions.length} pools`;
		} catch { /* silent */ }
	}

	private _syncOrbs() {
		const rect = this._canvas?.getBoundingClientRect();
		if (!rect) return;
		const w = rect.width;
		const h = rect.height;
		const existingIds = new Set(this._orbs.map(o => o.intent.id));
		const newIds = new Set(this._intents.map(i => i.id));

		// Remove stale orbs
		this._orbs = this._orbs.filter(o => newIds.has(o.intent.id));

		// Add new orbs
		for (const intent of this._intents) {
			if (existingIds.has(intent.id)) continue;

			const isBuy = intent.side === 'buy';
			const baseX = isBuy ? w * 0.25 : w * 0.75;
			const amount = intent.tokenAmountMax / 1_000_000;
			const radius = Math.max(12, Math.min(30, 12 + Math.sqrt(amount) * 2));

			this._orbs.push({
				intent,
				x: baseX + (Math.random() - 0.5) * w * 0.3,
				y: 60 + Math.random() * (h - 120),
				radius,
				vx: (Math.random() - 0.5) * 0.3,
				vy: (Math.random() - 0.5) * 0.3,
				phase: Math.random() * Math.PI * 2,
				opacity: 0,
				hoverT: 0,
				color: isBuy ? BUY_COLOR : SELL_COLOR,
			});
		}
	}

	private _syncLpOrbs() {
		const rect = this._canvas?.getBoundingClientRect();
		if (!rect) return;
		const w = rect.width;
		const h = rect.height;
		const existingIds = new Set(this._lpOrbs.map(o => o.position.id));
		const newIds = new Set(this._lpPositions.map(p => p.id));

		// Remove stale
		this._lpOrbs = this._lpOrbs.filter(o => newIds.has(o.position.id));

		// Add new LP orbs — centered on the divider
		for (const pos of this._lpPositions) {
			if (existingIds.has(pos.id)) continue;

			const totalDepth = (pos.tokenRemaining / 1_000_000) + pos.fiatRemaining;
			const radius = Math.max(16, Math.min(36, 16 + Math.sqrt(totalDepth) * 1.5));

			this._lpOrbs.push({
				position: pos,
				x: w * 0.5 + (Math.random() - 0.5) * 20,
				y: 60 + Math.random() * (h - 120),
				radius,
				vx: (Math.random() - 0.5) * 0.15,
				vy: (Math.random() - 0.5) * 0.15,
				phase: Math.random() * Math.PI * 2,
				opacity: 0,
				hoverT: 0,
			});
		}
	}

	private _onMouseMove(e: MouseEvent) {
		const rect = this._canvas?.getBoundingClientRect();
		if (!rect) return;
		const mx = e.clientX - rect.left;
		const my = e.clientY - rect.top;

		this._hovered = null;
		this._hoveredLp = null;

		// Check LP orbs first (they're on top)
		for (const orb of this._lpOrbs) {
			const dx = mx - orb.x, dy = my - orb.y;
			if (dx * dx + dy * dy < orb.radius * orb.radius) {
				this._hoveredLp = orb;
				break;
			}
		}
		if (!this._hoveredLp) {
			for (const orb of this._orbs) {
				const dx = mx - orb.x, dy = my - orb.y;
				if (dx * dx + dy * dy < orb.radius * orb.radius) {
					this._hovered = orb;
					break;
				}
			}
		}
		this._canvas!.style.cursor = (this._hovered || this._hoveredLp) ? 'pointer' : 'default';
	}

	private _animate() {
		this._animFrame = requestAnimationFrame(() => this._animate());
		this._update();
		this._draw();
	}

	private _update() {
		const rect = this._canvas?.getBoundingClientRect();
		if (!rect) return;
		const w = rect.width;
		const h = rect.height;

		for (const orb of this._orbs) {
			orb.phase += 0.006;
			orb.vx += Math.sin(orb.phase) * 0.002;
			orb.vy += Math.cos(orb.phase * 0.8 + 1) * 0.002;
			orb.vx *= 0.995;
			orb.vy *= 0.995;
			orb.x += orb.vx;
			orb.y += orb.vy;

			// Constrain to side (buy=left, sell=right)
			const isBuy = orb.intent.side === 'buy';
			const minX = isBuy ? orb.radius + 8 : w * 0.5 + orb.radius;
			const maxX = isBuy ? w * 0.5 - orb.radius : w - orb.radius - 8;
			const minY = 40 + orb.radius;
			const maxY = h - 40 - orb.radius;

			if (orb.x < minX) { orb.x = minX; orb.vx *= -0.5; }
			if (orb.x > maxX) { orb.x = maxX; orb.vx *= -0.5; }
			if (orb.y < minY) { orb.y = minY; orb.vy *= -0.5; }
			if (orb.y > maxY) { orb.y = maxY; orb.vy *= -0.5; }

			// Hover
			const isH = this._hovered === orb;
			orb.hoverT += ((isH ? 1 : 0) - orb.hoverT) * 0.12;
			if (orb.opacity < 1) orb.opacity = Math.min(1, orb.opacity + 0.03);
		}

		// Update LP orbs — constrained to straddle the divider
		for (const orb of this._lpOrbs) {
			orb.phase += 0.004;
			orb.vx += Math.sin(orb.phase) * 0.001;
			orb.vy += Math.cos(orb.phase * 0.7 + 1.5) * 0.001;
			orb.vx *= 0.99;
			orb.vy *= 0.99;
			orb.x += orb.vx;
			orb.y += orb.vy;

			// Constrain to center (spanning divider)
			const minX = w * 0.5 - orb.radius * 1.5;
			const maxX = w * 0.5 + orb.radius * 1.5;
			const minY = 40 + orb.radius;
			const maxY = h - 40 - orb.radius;

			if (orb.x < minX) { orb.x = minX; orb.vx *= -0.5; }
			if (orb.x > maxX) { orb.x = maxX; orb.vx *= -0.5; }
			if (orb.y < minY) { orb.y = minY; orb.vy *= -0.5; }
			if (orb.y > maxY) { orb.y = maxY; orb.vy *= -0.5; }

			const isH = this._hoveredLp === orb;
			orb.hoverT += ((isH ? 1 : 0) - orb.hoverT) * 0.12;
			if (orb.opacity < 1) orb.opacity = Math.min(1, orb.opacity + 0.03);
		}
	}

	private _draw() {
		const ctx = this._ctx;
		const rect = this._canvas?.getBoundingClientRect();
		if (!ctx || !rect) return;
		const w = rect.width;
		const h = rect.height;

		ctx.clearRect(0, 0, w, h);

		// Divider line
		ctx.save();
		ctx.strokeStyle = '#1e293b';
		ctx.lineWidth = 1;
		ctx.setLineDash([4, 4]);
		ctx.beginPath();
		ctx.moveTo(w / 2, 40);
		ctx.lineTo(w / 2, h - 30);
		ctx.stroke();
		ctx.setLineDash([]);
		ctx.restore();

		// Side labels
		ctx.save();
		ctx.font = '600 11px system-ui, sans-serif';
		ctx.textAlign = 'center';
		ctx.fillStyle = BUY_COLOR + '80';
		ctx.fillText('BUY', w * 0.25, 35);
		ctx.fillStyle = SELL_COLOR + '80';
		ctx.fillText('SELL', w * 0.75, 35);
		ctx.restore();

		// Draw orbs
		for (const orb of this._orbs) {
			if (orb.opacity < 0.01) continue;
			ctx.save();
			ctx.globalAlpha = orb.opacity;

			// Glow on hover
			if (orb.hoverT > 0.05) {
				ctx.shadowColor = orb.color;
				ctx.shadowBlur = 20 * orb.hoverT;
			}

			const r = orb.radius * (1 + orb.hoverT * 0.15);

			// Outer glow
			ctx.beginPath();
			ctx.arc(orb.x, orb.y, r, 0, Math.PI * 2);
			ctx.fillStyle = orb.color + '15';
			ctx.fill();

			// Inner circle
			ctx.beginPath();
			ctx.arc(orb.x, orb.y, r * 0.82, 0, Math.PI * 2);
			const g = ctx.createRadialGradient(
				orb.x - r * 0.15, orb.y - r * 0.15, 0,
				orb.x, orb.y, r * 0.82,
			);
			g.addColorStop(0, orb.color + 'dd');
			g.addColorStop(1, orb.color);
			ctx.fillStyle = g;
			ctx.fill();
			ctx.shadowBlur = 0;
			ctx.strokeStyle = orb.color;
			ctx.lineWidth = 1;
			ctx.stroke();

			// Token icon
			ctx.font = `${Math.max(10, r * 0.5)}px system-ui, sans-serif`;
			ctx.textAlign = 'center';
			ctx.textBaseline = 'middle';
			ctx.fillStyle = '#fff';
			const icon = TOKEN_ICONS[orb.intent.tokenId] || '$';
			ctx.fillText(icon, orb.x, orb.y);

			// Standing order badge
			if (orb.intent.isStandingOrder) {
				ctx.beginPath();
				ctx.arc(orb.x + r * 0.65, orb.y - r * 0.65, 4, 0, Math.PI * 2);
				ctx.fillStyle = '#3b82f6';
				ctx.fill();
			}

			ctx.restore();
		}

		// Draw LP orbs (on the divider)
		for (const orb of this._lpOrbs) {
			if (orb.opacity < 0.01) continue;
			ctx.save();
			ctx.globalAlpha = orb.opacity;

			if (orb.hoverT > 0.05) {
				ctx.shadowColor = '#a78bfa';
				ctx.shadowBlur = 24 * orb.hoverT;
			}

			const r = orb.radius * (1 + orb.hoverT * 0.15);

			// Outer glow — dual gradient
			ctx.beginPath();
			ctx.arc(orb.x, orb.y, r, 0, Math.PI * 2);
			const outerGrad = ctx.createLinearGradient(orb.x - r, orb.y, orb.x + r, orb.y);
			outerGrad.addColorStop(0, LP_COLOR_LEFT + '20');
			outerGrad.addColorStop(1, LP_COLOR_RIGHT + '20');
			ctx.fillStyle = outerGrad;
			ctx.fill();

			// Inner circle — dual-colored gradient (green left, amber right)
			ctx.beginPath();
			ctx.arc(orb.x, orb.y, r * 0.82, 0, Math.PI * 2);
			const innerGrad = ctx.createLinearGradient(orb.x - r, orb.y, orb.x + r, orb.y);
			innerGrad.addColorStop(0, LP_COLOR_LEFT);
			innerGrad.addColorStop(0.5, '#34d399');
			innerGrad.addColorStop(1, LP_COLOR_RIGHT);
			ctx.fillStyle = innerGrad;
			ctx.fill();
			ctx.shadowBlur = 0;

			// Border gradient
			ctx.strokeStyle = '#a78bfa';
			ctx.lineWidth = 1.5;
			ctx.stroke();

			// Token icon
			ctx.font = `${Math.max(10, r * 0.45)}px system-ui, sans-serif`;
			ctx.textAlign = 'center';
			ctx.textBaseline = 'middle';
			ctx.fillStyle = '#fff';
			const icon = TOKEN_ICONS[orb.position.tokenId] || '$';
			ctx.fillText(icon, orb.x, orb.y);

			// LP badge dot
			ctx.beginPath();
			ctx.arc(orb.x + r * 0.65, orb.y - r * 0.65, 5, 0, Math.PI * 2);
			ctx.fillStyle = '#8b5cf6';
			ctx.fill();
			ctx.strokeStyle = '#0f172a';
			ctx.lineWidth = 1;
			ctx.stroke();

			ctx.restore();
		}

		// Hover tooltip — LP positions
		if (this._hoveredLp) {
			const orb = this._hoveredLp;
			const p = orb.position;
			const tokenDepth = (p.tokenRemaining / 1_000_000).toFixed(2);
			const fiatSymbol = p.fiatCurrency === 'USD' ? '$' : p.fiatCurrency === 'EUR' ? '€' : p.fiatCurrency === 'GBP' ? '£' : '';
			const earned = `${fiatSymbol}${p.feesEarnedFiat.toFixed(2)}`;
			const lines = [
				`${p.creatorName} (LP)`,
				`${p.tokenId}/${p.fiatCurrency}`,
				`Depth: ${tokenDepth} / ${fiatSymbol}${p.fiatRemaining.toFixed(2)}`,
				`Spread: ${p.spreadBps}bps`,
				`Earned: ${earned} | ${p.tradesMatched} trades`,
			];

			const tooltipX = orb.x + orb.radius + 12;
			const tooltipY = Math.max(50, Math.min(h - 100, orb.y - 40));

			ctx.save();
			ctx.fillStyle = '#1e293bee';
			const tw = 180;
			const th = lines.length * 16 + 12;
			const tx = tooltipX + tw > w ? orb.x - orb.radius - tw - 12 : tooltipX;
			ctx.beginPath();
			ctx.roundRect(tx, tooltipY, tw, th, 6);
			ctx.fill();

			ctx.font = '600 11px system-ui, sans-serif';
			ctx.textAlign = 'left';
			ctx.textBaseline = 'top';
			for (let l = 0; l < lines.length; l++) {
				ctx.fillStyle = l === 0 ? '#a78bfa' : MUTED_COLOR;
				ctx.font = l === 0 ? '600 11px system-ui, sans-serif' : '11px system-ui, sans-serif';
				ctx.fillText(lines[l], tx + 8, tooltipY + 6 + l * 16);
			}
			ctx.restore();
		}

		// Hover tooltip
		if (this._hovered) {
			const orb = this._hovered;
			const i = orb.intent;
			const amount = i.tokenAmountMax / 1_000_000;
			const rateStr = i.rateType === 'fixed' ? `${i.rateFixed} ${i.fiatCurrency}` : `market+${i.rateMarketBps}bps`;
			const lines = [
				i.creatorName,
				`${i.side.toUpperCase()} ${amount.toFixed(2)} ${i.tokenId}`,
				`Rate: ${rateStr}`,
				`Pay: ${i.paymentMethods.join(', ')}`,
			];

			const tooltipX = orb.x + orb.radius + 12;
			const tooltipY = Math.max(50, Math.min(h - 80, orb.y - 30));

			ctx.save();
			ctx.fillStyle = '#1e293bee';
			const tw = 160;
			const th = lines.length * 16 + 12;
			const tx = tooltipX + tw > w ? orb.x - orb.radius - tw - 12 : tooltipX;
			ctx.beginPath();
			ctx.roundRect(tx, tooltipY, tw, th, 6);
			ctx.fill();

			ctx.font = '600 11px system-ui, sans-serif';
			ctx.fillStyle = TEXT_COLOR;
			ctx.textAlign = 'left';
			ctx.textBaseline = 'top';
			for (let l = 0; l < lines.length; l++) {
				ctx.fillStyle = l === 0 ? TEXT_COLOR : MUTED_COLOR;
				ctx.font = l === 0 ? '600 11px system-ui, sans-serif' : '11px system-ui, sans-serif';
				ctx.fillText(lines[l], tx + 8, tooltipY + 6 + l * 16);
			}
			ctx.restore();
		}
	}
}

if (typeof customElements !== 'undefined' && !customElements.get('folk-exchange-node')) {
	customElements.define('folk-exchange-node', FolkExchangeNode);
}
