/**
 * <folk-map-viewer> -- real-time location sharing map.
 *
 * Creates/joins map rooms, shows participant locations on a map,
 * and provides location sharing controls.
 *
 * Demo mode: interactive SVG world map with zoom/pan, 6 cosmolocal
 * print providers, connection arcs, city-level detail views, tooltips,
 * and feature highlights matching standalone rMaps capabilities.
 */

class FolkMapViewer extends HTMLElement {
	private shadow: ShadowRoot;
	private space = "";
	private room = "";
	private view: "lobby" | "map" = "lobby";
	private rooms: string[] = [];
	private loading = false;
	private error = "";
	private syncStatus: "disconnected" | "connected" = "disconnected";
	private providers: { name: string; city: string; country: string; lat: number; lng: number; color: string; desc: string; specialties: string[] }[] = [];

	// Zoom/pan state
	private vbX = 0;
	private vbY = 0;
	private vbW = 900;
	private vbH = 460;
	private isDragging = false;
	private dragStartX = 0;
	private dragStartY = 0;
	private dragVbX = 0;
	private dragVbY = 0;
	private zoomLevel = 1;
	private selectedProvider = -1;
	private searchQuery = "";
	private userLocation: { lat: number; lng: number } | null = null;

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

	connectedCallback() {
		this.space = this.getAttribute("space") || "demo";
		this.room = this.getAttribute("room") || "";
		if (this.space === "demo") {
			this.loadDemoData();
			return;
		}
		if (this.room) {
			this.view = "map";
		}
		this.checkSyncHealth();
		this.render();
	}

	private loadDemoData() {
		this.view = "map";
		this.room = "cosmolocal-providers";
		this.syncStatus = "connected";
		this.providers = [
			{
				name: "Radiant Hall Press", city: "Pittsburgh", country: "USA",
				lat: 40.44, lng: -79.99, color: "#ef4444",
				desc: "Worker-owned letterpress and risograph studio specializing in art prints and zines.",
				specialties: ["Letterpress", "Risograph", "Zines"],
			},
			{
				name: "Tiny Splendor", city: "Los Angeles", country: "USA",
				lat: 34.05, lng: -118.24, color: "#f59e0b",
				desc: "Artist-run collective creating hand-pulled screen prints and artist books.",
				specialties: ["Screen Print", "Artist Books", "Posters"],
			},
			{
				name: "People's Print Shop", city: "Toronto", country: "Canada",
				lat: 43.65, lng: -79.38, color: "#22c55e",
				desc: "Community print shop offering affordable risograph and offset printing for social movements.",
				specialties: ["Risograph", "Offset", "Community"],
			},
			{
				name: "Colour Code Press", city: "London", country: "UK",
				lat: 51.51, lng: -0.13, color: "#3b82f6",
				desc: "Independent risograph studio in East London, specializing in publications and packaging.",
				specialties: ["Risograph", "Publications", "Packaging"],
			},
			{
				name: "Druckwerkstatt Berlin", city: "Berlin", country: "Germany",
				lat: 52.52, lng: 13.40, color: "#8b5cf6",
				desc: "Open-access printmaking workshop in Kreuzberg with letterpress, screen print, and risograph.",
				specialties: ["Letterpress", "Screen Print", "Risograph"],
			},
			{
				name: "Kink\u014D Printing Collective", city: "Tokyo", country: "Japan",
				lat: 35.68, lng: 139.69, color: "#ec4899",
				desc: "Tokyo-based collective blending traditional Japanese woodblock with modern risograph techniques.",
				specialties: ["Woodblock", "Risograph", "Limited Editions"],
			},
		];
		this.renderDemo();
	}

	private getFilteredProviders(): { provider: typeof this.providers[0]; index: number }[] {
		if (!this.searchQuery.trim()) return this.providers.map((p, i) => ({ provider: p, index: i }));
		const q = this.searchQuery.toLowerCase();
		return this.providers.map((p, i) => ({ provider: p, index: i }))
			.filter(({ provider: p }) =>
				p.name.toLowerCase().includes(q) ||
				p.city.toLowerCase().includes(q) ||
				p.country.toLowerCase().includes(q) ||
				p.specialties.some(s => s.toLowerCase().includes(q))
			);
	}

	private renderDemo() {
		const W = 900;
		const H = 460;

		const px = (lng: number) => ((lng + 180) / 360) * W;
		const py = (lat: number) => ((90 - lat) / 180) * H;

		const filteredSet = new Set(this.getFilteredProviders().map(f => f.index));

		// Label offsets to avoid overlapping
		const labelOffsets: Record<string, [number, number]> = {
			"Radiant Hall Press": [10, -8],
			"Tiny Splendor": [-110, 14],
			"People's Print Shop": [10, 14],
			"Colour Code Press": [10, -8],
			"Druckwerkstatt Berlin": [10, 14],
			"Kink\u014D Printing Collective": [-150, -8],
		};

		// Connection arcs between providers
		const connections = [
			[0, 2], [0, 3], [3, 4], [4, 5], [1, 5], [0, 1],
		];

		const arcs = connections.map(([i, j]) => {
			const a = this.providers[i];
			const b = this.providers[j];
			const x1 = px(a.lng), y1 = py(a.lat);
			const x2 = px(b.lng), y2 = py(b.lat);
			const mx = (x1 + x2) / 2;
			const my = (y1 + y2) / 2 - Math.abs(x2 - x1) * 0.12;
			return `<path d="M${x1},${y1} Q${mx},${my} ${x2},${y2}" fill="none" stroke="rgba(148,163,184,0.15)" stroke-width="1" stroke-dasharray="4,3" />`;
		}).join("\n");

		// Provider pins
		// User location pin
		const userPin = this.userLocation ? (() => {
			const ux = px(this.userLocation!.lng);
			const uy = py(this.userLocation!.lat);
			return `
				<g>
					<circle cx="${ux}" cy="${uy}" r="6" fill="#22c55e" stroke="#fff" stroke-width="2" opacity="0.9">
						<animate attributeName="r" values="6;10;6" dur="2s" repeatCount="indefinite" />
					</circle>
					<text x="${ux + 10}" y="${uy + 4}" fill="#22c55e" font-size="9" font-weight="600" font-family="system-ui,sans-serif">You</text>
				</g>`;
		})() : "";

		const pins = this.providers.map((p, i) => {
			const x = px(p.lng);
			const y = py(p.lat);
			const [lx, ly] = labelOffsets[p.name] || [10, 4];
			const isSelected = this.selectedProvider === i;
			const isDimmed = this.searchQuery.trim() && !filteredSet.has(i);
			return `
				<g class="pin-group" data-idx="${i}" style="cursor:pointer;${isDimmed ? "opacity:0.2" : ""}">
					<circle cx="${x}" cy="${y}" r="4" fill="none" stroke="${p.color}" stroke-width="1" opacity="0.5">
						<animate attributeName="r" values="4;14;4" dur="3s" repeatCount="indefinite" />
						<animate attributeName="opacity" values="0.5;0;0.5" dur="3s" repeatCount="indefinite" />
					</circle>
					${isSelected ? `<circle cx="${x}" cy="${y}" r="18" fill="none" stroke="${p.color}" stroke-width="2" opacity="0.6" />` : ""}
					<path d="M${x},${y - 2} c0,-7 -6,-12 -6,-16 a6,6 0 1,1 12,0 c0,4 -6,9 -6,16z" fill="${p.color}" stroke="#0f172a" stroke-width="0.8" opacity="0.92" />
					<circle cx="${x}" cy="${y - 14}" r="2.5" fill="#fff" opacity="0.85" />
					<text x="${x + lx}" y="${y + ly}" fill="${p.color}" font-size="10" font-weight="600" font-family="system-ui,sans-serif" opacity="0.9">${this.esc(p.name)}</text>
					<text x="${x + lx}" y="${y + ly + 12}" fill="#64748b" font-size="8.5" font-family="system-ui,sans-serif">${this.esc(p.city)}, ${this.esc(p.country)}</text>
				</g>
			`;
		}).join("");

		// Legend items
		const legendItems = this.providers.map((p, i) => `
			<div class="legend-item ${this.selectedProvider === i ? "selected" : ""}" data-legend="${i}">
				<div style="width:10px;height:10px;border-radius:50%;background:${p.color};flex-shrink:0;box-shadow:0 0 6px ${p.color}40;"></div>
				<div style="flex:1;min-width:0">
					<span style="font-weight:600;font-size:13px;color:#e2e8f0;">${this.esc(p.name)}</span>
					<span style="font-size:12px;color:#64748b;margin-left:8px;">${this.esc(p.city)}, ${this.esc(p.country)}</span>
				</div>
			</div>
		`).join("");

		// Provider detail panel (shown when selected)
		let detailPanel = "";
		if (this.selectedProvider >= 0 && this.selectedProvider < this.providers.length) {
			const sp = this.providers[this.selectedProvider];
			detailPanel = `
			<div class="detail-panel">
				<div class="detail-header">
					<div style="width:14px;height:14px;border-radius:50%;background:${sp.color};flex-shrink:0;box-shadow:0 0 8px ${sp.color}60;"></div>
					<div style="flex:1">
						<div style="font-size:15px;font-weight:600;color:#e2e8f0;">${this.esc(sp.name)}</div>
						<div style="font-size:12px;color:#94a3b8;">${this.esc(sp.city)}, ${this.esc(sp.country)}</div>
					</div>
					<button class="detail-close" id="detail-close">\u2715</button>
				</div>
				<p style="font-size:13px;color:#94a3b8;line-height:1.5;margin:10px 0;">${this.esc(sp.desc)}</p>
				<div class="detail-tags">
					${sp.specialties.map(s => `<span class="detail-tag" style="border-color:${sp.color}40;color:${sp.color}">${this.esc(s)}</span>`).join("")}
				</div>
				<div style="font-size:11px;color:#4a5568;margin-top:10px;font-family:monospace;">
					${sp.lat.toFixed(4)}\u00B0N, ${Math.abs(sp.lng).toFixed(4)}\u00B0${sp.lng >= 0 ? "E" : "W"}
				</div>
				<div style="display:flex;gap:8px;margin-top:12px;">
					<a href="https://www.openstreetmap.org/?mlat=${sp.lat}&mlon=${sp.lng}#map=15/${sp.lat}/${sp.lng}" target="_blank" rel="noopener" style="flex:1;text-align:center;padding:8px;border-radius:6px;background:#4f46e520;border:1px solid #4f46e540;color:#818cf8;font-size:12px;font-weight:600;text-decoration:none;">Get Directions</a>
				</div>
			</div>`;
		}

		this.shadow.innerHTML = `
		<style>
			:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
			* { box-sizing: border-box; }
			.demo-nav {
				display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; flex-wrap: wrap;
			}
			.demo-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; min-width: 140px; }
			.demo-nav__badge {
				display: inline-flex; align-items: center; gap: 5px;
				font-size: 11px; color: #94a3b8; background: rgba(16,185,129,0.1);
				border: 1px solid rgba(16,185,129,0.2); border-radius: 20px; padding: 3px 10px;
			}
			.demo-nav__badge .dot { width: 6px; height: 6px; border-radius: 50%; background: #22c55e; }

			/* Zoom controls */
			.zoom-controls {
				display: flex; gap: 4px; align-items: center;
			}
			.zoom-btn {
				width: 28px; height: 28px; border-radius: 6px; border: 1px solid #333;
				background: #16161e; color: #94a3b8; cursor: pointer; font-size: 16px;
				display: flex; align-items: center; justify-content: center; transition: all 0.15s;
			}
			.zoom-btn:hover { border-color: #555; color: #e2e8f0; }
			.zoom-label { font-size: 10px; color: #4a5568; font-variant-numeric: tabular-nums; min-width: 32px; text-align: center; }

			.search-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
			.search-input {
				flex: 1; border: 1px solid #1e293b; border-radius: 8px; padding: 8px 12px;
				background: #0c1221; color: #e0e0e0; font-size: 13px; outline: none;
			}
			.search-input:focus { border-color: #6366f1; }
			.search-input::placeholder { color: #4a5568; }
			.geo-btn {
				padding: 8px 14px; border-radius: 8px; border: 1px solid #1e293b;
				background: #0c1221; color: #94a3b8; cursor: pointer; font-size: 12px; white-space: nowrap;
			}
			.geo-btn:hover { border-color: #334155; color: #e2e8f0; }
			.geo-btn.active { border-color: #22c55e; color: #22c55e; }

			.map-wrap {
				width: 100%; border-radius: 12px; background: #0c1221; border: 1px solid #1e293b;
				overflow: hidden; position: relative; cursor: grab;
			}
			.map-wrap.dragging { cursor: grabbing; }
			.map-svg { display: block; width: 100%; height: auto; user-select: none; }

			/* Hover tooltip */
			.pin-group { cursor: pointer; }
			.tooltip {
				position: absolute; pointer-events: none; opacity: 0; transition: opacity 0.15s;
				background: #1e293b; border: 1px solid #334155; border-radius: 8px;
				padding: 8px 12px; font-size: 12px; color: #e2e8f0; white-space: nowrap;
				box-shadow: 0 4px 12px rgba(0,0,0,0.4); z-index: 10;
			}
			.tooltip.visible { opacity: 1; }
			.tooltip strong { display: block; margin-bottom: 2px; }
			.tooltip .city { color: #94a3b8; font-size: 11px; }
			.tooltip .coords { color: #64748b; font-size: 10px; font-family: monospace; }

			/* Legend */
			.legend {
				background: rgba(15,23,42,0.6); border: 1px solid #1e293b; border-radius: 10px;
				padding: 16px; margin-top: 16px;
			}
			.legend-title {
				font-size: 12px; font-weight: 600; color: #94a3b8;
				text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 8px;
			}
			.legend-item {
				display: flex; align-items: center; gap: 8px; padding: 6px 8px;
				border-radius: 6px; cursor: pointer; transition: background 0.15s;
			}
			.legend-item:hover { background: rgba(255,255,255,0.04); }
			.legend-item.selected { background: rgba(99,102,241,0.1); border: 1px solid rgba(99,102,241,0.2); }

			/* Detail panel */
			.detail-panel {
				background: #1a1a2e; border: 1px solid #334155; border-radius: 10px;
				padding: 16px; margin-top: 12px;
			}
			.detail-header { display: flex; align-items: center; gap: 10px; }
			.detail-close { background: none; border: none; color: #64748b; font-size: 16px; cursor: pointer; padding: 4px; }
			.detail-close:hover { color: #e2e8f0; }
			.detail-tags { display: flex; gap: 6px; flex-wrap: wrap; }
			.detail-tag {
				font-size: 10px; padding: 3px 8px; border-radius: 10px;
				border: 1px solid #333; font-weight: 500;
			}

			/* Feature highlights row */
			.features {
				display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
				gap: 10px; margin-top: 16px;
			}
			.feat {
				background: rgba(15,23,42,0.4); border: 1px solid #1e293b; border-radius: 10px;
				padding: 12px; text-align: center;
			}
			.feat-icon { font-size: 20px; margin-bottom: 4px; }
			.feat-label { font-size: 12px; font-weight: 600; color: #e2e8f0; }
			.feat-desc { font-size: 10.5px; color: #64748b; margin-top: 2px; line-height: 1.4; }

			@media (max-width: 640px) {
				.features { grid-template-columns: repeat(2, 1fr); }
				.zoom-controls { gap: 2px; }
			}
		</style>

		<div class="demo-nav">
			<span class="demo-nav__title">Cosmolocal Print Network</span>
			<div class="zoom-controls">
				<button class="zoom-btn" id="zoom-out" title="Zoom out">\u2212</button>
				<span class="zoom-label">${Math.round(this.zoomLevel * 100)}%</span>
				<button class="zoom-btn" id="zoom-in" title="Zoom in">+</button>
				<button class="zoom-btn" id="zoom-reset" title="Reset view" style="font-size:12px">\u21BA</button>
			</div>
			<span class="demo-nav__badge"><span class="dot"></span> ${this.providers.length} providers online</span>
		</div>

		<div class="search-bar">
			<input class="search-input" type="text" id="map-search" placeholder="Search providers by name, city, or specialty..." value="${this.esc(this.searchQuery)}">
			<button class="geo-btn ${this.userLocation ? "active" : ""}" id="share-geo">${this.userLocation ? "\u{1F4CD} Sharing" : "\u{1F4CD} Share Location"}</button>
		</div>

		<div class="map-wrap" id="map-wrap">
			<div class="tooltip" id="tooltip"></div>
			<svg class="map-svg" id="map-svg" viewBox="${this.vbX} ${this.vbY} ${this.vbW} ${this.vbH}" xmlns="http://www.w3.org/2000/svg">
				<defs>
					<radialGradient id="ocean" cx="50%" cy="40%" r="70%">
						<stop offset="0%" stop-color="#0f1b33" />
						<stop offset="100%" stop-color="#060d1a" />
					</radialGradient>
				</defs>

				<!-- Ocean background -->
				<rect x="-200" y="-200" width="${W + 400}" height="${H + 400}" fill="url(#ocean)" />

				<!-- Graticule -->
				${this.graticule(W, H)}

				<!-- Continents -->
				${this.continents(W, H)}

				<!-- Connection arcs -->
				${arcs}

				<!-- Provider pins -->
				${pins}

				<!-- User location -->
				${userPin}
			</svg>
		</div>

		${detailPanel}

		<div class="legend">
			<div class="legend-title">Print Providers \u2014 click to explore</div>
			${legendItems}
		</div>

		<div class="features">
			<div class="feat">
				<div class="feat-icon">&#128205;</div>
				<div class="feat-label">Live GPS</div>
				<div class="feat-desc">Real-time location sharing via WebSocket</div>
			</div>
			<div class="feat">
				<div class="feat-icon">&#127970;</div>
				<div class="feat-label">Indoor Nav</div>
				<div class="feat-desc">c3nav integration for CCC events</div>
			</div>
			<div class="feat">
				<div class="feat-icon">&#128204;</div>
				<div class="feat-label">Waypoints</div>
				<div class="feat-desc">Drop meeting points and pins</div>
			</div>
			<div class="feat">
				<div class="feat-icon">&#128279;</div>
				<div class="feat-label">QR Sharing</div>
				<div class="feat-desc">Scan to join any room instantly</div>
			</div>
			<div class="feat">
				<div class="feat-icon">&#128225;</div>
				<div class="feat-label">Ping Friends</div>
				<div class="feat-desc">Request location with one tap</div>
			</div>
			<div class="feat">
				<div class="feat-icon">&#128737;</div>
				<div class="feat-label">Privacy First</div>
				<div class="feat-desc">Ghost mode, precision levels, zero tracking</div>
			</div>
		</div>
		`;

		this.attachDemoListeners();
	}

	private zoomTo(lat: number, lng: number, level: number) {
		const W = 900, H = 460;
		const cx = ((lng + 180) / 360) * W;
		const cy = ((90 - lat) / 180) * H;
		this.zoomLevel = level;
		this.vbW = W / level;
		this.vbH = H / level;
		this.vbX = cx - this.vbW / 2;
		this.vbY = cy - this.vbH / 2;
		// Clamp
		this.vbX = Math.max(-100, Math.min(W - this.vbW + 100, this.vbX));
		this.vbY = Math.max(-100, Math.min(H - this.vbH + 100, this.vbY));
		this.renderDemo();
	}

	private resetZoom() {
		this.vbX = 0;
		this.vbY = 0;
		this.vbW = 900;
		this.vbH = 460;
		this.zoomLevel = 1;
		this.selectedProvider = -1;
		this.renderDemo();
	}

	/** Generate SVG graticule lines */
	private graticule(W: number, H: number): string {
		const lines: string[] = [];
		for (let lat = -60; lat <= 60; lat += 30) {
			const y = ((90 - lat) / 180) * H;
			lines.push(`<line x1="-200" y1="${y}" x2="${W + 200}" y2="${y}" stroke="#1a2744" stroke-width="0.5" stroke-dasharray="3,5" />`);
		}
		for (let lng = -150; lng <= 180; lng += 30) {
			const x = ((lng + 180) / 360) * W;
			lines.push(`<line x1="${x}" y1="-200" x2="${x}" y2="${H + 200}" stroke="#1a2744" stroke-width="0.5" stroke-dasharray="3,5" />`);
		}
		const eq = ((90 - 0) / 180) * H;
		const pm = ((0 + 180) / 360) * W;
		lines.push(`<line x1="-200" y1="${eq}" x2="${W + 200}" y2="${eq}" stroke="#1e3050" stroke-width="0.7" stroke-dasharray="4,3" />`);
		lines.push(`<line x1="${pm}" y1="-200" x2="${pm}" y2="${H + 200}" stroke="#1e3050" stroke-width="0.7" stroke-dasharray="4,3" />`);
		return lines.join("\n");
	}

	/** Simplified continent outlines using equirectangular projection */
	private continents(W: number, H: number): string {
		const p = (lat: number, lng: number) => {
			const x = ((lng + 180) / 360) * W;
			const y = ((90 - lat) / 180) * H;
			return `${x.toFixed(1)},${y.toFixed(1)}`;
		};

		const fill = "#162236";
		const stroke = "#1e3050";

		const continents = [
			// North America
			`M${p(50, -130)} L${p(60, -130)} L${p(65, -120)} L${p(70, -100)} L${p(72, -80)}
			 L${p(65, -65)} L${p(50, -55)} L${p(45, -65)} L${p(40, -75)}
			 L${p(30, -82)} L${p(28, -90)} L${p(25, -100)} L${p(20, -105)}
			 L${p(18, -100)} L${p(15, -88)} L${p(10, -84)} L${p(10, -78)}
			 L${p(20, -88)} L${p(25, -80)} L${p(30, -82)} L${p(30, -75)}
			 L${p(40, -75)} L${p(48, -90)} L${p(48, -95)}
			 L${p(50, -120)} Z`,

			// South America
			`M${p(12, -75)} L${p(10, -68)} L${p(5, -60)} L${p(0, -50)}
			 L${p(-5, -45)} L${p(-10, -38)} L${p(-15, -40)} L${p(-20, -42)}
			 L${p(-25, -48)} L${p(-30, -50)} L${p(-35, -56)} L${p(-40, -62)}
			 L${p(-45, -66)} L${p(-50, -70)} L${p(-55, -68)}
			 L${p(-50, -74)} L${p(-42, -72)} L${p(-35, -70)}
			 L${p(-25, -70)} L${p(-20, -70)} L${p(-15, -76)}
			 L${p(-5, -78)} L${p(0, -80)} L${p(5, -78)} L${p(10, -76)} Z`,

			// Europe
			`M${p(40, -8)} L${p(42, 0)} L${p(44, 5)} L${p(46, 8)}
			 L${p(48, 10)} L${p(50, 5)} L${p(52, 8)} L${p(55, 10)}
			 L${p(58, 12)} L${p(60, 10)} L${p(62, 18)} L${p(65, 20)}
			 L${p(70, 25)} L${p(68, 30)} L${p(62, 32)} L${p(58, 30)}
			 L${p(55, 28)} L${p(50, 30)} L${p(48, 28)} L${p(45, 25)}
			 L${p(42, 28)} L${p(38, 25)} L${p(36, 22)} L${p(38, 10)}
			 L${p(38, 0)} Z`,

			// Africa
			`M${p(35, -5)} L${p(37, 10)} L${p(35, 20)} L${p(32, 32)}
			 L${p(28, 33)} L${p(15, 42)} L${p(10, 42)} L${p(5, 38)}
			 L${p(0, 40)} L${p(-5, 38)} L${p(-10, 34)} L${p(-15, 30)}
			 L${p(-20, 28)} L${p(-25, 28)} L${p(-30, 28)} L${p(-34, 25)}
			 L${p(-34, 20)} L${p(-30, 15)} L${p(-20, 12)} L${p(-15, 12)}
			 L${p(-10, 15)} L${p(-5, 10)} L${p(0, 8)} L${p(5, 2)}
			 L${p(10, 0)} L${p(15, -5)} L${p(20, -10)} L${p(25, -15)}
			 L${p(30, -10)} L${p(35, -5)} Z`,

			// Asia
			`M${p(70, 30)} L${p(72, 50)} L${p(72, 80)} L${p(70, 110)}
			 L${p(68, 140)} L${p(65, 165)} L${p(60, 165)} L${p(55, 140)}
			 L${p(50, 130)} L${p(45, 135)} L${p(40, 130)} L${p(35, 120)}
			 L${p(30, 120)} L${p(25, 105)} L${p(22, 100)} L${p(20, 98)}
			 L${p(15, 100)} L${p(10, 105)} L${p(5, 100)} L${p(0, 104)}
			 L${p(-5, 105)} L${p(-8, 112)} L${p(-6, 118)}
			 L${p(2, 110)} L${p(8, 108)} L${p(12, 110)}
			 L${p(20, 108)} L${p(22, 114)} L${p(30, 110)}
			 L${p(28, 88)} L${p(25, 68)} L${p(30, 50)}
			 L${p(35, 45)} L${p(40, 40)} L${p(42, 32)}
			 L${p(48, 30)} L${p(55, 30)} L${p(62, 32)}
			 L${p(68, 30)} Z`,

			// Australia
			`M${p(-12, 132)} L${p(-15, 140)} L${p(-20, 148)} L${p(-25, 150)}
			 L${p(-30, 148)} L${p(-35, 140)} L${p(-38, 146)} L${p(-35, 150)}
			 L${p(-32, 152)} L${p(-28, 153)} L${p(-25, 152)}
			 L${p(-20, 150)} L${p(-15, 145)} L${p(-12, 138)}
			 L${p(-14, 130)} L${p(-18, 122)} L${p(-22, 115)}
			 L${p(-28, 114)} L${p(-32, 116)} L${p(-35, 118)}
			 L${p(-34, 125)} L${p(-30, 130)} L${p(-25, 132)}
			 L${p(-20, 130)} L${p(-16, 128)} L${p(-12, 132)} Z`,

			// Japan
			`M${p(35, 133)} L${p(38, 136)} L${p(40, 140)} L${p(42, 142)}
			 L${p(44, 144)} L${p(45, 142)} L${p(43, 140)} L${p(40, 137)}
			 L${p(37, 135)} L${p(35, 133)} Z`,

			// UK/Ireland
			`M${p(51, -5)} L${p(53, 0)} L${p(55, -2)} L${p(57, -5)}
			 L${p(58, -3)} L${p(56, 0)} L${p(54, 1)} L${p(52, 0)}
			 L${p(50, -3)} L${p(51, -5)} Z`,

			// Greenland
			`M${p(62, -50)} L${p(68, -52)} L${p(75, -45)} L${p(78, -35)}
			 L${p(76, -20)} L${p(70, -22)} L${p(65, -35)} L${p(62, -45)} Z`,

			// Indonesia
			`M${p(-2, 100)} L${p(-4, 108)} L${p(-6, 112)} L${p(-8, 115)}
			 L${p(-7, 118)} L${p(-5, 116)} L${p(-3, 112)} L${p(-1, 106)} L${p(-2, 100)} Z`,

			// New Zealand
			`M${p(-36, 174)} L${p(-38, 176)} L${p(-42, 174)} L${p(-46, 168)}
			 L${p(-44, 168)} L${p(-42, 172)} L${p(-38, 174)} L${p(-36, 174)} Z`,
		];

		return continents.map((d) =>
			`<path d="${d.replace(/\s+/g, " ").trim()}" fill="${fill}" stroke="${stroke}" stroke-width="0.5" opacity="0.75" />`
		).join("\n");
	}

	private attachDemoListeners() {
		// Search input
		let searchTimeout: any;
		this.shadow.getElementById("map-search")?.addEventListener("input", (e) => {
			this.searchQuery = (e.target as HTMLInputElement).value;
			clearTimeout(searchTimeout);
			searchTimeout = setTimeout(() => this.renderDemo(), 200);
		});

		// Geolocation button
		this.shadow.getElementById("share-geo")?.addEventListener("click", () => {
			if (this.userLocation) {
				this.userLocation = null;
				this.renderDemo();
				return;
			}
			if ("geolocation" in navigator) {
				navigator.geolocation.getCurrentPosition(
					(pos) => {
						this.userLocation = { lat: pos.coords.latitude, lng: pos.coords.longitude };
						this.renderDemo();
					},
					() => { /* denied — do nothing */ }
				);
			}
		});

		const tooltip = this.shadow.getElementById("tooltip");
		const mapWrap = this.shadow.getElementById("map-wrap");
		const mapSvg = this.shadow.getElementById("map-svg");

		// Tooltip on hover
		if (tooltip) {
			this.shadow.querySelectorAll(".pin-group").forEach((el) => {
				const idx = parseInt((el as HTMLElement).dataset.idx || "0", 10);
				const p = this.providers[idx];

				el.addEventListener("mouseenter", (e) => {
					const rect = mapWrap?.getBoundingClientRect();
					const me = e as MouseEvent;
					if (rect) {
						tooltip.innerHTML = `<strong>${this.esc(p.name)}</strong><span class="city">${this.esc(p.city)}, ${this.esc(p.country)}</span><span class="coords">${p.lat.toFixed(2)}, ${p.lng.toFixed(2)}</span>`;
						tooltip.style.left = `${me.clientX - rect.left + 12}px`;
						tooltip.style.top = `${me.clientY - rect.top - 10}px`;
						tooltip.classList.add("visible");
					}
				});

				el.addEventListener("mousemove", (e) => {
					const rect = mapWrap?.getBoundingClientRect();
					const me = e as MouseEvent;
					if (rect) {
						tooltip.style.left = `${me.clientX - rect.left + 12}px`;
						tooltip.style.top = `${me.clientY - rect.top - 10}px`;
					}
				});

				el.addEventListener("mouseleave", () => {
					tooltip.classList.remove("visible");
				});
			});
		}

		// Click pin to select provider and zoom
		this.shadow.querySelectorAll(".pin-group").forEach((el) => {
			el.addEventListener("click", (e) => {
				e.stopPropagation();
				const idx = parseInt((el as HTMLElement).dataset.idx || "0", 10);
				if (this.selectedProvider === idx) {
					this.resetZoom();
				} else {
					this.selectedProvider = idx;
					const p = this.providers[idx];
					this.zoomTo(p.lat, p.lng, 3);
				}
			});
		});

		// Click legend item to select/zoom
		this.shadow.querySelectorAll("[data-legend]").forEach((el) => {
			el.addEventListener("click", () => {
				const idx = parseInt((el as HTMLElement).dataset.legend || "0", 10);
				if (this.selectedProvider === idx) {
					this.resetZoom();
				} else {
					this.selectedProvider = idx;
					const p = this.providers[idx];
					this.zoomTo(p.lat, p.lng, 3);
				}
			});
		});

		// Close detail panel
		this.shadow.getElementById("detail-close")?.addEventListener("click", () => {
			this.resetZoom();
		});

		// Zoom controls
		this.shadow.getElementById("zoom-in")?.addEventListener("click", () => {
			const newZoom = Math.min(this.zoomLevel * 1.5, 6);
			const cx = this.vbX + this.vbW / 2;
			const cy = this.vbY + this.vbH / 2;
			const lat = 90 - (cy / 460) * 180;
			const lng = (cx / 900) * 360 - 180;
			this.zoomTo(lat, lng, newZoom);
		});
		this.shadow.getElementById("zoom-out")?.addEventListener("click", () => {
			if (this.zoomLevel <= 1) {
				this.resetZoom();
				return;
			}
			const newZoom = Math.max(this.zoomLevel / 1.5, 1);
			const cx = this.vbX + this.vbW / 2;
			const cy = this.vbY + this.vbH / 2;
			const lat = 90 - (cy / 460) * 180;
			const lng = (cx / 900) * 360 - 180;
			this.zoomTo(lat, lng, newZoom);
		});
		this.shadow.getElementById("zoom-reset")?.addEventListener("click", () => {
			this.resetZoom();
		});

		// Mouse wheel zoom
		mapWrap?.addEventListener("wheel", (e) => {
			e.preventDefault();
			const we = e as WheelEvent;
			const delta = we.deltaY > 0 ? 0.8 : 1.25;
			const newZoom = Math.max(1, Math.min(6, this.zoomLevel * delta));
			// Zoom toward mouse position
			const rect = mapWrap.getBoundingClientRect();
			const mouseX = we.clientX - rect.left;
			const mouseY = we.clientY - rect.top;
			const svgX = this.vbX + (mouseX / rect.width) * this.vbW;
			const svgY = this.vbY + (mouseY / rect.height) * this.vbH;
			const lat = 90 - (svgY / 460) * 180;
			const lng = (svgX / 900) * 360 - 180;
			this.zoomTo(lat, lng, newZoom);
		}, { passive: false });

		// Drag to pan
		mapWrap?.addEventListener("mousedown", (e) => {
			const me = e as MouseEvent;
			// Don't start drag on pins
			if ((me.target as Element)?.closest?.(".pin-group")) return;
			this.isDragging = true;
			this.dragStartX = me.clientX;
			this.dragStartY = me.clientY;
			this.dragVbX = this.vbX;
			this.dragVbY = this.vbY;
			mapWrap.classList.add("dragging");
		});

		const onMouseMove = (e: Event) => {
			if (!this.isDragging) return;
			const me = e as MouseEvent;
			const rect = mapWrap?.getBoundingClientRect();
			if (!rect) return;
			const dx = (me.clientX - this.dragStartX) / rect.width * this.vbW;
			const dy = (me.clientY - this.dragStartY) / rect.height * this.vbH;
			this.vbX = this.dragVbX - dx;
			this.vbY = this.dragVbY - dy;
			// Clamp
			this.vbX = Math.max(-200, Math.min(900 - this.vbW + 200, this.vbX));
			this.vbY = Math.max(-200, Math.min(460 - this.vbH + 200, this.vbY));
			if (mapSvg) {
				mapSvg.setAttribute("viewBox", `${this.vbX} ${this.vbY} ${this.vbW} ${this.vbH}`);
			}
		};

		const onMouseUp = () => {
			this.isDragging = false;
			mapWrap?.classList.remove("dragging");
		};

		document.addEventListener("mousemove", onMouseMove);
		document.addEventListener("mouseup", onMouseUp);

		// Touch support for pan
		mapWrap?.addEventListener("touchstart", (e) => {
			const te = e as TouchEvent;
			if (te.touches.length === 1) {
				this.isDragging = true;
				this.dragStartX = te.touches[0].clientX;
				this.dragStartY = te.touches[0].clientY;
				this.dragVbX = this.vbX;
				this.dragVbY = this.vbY;
			}
		}, { passive: true });

		mapWrap?.addEventListener("touchmove", (e) => {
			if (!this.isDragging) return;
			const te = e as TouchEvent;
			if (te.touches.length !== 1) return;
			const rect = mapWrap.getBoundingClientRect();
			const dx = (te.touches[0].clientX - this.dragStartX) / rect.width * this.vbW;
			const dy = (te.touches[0].clientY - this.dragStartY) / rect.height * this.vbH;
			this.vbX = this.dragVbX - dx;
			this.vbY = this.dragVbY - dy;
			this.vbX = Math.max(-200, Math.min(900 - this.vbW + 200, this.vbX));
			this.vbY = Math.max(-200, Math.min(460 - this.vbH + 200, this.vbY));
			if (mapSvg) {
				mapSvg.setAttribute("viewBox", `${this.vbX} ${this.vbY} ${this.vbW} ${this.vbH}`);
			}
		}, { passive: true });

		mapWrap?.addEventListener("touchend", () => {
			this.isDragging = false;
		}, { passive: true });
	}

	private getApiBase(): string {
		const match = window.location.pathname.match(/^\/([^/]+)\/rmaps/);
		if (match) return `/${match[1]}/rmaps`;
		if (this.space) return `/${this.space}/rmaps`;
		return "";
	}

	private async checkSyncHealth() {
		try {
			const base = this.getApiBase();
			const res = await fetch(`${base}/api/health`, { signal: AbortSignal.timeout(3000) });
			if (res.ok) {
				const data = await res.json();
				this.syncStatus = data.sync !== false ? "connected" : "disconnected";
			}
		} catch {
			this.syncStatus = "disconnected";
		}
		this.render();
	}

	private async loadStats() {
		try {
			const base = this.getApiBase();
			const res = await fetch(`${base}/api/stats`, { signal: AbortSignal.timeout(3000) });
			if (res.ok) {
				const data = await res.json();
				this.rooms = Object.keys(data.rooms || {});
			}
		} catch {
			this.rooms = [];
		}
		this.render();
	}

	private joinRoom(slug: string) {
		this.room = slug;
		this.view = "map";
		this.render();
	}

	private createRoom() {
		const name = prompt("Room name (slug):");
		if (!name?.trim()) return;
		const slug = name.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-");
		this.joinRoom(slug);
	}

	private render() {
		this.shadow.innerHTML = `
		<style>
			:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
			* { box-sizing: border-box; }

			.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
			.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 13px; }
			.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
			.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

			.status-dot {
				width: 8px; height: 8px; border-radius: 50%; display: inline-block;
			}
			.status-connected { background: #22c55e; }
			.status-disconnected { background: #ef4444; }

			.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: #4f46e5; color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
			.rapp-nav__btn:hover { background: #6366f1; }

			.room-card {
				background: #1e1e2e; border: 1px solid #333; border-radius: 10px;
				padding: 16px; margin-bottom: 12px; cursor: pointer; transition: border-color 0.2s;
				display: flex; align-items: center; gap: 12px;
			}
			.room-card:hover { border-color: #555; }
			.room-icon { font-size: 24px; }
			.room-name { font-size: 15px; font-weight: 600; }

			.map-container {
				width: 100%; height: 500px; border-radius: 10px;
				background: #1a1a2e; border: 1px solid #333;
				display: flex; align-items: center; justify-content: center;
				position: relative; overflow: hidden;
			}
			.map-placeholder {
				text-align: center; color: #666; padding: 40px;
			}
			.map-placeholder p { margin: 8px 0; }

			.controls {
				display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap;
			}
			.ctrl-btn {
				padding: 8px 16px; border-radius: 8px; border: 1px solid #444;
				background: #1e1e2e; color: #ccc; cursor: pointer; font-size: 13px;
			}
			.ctrl-btn:hover { border-color: #666; }
			.ctrl-btn.sharing {
				border-color: #22c55e; color: #22c55e; animation: pulse 2s infinite;
			}
			@keyframes pulse {
				0%, 100% { opacity: 1; }
				50% { opacity: 0.7; }
			}

			.share-link {
				background: #1e1e2e; border: 1px solid #333; border-radius: 8px;
				padding: 12px; margin-top: 12px; font-family: monospace; font-size: 12px;
				color: #aaa; display: flex; align-items: center; gap: 8px;
			}
			.share-link span { flex: 1; overflow: hidden; text-overflow: ellipsis; }
			.copy-btn {
				padding: 4px 10px; border-radius: 4px; border: 1px solid #444;
				background: #2a2a3e; color: #ccc; cursor: pointer; font-size: 11px;
			}

			.empty { text-align: center; color: #666; padding: 40px; }

			@media (max-width: 768px) {
				.map-container { height: 300px; }
			}
		</style>

		${this.error ? `<div style="color:#ef5350;text-align:center;padding:12px">${this.esc(this.error)}</div>` : ""}
		${this.view === "lobby" ? this.renderLobby() : this.renderMap()}
		`;

		this.attachListeners();
	}

	private renderLobby(): string {
		return `
			<div class="rapp-nav">
				<span class="rapp-nav__title">Map Rooms</span>
				<span class="status-dot ${this.syncStatus === "connected" ? "status-connected" : "status-disconnected"}"></span>
				<span style="font-size:12px;color:#888;margin-right:12px">${this.syncStatus === "connected" ? "Sync online" : "Sync offline"}</span>
				<button class="rapp-nav__btn" id="create-room">+ New Room</button>
			</div>

			${this.rooms.length > 0 ? this.rooms.map((r) => `
				<div class="room-card" data-room="${r}">
					<span class="room-icon">&#128506;</span>
					<span class="room-name">${this.esc(r)}</span>
				</div>
			`).join("") : ""}

			<div class="empty">
				<p style="font-size:16px;margin-bottom:8px">Create or join a map room to share locations</p>
				<p style="font-size:13px">Share the room link with friends to see each other on the map in real-time</p>
			</div>
		`;
	}

	private renderMap(): string {
		const shareUrl = `${window.location.origin}/${this.space}/maps/${this.room}`;
		return `
			<div class="rapp-nav">
				<button class="rapp-nav__back" data-back="lobby">&#8592; Rooms</button>
				<span class="rapp-nav__title">&#128506; ${this.esc(this.room)}</span>
				<span class="status-dot ${this.syncStatus === "connected" ? "status-connected" : "status-disconnected"}"></span>
			</div>

			<div class="map-container">
				<div class="map-placeholder">
					<p style="font-size:48px">&#127758;</p>
					<p style="font-size:16px">Map Room: <strong>${this.esc(this.room)}</strong></p>
					<p>Connect the MapLibre GL library to display the interactive map.</p>
					<p style="font-size:12px;color:#555">WebSocket sync: ${this.syncStatus}</p>
				</div>
			</div>

			<div class="controls">
				<button class="ctrl-btn" id="share-location">Share My Location</button>
				<button class="ctrl-btn" id="copy-link">Copy Room Link</button>
			</div>

			<div class="share-link">
				<span>${this.esc(shareUrl)}</span>
				<button class="copy-btn" id="copy-url">Copy</button>
			</div>
		`;
	}

	private attachListeners() {
		this.shadow.getElementById("create-room")?.addEventListener("click", () => this.createRoom());

		this.shadow.querySelectorAll("[data-room]").forEach((el) => {
			el.addEventListener("click", () => {
				const room = (el as HTMLElement).dataset.room!;
				this.joinRoom(room);
			});
		});

		this.shadow.querySelectorAll("[data-back]").forEach((el) => {
			el.addEventListener("click", () => {
				this.view = "lobby";
				this.loadStats();
			});
		});

		this.shadow.getElementById("share-location")?.addEventListener("click", () => {
			if ("geolocation" in navigator) {
				navigator.geolocation.getCurrentPosition(
					(pos) => {
						const btn = this.shadow.getElementById("share-location");
						if (btn) {
							btn.textContent = `Location: ${pos.coords.latitude.toFixed(4)}, ${pos.coords.longitude.toFixed(4)}`;
							btn.classList.add("sharing");
						}
					},
					() => {
						this.error = "Location access denied";
						this.render();
					}
				);
			}
		});

		const copyUrl = this.shadow.getElementById("copy-url") || this.shadow.getElementById("copy-link");
		copyUrl?.addEventListener("click", () => {
			const url = `${window.location.origin}/${this.space}/maps/${this.room}`;
			navigator.clipboard.writeText(url).then(() => {
				if (copyUrl) copyUrl.textContent = "Copied!";
				setTimeout(() => { if (copyUrl) copyUrl.textContent = "Copy"; }, 2000);
			});
		});
	}

	private esc(s: string): string {
		const d = document.createElement("div");
		d.textContent = s || "";
		return d.innerHTML;
	}
}

customElements.define("folk-map-viewer", FolkMapViewer);
