/**
 * <folk-chat-app> — local-first chat with channels, threads, DMs, reactions.
 *
 * Single web component per space, loaded on /{space}/rchats.
 * Subscribes to Automerge CRDT docs for real-time sync.
 */

import {
	chatChannelSchema, chatsDirectorySchema,
	chatsDirectoryDocId, chatChannelDocId, dmChannelDocId,
	type ChatChannelDoc, type ChatsDirectoryDoc, type ChatMessage,
	type ChannelInfo, type Transclusion,
} from '../schemas';
import { startPresenceHeartbeat } from '../../../shared/collab-presence';

// ── Helpers ──

function timeAgo(ts: number): string {
	const diff = Date.now() - ts;
	if (diff < 60_000) return 'just now';
	if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
	if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
	return new Date(ts).toLocaleDateString();
}

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

/** Simple markdown: **bold**, *italic*, `code`, [text](url) */
function renderMarkdown(text: string): string {
	let html = escapeHtml(text);
	html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
	html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
	html = html.replace(/`(.+?)`/g, '<code>$1</code>');
	html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
	html = html.replace(/@([a-zA-Z0-9_.-]+)/g, '<span class="mention">@$1</span>');
	return html;
}

function getAuthToken(): string {
	try {
		const stored = localStorage.getItem('rspace_auth');
		if (stored) {
			const parsed = JSON.parse(stored);
			return parsed.token || '';
		}
	} catch {}
	return '';
}

function getMyDid(): string {
	try {
		const stored = localStorage.getItem('rspace_auth');
		if (stored) {
			const parsed = JSON.parse(stored);
			return parsed.did || parsed.userId || '';
		}
	} catch {}
	return '';
}

function getMyName(): string {
	try {
		const stored = localStorage.getItem('rspace_auth');
		if (stored) {
			const parsed = JSON.parse(stored);
			return parsed.displayName || parsed.username || 'Anonymous';
		}
	} catch {}
	return 'Anonymous';
}

// ── Component ──

class FolkChatApp extends HTMLElement {
	private shadow: ShadowRoot;
	private space = '';
	private _offlineUnsubs: (() => void)[] = [];
	private _stopPresence: (() => void) | null = null;

	// State
	private channels: ChannelInfo[] = [];
	private activeChannelId = '';
	private messages: ChatMessage[] = [];
	private threadRootId: string | null = null;
	private threadMessages: ChatMessage[] = [];
	private dmChannels: ChannelInfo[] = [];
	private isDmView = false;
	private activeDmDocId = '';
	private loading = false;
	private composerText = '';
	private threadComposerText = '';
	private showCreateChannel = false;
	private showEmojiPicker: string | null = null; // msgId
	private typingUsers: string[] = [];
	private _typingTimeout: ReturnType<typeof setTimeout> | null = null;

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

	connectedCallback() {
		this.space = this.getAttribute('space') || 'demo';

		// Parse URL params for deep links
		const params = new URLSearchParams(window.location.search);
		const channelParam = params.get('channel');
		const dmParam = params.get('dm');
		const threadParam = params.get('thread');

		if (dmParam) {
			this.isDmView = true;
		}

		this.render();
		this.loadChannels().then(() => {
			if (channelParam && this.channels.find(c => c.id === channelParam)) {
				this.selectChannel(channelParam);
			} else if (this.channels.length > 0) {
				this.selectChannel(this.channels[0].id);
			}
			if (threadParam) {
				this.openThread(threadParam);
			}
		});
		this.loadDmChannels();

		this._stopPresence = startPresenceHeartbeat(() => ({
			module: 'rchats',
			context: this.activeChannelId || 'channels',
		}));
	}

	disconnectedCallback() {
		for (const unsub of this._offlineUnsubs) unsub();
		this._offlineUnsubs = [];
		this._stopPresence?.();
	}

	// ── Data Loading ──

	private async loadChannels() {
		try {
			const token = getAuthToken();
			const headers: Record<string, string> = {};
			if (token) headers['Authorization'] = `Bearer ${token}`;
			const res = await fetch(`/${this.space}/rchats/api/channels`, { headers });
			const data = await res.json();
			this.channels = (data.channels || []).filter((c: ChannelInfo) => !c.isDm);
			this.render();
		} catch { this.channels = []; }
	}

	private async loadDmChannels() {
		try {
			const token = getAuthToken();
			if (!token) return;
			const res = await fetch(`/${this.space}/rchats/api/dm`, {
				headers: { 'Authorization': `Bearer ${token}` },
			});
			const data = await res.json();
			this.dmChannels = data.channels || [];
			this.render();
		} catch {}
	}

	private async loadMessages(channelId: string) {
		this.loading = true;
		this.render();
		try {
			const token = getAuthToken();
			const headers: Record<string, string> = {};
			if (token) headers['Authorization'] = `Bearer ${token}`;
			const res = await fetch(`/${this.space}/rchats/api/channels/${channelId}/messages`, { headers });
			const data = await res.json();
			this.messages = data.messages || [];
		} catch { this.messages = []; }
		this.loading = false;
		this.render();
		this.scrollToBottom();
	}

	private async loadThreadMessages(threadId: string) {
		try {
			const token = getAuthToken();
			const headers: Record<string, string> = {};
			if (token) headers['Authorization'] = `Bearer ${token}`;
			const res = await fetch(`/${this.space}/rchats/api/channels/${this.activeChannelId}/threads/${threadId}`, { headers });
			const data = await res.json();
			this.threadMessages = data.replies || [];
			this.render();
		} catch {}
	}

	// ── Actions ──

	private selectChannel(channelId: string) {
		this.activeChannelId = channelId;
		this.isDmView = false;
		this.threadRootId = null;
		this.threadMessages = [];
		this.loadMessages(channelId);
		// Subscribe to offline runtime for real-time sync
		this.subscribeToChannel(channelId);
	}

	private async subscribeToChannel(channelId: string) {
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;
		try {
			const docId = `${this.space}:chats:channel:${channelId}`;
			await runtime.subscribe(docId, chatChannelSchema);
			const unsub = runtime.onChange(docId, (doc: ChatChannelDoc) => {
				this.messages = Object.values(doc.messages || {})
					.filter(m => !m.threadId)
					.sort((a, b) => a.createdAt - b.createdAt);
				this.render();
				this.scrollToBottom();
			});
			this._offlineUnsubs.push(unsub);
		} catch {}
	}

	private openThread(msgId: string) {
		this.threadRootId = msgId;
		this.threadComposerText = '';
		this.loadThreadMessages(msgId);
	}

	private closeThread() {
		this.threadRootId = null;
		this.threadMessages = [];
		this.threadComposerText = '';
		this.render();
	}

	private async sendMessage() {
		const content = this.composerText.trim();
		if (!content) return;
		const token = getAuthToken();
		if (!token) return;

		this.composerText = '';
		this.render();

		try {
			await fetch(`/${this.space}/rchats/api/channels/${this.activeChannelId}/messages`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
				body: JSON.stringify({ content }),
			});
			await this.loadMessages(this.activeChannelId);
		} catch {}
	}

	private async sendThreadReply() {
		const content = this.threadComposerText.trim();
		if (!content || !this.threadRootId) return;
		const token = getAuthToken();
		if (!token) return;

		this.threadComposerText = '';
		this.render();

		try {
			await fetch(`/${this.space}/rchats/api/channels/${this.activeChannelId}/messages/${this.threadRootId}/thread`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
				body: JSON.stringify({ content }),
			});
			await this.loadThreadMessages(this.threadRootId);
		} catch {}
	}

	private async toggleReaction(msgId: string, emoji: string) {
		const token = getAuthToken();
		if (!token) return;
		try {
			await fetch(`/${this.space}/rchats/api/channels/${this.activeChannelId}/messages/${msgId}/react`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
				body: JSON.stringify({ emoji }),
			});
			await this.loadMessages(this.activeChannelId);
		} catch {}
	}

	private async createChannel() {
		const nameInput = this.shadow.querySelector('#new-channel-name') as HTMLInputElement;
		const descInput = this.shadow.querySelector('#new-channel-desc') as HTMLInputElement;
		if (!nameInput?.value.trim()) return;
		const token = getAuthToken();
		if (!token) return;

		try {
			const res = await fetch(`/${this.space}/rchats/api/channels`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
				body: JSON.stringify({ name: nameInput.value.trim(), description: descInput?.value || '' }),
			});
			if (res.ok) {
				const channel = await res.json();
				this.showCreateChannel = false;
				await this.loadChannels();
				this.selectChannel(channel.id);
			}
		} catch {}
	}

	private async joinChannel(channelId: string) {
		const token = getAuthToken();
		if (!token) return;
		try {
			await fetch(`/${this.space}/rchats/api/channels/${channelId}/join`, {
				method: 'POST',
				headers: { 'Authorization': `Bearer ${token}` },
			});
		} catch {}
	}

	private async deleteMessage(msgId: string) {
		const token = getAuthToken();
		if (!token) return;
		try {
			await fetch(`/${this.space}/rchats/api/channels/${this.activeChannelId}/messages/${msgId}`, {
				method: 'DELETE',
				headers: { 'Authorization': `Bearer ${token}` },
			});
			await this.loadMessages(this.activeChannelId);
		} catch {}
	}

	private async togglePin(msgId: string) {
		const token = getAuthToken();
		if (!token) return;
		try {
			await fetch(`/${this.space}/rchats/api/channels/${this.activeChannelId}/pins`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
				body: JSON.stringify({ messageId: msgId }),
			});
			await this.loadMessages(this.activeChannelId);
		} catch {}
	}

	// ── Scroll ──

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

	// ── Rendering ──

	private render() {
		const myDid = getMyDid();
		const hasAuth = !!getAuthToken();

		this.shadow.innerHTML = `
<style>${STYLES}</style>
<div class="chat-layout ${this.threadRootId ? 'has-thread' : ''}">
	${this.renderSidebar()}
	${this.renderMainArea(myDid, hasAuth)}
	${this.threadRootId ? this.renderThreadPanel(myDid, hasAuth) : ''}
</div>`;

		this.attachEventListeners();
	}

	private renderSidebar(): string {
		return `
<aside class="sidebar">
	<div class="sidebar-header">
		<h2>Channels</h2>
		<button class="btn-icon" id="btn-create-channel" title="Create channel">+</button>
	</div>
	${this.showCreateChannel ? `
	<div class="create-channel-form">
		<input type="text" id="new-channel-name" placeholder="Channel name" maxlength="40">
		<input type="text" id="new-channel-desc" placeholder="Description (optional)" maxlength="120">
		<div class="form-actions">
			<button class="btn-sm btn-primary" id="btn-confirm-create">Create</button>
			<button class="btn-sm btn-ghost" id="btn-cancel-create">Cancel</button>
		</div>
	</div>` : ''}
	<ul class="channel-list">
		${this.channels.map(ch => `
		<li class="channel-item ${ch.id === this.activeChannelId && !this.isDmView ? 'active' : ''}"
			data-channel="${ch.id}">
			<span class="channel-hash">#</span>
			<span class="channel-name">${escapeHtml(ch.name)}</span>
			${ch.isPrivate ? '<span class="channel-lock">🔒</span>' : ''}
		</li>`).join('')}
	</ul>
	<div class="sidebar-section">
		<h3>Direct Messages</h3>
	</div>
	<ul class="channel-list dm-list">
		${this.dmChannels.map(ch => `
		<li class="channel-item dm-item ${ch.id === this.activeChannelId && this.isDmView ? 'active' : ''}"
			data-dm="${ch.id}">
			<span class="dm-avatar">👤</span>
			<span class="channel-name">${escapeHtml(ch.name || 'DM')}</span>
		</li>`).join('')}
	</ul>
</aside>`;
	}

	private renderMainArea(myDid: string, hasAuth: boolean): string {
		const activeChannel = this.channels.find(c => c.id === this.activeChannelId);
		const channelName = activeChannel?.name || this.activeChannelId || 'Select a channel';

		return `
<main class="main-area">
	<div class="channel-header">
		<h2><span class="channel-hash">#</span> ${escapeHtml(channelName)}</h2>
		${activeChannel?.description ? `<span class="channel-desc">${escapeHtml(activeChannel.description)}</span>` : ''}
	</div>
	<div class="message-feed">
		${this.loading ? '<div class="loading">Loading messages...</div>' : ''}
		${!this.loading && this.messages.length === 0 ? `
		<div class="empty-state">
			<div class="empty-icon">💬</div>
			<p>No messages yet. Start the conversation!</p>
		</div>` : ''}
		${this.messages.map(m => this.renderMessage(m, myDid)).join('')}
	</div>
	${this.typingUsers.length > 0 ? `
	<div class="typing-indicator">${this.typingUsers.join(', ')} typing...</div>` : ''}
	${hasAuth ? `
	<div class="composer">
		<textarea class="composer-input" id="composer-main"
			placeholder="Message #${escapeHtml(channelName)}..."
			rows="1">${escapeHtml(this.composerText)}</textarea>
		<button class="btn-send" id="btn-send" title="Send (Enter)">
			<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
				<path d="M22 2L11 13M22 2L15 22L11 13M22 2L2 9L11 13"/>
			</svg>
		</button>
	</div>` : `
	<div class="composer auth-prompt">
		<p>Sign in to send messages</p>
	</div>`}
</main>`;
	}

	private renderMessage(msg: ChatMessage, myDid: string): string {
		const isOwn = msg.authorId === myDid;
		const reactions = msg.reactions || {};
		const reactionKeys = Object.keys(reactions);
		const threadMeta = this.messages.find(m => m.id === msg.id); // for thread count
		const hasThread = this.messages.some(m => m.replyTo === msg.id) ||
			(msg as any)._threadReplyCount > 0;

		return `
<div class="message ${isOwn ? 'own' : ''}" data-msg-id="${msg.id}">
	<div class="msg-avatar">${escapeHtml(msg.authorName.charAt(0).toUpperCase())}</div>
	<div class="msg-body">
		<div class="msg-header">
			<span class="msg-author">${escapeHtml(msg.authorName)}</span>
			<span class="msg-time">${timeAgo(msg.createdAt)}</span>
			${msg.editedAt ? '<span class="msg-edited">(edited)</span>' : ''}
			<div class="msg-actions">
				<button class="msg-action" data-action="react" data-msg="${msg.id}" title="React">😀</button>
				<button class="msg-action" data-action="thread" data-msg="${msg.id}" title="Reply in thread">💬</button>
				<button class="msg-action" data-action="pin" data-msg="${msg.id}" title="Pin">📌</button>
				${isOwn ? `<button class="msg-action" data-action="delete" data-msg="${msg.id}" title="Delete">🗑</button>` : ''}
			</div>
		</div>
		<div class="msg-content">${renderMarkdown(msg.content)}</div>
		${this.renderTransclusions(msg.transclusions || [])}
		${reactionKeys.length > 0 ? `
		<div class="msg-reactions">
			${reactionKeys.map(emoji => {
				const dids = reactions[emoji] || [];
				const active = dids.includes(myDid);
				return `<button class="reaction-chip ${active ? 'active' : ''}"
					data-action="react-toggle" data-msg="${msg.id}" data-emoji="${emoji}">
					${emoji} ${dids.length}
				</button>`;
			}).join('')}
		</div>` : ''}
	</div>
</div>`;
	}

	private renderTransclusions(transclusions: Transclusion[]): string {
		if (!transclusions || transclusions.length === 0) return '';
		return `<div class="transclusions">${transclusions.map(t => {
			const title = t.snapshot?.title || `${t.module}/${t.objectId || t.docId}`;
			const summary = t.snapshot?.summary || '';
			if (t.display === 'link') {
				return `<a class="transclusion-link" href="/${this.space}/${t.module}" title="${escapeHtml(title)}">${escapeHtml(title)}</a>`;
			}
			return `
			<div class="transclusion-card">
				<div class="tc-module">${escapeHtml(t.module)}</div>
				<div class="tc-title">${escapeHtml(title)}</div>
				${summary ? `<div class="tc-summary">${escapeHtml(summary)}</div>` : ''}
			</div>`;
		}).join('')}</div>`;
	}

	private renderThreadPanel(myDid: string, hasAuth: boolean): string {
		const rootMsg = this.messages.find(m => m.id === this.threadRootId);
		return `
<aside class="thread-panel">
	<div class="thread-header">
		<h3>Thread</h3>
		<button class="btn-icon" id="btn-close-thread" title="Close">&times;</button>
	</div>
	<div class="thread-feed">
		${rootMsg ? this.renderMessage(rootMsg, myDid) : ''}
		<div class="thread-divider">${this.threadMessages.length} ${this.threadMessages.length === 1 ? 'reply' : 'replies'}</div>
		${this.threadMessages.map(m => this.renderMessage(m, myDid)).join('')}
	</div>
	${hasAuth ? `
	<div class="composer thread-composer">
		<textarea class="composer-input" id="composer-thread"
			placeholder="Reply in thread..."
			rows="1">${escapeHtml(this.threadComposerText)}</textarea>
		<button class="btn-send" id="btn-send-thread" title="Send reply">
			<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
				<path d="M22 2L11 13M22 2L15 22L11 13M22 2L2 9L11 13"/>
			</svg>
		</button>
	</div>` : ''}
</aside>`;
	}

	// ── Event Listeners ──

	private attachEventListeners() {
		// Channel selection
		this.shadow.querySelectorAll('.channel-item[data-channel]').forEach(el => {
			el.addEventListener('click', () => {
				const channelId = (el as HTMLElement).dataset.channel!;
				this.selectChannel(channelId);
			});
		});

		// Create channel
		this.shadow.getElementById('btn-create-channel')?.addEventListener('click', () => {
			this.showCreateChannel = !this.showCreateChannel;
			this.render();
			if (this.showCreateChannel) {
				setTimeout(() => this.shadow.getElementById('new-channel-name')?.focus(), 50);
			}
		});
		this.shadow.getElementById('btn-confirm-create')?.addEventListener('click', () => this.createChannel());
		this.shadow.getElementById('btn-cancel-create')?.addEventListener('click', () => {
			this.showCreateChannel = false;
			this.render();
		});

		// Main composer
		const mainComposer = this.shadow.getElementById('composer-main') as HTMLTextAreaElement;
		if (mainComposer) {
			mainComposer.addEventListener('input', () => {
				this.composerText = mainComposer.value;
				this.autoResize(mainComposer);
			});
			mainComposer.addEventListener('keydown', (e) => {
				if (e.key === 'Enter' && !e.shiftKey) {
					e.preventDefault();
					this.sendMessage();
				}
			});
		}
		this.shadow.getElementById('btn-send')?.addEventListener('click', () => this.sendMessage());

		// Thread composer
		const threadComposer = this.shadow.getElementById('composer-thread') as HTMLTextAreaElement;
		if (threadComposer) {
			threadComposer.addEventListener('input', () => {
				this.threadComposerText = threadComposer.value;
				this.autoResize(threadComposer);
			});
			threadComposer.addEventListener('keydown', (e) => {
				if (e.key === 'Enter' && !e.shiftKey) {
					e.preventDefault();
					this.sendThreadReply();
				}
			});
		}
		this.shadow.getElementById('btn-send-thread')?.addEventListener('click', () => this.sendThreadReply());

		// Close thread
		this.shadow.getElementById('btn-close-thread')?.addEventListener('click', () => this.closeThread());

		// Message actions
		this.shadow.querySelectorAll('[data-action]').forEach(el => {
			el.addEventListener('click', (e) => {
				const action = (el as HTMLElement).dataset.action!;
				const msgId = (el as HTMLElement).dataset.msg!;
				const emoji = (el as HTMLElement).dataset.emoji;

				switch (action) {
					case 'thread':
						this.openThread(msgId);
						break;
					case 'react':
						this.showQuickReactions(msgId, el as HTMLElement);
						break;
					case 'react-toggle':
						if (emoji) this.toggleReaction(msgId, emoji);
						break;
					case 'pin':
						this.togglePin(msgId);
						break;
					case 'delete':
						if (confirm('Delete this message?')) this.deleteMessage(msgId);
						break;
				}
				e.stopPropagation();
			});
		});
	}

	private autoResize(textarea: HTMLTextAreaElement) {
		textarea.style.height = 'auto';
		textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
	}

	private showQuickReactions(msgId: string, anchor: HTMLElement) {
		const quickEmojis = ['👍', '❤️', '😂', '🎉', '🤔', '👀'];
		// Remove existing picker
		this.shadow.querySelector('.emoji-picker')?.remove();

		const picker = document.createElement('div');
		picker.className = 'emoji-picker';
		picker.innerHTML = quickEmojis.map(e =>
			`<button class="emoji-btn" data-emoji="${e}">${e}</button>`
		).join('');
		picker.style.position = 'absolute';

		const rect = anchor.getBoundingClientRect();
		const shadowRect = this.shadow.host.getBoundingClientRect();
		picker.style.top = (rect.bottom - shadowRect.top) + 'px';
		picker.style.left = (rect.left - shadowRect.left) + 'px';

		picker.querySelectorAll('.emoji-btn').forEach(btn => {
			btn.addEventListener('click', () => {
				const emoji = (btn as HTMLElement).dataset.emoji!;
				this.toggleReaction(msgId, emoji);
				picker.remove();
			});
		});

		// Close on outside click
		const closeHandler = (e: Event) => {
			if (!picker.contains(e.target as Node)) {
				picker.remove();
				document.removeEventListener('click', closeHandler);
			}
		};
		setTimeout(() => document.addEventListener('click', closeHandler), 10);

		this.shadow.querySelector('.chat-layout')?.appendChild(picker);
	}
}

// ── Styles ──

const STYLES = `
:host {
	display: block;
	height: calc(100vh - 60px);
	font-family: var(--rs-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
	color: var(--rs-text-primary, #e2e8f0);
	background: var(--rs-bg-base, #0f172a);
	--chat-sidebar-w: 240px;
	--chat-thread-w: 340px;
	--chat-accent: var(--rs-accent, #6366f1);
	--chat-surface: var(--rs-bg-surface, #1e293b);
	--chat-border: var(--rs-border, #334155);
	--chat-text-secondary: var(--rs-text-secondary, #94a3b8);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

.chat-layout {
	display: grid;
	grid-template-columns: var(--chat-sidebar-w) 1fr;
	height: 100%;
	position: relative;
}
.chat-layout.has-thread {
	grid-template-columns: var(--chat-sidebar-w) 1fr var(--chat-thread-w);
}

/* ── Sidebar ── */

.sidebar {
	background: var(--chat-surface);
	border-right: 1px solid var(--chat-border);
	display: flex;
	flex-direction: column;
	overflow-y: auto;
}

.sidebar-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 16px 12px 8px;
}
.sidebar-header h2 {
	font-size: 0.8rem;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--chat-text-secondary);
	font-weight: 600;
}

.sidebar-section {
	padding: 16px 12px 4px;
}
.sidebar-section h3 {
	font-size: 0.75rem;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--chat-text-secondary);
	font-weight: 600;
}

.btn-icon {
	background: none;
	border: none;
	color: var(--chat-text-secondary);
	font-size: 1.2rem;
	cursor: pointer;
	padding: 2px 6px;
	border-radius: 4px;
	line-height: 1;
}
.btn-icon:hover { background: rgba(255,255,255,0.08); color: var(--rs-text-primary, #e2e8f0); }

.channel-list {
	list-style: none;
	padding: 0 4px;
}

.channel-item {
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 6px 12px;
	border-radius: 6px;
	cursor: pointer;
	font-size: 0.9rem;
	color: var(--chat-text-secondary);
	transition: background 0.15s;
}
.channel-item:hover { background: rgba(255,255,255,0.06); color: var(--rs-text-primary, #e2e8f0); }
.channel-item.active { background: rgba(99,102,241,0.15); color: var(--chat-accent); font-weight: 600; }

.channel-hash { opacity: 0.5; font-weight: 700; }
.channel-lock { font-size: 0.7rem; opacity: 0.5; }
.channel-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.dm-avatar { font-size: 0.85rem; }

.create-channel-form {
	padding: 8px 12px;
	display: flex;
	flex-direction: column;
	gap: 6px;
}
.create-channel-form input {
	background: var(--rs-bg-base, #0f172a);
	border: 1px solid var(--chat-border);
	border-radius: 6px;
	padding: 6px 10px;
	color: var(--rs-text-primary, #e2e8f0);
	font-size: 0.85rem;
	outline: none;
}
.create-channel-form input:focus { border-color: var(--chat-accent); }

.form-actions { display: flex; gap: 6px; }
.btn-sm {
	padding: 4px 12px;
	border-radius: 6px;
	font-size: 0.8rem;
	cursor: pointer;
	border: none;
}
.btn-primary { background: var(--chat-accent); color: white; }
.btn-primary:hover { opacity: 0.9; }
.btn-ghost { background: transparent; color: var(--chat-text-secondary); border: 1px solid var(--chat-border); }
.btn-ghost:hover { background: rgba(255,255,255,0.06); }

/* ── Main Area ── */

.main-area {
	display: flex;
	flex-direction: column;
	min-width: 0;
}

.channel-header {
	display: flex;
	align-items: baseline;
	gap: 12px;
	padding: 12px 20px;
	border-bottom: 1px solid var(--chat-border);
	background: var(--chat-surface);
}
.channel-header h2 { font-size: 1.1rem; font-weight: 700; }
.channel-desc { font-size: 0.82rem; color: var(--chat-text-secondary); }

.message-feed {
	flex: 1;
	overflow-y: auto;
	padding: 16px 20px;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.loading, .empty-state {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	height: 100%;
	color: var(--chat-text-secondary);
	gap: 8px;
}
.empty-icon { font-size: 2.5rem; opacity: 0.4; }
.empty-state p { font-size: 0.9rem; }

/* ── Messages ── */

.message {
	display: flex;
	gap: 10px;
	padding: 6px 8px;
	border-radius: 6px;
	transition: background 0.1s;
}
.message:hover { background: rgba(255,255,255,0.03); }
.message:hover .msg-actions { opacity: 1; }

.msg-avatar {
	width: 36px;
	height: 36px;
	border-radius: 50%;
	background: var(--chat-accent);
	display: flex;
	align-items: center;
	justify-content: center;
	font-weight: 700;
	font-size: 0.85rem;
	color: white;
	flex-shrink: 0;
}

.msg-body { flex: 1; min-width: 0; }

.msg-header {
	display: flex;
	align-items: baseline;
	gap: 8px;
	margin-bottom: 2px;
}
.msg-author { font-weight: 600; font-size: 0.9rem; }
.msg-time { font-size: 0.75rem; color: var(--chat-text-secondary); }
.msg-edited { font-size: 0.7rem; color: var(--chat-text-secondary); font-style: italic; }

.msg-actions {
	margin-left: auto;
	display: flex;
	gap: 2px;
	opacity: 0;
	transition: opacity 0.15s;
}

.msg-action {
	background: none;
	border: none;
	cursor: pointer;
	padding: 2px 4px;
	border-radius: 4px;
	font-size: 0.8rem;
	line-height: 1;
}
.msg-action:hover { background: rgba(255,255,255,0.1); }

.msg-content {
	font-size: 0.9rem;
	line-height: 1.5;
	word-break: break-word;
}
.msg-content code {
	background: rgba(0,0,0,0.3);
	padding: 1px 4px;
	border-radius: 3px;
	font-size: 0.85em;
}
.msg-content a { color: var(--chat-accent); text-decoration: none; }
.msg-content a:hover { text-decoration: underline; }
.msg-content .mention { color: var(--chat-accent); font-weight: 600; cursor: pointer; }

/* ── Reactions ── */

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

.reaction-chip {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	padding: 2px 8px;
	border-radius: 12px;
	font-size: 0.8rem;
	cursor: pointer;
	background: rgba(255,255,255,0.06);
	border: 1px solid transparent;
	color: var(--rs-text-primary, #e2e8f0);
	transition: all 0.15s;
}
.reaction-chip:hover { background: rgba(255,255,255,0.1); }
.reaction-chip.active { border-color: var(--chat-accent); background: rgba(99,102,241,0.15); }

.emoji-picker {
	display: flex;
	gap: 4px;
	padding: 6px 8px;
	background: var(--chat-surface);
	border: 1px solid var(--chat-border);
	border-radius: 8px;
	box-shadow: 0 4px 12px rgba(0,0,0,0.3);
	z-index: 100;
}
.emoji-btn {
	background: none;
	border: none;
	font-size: 1.2rem;
	cursor: pointer;
	padding: 4px;
	border-radius: 4px;
	line-height: 1;
}
.emoji-btn:hover { background: rgba(255,255,255,0.1); }

/* ── Transclusions ── */

.transclusions { margin-top: 6px; display: flex; flex-direction: column; gap: 4px; }

.transclusion-card {
	padding: 8px 12px;
	border-radius: 8px;
	border: 1px solid var(--chat-border);
	background: rgba(255,255,255,0.03);
	max-width: 360px;
}
.tc-module { font-size: 0.7rem; text-transform: uppercase; color: var(--chat-accent); letter-spacing: 0.04em; margin-bottom: 2px; }
.tc-title { font-size: 0.85rem; font-weight: 600; }
.tc-summary { font-size: 0.8rem; color: var(--chat-text-secondary); margin-top: 2px; }

.transclusion-link {
	color: var(--chat-accent);
	text-decoration: none;
	font-size: 0.85rem;
}
.transclusion-link:hover { text-decoration: underline; }

/* ── Composer ── */

.composer {
	display: flex;
	align-items: flex-end;
	gap: 8px;
	padding: 12px 20px 16px;
	border-top: 1px solid var(--chat-border);
	background: var(--chat-surface);
}

.composer-input {
	flex: 1;
	background: var(--rs-bg-base, #0f172a);
	border: 1px solid var(--chat-border);
	border-radius: 8px;
	padding: 10px 14px;
	color: var(--rs-text-primary, #e2e8f0);
	font-size: 0.9rem;
	font-family: inherit;
	resize: none;
	outline: none;
	min-height: 40px;
	max-height: 120px;
	line-height: 1.4;
}
.composer-input:focus { border-color: var(--chat-accent); }
.composer-input::placeholder { color: var(--chat-text-secondary); }

.btn-send {
	background: var(--chat-accent);
	border: none;
	border-radius: 8px;
	padding: 8px 12px;
	cursor: pointer;
	color: white;
	display: flex;
	align-items: center;
	justify-content: center;
	transition: opacity 0.15s;
}
.btn-send:hover { opacity: 0.85; }

.auth-prompt {
	justify-content: center;
}
.auth-prompt p { color: var(--chat-text-secondary); font-size: 0.85rem; }

.typing-indicator {
	padding: 4px 20px;
	font-size: 0.78rem;
	color: var(--chat-text-secondary);
	font-style: italic;
}

/* ── Thread Panel ── */

.thread-panel {
	border-left: 1px solid var(--chat-border);
	background: var(--chat-surface);
	display: flex;
	flex-direction: column;
}

.thread-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 12px 16px;
	border-bottom: 1px solid var(--chat-border);
}
.thread-header h3 { font-size: 1rem; font-weight: 700; }

.thread-feed {
	flex: 1;
	overflow-y: auto;
	padding: 12px;
}

.thread-divider {
	display: flex;
	align-items: center;
	gap: 8px;
	font-size: 0.75rem;
	color: var(--chat-text-secondary);
	margin: 12px 0 8px;
}
.thread-divider::before, .thread-divider::after {
	content: '';
	flex: 1;
	height: 1px;
	background: var(--chat-border);
}

.thread-composer {
	border-top: 1px solid var(--chat-border);
}

/* ── Responsive ── */

@media (max-width: 768px) {
	:host { --chat-sidebar-w: 0px; }
	.sidebar { display: none; }
	.chat-layout.has-thread { grid-template-columns: 1fr; }
	.thread-panel {
		position: fixed;
		top: 60px;
		right: 0;
		bottom: 0;
		width: 100%;
		z-index: 50;
	}
}
`;

customElements.define('folk-chat-app', FolkChatApp);
