/**
 * <rstack-collab-overlay> — drop-in multiplayer presence for all rApps.
 *
 * Features:
 *   - "N online" badge (top-right pill with colored dots)
 *   - Remote cursors (SVG arrows with username labels, viewport-relative)
 *   - Focus highlighting (colored outline rings on data-collab-id elements)
 *   - Auto-discovery via rspace-doc-subscribe events from runtime
 *   - Hides on canvas page (rSpace has its own CommunitySync)
 *
 * Attributes:
 *   module-id  — current module identifier
 *   space      — current space slug
 *   doc-id     — explicit doc ID (fallback if auto-discovery misses)
 *   mode       — "badge-only" for iframe/proxy modules (no cursors)
 */

import type { AwarenessMessage } from '../local-first/sync';

// ── Peer color palette (same as canvas PresenceManager) ──
const PEER_COLORS = [
	'#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
	'#ec4899', '#06b6d4', '#f97316', '#14b8a6', '#6366f1',
];

interface PeerState {
	peerId: string;
	username: string;
	color: string;
	cursor: { x: number; y: number } | null;
	selection: string | null;
	lastSeen: number;
}

export class RStackCollabOverlay extends HTMLElement {
	#shadow: ShadowRoot;
	#peers = new Map<string, PeerState>();
	#docId: string | null = null;
	#moduleId: string | null = null;
	#localPeerId: string | null = null;
	#localColor = PEER_COLORS[0];
	#localUsername = 'Anonymous';
	#unsubAwareness: (() => void) | null = null;
	#mouseMoveTimer: ReturnType<typeof setInterval> | null = null;
	#lastCursor = { x: 0, y: 0 };
	#gcInterval: ReturnType<typeof setInterval> | null = null;
	#badgeOnly = false;
	#hidden = false; // true on canvas page

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

	connectedCallback() {
		this.#moduleId = this.getAttribute('module-id');
		this.#badgeOnly = this.getAttribute('mode') === 'badge-only';

		// Hide on canvas page — it has its own CommunitySync + PresenceManager
		if (this.#moduleId === 'rspace') {
			this.#hidden = true;
			return;
		}

		// Explicit doc-id attribute (fallback)
		const explicitDocId = this.getAttribute('doc-id');
		if (explicitDocId) this.#docId = explicitDocId;

		// Listen for runtime doc subscriptions (auto-discovery)
		window.addEventListener('rspace-doc-subscribe', this.#onDocSubscribe);

		// Resolve local identity
		this.#resolveIdentity();

		// Render initial (empty badge)
		this.#render();

		// Try connecting to runtime
		this.#tryConnect();

		// GC stale peers every 5s
		this.#gcInterval = setInterval(() => this.#gcPeers(), 5000);
	}

	disconnectedCallback() {
		if (this.#hidden) return;
		window.removeEventListener('rspace-doc-subscribe', this.#onDocSubscribe);
		this.#unsubAwareness?.();
		this.#stopMouseTracking();
		this.#stopFocusTracking();
		if (this.#gcInterval) clearInterval(this.#gcInterval);
		this.#gcInterval = null;
	}

	// ── Auto-discovery ──

	#onDocSubscribe = (e: Event) => {
		const { docId } = (e as CustomEvent).detail;
		if (!this.#docId && docId) {
			this.#docId = docId;
			this.#connectToDoc();
		}
	};

	// ── Runtime connection ──

	#tryConnect() {
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (runtime?.isInitialized) {
			this.#onRuntimeReady(runtime);
		} else {
			// Retry until runtime is ready
			const check = setInterval(() => {
				const rt = (window as any).__rspaceOfflineRuntime;
				if (rt?.isInitialized) {
					clearInterval(check);
					this.#onRuntimeReady(rt);
				}
			}, 500);
			// Give up after 15s
			setTimeout(() => clearInterval(check), 15000);
		}
	}

	#onRuntimeReady(runtime: any) {
		this.#localPeerId = runtime.peerId;
		// Assign a deterministic color from peer ID
		this.#localColor = this.#colorForPeer(this.#localPeerId!);

		if (this.#docId) {
			this.#connectToDoc();
		}
	}

	#connectToDoc() {
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime || !this.#docId) return;

		// Unsubscribe from previous
		this.#unsubAwareness?.();

		// Listen for remote awareness
		this.#unsubAwareness = runtime.onAwareness(this.#docId, (msg: AwarenessMessage) => {
			if (msg.peer === this.#localPeerId) return; // ignore self
			this.#handleRemoteAwareness(msg);
		});

		// Start broadcasting local presence
		if (!this.#badgeOnly) {
			this.#startMouseTracking();
			this.#startFocusTracking();
		}

		// Send initial presence (announce we're here)
		this.#broadcastPresence();
	}

	// ── Identity ──

	#resolveIdentity() {
		try {
			const raw = localStorage.getItem('encryptid_session');
			if (raw) {
				const session = JSON.parse(raw);
				if (session?.username) this.#localUsername = session.username;
				else if (session?.displayName) this.#localUsername = session.displayName;
			}
		} catch { /* no session */ }
	}

	// ── Remote awareness handling ──

	#handleRemoteAwareness(msg: AwarenessMessage) {
		const existing = this.#peers.get(msg.peer);
		const peer: PeerState = {
			peerId: msg.peer,
			username: msg.username || existing?.username || 'Anonymous',
			color: msg.color || existing?.color || this.#colorForPeer(msg.peer),
			cursor: msg.cursor ?? existing?.cursor ?? null,
			selection: msg.selection ?? existing?.selection ?? null,
			lastSeen: Date.now(),
		};
		this.#peers.set(msg.peer, peer);
		this.#renderBadge();
		if (!this.#badgeOnly) {
			this.#renderCursors();
			this.#renderFocusRings();
		}
	}

	// ── Local broadcasting ──

	#broadcastPresence(cursor?: { x: number; y: number }, selection?: string) {
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime || !this.#docId) return;

		runtime.sendAwareness(this.#docId, {
			cursor,
			selection,
			username: this.#localUsername,
			color: this.#localColor,
		});
	}

	// ── Mouse tracking (15Hz throttle) ──

	#mouseHandler = (e: MouseEvent) => {
		this.#lastCursor = { x: e.clientX, y: e.clientY };
	};

	#startMouseTracking() {
		document.addEventListener('mousemove', this.#mouseHandler, { passive: true });
		// Broadcast at 15Hz
		this.#mouseMoveTimer = setInterval(() => {
			this.#broadcastPresence(this.#lastCursor, undefined);
		}, 67); // ~15Hz
	}

	#stopMouseTracking() {
		document.removeEventListener('mousemove', this.#mouseHandler);
		if (this.#mouseMoveTimer) clearInterval(this.#mouseMoveTimer);
		this.#mouseMoveTimer = null;
	}

	// ── Focus tracking on data-collab-id elements ──

	#focusHandler = (e: FocusEvent) => {
		const target = (e.target as HTMLElement)?.closest?.('[data-collab-id]');
		if (target) {
			const collabId = target.getAttribute('data-collab-id');
			if (collabId) this.#broadcastPresence(undefined, collabId);
		}
	};

	#blurHandler = () => {
		this.#broadcastPresence(undefined, '');
	};

	#startFocusTracking() {
		document.addEventListener('focusin', this.#focusHandler, { passive: true });
		document.addEventListener('click', this.#clickHandler, { passive: true });
		document.addEventListener('focusout', this.#blurHandler, { passive: true });
	}

	#stopFocusTracking() {
		document.removeEventListener('focusin', this.#focusHandler);
		document.removeEventListener('click', this.#clickHandler);
		document.removeEventListener('focusout', this.#blurHandler);
	}

	// Also track clicks on data-collab-id (many elements aren't focusable)
	#clickHandler = (e: MouseEvent) => {
		const target = (e.target as HTMLElement)?.closest?.('[data-collab-id]');
		if (target) {
			const collabId = target.getAttribute('data-collab-id');
			if (collabId) this.#broadcastPresence(undefined, collabId);
		}
	};

	// ── GC stale peers ──

	#gcPeers() {
		const now = Date.now();
		let changed = false;
		for (const [id, peer] of this.#peers) {
			if (now - peer.lastSeen > 15000) {
				this.#peers.delete(id);
				changed = true;
			}
		}
		if (changed) {
			this.#renderBadge();
			if (!this.#badgeOnly) {
				this.#renderCursors();
				this.#renderFocusRings();
			}
		}
	}

	// ── Color assignment ──

	#colorForPeer(peerId: string): string {
		let hash = 0;
		for (let i = 0; i < peerId.length; i++) {
			hash = ((hash << 5) - hash + peerId.charCodeAt(i)) | 0;
		}
		return PEER_COLORS[Math.abs(hash) % PEER_COLORS.length];
	}

	// ── Rendering ──

	#render() {
		this.#shadow.innerHTML = `
			<style>${OVERLAY_CSS}</style>
			<div class="collab-badge" id="badge"></div>
			<div class="collab-cursors" id="cursors"></div>
		`;
	}

	#renderBadge() {
		const badge = this.#shadow.getElementById('badge');
		if (!badge) return;

		const count = this.#peers.size + 1; // +1 for self
		if (count <= 1) {
			badge.innerHTML = '';
			badge.classList.remove('visible');
			return;
		}

		const dots = Array.from(this.#peers.values())
			.slice(0, 5) // show max 5 dots
			.map(p => `<span class="dot" style="background:${p.color}"></span>`)
			.join('');

		badge.innerHTML = `
			<span class="dot" style="background:${this.#localColor}"></span>
			${dots}
			<span class="count">${count} online</span>
		`;
		badge.classList.add('visible');
	}

	#renderCursors() {
		const container = this.#shadow.getElementById('cursors');
		if (!container) return;

		const now = Date.now();
		const fragments: string[] = [];

		for (const peer of this.#peers.values()) {
			if (!peer.cursor) continue;
			const age = now - peer.lastSeen;
			const opacity = age > 5000 ? 0.3 : 1;

			fragments.push(`
				<div class="cursor" style="left:${peer.cursor.x}px;top:${peer.cursor.y}px;opacity:${opacity}">
					<svg width="16" height="20" viewBox="0 0 16 20" fill="none">
						<path d="M1 1L6.5 18L8.5 11L15 9L1 1Z" fill="${peer.color}" stroke="white" stroke-width="1.5"/>
					</svg>
					<span class="cursor-label" style="background:${peer.color}">${this.#escHtml(peer.username)}</span>
				</div>
			`);
		}

		container.innerHTML = fragments.join('');
	}

	#renderFocusRings() {
		// Remove all existing focus rings from the document
		document.querySelectorAll('.rstack-collab-focus-ring').forEach(el => el.remove());

		for (const peer of this.#peers.values()) {
			if (!peer.selection) continue;
			const target = document.querySelector(`[data-collab-id="${CSS.escape(peer.selection)}"]`);
			if (!target) continue;

			const rect = target.getBoundingClientRect();
			const ring = document.createElement('div');
			ring.className = 'rstack-collab-focus-ring';
			ring.style.cssText = `
				position: fixed;
				left: ${rect.left - 3}px;
				top: ${rect.top - 3}px;
				width: ${rect.width + 6}px;
				height: ${rect.height + 6}px;
				border: 2px solid ${peer.color};
				border-radius: 6px;
				pointer-events: none;
				z-index: 9998;
				box-shadow: 0 0 0 1px ${peer.color}33;
				transition: all 0.15s ease;
			`;

			// Username label on the ring
			const label = document.createElement('span');
			label.textContent = peer.username;
			label.style.cssText = `
				position: absolute;
				top: -18px;
				left: 4px;
				font-size: 10px;
				color: white;
				background: ${peer.color};
				padding: 1px 5px;
				border-radius: 3px;
				white-space: nowrap;
				line-height: 14px;
			`;
			ring.appendChild(label);

			document.body.appendChild(ring);
		}
	}

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

	static define() {
		if (!customElements.get('rstack-collab-overlay')) {
			customElements.define('rstack-collab-overlay', RStackCollabOverlay);
		}
	}
}

// ── Styles (inside shadow DOM) ──

const OVERLAY_CSS = `
	:host {
		display: block;
		position: fixed;
		top: 0;
		left: 0;
		width: 0;
		height: 0;
		z-index: 9999;
		pointer-events: none;
	}

	.collab-badge {
		position: fixed;
		top: 8px;
		right: 80px;
		display: none;
		align-items: center;
		gap: 4px;
		padding: 4px 10px 4px 6px;
		border-radius: 16px;
		background: var(--rs-bg-secondary, rgba(30, 30, 30, 0.85));
		backdrop-filter: blur(8px);
		font-size: 11px;
		color: var(--rs-text-secondary, #ccc);
		pointer-events: auto;
		cursor: default;
		user-select: none;
		z-index: 10000;
		border: 1px solid var(--rs-border, rgba(255,255,255,0.08));
	}

	.collab-badge.visible {
		display: inline-flex;
	}

	.dot {
		width: 7px;
		height: 7px;
		border-radius: 50%;
		flex-shrink: 0;
	}

	.count {
		margin-left: 2px;
		font-weight: 500;
	}

	.collab-cursors {
		position: fixed;
		top: 0;
		left: 0;
		width: 100vw;
		height: 100vh;
		pointer-events: none;
		z-index: 9999;
		overflow: hidden;
	}

	.cursor {
		position: fixed;
		pointer-events: none;
		transition: left 0.1s linear, top 0.1s linear, opacity 0.3s ease;
		z-index: 9999;
	}

	.cursor-label {
		position: absolute;
		left: 14px;
		top: 14px;
		font-size: 10px;
		color: white;
		padding: 1px 5px;
		border-radius: 3px;
		white-space: nowrap;
		line-height: 14px;
		font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
	}
`;
