/**
 * <rstack-chat-widget> — Global chat panel in the header.
 *
 * Shows a chat icon + unread badge. Click toggles a right-side sliding panel
 * with channel selector, message feed, and composer. Works on every page.
 */

import { getSession } from "./rstack-identity";

const POLL_CLOSED_MS = 30_000; // badge poll when panel closed
const POLL_OPEN_MS = 10_000;   // message poll when panel open (REST fallback)

interface Channel { id: string; name: string; description: string }
interface Message {
	id: string; channelId: string;
	authorId: string; authorName: string;
	content: string; createdAt: number;
	editedAt: number | null;
	reactions: Record<string, string[]>;
}

export class RStackChatWidget extends HTMLElement {
	#shadow: ShadowRoot;
	#open = false;
	#channels: Channel[] = [];
	#activeChannelId = "";
	#messages: Message[] = [];
	#unreadCount = 0;
	#composerText = "";
	#sending = false;
	#loading = false;
	#badgeTimer: ReturnType<typeof setInterval> | null = null;
	#msgTimer: ReturnType<typeof setInterval> | null = null;

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

	get #space(): string {
		return document.body?.getAttribute("data-space-slug") || "";
	}

	get #token(): string | null {
		return getSession()?.accessToken ?? null;
	}

	get #basePath(): string {
		return `/${this.#space}/rchats`;
	}

	// ── Lifecycle ──

	connectedCallback() {
		// Restore persisted state
		try {
			this.#open = localStorage.getItem("rspace_chat_open") === "1";
			this.#activeChannelId = localStorage.getItem(`rspace_chat_channel_${this.#space}`) || "";
		} catch {}

		this.#render();
		this.#fetchUnreadCount();
		this.#badgeTimer = setInterval(() => this.#fetchUnreadCount(), POLL_CLOSED_MS);

		if (this.#open) this.#openPanel();

		document.addEventListener("auth-change", this.#onAuthChange);
	}

	disconnectedCallback() {
		if (this.#badgeTimer) clearInterval(this.#badgeTimer);
		if (this.#msgTimer) clearInterval(this.#msgTimer);
		document.removeEventListener("auth-change", this.#onAuthChange);
	}

	#onAuthChange = () => {
		this.#unreadCount = 0;
		this.#messages = [];
		this.#channels = [];
		this.#render();
		this.#fetchUnreadCount();
	};

	// ── Data fetching ──

	async #fetchUnreadCount() {
		if (!this.#space) return;
		const since = this.#getLastRead();
		try {
			const res = await fetch(`${this.#basePath}/api/unread-count?since=${since}`);
			if (res.ok) {
				const data = await res.json();
				this.#unreadCount = data.count || 0;
				this.#render();
			}
		} catch {}
	}

	async #fetchChannels() {
		if (!this.#space) return;
		try {
			const headers: Record<string, string> = {};
			if (this.#token) headers.Authorization = `Bearer ${this.#token}`;
			const res = await fetch(`${this.#basePath}/api/channels`, { headers });
			if (res.ok) {
				const data = await res.json();
				this.#channels = data.channels || [];
				if (!this.#activeChannelId && this.#channels.length > 0) {
					this.#activeChannelId = this.#channels[0].id;
				}
			}
		} catch {}
	}

	async #fetchMessages() {
		if (!this.#space || !this.#activeChannelId) return;
		try {
			const headers: Record<string, string> = {};
			if (this.#token) headers.Authorization = `Bearer ${this.#token}`;
			const res = await fetch(`${this.#basePath}/api/channels/${this.#activeChannelId}/messages`, { headers });
			if (res.ok) {
				const data = await res.json();
				this.#messages = (data.messages || []).slice(-100); // last 100
				this.#markChannelRead();
				this.#render();
				this.#scrollToBottom();
			}
		} catch {}
	}

	async #sendMessage() {
		if (!this.#composerText.trim() || !this.#token || this.#sending) return;
		this.#sending = true;
		this.#render();

		try {
			// Auto-join channel
			await fetch(`${this.#basePath}/api/channels/${this.#activeChannelId}/join`, {
				method: "POST",
				headers: { Authorization: `Bearer ${this.#token}` },
			}).catch(() => {});

			const res = await fetch(`${this.#basePath}/api/channels/${this.#activeChannelId}/messages`, {
				method: "POST",
				headers: {
					Authorization: `Bearer ${this.#token}`,
					"Content-Type": "application/json",
				},
				body: JSON.stringify({ content: this.#composerText.trim() }),
			});
			if (res.ok) {
				this.#composerText = "";
				await this.#fetchMessages();
			}
		} catch {}
		this.#sending = false;
		this.#render();
		// Re-focus composer
		const input = this.#shadow.querySelector<HTMLTextAreaElement>(".composer-input");
		input?.focus();
	}

	// ── Panel toggle ──

	async #openPanel() {
		this.#open = true;
		try { localStorage.setItem("rspace_chat_open", "1"); } catch {}
		this.#loading = true;
		this.#render();

		await this.#fetchChannels();
		if (this.#activeChannelId) {
			await this.#fetchMessages();
		}
		this.#loading = false;
		this.#render();
		this.#scrollToBottom();

		// Start message polling
		if (this.#msgTimer) clearInterval(this.#msgTimer);
		this.#msgTimer = setInterval(() => this.#fetchMessages(), POLL_OPEN_MS);
	}

	#closePanel() {
		this.#open = false;
		try { localStorage.setItem("rspace_chat_open", "0"); } catch {}
		if (this.#msgTimer) { clearInterval(this.#msgTimer); this.#msgTimer = null; }
		this.#render();
	}

	#togglePanel() {
		if (this.#open) this.#closePanel();
		else this.#openPanel();
	}

	async #switchChannel(id: string) {
		this.#activeChannelId = id;
		try { localStorage.setItem(`rspace_chat_channel_${this.#space}`, id); } catch {}
		this.#messages = [];
		this.#render();
		await this.#fetchMessages();
	}

	// ── Last-read tracking ──

	#getLastRead(): number {
		try {
			const key = `rspace_chat_last_read_${this.#space}`;
			return parseInt(localStorage.getItem(key) || "0", 10) || 0;
		} catch { return 0; }
	}

	#markChannelRead() {
		try {
			const key = `rspace_chat_last_read_${this.#space}`;
			localStorage.setItem(key, String(Date.now()));
		} catch {}
		this.#unreadCount = 0;
	}

	// ── Helpers ──

	#scrollToBottom() {
		requestAnimationFrame(() => {
			const feed = this.#shadow.querySelector(".message-feed");
			if (feed) feed.scrollTop = feed.scrollHeight;
		});
	}

	#timeAgo(ts: number): string {
		const diff = Date.now() - ts;
		const mins = Math.floor(diff / 60_000);
		if (mins < 1) return "now";
		if (mins < 60) return `${mins}m`;
		const hrs = Math.floor(mins / 60);
		if (hrs < 24) return `${hrs}h`;
		return `${Math.floor(hrs / 24)}d`;
	}

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

	// ── Render ──

	#render() {
		const space = this.#space;
		if (!space) { this.#shadow.innerHTML = ""; return; }

		const badge = this.#unreadCount > 0
			? `<span class="badge">${this.#unreadCount > 99 ? "99+" : this.#unreadCount}</span>`
			: "";

		let panelHTML = "";
		if (this.#open) {
			// Channel selector
			const channelOptions = this.#channels.map(ch =>
				`<option value="${this.#escapeHtml(ch.id)}" ${ch.id === this.#activeChannelId ? "selected" : ""}>#${this.#escapeHtml(ch.name)}</option>`
			).join("");

			// Messages
			let feedHTML: string;
			if (this.#loading) {
				feedHTML = `<div class="empty">Loading...</div>`;
			} else if (this.#messages.length === 0) {
				feedHTML = `<div class="empty">No messages yet. Start the conversation!</div>`;
			} else {
				feedHTML = this.#messages.map(m => {
					const reactions = Object.entries(m.reactions || {})
						.filter(([, users]) => users.length > 0)
						.map(([emoji, users]) => `<span class="reaction">${emoji} ${users.length}</span>`)
						.join("");
					return `<div class="msg">
						<div class="msg-header">
							<span class="msg-author">${this.#escapeHtml(m.authorName)}</span>
							<span class="msg-time">${this.#timeAgo(m.createdAt)}</span>
						</div>
						<div class="msg-content">${this.#escapeHtml(m.content)}</div>
						${reactions ? `<div class="msg-reactions">${reactions}</div>` : ""}
					</div>`;
				}).join("");
			}

			// Composer
			const session = getSession();
			const composerHTML = session
				? `<div class="composer">
					<textarea class="composer-input" placeholder="Type a message..." rows="1">${this.#escapeHtml(this.#composerText)}</textarea>
					<button class="composer-send" ${this.#sending ? "disabled" : ""}>
						<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13"/><path d="M22 2L15 22L11 13L2 9L22 2Z"/></svg>
					</button>
				</div>`
				: `<div class="composer-signin">Sign in to chat</div>`;

			panelHTML = `
			<div class="panel open">
				<div class="panel-header">
					<select class="channel-select">${channelOptions}</select>
					<button class="close-btn" title="Close">&times;</button>
				</div>
				<div class="message-feed">${feedHTML}</div>
				${composerHTML}
				<a class="full-link" href="/${this.#escapeHtml(space)}/rchats">Open rChats &rarr;</a>
			</div>`;
		}

		this.#shadow.innerHTML = `
			<style>${STYLES}</style>
			<div class="widget-wrapper">
				<button class="chat-btn" aria-label="Chat">
					<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
						<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
					</svg>
					${badge}
				</button>
				${panelHTML}
			</div>
		`;

		this.#bindEvents();
	}

	#bindEvents() {
		// Toggle button
		this.#shadow.querySelector(".chat-btn")?.addEventListener("click", (e) => {
			e.stopPropagation();
			this.#togglePanel();
		});

		// Close button
		this.#shadow.querySelector(".close-btn")?.addEventListener("click", (e) => {
			e.stopPropagation();
			this.#closePanel();
		});

		// Channel selector
		this.#shadow.querySelector(".channel-select")?.addEventListener("change", (e) => {
			const val = (e.target as HTMLSelectElement).value;
			if (val !== this.#activeChannelId) this.#switchChannel(val);
		});

		// Composer input
		const input = this.#shadow.querySelector<HTMLTextAreaElement>(".composer-input");
		if (input) {
			// Sync text on input
			input.addEventListener("input", () => {
				this.#composerText = input.value;
				// Auto-resize
				input.style.height = "auto";
				input.style.height = Math.min(input.scrollHeight, 80) + "px";
			});
			// Enter to send, Shift+Enter newline
			input.addEventListener("keydown", (e) => {
				if (e.key === "Enter" && !e.shiftKey) {
					e.preventDefault();
					this.#sendMessage();
				}
			});
		}

		// Send button
		this.#shadow.querySelector(".composer-send")?.addEventListener("click", (e) => {
			e.stopPropagation();
			this.#sendMessage();
		});

		// Stop panel clicks from propagating
		this.#shadow.querySelector(".panel")?.addEventListener("pointerdown", (e) => e.stopPropagation());

		// Close on outside click
		if (this.#open) {
			const closeOnOutside = () => {
				if (this.#open) this.#closePanel();
			};
			document.addEventListener("pointerdown", closeOnOutside, { once: true });
		}
	}

	static define(tag = "rstack-chat-widget") {
		if (!customElements.get(tag)) customElements.define(tag, RStackChatWidget);
	}
}

// ============================================================================
// STYLES
// ============================================================================

const STYLES = `
:host {
	display: inline-flex;
	align-items: center;
}

.widget-wrapper {
	position: relative;
}

.chat-btn {
	position: relative;
	background: none;
	border: none;
	color: var(--rs-text-muted, #94a3b8);
	cursor: pointer;
	padding: 6px;
	border-radius: 8px;
	display: flex;
	align-items: center;
	justify-content: center;
	transition: color 0.15s, background 0.15s;
}
.chat-btn:hover, .chat-btn:active {
	color: var(--rs-text-primary, #e2e8f0);
	background: var(--rs-bg-hover, rgba(255,255,255,0.05));
}

.badge {
	position: absolute;
	top: 2px;
	right: 2px;
	min-width: 16px;
	height: 16px;
	border-radius: 8px;
	background: #ef4444;
	color: white;
	font-size: 10px;
	font-weight: 700;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0 4px;
	line-height: 1;
	pointer-events: none;
}

/* ── Panel ── */

.panel {
	position: fixed;
	top: 56px;
	right: 0;
	bottom: 0;
	width: 360px;
	z-index: 200;
	background: var(--rs-bg-surface, #1e293b);
	border-left: 1px solid var(--rs-border, rgba(255,255,255,0.1));
	box-shadow: -4px 0 20px rgba(0,0,0,0.25);
	display: flex;
	flex-direction: column;
	transform: translateX(100%);
	transition: transform 0.2s ease;
}
.panel.open {
	transform: translateX(0);
}

.panel-header {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 10px 12px;
	border-bottom: 1px solid var(--rs-border, rgba(255,255,255,0.1));
	flex-shrink: 0;
}

.channel-select {
	flex: 1;
	background: var(--rs-bg-hover, rgba(255,255,255,0.05));
	color: var(--rs-text-primary, #e2e8f0);
	border: 1px solid var(--rs-border, rgba(255,255,255,0.1));
	border-radius: 6px;
	padding: 6px 8px;
	font-size: 0.8rem;
	cursor: pointer;
	outline: none;
}
.channel-select:focus {
	border-color: var(--rs-accent, #06b6d4);
}
.channel-select option {
	background: var(--rs-bg-surface, #1e293b);
	color: var(--rs-text-primary, #e2e8f0);
}

.close-btn {
	background: none;
	border: none;
	color: var(--rs-text-muted, #94a3b8);
	font-size: 1.25rem;
	cursor: pointer;
	padding: 4px 8px;
	border-radius: 6px;
	line-height: 1;
	transition: color 0.15s, background 0.15s;
}
.close-btn:hover {
	color: var(--rs-text-primary, #e2e8f0);
	background: var(--rs-bg-hover, rgba(255,255,255,0.05));
}

/* ── Message feed ── */

.message-feed {
	flex: 1;
	overflow-y: auto;
	padding: 8px 0;
}

.empty {
	padding: 32px 16px;
	text-align: center;
	color: var(--rs-text-muted, #94a3b8);
	font-size: 0.8rem;
}

.msg {
	padding: 6px 12px;
	transition: background 0.1s;
}
.msg:hover {
	background: var(--rs-bg-hover, rgba(255,255,255,0.03));
}

.msg-header {
	display: flex;
	align-items: baseline;
	gap: 6px;
}

.msg-author {
	font-size: 0.75rem;
	font-weight: 600;
	color: var(--rs-accent, #06b6d4);
}

.msg-time {
	font-size: 0.65rem;
	color: var(--rs-text-muted, #64748b);
}

.msg-content {
	font-size: 0.8rem;
	color: var(--rs-text-primary, #e2e8f0);
	line-height: 1.4;
	white-space: pre-wrap;
	word-break: break-word;
	margin-top: 1px;
}

.msg-reactions {
	display: flex;
	gap: 4px;
	margin-top: 3px;
	flex-wrap: wrap;
}

.reaction {
	font-size: 0.7rem;
	background: var(--rs-bg-hover, rgba(255,255,255,0.05));
	border: 1px solid var(--rs-border, rgba(255,255,255,0.1));
	border-radius: 10px;
	padding: 1px 6px;
	color: var(--rs-text-muted, #94a3b8);
}

/* ── Composer ── */

.composer {
	display: flex;
	align-items: flex-end;
	gap: 6px;
	padding: 8px 12px;
	border-top: 1px solid var(--rs-border, rgba(255,255,255,0.1));
	flex-shrink: 0;
}

.composer-input {
	flex: 1;
	background: var(--rs-bg-hover, rgba(255,255,255,0.05));
	color: var(--rs-text-primary, #e2e8f0);
	border: 1px solid var(--rs-border, rgba(255,255,255,0.1));
	border-radius: 8px;
	padding: 8px 10px;
	font-size: 0.8rem;
	font-family: inherit;
	resize: none;
	outline: none;
	min-height: 36px;
	max-height: 80px;
	line-height: 1.4;
}
.composer-input::placeholder {
	color: var(--rs-text-muted, #64748b);
}
.composer-input:focus {
	border-color: var(--rs-accent, #06b6d4);
}

.composer-send {
	background: var(--rs-accent, #06b6d4);
	border: none;
	color: white;
	border-radius: 8px;
	padding: 8px;
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: center;
	transition: opacity 0.15s;
	flex-shrink: 0;
}
.composer-send:hover { opacity: 0.85; }
.composer-send:disabled { opacity: 0.5; cursor: not-allowed; }

.composer-signin {
	padding: 10px 12px;
	text-align: center;
	color: var(--rs-text-muted, #64748b);
	font-size: 0.8rem;
	border-top: 1px solid var(--rs-border, rgba(255,255,255,0.1));
	flex-shrink: 0;
}

/* ── Full page link ── */

.full-link {
	display: block;
	padding: 8px 12px;
	text-align: center;
	font-size: 0.75rem;
	color: var(--rs-accent, #06b6d4);
	text-decoration: none;
	border-top: 1px solid var(--rs-border, rgba(255,255,255,0.1));
	flex-shrink: 0;
	transition: background 0.15s;
}
.full-link:hover {
	background: var(--rs-bg-hover, rgba(255,255,255,0.05));
}

/* ── Mobile ── */

@media (max-width: 640px) {
	.panel {
		width: 100%;
		left: 0;
	}
}
`;
