import type { GeoPoint, FittedRoute, RouteInput, IntersectionPoint, ConicArc } from "../lib/types";
import { createProjection, type Projection } from "../lib/projection";
import { fitRoute, findAllIntersections, bezierToSVGCubic, sampleConicCurve, conicTangentAt } from "../lib/conic-math";

// Monaco demo routes that cross each other
const DEMO_ROUTES: [RouteInput, RouteInput] = [
	{
		label: "Route A",
		start: { lng: 7.4267, lat: 43.7396 }, // Casino Monte-Carlo
		end: { lng: 7.4163, lat: 43.7275 },   // Fontvieille
	},
	{
		label: "Route B",
		start: { lng: 7.4317, lat: 43.7519 }, // Larvotto Beach area
		end: { lng: 7.4197, lat: 43.7311 },   // Monaco-Ville / Palace
	},
];

// Distinct shades per arc segment
const COLORS_A = ["#00dcff", "#00b8d9", "#0099b3", "#007a8c"];
const COLORS_B = ["#ffb400", "#e6a200", "#cc9000", "#b37e00"];

class FolkRoutePlanner extends HTMLElement {
	private shadow: ShadowRoot;
	private routeA: FittedRoute | null = null;
	private routeB: FittedRoute | null = null;
	private intersections: IntersectionPoint[] = [];
	private projection: Projection | null = null;
	private loading = false;
	private error = "";
	private inputA: RouteInput = DEMO_ROUTES[0];
	private inputB: RouteInput = DEMO_ROUTES[1];

	constructor() {
		super();
		this.shadow = this.attachShadow({ mode: "open" });
	}

	connectedCallback() {
		this.render();
	}

	private getApiBase(): string {
		const path = window.location.pathname;
		const match = path.match(/^(\/[^/]+)?\/rtrips/);
		return match ? match[0] : "/rtrips";
	}

	private async fetchRoute(input: RouteInput): Promise<FittedRoute> {
		const res = await fetch(`${this.getApiBase()}/api/route`, {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify({ start: input.start, end: input.end }),
		});

		if (!res.ok) {
			const err = await res.json().catch(() => ({ error: "Unknown error" }));
			throw new Error(err.error || `OSRM returned ${res.status}`);
		}

		const data = await res.json();
		if (data.code !== "Ok" || !data.routes?.length) {
			throw new Error("No route found between the given points");
		}

		const route = data.routes[0];
		const rawCoords: GeoPoint[] = route.geometry.coordinates.map(
			([lng, lat]: [number, number]) => ({ lng, lat }),
		);

		return {
			rawCoords,
			projectedPoints: [], // filled after projection
			arcs: [],
			distance: route.distance,
			duration: route.duration,
		};
	}

	private async compute() {
		this.loading = true;
		this.error = "";
		this.render();

		try {
			// Fetch both routes
			const [rawA, rawB] = await Promise.all([
				this.fetchRoute(this.inputA),
				this.fetchRoute(this.inputB),
			]);

			// Create unified projection from all coordinates
			const allGeo = [...rawA.rawCoords, ...rawB.rawCoords];
			this.projection = createProjection(allGeo);

			// Project and fit route A
			rawA.projectedPoints = rawA.rawCoords.map((g) => this.projection!.toSVG(g));
			rawA.arcs = fitRoute(rawA.projectedPoints);
			this.routeA = rawA;

			// Project and fit route B
			rawB.projectedPoints = rawB.rawCoords.map((g) => this.projection!.toSVG(g));
			rawB.arcs = fitRoute(rawB.projectedPoints);
			this.routeB = rawB;

			// Find intersections
			this.intersections = findAllIntersections(this.routeA.arcs, this.routeB.arcs);
		} catch (e: any) {
			this.error = e.message || String(e);
		}

		this.loading = false;
		this.render();
	}

	private readInputs() {
		const q = (sel: string) => this.shadow.querySelector(sel) as HTMLInputElement | null;
		const parse = (v: string | undefined, fallback: number) => {
			const n = parseFloat(v || "");
			return isNaN(n) ? fallback : n;
		};
		this.inputA = {
			label: "Route A",
			start: {
				lat: parse(q("#a-start-lat")?.value, this.inputA.start.lat),
				lng: parse(q("#a-start-lng")?.value, this.inputA.start.lng),
			},
			end: {
				lat: parse(q("#a-end-lat")?.value, this.inputA.end.lat),
				lng: parse(q("#a-end-lng")?.value, this.inputA.end.lng),
			},
		};
		this.inputB = {
			label: "Route B",
			start: {
				lat: parse(q("#b-start-lat")?.value, this.inputB.start.lat),
				lng: parse(q("#b-start-lng")?.value, this.inputB.start.lng),
			},
			end: {
				lat: parse(q("#b-end-lat")?.value, this.inputB.end.lat),
				lng: parse(q("#b-end-lng")?.value, this.inputB.end.lng),
			},
		};
	}

	private renderSVG(): string {
		if (!this.routeA || !this.routeB || !this.projection) return "";

		const vb = this.projection.viewBox;

		let svg = `<svg viewBox="${vb}" xmlns="http://www.w3.org/2000/svg" class="route-svg">`;

		// Defs: grid + pulse animation
		svg += `<defs>
			<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
				<path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="0.5"/>
			</pattern>
		</defs>
		<rect width="100%" height="100%" fill="url(#grid)"/>`;

		// Layer 1: Grid (done above)

		// Layer 2: Overlap zone rectangles
		svg += this.renderOverlapZones(this.routeA.arcs, this.routeB.arcs);

		// Layer 3: Ghost conic curves (dashed, low opacity)
		svg += this.renderGhostCurves(this.routeA.arcs, "A");
		svg += this.renderGhostCurves(this.routeB.arcs, "B");

		// Layer 4: Route waypoints (subtle)
		svg += this.renderWaypoints(this.routeA.projectedPoints, "rgba(0,220,255,0.2)");
		svg += this.renderWaypoints(this.routeB.projectedPoints, "rgba(255,180,0,0.2)");

		// Layer 5: Fitted arc paths (solid, distinct shades)
		svg += this.renderArcs(this.routeA.arcs, COLORS_A, "routeA");
		svg += this.renderArcs(this.routeB.arcs, COLORS_B, "routeB");

		// Layer 6: Arc labels
		svg += this.renderArcLabels(this.routeA.arcs, "A", COLORS_A);
		svg += this.renderArcLabels(this.routeB.arcs, "B", COLORS_B);

		// Layer 7: Intersection crossing lines
		svg += this.renderCrossingLines();

		// Layer 8: Pulsing intersection markers + labels
		for (let i = 0; i < this.intersections.length; i++) {
			const pt = this.intersections[i].point;
			svg += `<circle cx="${pt.x.toFixed(1)}" cy="${pt.y.toFixed(1)}" r="8" fill="none" stroke="#ff3355" stroke-width="1.5" class="pulse-ring"/>`;
			svg += `<circle cx="${pt.x.toFixed(1)}" cy="${pt.y.toFixed(1)}" r="5" fill="#ff3355" stroke="#fff" stroke-width="1.5" class="pulse-dot"/>`;
			svg += `<text x="${(pt.x + 12).toFixed(1)}" y="${(pt.y - 10).toFixed(1)}" fill="#ff3355" font-size="11" font-family="monospace" font-weight="bold">X${i + 1}</text>`;
		}

		// Layer 9: Start/end markers
		svg += this.renderMarker(this.routeA.projectedPoints[0], "A0", "#00dcff");
		svg += this.renderMarker(this.routeA.projectedPoints[this.routeA.projectedPoints.length - 1], "A1", "#00dcff");
		svg += this.renderMarker(this.routeB.projectedPoints[0], "B0", "#ffb400");
		svg += this.renderMarker(this.routeB.projectedPoints[this.routeB.projectedPoints.length - 1], "B1", "#ffb400");

		// Legend
		svg += this.renderLegend(vb);

		svg += `</svg>`;
		return svg;
	}

	private renderOverlapZones(arcsA: ConicArc[], arcsB: ConicArc[]): string {
		let svg = "";
		for (const aA of arcsA) {
			const bbA = this.arcBounds(aA.points);
			for (const aB of arcsB) {
				const bbB = this.arcBounds(aB.points);
				// Compute intersection of bounding boxes
				const x1 = Math.max(bbA.minX, bbB.minX);
				const y1 = Math.max(bbA.minY, bbB.minY);
				const x2 = Math.min(bbA.maxX, bbB.maxX);
				const y2 = Math.min(bbA.maxY, bbB.maxY);
				if (x2 > x1 && y2 > y1) {
					const pad = 8;
					svg += `<rect x="${(x1 - pad).toFixed(1)}" y="${(y1 - pad).toFixed(1)}" width="${(x2 - x1 + pad * 2).toFixed(1)}" height="${(y2 - y1 + pad * 2).toFixed(1)}" fill="rgba(255,0,200,0.06)" stroke="rgba(255,0,200,0.15)" stroke-width="0.5" stroke-dasharray="4 3" rx="3"/>`;
				}
			}
		}
		return svg;
	}

	private renderGhostCurves(arcs: ConicArc[], routeId: string): string {
		const colors = routeId === "A" ? COLORS_A : COLORS_B;
		let svg = "";
		for (let i = 0; i < arcs.length; i++) {
			const arc = arcs[i];
			if (arc.points.length < 2 || arc.type === "degenerate") continue;

			const extend = 80;
			const sampled = sampleConicCurve(arc.coeffs, arc.points, extend);
			if (sampled.length < 2) continue;

			let d = `M ${sampled[0].x.toFixed(1)} ${sampled[0].y.toFixed(1)}`;
			for (let j = 1; j < sampled.length; j++) {
				d += ` L ${sampled[j].x.toFixed(1)} ${sampled[j].y.toFixed(1)}`;
			}
			const color = colors[i % colors.length];
			svg += `<path d="${d}" fill="none" stroke="${color}" stroke-width="1" stroke-dasharray="5 4" opacity="0.25" stroke-linecap="round"/>`;
		}
		return svg;
	}

	private renderWaypoints(pts: { x: number; y: number }[], color: string): string {
		return pts.map((p) =>
			`<circle cx="${p.x.toFixed(1)}" cy="${p.y.toFixed(1)}" r="1.5" fill="${color}"/>`
		).join("");
	}

	private renderArcs(arcs: ConicArc[], colors: string[], id: string): string {
		let svg = "";
		for (let i = 0; i < arcs.length; i++) {
			const arc = arcs[i];
			if (arc.beziers.length === 0) continue;

			const first = arc.beziers[0].p0;
			let d = `M ${first.x.toFixed(1)} ${first.y.toFixed(1)} `;
			for (const b of arc.beziers) {
				d += bezierToSVGCubic(b) + " ";
			}
			const color = colors[i % colors.length];
			svg += `<path d="${d}" fill="none" stroke="${color}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.85" class="arc-path" data-id="${id}-${i}"/>`;
		}
		return svg;
	}

	private renderArcLabels(arcs: ConicArc[], prefix: string, colors: string[]): string {
		let svg = "";
		for (let i = 0; i < arcs.length; i++) {
			const arc = arcs[i];
			if (arc.points.length < 2) continue;
			// Label at the midpoint of the arc
			const midIdx = Math.floor(arc.points.length / 2);
			const mp = arc.points[midIdx];
			const label = `${prefix}.${i + 1} ${arc.type}`;
			const color = colors[i % colors.length];
			// Background for readability
			svg += `<rect x="${(mp.x + 5).toFixed(1)}" y="${(mp.y - 11).toFixed(1)}" width="${(label.length * 6.2 + 6).toFixed(0)}" height="14" rx="3" fill="rgba(13,13,26,0.8)"/>`;
			svg += `<text x="${(mp.x + 8).toFixed(1)}" y="${(mp.y).toFixed(1)}" fill="${color}" font-size="9" font-family="monospace" opacity="0.8">${label}</text>`;
		}
		return svg;
	}

	private renderCrossingLines(): string {
		if (!this.routeA || !this.routeB) return "";
		let svg = "";
		const lineLen = 20;

		for (let i = 0; i < this.intersections.length; i++) {
			const ix = this.intersections[i];
			const pt = ix.point;
			const arcA = this.routeA.arcs[ix.arcIndexA];
			const arcB = this.routeB.arcs[ix.arcIndexB];
			if (!arcA || !arcB) continue;

			const tA = conicTangentAt(arcA.coeffs, pt);
			const tB = conicTangentAt(arcB.coeffs, pt);

			// Draw tangent lines
			const colorA = COLORS_A[ix.arcIndexA % COLORS_A.length];
			const colorB = COLORS_B[ix.arcIndexB % COLORS_B.length];
			svg += `<line x1="${(pt.x - tA.dx * lineLen).toFixed(1)}" y1="${(pt.y - tA.dy * lineLen).toFixed(1)}" x2="${(pt.x + tA.dx * lineLen).toFixed(1)}" y2="${(pt.y + tA.dy * lineLen).toFixed(1)}" stroke="${colorA}" stroke-width="1.5" opacity="0.7" stroke-linecap="round"/>`;
			svg += `<line x1="${(pt.x - tB.dx * lineLen).toFixed(1)}" y1="${(pt.y - tB.dy * lineLen).toFixed(1)}" x2="${(pt.x + tB.dx * lineLen).toFixed(1)}" y2="${(pt.y + tB.dy * lineLen).toFixed(1)}" stroke="${colorB}" stroke-width="1.5" opacity="0.7" stroke-linecap="round"/>`;

			// Compute crossing angle
			const dot = Math.abs(tA.dx * tB.dx + tA.dy * tB.dy);
			const angle = Math.acos(Math.min(1, dot)) * (180 / Math.PI);
			svg += `<text x="${(pt.x + 14).toFixed(1)}" y="${(pt.y + 18).toFixed(1)}" fill="rgba(255,255,255,0.6)" font-size="8" font-family="monospace">${angle.toFixed(1)}°</text>`;
		}
		return svg;
	}

	private renderMarker(pt: { x: number; y: number }, label: string, color: string): string {
		return `<circle cx="${pt.x.toFixed(1)}" cy="${pt.y.toFixed(1)}" r="5" fill="${color}" stroke="#1a1a2e" stroke-width="1.5"/>` +
			`<text x="${(pt.x + 8).toFixed(1)}" y="${(pt.y + 4).toFixed(1)}" fill="${color}" font-size="10" font-family="monospace" font-weight="bold">${label}</text>`;
	}

	private renderLegend(viewBox: string): string {
		// Parse viewBox to position legend in top-right
		const parts = viewBox.split(" ").map(Number);
		const vx = parts[0], vy = parts[1], vw = parts[2], vh = parts[3];
		const lx = vx + vw - 155;
		const ly = vy + 10;
		let svg = `<g transform="translate(${lx.toFixed(0)},${ly.toFixed(0)})">`;
		svg += `<rect x="0" y="0" width="150" height="78" rx="5" fill="rgba(13,13,26,0.85)" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>`;
		// Ghost curve
		svg += `<line x1="8" y1="14" x2="30" y2="14" stroke="#aaa" stroke-width="1" stroke-dasharray="4 3" opacity="0.5"/>`;
		svg += `<text x="36" y="17" fill="rgba(255,255,255,0.5)" font-size="8" font-family="monospace">Ghost conic</text>`;
		// Fitted arc
		svg += `<line x1="8" y1="28" x2="30" y2="28" stroke="#00dcff" stroke-width="2.5" opacity="0.85"/>`;
		svg += `<text x="36" y="31" fill="rgba(255,255,255,0.5)" font-size="8" font-family="monospace">Fitted arc</text>`;
		// Overlap zone
		svg += `<rect x="8" y="38" width="22" height="10" fill="rgba(255,0,200,0.12)" stroke="rgba(255,0,200,0.3)" stroke-width="0.5" rx="2"/>`;
		svg += `<text x="36" y="47" fill="rgba(255,255,255,0.5)" font-size="8" font-family="monospace">Overlap zone</text>`;
		// Intersection
		svg += `<circle cx="19" cy="63" r="4" fill="#ff3355" stroke="#fff" stroke-width="1" class="pulse-dot"/>`;
		svg += `<text x="36" y="66" fill="rgba(255,255,255,0.5)" font-size="8" font-family="monospace">Intersection</text>`;
		svg += `</g>`;
		return svg;
	}

	private arcBounds(pts: { x: number; y: number }[]): { minX: number; maxX: number; minY: number; maxY: number } {
		let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
		for (const p of pts) {
			if (p.x < minX) minX = p.x;
			if (p.x > maxX) maxX = p.x;
			if (p.y < minY) minY = p.y;
			if (p.y > maxY) maxY = p.y;
		}
		return { minX, maxX, minY, maxY };
	}

	private renderInfo(): string {
		if (!this.routeA || !this.routeB) return "";

		const fmtDist = (m: number) => m >= 1000 ? `${(m / 1000).toFixed(1)}km` : `${Math.round(m)}m`;
		const fmtTime = (s: number) => {
			const min = Math.floor(s / 60);
			return min > 0 ? `${min}m ${Math.round(s % 60)}s` : `${Math.round(s)}s`;
		};

		let html = `<div class="info-grid">`;

		// Summary row
		html += `<div class="info-summary">
			<span class="tag cyan">Route A: ${this.routeA.arcs.length} arcs | ${fmtDist(this.routeA.distance)} | ${fmtTime(this.routeA.duration)}</span>
			<span class="tag amber">Route B: ${this.routeB.arcs.length} arcs | ${fmtDist(this.routeB.distance)} | ${fmtTime(this.routeB.duration)}</span>
			<span class="tag red">${this.intersections.length} intersection${this.intersections.length !== 1 ? "s" : ""}</span>
		</div>`;

		// Arc details
		html += `<div class="arc-details"><h4>Route A Arcs</h4>`;
		for (let i = 0; i < this.routeA.arcs.length; i++) {
			const arc = this.routeA.arcs[i];
			html += `<div class="arc-item cyan">A.${i + 1}: <strong>${arc.type}</strong> e=${arc.eccentricity.toFixed(3)}</div>`;
		}
		html += `</div>`;

		html += `<div class="arc-details"><h4>Route B Arcs</h4>`;
		for (let i = 0; i < this.routeB.arcs.length; i++) {
			const arc = this.routeB.arcs[i];
			html += `<div class="arc-item amber">B.${i + 1}: <strong>${arc.type}</strong> e=${arc.eccentricity.toFixed(3)}</div>`;
		}
		html += `</div>`;

		// Intersection details
		if (this.intersections.length > 0 && this.projection) {
			html += `<div class="arc-details"><h4>Intersections</h4>`;
			for (let i = 0; i < this.intersections.length; i++) {
				const ix = this.intersections[i];
				const geo = this.projection.toGeo(ix.point);
				html += `<div class="arc-item red">X${i + 1}: ${geo.lat.toFixed(5)}, ${geo.lng.toFixed(5)} (A.${ix.arcIndexA + 1} / B.${ix.arcIndexB + 1})</div>`;
			}
			html += `</div>`;
		}

		html += `</div>`;
		return html;
	}

	private render() {
		this.shadow.innerHTML = `
		<style>
			:host { display: block; font-family: -apple-system, system-ui, sans-serif; color: #e0e0e0; }
			.container { background: #1a1a2e; border-radius: 12px; overflow: hidden; }
			.rapp-nav { padding: 16px 20px; border-bottom: 1px solid rgba(255,255,255,0.08); }
			.rapp-nav__title { font-size: 15px; font-weight: 600; color: #e2e8f0; display: block; margin-bottom: 4px; }
			.rapp-nav__desc { font-size: 13px; color: #94a3b8; }

			.input-panel { padding: 16px 20px; display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; border-bottom: 1px solid rgba(255,255,255,0.08); }
			.route-group { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
			.route-group label { font-size: 12px; font-weight: 600; min-width: 52px; }
			.route-group.a label { color: #00dcff; }
			.route-group.b label { color: #ffb400; }
			input { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12); border-radius: 6px; padding: 6px 8px; color: #e0e0e0; font-size: 12px; font-family: monospace; width: 88px; }
			input:focus { outline: none; border-color: rgba(255,255,255,0.3); }
			input::placeholder { color: rgba(255,255,255,0.3); }

			.btn-row { display: flex; gap: 8px; }
			button { background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 6px 14px; color: #e0e0e0; font-size: 12px; cursor: pointer; transition: background 0.15s; }
			button:hover { background: rgba(255,255,255,0.14); }
			button.primary { background: rgba(0,220,255,0.15); border-color: rgba(0,220,255,0.3); color: #00dcff; }
			button.primary:hover { background: rgba(0,220,255,0.25); }
			button:disabled { opacity: 0.4; cursor: not-allowed; }

			.svg-container { padding: 12px; background: #12121f; }
			.route-svg { width: 100%; height: auto; max-height: 500px; border-radius: 8px; background: #0d0d1a; }
			.arc-path:hover { stroke-width: 4; opacity: 1; }

			@keyframes pulse { 0%,100% { opacity: 0.9; r: 5; } 50% { opacity: 0.5; r: 8; } }
			@keyframes pulse-ring { 0%,100% { opacity: 0.6; r: 8; } 50% { opacity: 0; r: 14; } }
			.pulse-dot { animation: pulse 2s ease-in-out infinite; }
			.pulse-ring { animation: pulse-ring 2s ease-in-out infinite; }

			.error { padding: 12px 20px; color: #ff5577; font-size: 13px; }
			.loading { padding: 20px; text-align: center; color: rgba(255,255,255,0.5); font-size: 13px; }
			.empty { padding: 40px 20px; text-align: center; color: rgba(255,255,255,0.3); font-size: 14px; }

			.info-grid { padding: 16px 20px; }
			.info-summary { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
			.tag { padding: 4px 10px; border-radius: 12px; font-size: 12px; font-family: monospace; }
			.tag.cyan { background: rgba(0,220,255,0.12); color: #00dcff; }
			.tag.amber { background: rgba(255,180,0,0.12); color: #ffb400; }
			.tag.red { background: rgba(255,51,85,0.12); color: #ff3355; }

			.arc-details { margin-bottom: 8px; }
			.arc-details h4 { margin: 0 0 4px 0; font-size: 12px; opacity: 0.5; text-transform: uppercase; letter-spacing: 0.5px; }
			.arc-item { font-size: 12px; font-family: monospace; padding: 2px 0; }
			.arc-item.cyan { color: rgba(0,220,255,0.7); }
			.arc-item.amber { color: rgba(255,180,0,0.7); }
			.arc-item.red { color: rgba(255,51,85,0.8); }

			.math-note { padding: 12px 20px; border-top: 1px solid rgba(255,255,255,0.05); font-size: 11px; opacity: 0.4; font-family: monospace; }
		</style>
		<div class="container">
			<div class="rapp-nav">
				<span class="rapp-nav__title">Route Planner</span>
				<span class="rapp-nav__desc">Plan paths between destinations. Fit conic arcs and find where routes intersect.</span>
			</div>

			<div class="input-panel">
				<div class="route-group a">
					<label>Route A</label>
					<input id="a-start-lat" value="${this.inputA.start.lat}" placeholder="lat" title="Start latitude">
					<input id="a-start-lng" value="${this.inputA.start.lng}" placeholder="lng" title="Start longitude">
					<span style="opacity:0.3">to</span>
					<input id="a-end-lat" value="${this.inputA.end.lat}" placeholder="lat" title="End latitude">
					<input id="a-end-lng" value="${this.inputA.end.lng}" placeholder="lng" title="End longitude">
				</div>
				<div class="route-group b">
					<label>Route B</label>
					<input id="b-start-lat" value="${this.inputB.start.lat}" placeholder="lat" title="Start latitude">
					<input id="b-start-lng" value="${this.inputB.start.lng}" placeholder="lng" title="Start longitude">
					<span style="opacity:0.3">to</span>
					<input id="b-end-lat" value="${this.inputB.end.lat}" placeholder="lat" title="End latitude">
					<input id="b-end-lng" value="${this.inputB.end.lng}" placeholder="lng" title="End longitude">
				</div>
				<div class="btn-row">
					<button class="primary" id="btn-compute" ${this.loading ? "disabled" : ""}>Compute</button>
					<button id="btn-demo">Use Demo</button>
				</div>
			</div>

			${this.error ? `<div class="error">${this.error}</div>` : ""}
			${this.loading ? `<div class="loading">Fetching routes and computing conic fits...</div>` : ""}

			${this.routeA && this.routeB ? `
				<div class="svg-container">${this.renderSVG()}</div>
				${this.renderInfo()}
				<div class="math-note">
					Conic: Ax²+Bxy+Cy²+Dx+Ey+F=0 | Discriminant Δ=B²−4AC |
					Intersection via Sylvester resultant → degree-4 polynomial → companion matrix eigenvalues
				</div>
			` : ""}

			${!this.routeA && !this.routeB && !this.loading && !this.error ? `
				<div class="empty">
					Enter start/end coordinates for two routes, or click <strong>Use Demo</strong> to load Monaco routes.
				</div>
			` : ""}
		</div>`;

		// Attach event listeners
		this.shadow.querySelector("#btn-compute")?.addEventListener("click", () => {
			this.readInputs();
			this.compute();
		});

		this.shadow.querySelector("#btn-demo")?.addEventListener("click", () => {
			this.inputA = DEMO_ROUTES[0];
			this.inputB = DEMO_ROUTES[1];
			this.compute();
		});
	}

	static define() {
		if (!customElements.get("folk-route-planner")) {
			customElements.define("folk-route-planner", FolkRoutePlanner);
		}
	}
}

FolkRoutePlanner.define();

export { FolkRoutePlanner };
