/**
 * <notes-comment-panel> — Right sidebar panel for viewing/managing inline comments.
 *
 * Shows threaded comments anchored to highlighted text in the editor.
 * Comment thread data is stored in Automerge, while the highlight mark
 * position is stored in Yjs (part of the document content).
 *
 * Supports: demo mode (in-memory), emoji reactions, date reminders.
 */

import type { Editor } from '@tiptap/core';
import type { DocumentId } from '../../../shared/local-first/document';
import { getModuleApiBase } from "../../../shared/url-helpers";

interface CommentMessage {
	id: string;
	authorId: string;
	authorName: string;
	text: string;
	createdAt: number;
}

interface CommentThread {
	id: string;
	anchor: string;
	resolved: boolean;
	messages: CommentMessage[];
	createdAt: number;
	reactions?: Record<string, string[]>;
	reminderAt?: number;
	reminderId?: string;
}

interface NotebookDoc {
	items: Record<string, {
		comments?: Record<string, CommentThread>;
		[key: string]: any;
	}>;
	[key: string]: any;
}

const REACTION_EMOJIS = ['👍', '👎', '❤️', '🎉', '😂', '😮', '🔥'];

interface SuggestionEntry {
	id: string;
	type: 'insert' | 'delete';
	text: string;
	authorId: string;
	authorName: string;
	createdAt: number;
}

class NotesCommentPanel extends HTMLElement {
	private shadow: ShadowRoot;
	private _noteId: string | null = null;
	private _doc: any = null;
	private _subscribedDocId: string | null = null;
	private _activeThreadId: string | null = null;
	private _editor: Editor | null = null;
	private _demoThreads: Record<string, CommentThread> | null = null;
	private _space = '';
	private _suggestions: SuggestionEntry[] = [];

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

	set noteId(v: string | null) { this._noteId = v; this.render(); }
	set doc(v: any) { this._doc = v; this.render(); }
	set subscribedDocId(v: string | null) { this._subscribedDocId = v; }
	set activeThreadId(v: string | null) { this._activeThreadId = v; this.render(); }
	set editor(v: Editor | null) { this._editor = v; }
	set space(v: string) { this._space = v; }
	set demoThreads(v: Record<string, CommentThread> | null) {
		this._demoThreads = v;
		this.render();
	}
	set suggestions(v: SuggestionEntry[]) {
		this._suggestions = v;
		this.render();
	}

	private get isDemo(): boolean {
		return this._space === 'demo';
	}

	private getSessionInfo(): { authorName: string; authorId: string } {
		try {
			const sess = JSON.parse(localStorage.getItem('encryptid_session') || '{}');
			const c = sess?.claims;
			return {
				authorName: c?.username || c?.displayName || sess?.username || 'Anonymous',
				authorId: c?.sub || sess?.userId || 'anon',
			};
		} catch {
			return { authorName: 'Anonymous', authorId: 'anon' };
		}
	}

	private getThreads(): CommentThread[] {
		// Demo threads take priority
		if (this._demoThreads) {
			return Object.values(this._demoThreads).sort((a, b) => a.createdAt - b.createdAt);
		}
		if (!this._doc || !this._noteId) return [];
		const item = this._doc.items?.[this._noteId];
		if (!item?.comments) return [];
		return Object.values(item.comments as Record<string, CommentThread>)
			.sort((a, b) => a.createdAt - b.createdAt);
	}

	private dispatchDemoMutation() {
		if (!this._demoThreads || !this._noteId) return;
		this.dispatchEvent(new CustomEvent('comment-demo-mutation', {
			detail: { noteId: this._noteId, threads: { ...this._demoThreads } },
			bubbles: true,
			composed: true,
		}));
	}

	private render() {
		const threads = this.getThreads();
		const suggestions = this._suggestions || [];
		if (threads.length === 0 && suggestions.length === 0 && !this._activeThreadId) {
			this.shadow.innerHTML = '';
			return;
		}

		const esc = (s: string) => { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; };
		const timeAgo = (ts: number) => {
			const diff = Date.now() - ts;
			if (diff < 60000) return 'just now';
			if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
			if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
			return `${Math.floor(diff / 86400000)}d ago`;
		};
		const formatDate = (ts: number) => new Date(ts).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
		const { authorId: currentUserId, authorName: currentUserName } = this.getSessionInfo();
		const initials = (name: string) => name.split(/\s+/).map(w => w[0] || '').join('').slice(0, 2).toUpperCase() || '?';
		const avatarColor = (id: string) => {
			let h = 0;
			for (let i = 0; i < id.length; i++) h = id.charCodeAt(i) + ((h << 5) - h);
			return `hsl(${Math.abs(h) % 360}, 55%, 55%)`;
		};

		this.shadow.innerHTML = `
			<style>
				:host { display: block; font-family: system-ui, -apple-system, sans-serif; font-size: 13px; }
				*, *::before, *::after { box-sizing: border-box; }
				.panel { padding: 8px 10px; overflow-y: auto; max-height: calc(100vh - 180px); }
				.panel.collapsed .thread, .panel.collapsed .panel-empty { display: none; }
				.panel-title {
					font-weight: 600; font-size: 13px; padding: 8px 0;
					color: var(--rs-text-secondary, #666);
					display: flex; justify-content: space-between; align-items: center;
					border-bottom: 1px solid var(--rs-border-subtle, #f0f0f0);
					margin-bottom: 8px;
					cursor: pointer; user-select: none;
				}
				.panel-title:hover { color: var(--rs-text-primary, #111); }
				.collapse-btn {
					border: none; background: none; cursor: pointer; padding: 2px 4px;
					color: var(--rs-text-muted, #999); font-size: 12px; line-height: 1;
					border-radius: 4px; transition: transform 0.15s;
				}
				.collapse-btn:hover { background: var(--rs-bg-hover, #f5f5f5); }
				.panel.collapsed .collapse-btn { transform: rotate(-90deg); }
				.thread {
					margin-bottom: 8px;
					padding: 12px;
					border-radius: 8px;
					background: var(--rs-bg-surface, #fff);
					border: 1px solid var(--rs-border-subtle, #e8e8e8);
					cursor: pointer;
					transition: all 0.15s;
					border-left: 3px solid transparent;
				}
				.thread:hover { box-shadow: 0 1px 4px rgba(0,0,0,0.08); }
				.thread.active {
					border-left-color: #fbbc04;
					box-shadow: 0 1px 6px rgba(251, 188, 4, 0.2);
					background: color-mix(in srgb, #fbbc04 4%, var(--rs-bg-surface, #fff));
				}
				.thread.resolved { opacity: 0.5; }
				.thread.resolved:hover { opacity: 0.7; }

				/* Author row with avatar */
				.thread-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
				.avatar {
					width: 26px; height: 26px; border-radius: 50%;
					display: flex; align-items: center; justify-content: center;
					font-size: 11px; font-weight: 600; color: #fff; flex-shrink: 0;
				}
				.header-info { flex: 1; min-width: 0; }
				.thread-author { font-weight: 600; font-size: 13px; color: var(--rs-text-primary, #111); }
				.thread-time { color: var(--rs-text-muted, #999); font-size: 11px; margin-left: 6px; }

				/* Messages */
				.message { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--rs-border-subtle, #f0f0f0); }
				.message-header { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
				.message-avatar { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; font-weight: 600; color: #fff; flex-shrink: 0; }
				.message-author { font-weight: 500; font-size: 12px; color: var(--rs-text-secondary, #666); }
				.message-time { font-size: 10px; color: var(--rs-text-muted, #aaa); }
				.message-text { margin-top: 2px; color: var(--rs-text-primary, #111); line-height: 1.5; padding-left: 26px; }
				.first-message-text { color: var(--rs-text-primary, #111); line-height: 1.5; }

				/* Reply form — Google Docs style */
				.reply-form { margin-top: 10px; border-top: 1px solid var(--rs-border-subtle, #f0f0f0); padding-top: 10px; }
				.reply-input {
					width: 100%; padding: 6px 8px; border: 1px solid var(--rs-input-border, #ddd);
					border-radius: 6px; font-size: 12px; font-family: inherit;
					background: var(--rs-input-bg, #fff); color: var(--rs-input-text, #111);
				}
				.reply-input:focus { border-color: #1a73e8; outline: none; box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.15); }
				.reply-input::placeholder { color: var(--rs-text-muted, #999); }
				.reply-actions { display: flex; justify-content: flex-end; gap: 6px; margin-top: 6px; }
				.reply-btn {
					padding: 6px 14px; border: none; background: #1a73e8; color: #fff;
					border-radius: 6px; font-size: 12px; cursor: pointer; font-weight: 500;
				}
				.reply-btn:hover { background: #1557b0; }
				.reply-cancel-btn {
					padding: 6px 14px; border: none; background: transparent; color: var(--rs-text-secondary, #666);
					border-radius: 6px; font-size: 12px; cursor: pointer;
				}
				.reply-cancel-btn:hover { background: var(--rs-bg-hover, #f5f5f5); }

				/* Thread actions */
				.thread-actions { display: flex; gap: 2px; margin-top: 8px; justify-content: flex-end; }
				.thread-action {
					padding: 4px 8px; border: none; background: none;
					color: var(--rs-text-muted, #999); cursor: pointer;
					font-size: 11px; border-radius: 4px;
				}
				.thread-action:hover { background: var(--rs-bg-hover, #f5f5f5); color: var(--rs-text-primary, #111); }
				.thread-action.resolve-btn { color: #1a73e8; }
				.thread-action.resolve-btn:hover { background: color-mix(in srgb, #1a73e8 8%, transparent); }

				/* Reactions */
				.reactions-row { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; align-items: center; }
				.reaction-pill { display: inline-flex; align-items: center; gap: 2px; padding: 2px 6px; border-radius: 12px; border: 1px solid var(--rs-border-subtle, #e0e0e0); background: var(--rs-bg-surface, #fff); font-size: 12px; cursor: pointer; transition: all 0.15s; user-select: none; }
				.reaction-pill:hover { border-color: #1a73e8; }
				.reaction-pill.active { border-color: #1a73e8; background: color-mix(in srgb, #1a73e8 10%, transparent); }
				.reaction-pill .count { font-size: 11px; color: var(--rs-text-secondary, #666); }
				.reaction-add { padding: 2px 6px; border-radius: 12px; border: 1px dashed var(--rs-border-subtle, #ddd); background: none; font-size: 12px; cursor: pointer; color: var(--rs-text-muted, #999); }
				.reaction-add:hover { border-color: #1a73e8; color: var(--rs-text-primary, #111); }
				.emoji-picker { display: none; flex-wrap: wrap; gap: 2px; padding: 4px; background: var(--rs-bg-surface, #fff); border: 1px solid var(--rs-border, #e5e7eb); border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.12); margin-top: 4px; }
				.emoji-picker.open { display: flex; }
				.emoji-pick { padding: 4px 6px; border: none; background: none; font-size: 16px; cursor: pointer; border-radius: 4px; }
				.emoji-pick:hover { background: var(--rs-bg-hover, #f5f5f5); }

				/* Reminders */
				.reminder-row { margin-top: 6px; display: flex; align-items: center; gap: 6px; font-size: 12px; }
				.reminder-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 12px; background: color-mix(in srgb, var(--rs-warning, #f59e0b) 15%, transparent); color: var(--rs-text-primary, #111); font-size: 11px; }
				.reminder-btn { padding: 2px 8px; border: 1px solid var(--rs-border-subtle, #ddd); border-radius: 12px; background: none; font-size: 11px; cursor: pointer; color: var(--rs-text-secondary, #666); }
				.reminder-btn:hover { border-color: #1a73e8; }
				.reminder-clear { padding: 1px 4px; border: none; background: none; font-size: 10px; cursor: pointer; color: var(--rs-text-muted, #999); }
				.reminder-date-input { padding: 2px 6px; border: 1px solid var(--rs-input-border, #ddd); border-radius: 6px; font-size: 11px; background: var(--rs-input-bg, #fff); color: var(--rs-input-text, #111); }

				/* ── Suggestion Cards ── */
				.suggestion-section-title {
					font-weight: 600; font-size: 12px; color: #b45309;
					padding: 6px 0 4px; margin-bottom: 4px;
					border-bottom: 1px solid color-mix(in srgb, #f59e0b 20%, var(--rs-border-subtle, #f0f0f0));
				}
				.suggestion-card {
					margin-bottom: 8px; padding: 10px 12px;
					border-radius: 8px; border: 1px solid color-mix(in srgb, #f59e0b 25%, var(--rs-border-subtle, #e8e8e8));
					background: color-mix(in srgb, #f59e0b 4%, var(--rs-bg-surface, #fff));
					border-left: 3px solid #f59e0b;
				}
				.suggestion-card .sg-header { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
				.suggestion-card .sg-avatar {
					width: 22px; height: 22px; border-radius: 50%;
					display: flex; align-items: center; justify-content: center;
					font-size: 9px; font-weight: 600; color: #fff; flex-shrink: 0;
				}
				.suggestion-card .sg-author { font-weight: 600; font-size: 12px; color: var(--rs-text-primary, #111); }
				.suggestion-card .sg-time { font-size: 10px; color: var(--rs-text-muted, #aaa); margin-left: auto; }
				.suggestion-card .sg-type {
					font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 4px;
				}
				.sg-type-insert { background: rgba(22, 163, 74, 0.1); color: #137333; }
				.sg-type-delete { background: rgba(220, 38, 38, 0.1); color: #c5221f; }
				.suggestion-card .sg-text {
					font-size: 13px; line-height: 1.5; padding: 4px 6px;
					border-radius: 4px; margin-bottom: 8px;
					word-break: break-word; overflow-wrap: anywhere;
				}
				.sg-text-insert { background: rgba(22, 163, 74, 0.08); color: var(--rs-text-primary, #111); }
				.sg-text-delete { background: rgba(220, 38, 38, 0.06); color: var(--rs-text-muted, #666); text-decoration: line-through; }
				.suggestion-card .sg-actions { display: flex; gap: 6px; justify-content: flex-end; }
				.sg-accept, .sg-reject {
					padding: 4px 12px; border-radius: 6px; font-size: 12px; font-weight: 500;
					cursor: pointer; border: 1px solid; transition: all 0.15s;
				}
				.sg-accept { color: #137333; border-color: #137333; background: rgba(22, 163, 74, 0.06); }
				.sg-accept:hover { background: rgba(22, 163, 74, 0.15); }
				.sg-reject { color: #c5221f; border-color: #c5221f; background: rgba(220, 38, 38, 0.04); }
				.sg-reject:hover { background: rgba(220, 38, 38, 0.12); }

				/* New comment input — shown when thread has no messages */
				.new-comment-form { margin-top: 4px; }
				.new-comment-input {
					width: 100%; padding: 8px 10px; border: 1px solid var(--rs-input-border, #ddd);
					border-radius: 8px; font-size: 13px; font-family: inherit;
					background: var(--rs-input-bg, #fff); color: var(--rs-input-text, #111);
					resize: vertical; min-height: 52px; max-height: 150px;
				}
				.new-comment-input:focus { border-color: #1a73e8; outline: none; box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.15); }
				.new-comment-actions { display: flex; justify-content: flex-end; gap: 6px; margin-top: 6px; }
				.first-message-text { word-break: break-word; overflow-wrap: anywhere; }
				.message-text { word-break: break-word; overflow-wrap: anywhere; }
				@media (max-width: 480px) {
					.panel { max-height: none; height: 100%; }
					.thread-action { padding: 8px 10px; font-size: 12px; }
					.reply-btn, .reply-cancel-btn { padding: 8px 16px; }
					.reply-input { padding: 8px 10px; font-size: 14px; }
					.emoji-pick { padding: 6px 8px; font-size: 18px; }
					.new-comment-input { min-height: 44px; max-height: 100px; font-size: 14px; }
				}
			</style>
			<div class="panel" id="comment-panel">
				<div class="panel-title" data-action="toggle-collapse">
					<span>${suggestions.length > 0 ? `Suggestions (${suggestions.length})` : ''} ${threads.length > 0 ? `Comments (${threads.filter(t => !t.resolved).length})` : ''}</span>
					<button class="collapse-btn" title="Minimize">&#9660;</button>
				</div>
				${suggestions.length > 0 ? `
					${suggestions.map(s => `
						<div class="suggestion-card" data-suggestion-id="${s.id}">
							<div class="sg-header">
								<div class="sg-avatar" style="background: ${avatarColor(s.authorId)}">${initials(s.authorName)}</div>
								<span class="sg-author">${esc(s.authorName)}</span>
								<span class="sg-type ${s.type === 'insert' ? 'sg-type-insert' : 'sg-type-delete'}">${s.type === 'insert' ? 'Added' : 'Deleted'}</span>
								<span class="sg-time">${timeAgo(s.createdAt)}</span>
							</div>
							<div class="sg-text ${s.type === 'insert' ? 'sg-text-insert' : 'sg-text-delete'}">${esc(s.text)}</div>
							<div class="sg-actions">
								<button class="sg-accept" data-accept-suggestion="${s.id}">Accept</button>
								<button class="sg-reject" data-reject-suggestion="${s.id}">Reject</button>
							</div>
						</div>
					`).join('')}
				` : ''}
				${threads.map(thread => {
					const reactions = thread.reactions || {};
					const reactionEntries = Object.entries(reactions).filter(([, users]) => users.length > 0);
					const isActive = thread.id === this._activeThreadId;
					const hasMessages = thread.messages.length > 0;
					const firstMsg = thread.messages[0];
					const authorName = firstMsg?.authorName || currentUserName;
					const authorId = firstMsg?.authorId || currentUserId;

					return `
					<div class="thread ${isActive ? 'active' : ''} ${thread.resolved ? 'resolved' : ''}" data-thread="${thread.id}">
						<div class="thread-header">
							<div class="avatar" style="background: ${avatarColor(authorId)}">${initials(authorName)}</div>
							<div class="header-info">
								<span class="thread-author">${esc(authorName)}</span>
								<span class="thread-time">${timeAgo(thread.createdAt)}</span>
							</div>
						</div>
						${hasMessages ? `
							<div class="first-message-text">${esc(firstMsg.text)}</div>
							${thread.messages.slice(1).map(msg => `
								<div class="message">
									<div class="message-header">
										<div class="message-avatar" style="background: ${avatarColor(msg.authorId)}">${initials(msg.authorName)}</div>
										<span class="message-author">${esc(msg.authorName)}</span>
										<span class="message-time">${timeAgo(msg.createdAt)}</span>
									</div>
									<div class="message-text">${esc(msg.text)}</div>
								</div>
							`).join('')}
						` : `
							<div class="new-comment-form">
								<textarea class="new-comment-input" placeholder="Add your comment..." data-new-thread="${thread.id}" autofocus></textarea>
								<div class="new-comment-actions">
									<button class="reply-cancel-btn" data-cancel-new="${thread.id}">Cancel</button>
									<button class="reply-btn" data-submit-new="${thread.id}">Comment</button>
								</div>
							</div>
						`}
						${hasMessages && reactionEntries.length > 0 ? `
						<div class="reactions-row">
							${reactionEntries.map(([emoji, users]) => `
								<button class="reaction-pill ${users.includes(currentUserId) ? 'active' : ''}" data-react-thread="${thread.id}" data-react-emoji="${emoji}">${emoji} <span class="count">${users.length}</span></button>
							`).join('')}
							<button class="reaction-add" data-react-add="${thread.id}">+</button>
						</div>
						<div class="emoji-picker" data-picker="${thread.id}">
							${REACTION_EMOJIS.map(e => `<button class="emoji-pick" data-pick-thread="${thread.id}" data-pick-emoji="${e}">${e}</button>`).join('')}
						</div>
						` : ''}
						${hasMessages && thread.reminderAt ? `
						<div class="reminder-row">
							<span class="reminder-badge">&#9200; ${formatDate(thread.reminderAt)}</span>
							<button class="reminder-clear" data-remind-clear="${thread.id}">&#10005;</button>
						</div>
						` : ''}
						${hasMessages ? `
						<div class="reply-form">
							<input class="reply-input" placeholder="Reply..." data-thread="${thread.id}">
							<div class="reply-actions">
								<button class="reply-btn" data-reply="${thread.id}">Reply</button>
							</div>
						</div>
						` : ''}
						<div class="thread-actions">
							${hasMessages ? `
								<button class="reaction-add" data-react-add="${thread.id}" title="React" style="font-size:13px">+</button>
								<button class="thread-action" data-remind-set="${thread.id}" title="Set reminder">&#9200;</button>
								<input type="date" class="reminder-date-input" data-remind-input="${thread.id}" style="display:none">
							` : ''}
							<button class="thread-action resolve-btn" data-resolve="${thread.id}">${thread.resolved ? 'Re-open' : 'Resolve'}</button>
							<button class="thread-action" data-delete="${thread.id}">Delete</button>
						</div>
					</div>`;
				}).join('')}
			</div>
		`;

		this.wireEvents();

		// Auto-focus new comment textarea
		requestAnimationFrame(() => {
			const newInput = this.shadow.querySelector('.new-comment-input') as HTMLTextAreaElement;
			if (newInput) newInput.focus();
		});
	}

	private wireEvents() {
		// Suggestion accept/reject
		this.shadow.querySelectorAll('[data-accept-suggestion]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const id = (btn as HTMLElement).dataset.acceptSuggestion;
				if (id) this.dispatchEvent(new CustomEvent('suggestion-accept', { detail: { suggestionId: id }, bubbles: true, composed: true }));
			});
		});
		this.shadow.querySelectorAll('[data-reject-suggestion]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const id = (btn as HTMLElement).dataset.rejectSuggestion;
				if (id) this.dispatchEvent(new CustomEvent('suggestion-reject', { detail: { suggestionId: id }, bubbles: true, composed: true }));
			});
		});

		// Click suggestion card to scroll editor to it
		this.shadow.querySelectorAll('.suggestion-card[data-suggestion-id]').forEach(el => {
			el.addEventListener('click', (e) => {
				if ((e.target as HTMLElement).closest('button')) return;
				const id = (el as HTMLElement).dataset.suggestionId;
				if (!id || !this._editor) return;
				this._editor.state.doc.descendants((node, pos) => {
					if (!node.isText) return;
					const mark = node.marks.find(m =>
						(m.type.name === 'suggestionInsert' || m.type.name === 'suggestionDelete') &&
						m.attrs.suggestionId === id
					);
					if (mark) {
						this._editor!.commands.setTextSelection(pos);
						this._editor!.commands.scrollIntoView();
						return false;
					}
				});
			});
		});

		// Collapse/expand panel
		const collapseBtn = this.shadow.querySelector('[data-action="toggle-collapse"]');
		if (collapseBtn) {
			collapseBtn.addEventListener('click', (e) => {
				if ((e.target as HTMLElement).closest('.thread, input, textarea, button:not(.collapse-btn)')) return;
				const panel = this.shadow.getElementById('comment-panel');
				if (panel) panel.classList.toggle('collapsed');
			});
		}

		// Click thread to scroll editor to it
		this.shadow.querySelectorAll('.thread[data-thread]').forEach(el => {
			el.addEventListener('click', (e) => {
				// Don't handle clicks on inputs/buttons/textareas
				const target = e.target as HTMLElement;
				if (target.closest('input, textarea, button')) return;
				const threadId = (el as HTMLElement).dataset.thread;
				if (!threadId || !this._editor) return;
				this._activeThreadId = threadId;
				this._editor.state.doc.descendants((node, pos) => {
					if (!node.isText) return;
					const mark = node.marks.find(m => m.type.name === 'comment' && m.attrs.threadId === threadId);
					if (mark) {
						this._editor!.commands.setTextSelection(pos);
						this._editor!.commands.scrollIntoView();
						return false;
					}
				});
				this.render();
			});
		});

		// New comment submit (thread with no messages yet)
		this.shadow.querySelectorAll('[data-submit-new]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const threadId = (btn as HTMLElement).dataset.submitNew;
				if (!threadId) return;
				const textarea = this.shadow.querySelector(`textarea[data-new-thread="${threadId}"]`) as HTMLTextAreaElement;
				const text = textarea?.value?.trim();
				if (!text) return;
				this.addReply(threadId, text);
			});
		});

		// New comment cancel — delete the empty thread
		this.shadow.querySelectorAll('[data-cancel-new]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const threadId = (btn as HTMLElement).dataset.cancelNew;
				if (threadId) this.deleteThread(threadId);
			});
		});

		// New comment textarea — Ctrl+Enter to submit, Escape to cancel
		this.shadow.querySelectorAll('.new-comment-input').forEach(textarea => {
			textarea.addEventListener('keydown', (e) => {
				const ke = e as KeyboardEvent;
				if (ke.key === 'Enter' && (ke.ctrlKey || ke.metaKey)) {
					e.stopPropagation();
					const threadId = (textarea as HTMLTextAreaElement).dataset.newThread;
					const text = (textarea as HTMLTextAreaElement).value.trim();
					if (threadId && text) this.addReply(threadId, text);
				} else if (ke.key === 'Escape') {
					e.stopPropagation();
					const threadId = (textarea as HTMLTextAreaElement).dataset.newThread;
					if (threadId) this.deleteThread(threadId);
				}
			});
			textarea.addEventListener('click', (e) => e.stopPropagation());
		});

		// Reply
		this.shadow.querySelectorAll('[data-reply]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const threadId = (btn as HTMLElement).dataset.reply;
				if (!threadId) return;
				const input = this.shadow.querySelector(`input[data-thread="${threadId}"]`) as HTMLInputElement;
				const text = input?.value?.trim();
				if (!text) return;
				this.addReply(threadId, text);
				input.value = '';
			});
		});

		// Reply on Enter
		this.shadow.querySelectorAll('.reply-input').forEach(input => {
			input.addEventListener('keydown', (e) => {
				if ((e as KeyboardEvent).key === 'Enter') {
					e.stopPropagation();
					const threadId = (input as HTMLInputElement).dataset.thread;
					const text = (input as HTMLInputElement).value.trim();
					if (threadId && text) {
						this.addReply(threadId, text);
						(input as HTMLInputElement).value = '';
					}
				}
			});
			input.addEventListener('click', (e) => e.stopPropagation());
		});

		// Resolve / re-open
		this.shadow.querySelectorAll('[data-resolve]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const threadId = (btn as HTMLElement).dataset.resolve;
				if (threadId) this.toggleResolve(threadId);
			});
		});

		// Delete
		this.shadow.querySelectorAll('[data-delete]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const threadId = (btn as HTMLElement).dataset.delete;
				if (threadId) this.deleteThread(threadId);
			});
		});

		// Reaction pill toggle (existing reaction)
		this.shadow.querySelectorAll('[data-react-thread]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const el = btn as HTMLElement;
				this.toggleReaction(el.dataset.reactThread!, el.dataset.reactEmoji!);
			});
		});

		// Reaction add "+" button — toggle emoji picker
		this.shadow.querySelectorAll('[data-react-add]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const threadId = (btn as HTMLElement).dataset.reactAdd!;
				const picker = this.shadow.querySelector(`[data-picker="${threadId}"]`);
				if (picker) picker.classList.toggle('open');
			});
		});

		// Emoji picker buttons
		this.shadow.querySelectorAll('[data-pick-thread]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const el = btn as HTMLElement;
				this.toggleReaction(el.dataset.pickThread!, el.dataset.pickEmoji!);
				// Close picker
				const picker = this.shadow.querySelector(`[data-picker="${el.dataset.pickThread}"]`);
				if (picker) picker.classList.remove('open');
			});
		});

		// Reminder "set" button
		this.shadow.querySelectorAll('[data-remind-set]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const threadId = (btn as HTMLElement).dataset.remindSet!;
				const input = this.shadow.querySelector(`[data-remind-input="${threadId}"]`) as HTMLInputElement;
				if (input) {
					input.style.display = input.style.display === 'none' ? 'inline-block' : 'none';
					if (input.style.display !== 'none') input.focus();
				}
			});
		});

		// Reminder date change
		this.shadow.querySelectorAll('[data-remind-input]').forEach(input => {
			input.addEventListener('click', (e) => e.stopPropagation());
			input.addEventListener('change', (e) => {
				e.stopPropagation();
				const threadId = (input as HTMLInputElement).dataset.remindInput!;
				const val = (input as HTMLInputElement).value;
				if (val) this.setReminder(threadId, new Date(val + 'T09:00:00').getTime());
			});
		});

		// Reminder clear
		this.shadow.querySelectorAll('[data-remind-clear]').forEach(btn => {
			btn.addEventListener('click', (e) => {
				e.stopPropagation();
				const threadId = (btn as HTMLElement).dataset.remindClear!;
				this.clearReminder(threadId);
			});
		});
	}

	private addReply(threadId: string, text: string) {
		const { authorName, authorId } = this.getSessionInfo();
		const msg: CommentMessage = {
			id: `m_${Date.now()}`,
			authorId,
			authorName,
			text,
			createdAt: Date.now(),
		};

		if (this._demoThreads) {
			const thread = this._demoThreads[threadId];
			if (!thread) return;
			if (!thread.messages) thread.messages = [];
			thread.messages.push(msg);
			this.dispatchDemoMutation();
			this.render();
			return;
		}

		if (!this._noteId || !this._subscribedDocId) return;
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;

		const noteId = this._noteId;
		runtime.change(this._subscribedDocId as DocumentId, 'Add comment reply', (d: NotebookDoc) => {
			const item = d.items[noteId];
			if (!item?.comments?.[threadId]) return;
			const thread = item.comments[threadId] as any;
			if (!thread.messages) thread.messages = [];
			thread.messages.push(msg);
		});
		this._doc = runtime.get(this._subscribedDocId as DocumentId);
		this.render();
	}

	private toggleReaction(threadId: string, emoji: string) {
		const { authorId } = this.getSessionInfo();

		if (this._demoThreads) {
			const thread = this._demoThreads[threadId];
			if (!thread) return;
			if (!thread.reactions) thread.reactions = {};
			if (!thread.reactions[emoji]) thread.reactions[emoji] = [];
			const idx = thread.reactions[emoji].indexOf(authorId);
			if (idx >= 0) thread.reactions[emoji].splice(idx, 1);
			else thread.reactions[emoji].push(authorId);
			this.dispatchDemoMutation();
			this.render();
			return;
		}

		if (!this._noteId || !this._subscribedDocId) return;
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;

		const noteId = this._noteId;
		runtime.change(this._subscribedDocId as DocumentId, 'Toggle reaction', (d: NotebookDoc) => {
			const item = d.items[noteId];
			if (!item?.comments?.[threadId]) return;
			const thread = item.comments[threadId] as any;
			if (!thread.reactions) thread.reactions = {};
			if (!thread.reactions[emoji]) thread.reactions[emoji] = [];
			const users: string[] = thread.reactions[emoji];
			const idx = users.indexOf(authorId);
			if (idx >= 0) users.splice(idx, 1);
			else users.push(authorId);
		});
		this._doc = runtime.get(this._subscribedDocId as DocumentId);
		this.render();
	}

	private async setReminder(threadId: string, reminderAt: number) {
		// Set reminder on thread
		let reminderId: string | undefined;

		// Try creating a reminder via rSchedule API (non-demo only)
		if (!this.isDemo && this._space) {
			try {
				const res = await fetch(`${getModuleApiBase("rschedule")}/api/reminders`, {
					method: 'POST',
					headers: { 'Content-Type': 'application/json', ...this.authHeaders() },
					body: JSON.stringify({
						title: `Comment reminder`,
						remindAt: new Date(reminderAt).toISOString(),
						allDay: true,
						sourceModule: 'rnotes',
						sourceEntityId: threadId,
					}),
				});
				if (res.ok) {
					const data = await res.json();
					reminderId = data.id;
				}
			} catch {}
		}

		if (this._demoThreads) {
			const thread = this._demoThreads[threadId];
			if (thread) {
				thread.reminderAt = reminderAt;
				if (reminderId) thread.reminderId = reminderId;
			}
			this.dispatchDemoMutation();
			this.render();
			return;
		}

		if (!this._noteId || !this._subscribedDocId) return;
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;

		const noteId = this._noteId;
		runtime.change(this._subscribedDocId as DocumentId, 'Set comment reminder', (d: NotebookDoc) => {
			const item = d.items[noteId];
			if (!item?.comments?.[threadId]) return;
			const thread = item.comments[threadId] as any;
			thread.reminderAt = reminderAt;
			if (reminderId) thread.reminderId = reminderId;
		});
		this._doc = runtime.get(this._subscribedDocId as DocumentId);
		this.render();
	}

	private async clearReminder(threadId: string) {
		// Get existing reminderId before clearing
		const threads = this.getThreads();
		const thread = threads.find(t => t.id === threadId);
		const reminderId = thread?.reminderId;

		// Delete from rSchedule if exists
		if (reminderId && !this.isDemo && this._space) {
			try {
				await fetch(`${getModuleApiBase("rschedule")}/api/reminders/${reminderId}`, {
					method: 'DELETE',
					headers: this.authHeaders(),
				});
			} catch {}
		}

		if (this._demoThreads) {
			const t = this._demoThreads[threadId];
			if (t) { delete t.reminderAt; delete t.reminderId; }
			this.dispatchDemoMutation();
			this.render();
			return;
		}

		if (!this._noteId || !this._subscribedDocId) return;
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;

		const noteId = this._noteId;
		runtime.change(this._subscribedDocId as DocumentId, 'Clear comment reminder', (d: NotebookDoc) => {
			const item = d.items[noteId];
			if (!item?.comments?.[threadId]) return;
			const t = item.comments[threadId] as any;
			delete t.reminderAt;
			delete t.reminderId;
		});
		this._doc = runtime.get(this._subscribedDocId as DocumentId);
		this.render();
	}

	private authHeaders(): Record<string, string> {
		try {
			const s = JSON.parse(localStorage.getItem('encryptid_session') || '{}');
			if (s?.accessToken) return { 'Authorization': 'Bearer ' + s.accessToken };
		} catch {}
		return {};
	}

	private toggleResolve(threadId: string) {
		if (this._demoThreads) {
			const thread = this._demoThreads[threadId];
			if (thread) thread.resolved = !thread.resolved;
			this.dispatchDemoMutation();
			// Update editor mark
			this.updateEditorResolveMark(threadId, this._demoThreads[threadId]?.resolved ?? false);
			this.render();
			return;
		}

		if (!this._noteId || !this._subscribedDocId) return;
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;

		const noteId = this._noteId;
		runtime.change(this._subscribedDocId as DocumentId, 'Toggle comment resolve', (d: NotebookDoc) => {
			const item = d.items[noteId];
			if (!item?.comments?.[threadId]) return;
			(item.comments[threadId] as any).resolved = !(item.comments[threadId] as any).resolved;
		});
		this._doc = runtime.get(this._subscribedDocId as DocumentId);

		const thread = this._doc?.items?.[this._noteId]?.comments?.[threadId];
		if (thread) this.updateEditorResolveMark(threadId, thread.resolved);
		this.render();
	}

	private updateEditorResolveMark(threadId: string, resolved: boolean) {
		if (!this._editor) return;
		this._editor.state.doc.descendants((node, pos) => {
			if (!node.isText) return;
			const mark = node.marks.find(m => m.type.name === 'comment' && m.attrs.threadId === threadId);
			if (mark) {
				const { tr } = this._editor!.state;
				tr.removeMark(pos, pos + node.nodeSize, mark);
				tr.addMark(pos, pos + node.nodeSize,
					this._editor!.schema.marks.comment.create({ threadId, resolved })
				);
				this._editor!.view.dispatch(tr);
				return false;
			}
		});
	}

	private deleteThread(threadId: string) {
		if (this._demoThreads) {
			delete this._demoThreads[threadId];
			this.dispatchDemoMutation();
			this.removeEditorCommentMark(threadId);
			if (this._activeThreadId === threadId) this._activeThreadId = null;
			this.render();
			return;
		}

		if (!this._noteId || !this._subscribedDocId) return;
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;

		const noteId = this._noteId;
		runtime.change(this._subscribedDocId as DocumentId, 'Delete comment thread', (d: NotebookDoc) => {
			const item = d.items[noteId];
			if (item?.comments?.[threadId]) {
				delete (item.comments as any)[threadId];
			}
		});
		this._doc = runtime.get(this._subscribedDocId as DocumentId);

		this.removeEditorCommentMark(threadId);
		if (this._activeThreadId === threadId) this._activeThreadId = null;
		this.render();
	}

	private removeEditorCommentMark(threadId: string) {
		if (!this._editor) return;
		const { state } = this._editor;
		const { tr } = state;
		state.doc.descendants((node, pos) => {
			if (!node.isText) return;
			const mark = node.marks.find(m => m.type.name === 'comment' && m.attrs.threadId === threadId);
			if (mark) {
				tr.removeMark(pos, pos + node.nodeSize, mark);
			}
		});
		if (tr.docChanged) {
			this._editor.view.dispatch(tr);
		}
	}
}

customElements.define('notes-comment-panel', NotesCommentPanel);
