/**
 * Chat — Surface Entry Point (v0.2.0)
 *
 * Messaging surface built on chat-core library:
 *   sw.api.ext('chat-core')  — conversation/message CRUD
 *   sw.api.ext('chat')       — typing indicators
 *   sw.realtime              — live events
 *   sw.ui.*                  — primitive components
 *   sw.shell.Topbar          — navigation bar
 */
(async function () {
  'use strict';

  var mount = document.getElementById('extension-mount');
  if (!mount) return;

  var base = window.__BASE__ || '';
  var ver  = window.__VERSION__ || '0';

  // ── Boot SDK ───────────────────────────────
  try {
    if (!window.preact) {
      var { h, render } = await import(base + '/js/sw/vendor/preact.module.js');
      var hooksModule   = await import(base + '/js/sw/vendor/hooks.module.js');
      var htmModule     = await import(base + '/js/sw/vendor/htm.module.js');
      window.preact = { h, render };
      window.hooks  = hooksModule;
      window.html   = htmModule.default.bind(h);
    }
    var sdk = await import(base + '/js/sw/sdk/index.js?v=' + ver);
    await sdk.boot();
  } catch (e) {
    mount.innerHTML = '<p style="color:var(--danger);padding:24px;">SDK boot failed: ' + e.message + '</p>';
    return;
  }

  var { html } = window;
  var { useState, useEffect, useCallback, useRef, useMemo } = hooks;
  var { render } = preact;

  // ── SDK modules ────────────────────────────
  var api     = sw.api.ext('chat-core');
  var chatApi = sw.api.ext('chat');
  var { Button, Spinner, Avatar, Dialog, Tabs } = sw.ui;
  var Topbar = sw.shell.Topbar;

  // Import UserPicker directly (not in sw.ui index)
  var { UserPicker } = await import(base + '/js/sw/primitives/user-picker.js?v=' + ver);

  // ── Helpers ────────────────────────────────
  var _timers = {};
  function debounce(key, fn, ms) {
    clearTimeout(_timers[key]);
    _timers[key] = setTimeout(fn, ms);
  }

  function timeAgo(ts) {
    if (!ts) return '';
    var d = new Date(ts);
    var now = Date.now();
    var diff = Math.floor((now - d.getTime()) / 1000);
    if (diff < 60) return 'now';
    if (diff < 3600) return Math.floor(diff / 60) + 'm';
    if (diff < 86400) return Math.floor(diff / 3600) + 'h';
    if (diff < 604800) return Math.floor(diff / 86400) + 'd';
    return d.toLocaleDateString();
  }

  function truncate(str, len) {
    if (!str) return '';
    return str.length > len ? str.slice(0, len) + '\u2026' : str;
  }

  function currentUserId() {
    return sw.auth?.user?.id || '';
  }

  function currentDisplayName() {
    return sw.auth?.user?.display_name || sw.auth?.user?.username || '';
  }


  // ═══════════════════════════════════════════
  //  ConversationList — left sidebar
  // ═══════════════════════════════════════════

  function ConversationList({ selected, onSelect, onNew, conversations, unread }) {
    var [searchQuery, setSearchQuery] = useState('');
    var [searchResults, setSearchResults] = useState(null);
    var [searching, setSearching] = useState(false);

    function handleSearchInput(e) {
      var q = e.target.value;
      setSearchQuery(q);
      if (q.length < 2) {
        setSearchResults(null);
        setSearching(false);
        return;
      }
      debounce('search', () => {
        setSearching(true);
        api.get('/search?q=' + encodeURIComponent(q)).then(res => {
          setSearchResults(res || { conversations: [], messages: [] });
          setSearching(false);
        }).catch(() => { setSearching(false); });
      }, 300);
    }

    function clearSearch() {
      setSearchQuery('');
      setSearchResults(null);
      setSearching(false);
    }

    function selectFromSearch(cid) {
      clearSearch();
      onSelect(cid);
    }

    // Render search results
    var showSearch = searchResults !== null;
    var sConvs = showSearch ? (searchResults.conversations || []) : [];
    var sMsgs  = showSearch ? (searchResults.messages || []) : [];

    return html`
      <div class="chat-sidebar">
        <div class="chat-sidebar__header">
          <span class="chat-sidebar__title">Conversations</span>
          <${Button} size="sm" onClick=${onNew}>New<//>
        </div>
        <div class="chat-sidebar__search">
          <input class="chat-sidebar__search-input"
                 type="text"
                 value=${searchQuery}
                 placeholder="Search\u2026"
                 onInput=${handleSearchInput} />
          ${searchQuery && html`
            <button class="chat-sidebar__search-clear" onClick=${clearSearch}>\u00d7</button>`}
        </div>
        ${showSearch ? html`
          <div class="chat-sidebar__search-results">
            ${searching && html`<div class="chat-sidebar__search-loading"><${Spinner} size="sm" /></div>`}
            ${!searching && sConvs.length === 0 && sMsgs.length === 0 && html`
              <div class="chat-sidebar__empty">No results</div>`}
            ${sConvs.length > 0 && html`
              <div class="chat-sidebar__search-section">Conversations</div>
              ${sConvs.map(c => html`
                <div key=${c.id} class="chat-sidebar__item" onClick=${() => selectFromSearch(c.id)}>
                  <div class="chat-sidebar__item-top">
                    <span class="chat-sidebar__item-title">${c.title || 'Untitled'}</span>
                    <span class="chat-sidebar__item-time">${timeAgo(c.updated_at || c.created_at)}</span>
                  </div>
                </div>`)}`}
            ${sMsgs.length > 0 && html`
              <div class="chat-sidebar__search-section">Messages</div>
              ${sMsgs.map(m => html`
                <div key=${m.id} class="chat-sidebar__item chat-sidebar__item--search-msg" onClick=${() => selectFromSearch(m.conversation_id)}>
                  <div class="chat-sidebar__item-top">
                    <span class="chat-sidebar__item-preview">${truncate(m.content, 80)}</span>
                  </div>
                  <div class="chat-sidebar__item-bottom">
                    <span class="chat-sidebar__item-time">${timeAgo(m.created_at)}</span>
                  </div>
                </div>`)}`}
          </div>
        ` : html`
          <div class="chat-sidebar__list">
            ${conversations.length === 0 && html`
              <div class="chat-sidebar__empty">No conversations yet</div>`}
            ${conversations.map(c => html`
              <div key=${c.id}
                   class=${'chat-sidebar__item' + (selected === c.id ? ' chat-sidebar__item--active' : '')}
                   onClick=${() => onSelect(c.id)}>
                <div class="chat-sidebar__item-top">
                  <span class="chat-sidebar__item-title">${c.title || 'Untitled'}</span>
                  <span class="chat-sidebar__item-time">${timeAgo(c.updated_at || c.created_at)}</span>
                </div>
                <div class="chat-sidebar__item-bottom">
                  <span class="chat-sidebar__item-preview">
                    ${c.last_message
                      ? truncate(c.last_message.content_type === 'system'
                          ? '\u2022 ' + c.last_message.content
                          : c.last_message.content, 60)
                      : 'No messages yet'}
                  </span>
                  ${(unread[c.id] || 0) > 0 && html`
                    <span class="chat-sidebar__badge">${unread[c.id]}</span>`}
                </div>
              </div>`)}
          </div>
        `}
      </div>`;
  }


  // ═══════════════════════════════════════════
  //  MessageBubble — single message
  // ═══════════════════════════════════════════

  function MessageBubble({ msg, isOwn, onEdit, onDelete }) {
    var [editing, setEditing] = useState(false);
    var [editText, setEditText] = useState('');
    var [hover, setHover] = useState(false);

    if (msg._deleted) {
      return html`
        <div class="chat-msg chat-msg--deleted">
          <em>This message was deleted</em>
        </div>`;
    }

    if (msg.content_type === 'system') {
      return html`
        <div class="chat-msg chat-msg--system">
          <span>${msg.content}</span>
        </div>`;
    }

    function startEdit() {
      setEditText(msg.content);
      setEditing(true);
    }

    function cancelEdit() {
      setEditing(false);
      setEditText('');
    }

    function saveEdit() {
      if (editText.trim() && editText !== msg.content) {
        onEdit(msg.id, editText.trim());
      }
      setEditing(false);
    }

    function onEditKeyDown(e) {
      if (e.key === 'Escape') cancelEdit();
      if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); saveEdit(); }
    }

    return html`
      <div class=${'chat-msg' + (isOwn ? ' chat-msg--own' : '')}
           onMouseEnter=${() => setHover(true)}
           onMouseLeave=${() => setHover(false)}>
        ${!isOwn && html`
          <${Avatar} name=${msg._display_name || msg.participant_id} size="sm" />`}
        <div class="chat-msg__body">
          ${!isOwn && html`<span class="chat-msg__name">${msg._display_name || msg.participant_id}</span>`}
          ${editing ? html`
            <div class="chat-msg__edit">
              <textarea class="chat-msg__edit-input"
                        value=${editText}
                        onInput=${e => setEditText(e.target.value)}
                        onKeyDown=${onEditKeyDown}
                        rows="2" />
              <div class="chat-msg__edit-actions">
                <${Button} size="sm" variant="secondary" onClick=${cancelEdit}>Cancel<//>
                <${Button} size="sm" onClick=${saveEdit}>Save<//>
              </div>
            </div>
          ` : msg.content_type === 'markdown' && sw?.markdown?.ready ? html`
            <div class="chat-msg__content" dangerouslySetInnerHTML=${{ __html: sw.markdown.renderSync(msg.content, { sanitize: true }) }} />` : html`
            <div class="chat-msg__content">${msg.content}</div>`}
          <div class="chat-msg__meta">
            <span class="chat-msg__time">${timeAgo(msg.created_at)}</span>
            ${msg.edited_at && html`<span class="chat-msg__edited">(edited)</span>`}
          </div>
        </div>
        ${hover && isOwn && !editing && html`
          <div class="chat-msg__actions">
            <button class="chat-msg__action" onClick=${startEdit} title="Edit">&#9998;</button>
            <button class="chat-msg__action chat-msg__action--danger" onClick=${() => onDelete(msg.id)} title="Delete">&#128465;</button>
          </div>`}
      </div>`;
  }


  // ═══════════════════════════════════════════
  //  MessageThread — center pane
  // ═══════════════════════════════════════════

  function MessageThread({ conversationId, participants }) {
    var [messages, setMessages] = useState([]);
    var [loading, setLoading] = useState(false);
    var [hasMore, setHasMore] = useState(false);
    var [nextCursor, setNextCursor] = useState('');
    var [typingUsers, setTypingUsers] = useState({});
    var bottomRef = useRef(null);
    var listRef = useRef(null);
    var userId = currentUserId();

    // Build participant lookup
    var partMap = useMemo(() => {
      var m = {};
      (participants || []).forEach(p => { m[p.participant_id] = p.display_name || p.participant_id; });
      return m;
    }, [participants]);

    // Enrich messages with display names
    function enrichMessages(msgs) {
      return msgs.map(m => ({ ...m, _display_name: partMap[m.participant_id] || m.participant_id }));
    }

    // Load initial messages
    useEffect(() => {
      if (!conversationId) { setMessages([]); return; }
      setLoading(true);
      setMessages([]);
      setNextCursor('');
      setHasMore(false);
      api.get('/messages/' + conversationId + '?limit=50').then(res => {
        var data = res || {};
        var msgs = (data.messages || []).reverse();
        setMessages(enrichMessages(msgs));
        setHasMore(!!data.has_more);
        setNextCursor(data.next_cursor || '');
        setLoading(false);
        setTimeout(() => scrollToBottom(), 50);
      }).catch(() => setLoading(false));
    }, [conversationId, partMap]);

    // Load older messages (preserves scroll position)
    function loadMore() {
      if (!hasMore || !nextCursor || loading) return;
      var el = listRef.current;
      var prevHeight = el ? el.scrollHeight : 0;
      setLoading(true);
      api.get('/messages/' + conversationId + '?limit=50&cursor=' + encodeURIComponent(nextCursor)).then(res => {
        var data = res || {};
        var older = (data.messages || []).reverse();
        setMessages(prev => [...enrichMessages(older), ...prev]);
        setHasMore(!!data.has_more);
        setNextCursor(data.next_cursor || '');
        setLoading(false);
        // Restore scroll position after prepending older messages
        requestAnimationFrame(() => {
          if (el) el.scrollTop = el.scrollHeight - prevHeight;
        });
      }).catch(() => setLoading(false));
    }

    function scrollToBottom() {
      if (bottomRef.current) bottomRef.current.scrollIntoView({ behavior: 'auto' });
    }

    // Scroll listener for load-more
    useEffect(() => {
      var el = listRef.current;
      if (!el) return;
      function onScroll() {
        if (el.scrollTop < 80 && hasMore && !loading) loadMore();
      }
      el.addEventListener('scroll', onScroll);
      return () => el.removeEventListener('scroll', onScroll);
    }, [hasMore, loading, nextCursor, conversationId]);

    // Realtime: new messages, edits, deletes
    useEffect(() => {
      if (!conversationId) return;
      var channel = 'conversation:' + conversationId;

      var unsubs = [
        sw.realtime.subscribe(channel, 'message', (payload) => {
          var msg = { ...payload, _display_name: partMap[payload.participant_id] || payload.participant_id };
          setMessages(prev => [...prev, msg]);
          setTimeout(() => scrollToBottom(), 50);
          // Auto mark read if from someone else
          if (payload.participant_id !== userId && payload.id) {
            api.post('/read/' + conversationId, { last_read_message_id: payload.id }).catch(() => {});
          }
        }),
        sw.realtime.subscribe(channel, 'message.edited', (payload) => {
          setMessages(prev => prev.map(m =>
            m.id === payload.id ? { ...m, content: payload.content, edited_at: 'true' } : m
          ));
        }),
        sw.realtime.subscribe(channel, 'message.deleted', (payload) => {
          setMessages(prev => prev.map(m =>
            m.id === payload.id ? { ...m, _deleted: true, content: '' } : m
          ));
        }),
      ];

      // Typing indicators
      var typingTimers = {};
      unsubs.push(sw.realtime.subscribe(channel, 'typing', (payload) => {
        var pid = payload.participant_id;
        if (pid === userId) return;
        setTypingUsers(prev => ({ ...prev, [pid]: payload.display_name || pid }));
        clearTimeout(typingTimers[pid]);
        typingTimers[pid] = setTimeout(() => {
          setTypingUsers(prev => {
            var next = { ...prev };
            delete next[pid];
            return next;
          });
        }, 4000);
      }));

      return () => {
        unsubs.forEach(fn => fn());
        Object.values(typingTimers).forEach(clearTimeout);
        setTypingUsers({});
      };
    }, [conversationId, partMap, userId]);

    // Mark read on first load
    useEffect(() => {
      if (!conversationId || messages.length === 0) return;
      var last = messages[messages.length - 1];
      if (last && last.id) {
        api.post('/read/' + conversationId, { last_read_message_id: last.id }).catch(() => {});
      }
    }, [conversationId, messages.length > 0]);

    // Edit message
    function handleEdit(msgId, newContent) {
      api.put('/messages/' + conversationId + '/' + msgId, { content: newContent }).then(() => {
        setMessages(prev => prev.map(m =>
          m.id === msgId ? { ...m, content: newContent, edited_at: 'true' } : m
        ));
      }).catch(() => {});
    }

    // Delete message
    async function handleDelete(msgId) {
      var ok = await sw.ui.confirm('Delete this message?', { destructive: true });
      if (!ok) return;
      api.del('/messages/' + conversationId + '/' + msgId).then(() => {
        setMessages(prev => prev.map(m =>
          m.id === msgId ? { ...m, _deleted: true, content: '' } : m
        ));
      }).catch(() => {});
    }

    // Typing indicator text
    var typingNames = Object.values(typingUsers);
    var typingText = '';
    if (typingNames.length === 1) typingText = typingNames[0] + ' is typing\u2026';
    else if (typingNames.length === 2) typingText = typingNames.join(' and ') + ' are typing\u2026';
    else if (typingNames.length > 2) typingText = 'Several people are typing\u2026';

    if (!conversationId) {
      return html`
        <div class="chat-thread chat-thread--empty">
          <p>Select a conversation or start a new one</p>
        </div>`;
    }

    return html`
      <div class="chat-thread">
        <div class="chat-thread__messages" ref=${listRef}>
          ${loading && messages.length === 0 && html`<div class="chat-thread__loading"><${Spinner} /></div>`}
          ${loading && messages.length > 0 && html`<div class="chat-thread__loading-more"><${Spinner} size="sm" /></div>`}
          ${hasMore && !loading && html`
            <button class="chat-thread__load-more" onClick=${loadMore}>
              Load older messages
            </button>`}
          ${messages.map(m => html`
            <${MessageBubble}
              key=${m.id}
              msg=${m}
              isOwn=${m.participant_id === userId}
              onEdit=${handleEdit}
              onDelete=${handleDelete}
            />`)}
          <div ref=${bottomRef} />
        </div>
        ${typingText && html`<div class="chat-thread__typing">${typingText}</div>`}
      </div>`;
  }


  // ═══════════════════════════════════════════
  //  ComposeBar — message input
  // ═══════════════════════════════════════════

  function ComposeBar({ conversationId }) {
    var [text, setText] = useState('');
    var [sending, setSending] = useState(false);
    var textareaRef = useRef(null);

    // Reset on conversation change
    useEffect(() => { setText(''); }, [conversationId]);

    // Auto-focus
    useEffect(() => {
      if (textareaRef.current) textareaRef.current.focus();
    }, [conversationId]);

    // Auto-resize
    function autoResize(el) {
      if (!el) return;
      el.style.height = 'auto';
      el.style.height = Math.min(el.scrollHeight, 160) + 'px';
    }

    function handleInput(e) {
      setText(e.target.value);
      autoResize(e.target);
      // Typing indicator (debounced)
      debounce('typing', () => {
        chatApi.post('/typing/' + conversationId, {
          display_name: currentDisplayName(),
        }).catch(() => {});
      }, 3000);
    }

    async function handleSend() {
      var content = text.trim();
      if (!content || sending) return;
      setSending(true);
      try {
        await api.post('/messages/' + conversationId, { content: content, content_type: 'text' });
        setText('');
        if (textareaRef.current) {
          textareaRef.current.style.height = 'auto';
          textareaRef.current.focus();
        }
      } catch (e) {
        console.error('[chat] send failed:', e);
      }
      setSending(false);
    }

    function onKeyDown(e) {
      if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        handleSend();
      }
    }

    if (!conversationId) return null;

    return html`
      <div class="chat-compose">
        <textarea class="chat-compose__input"
                  ref=${textareaRef}
                  value=${text}
                  placeholder="Type a message\u2026"
                  rows="1"
                  onInput=${handleInput}
                  onKeyDown=${onKeyDown} />
        <${Button} onClick=${handleSend} disabled=${!text.trim() || sending}>Send<//>
      </div>`;
  }


  // ═══════════════════════════════════════════
  //  ParticipantSidebar — right panel
  // ═══════════════════════════════════════════

  function ParticipantSidebar({ conversationId, participants, onRefresh, isAdmin }) {
    var [addOpen, setAddOpen] = useState(false);
    var [presence, setPresence] = useState({});

    // Query presence
    useEffect(() => {
      if (!participants || participants.length === 0) return;
      var ids = participants.map(p => p.participant_id).join(',');
      sw.api.get('/api/v1/presence?users=' + ids).then(res => {
        setPresence(res || {});
      }).catch(() => {});
    }, [participants]);

    async function addUser(user) {
      setAddOpen(false);
      await api.post('/participants/' + conversationId, {
        participant_id: user.id,
        display_name: user.display_name || user.username,
      }).catch(() => {});
      onRefresh();
    }

    async function removeUser(pid) {
      var ok = await sw.ui.confirm('Remove this participant?', { destructive: true });
      if (!ok) return;
      await api.del('/participants/' + conversationId + '/' + pid).catch(() => {});
      onRefresh();
    }

    return html`
      <div class="chat-participants">
        <div class="chat-participants__header">
          <span>Participants (${(participants || []).length})</span>
          ${isAdmin && html`<${Button} size="sm" onClick=${() => setAddOpen(true)}>Add<//>` }
        </div>
        <div class="chat-participants__list">
          ${(participants || []).map(p => html`
            <div key=${p.participant_id} class="chat-participants__item">
              <${Avatar} name=${p.display_name || p.participant_id} size="sm" />
              <span class="chat-participants__name">
                ${p.display_name || p.participant_id}
                ${p.role === 'admin' && html`<span class="chat-participants__badge">admin</span>`}
              </span>
              <span class=${'chat-participants__status' + (presence[p.participant_id] ? ' chat-participants__status--online' : '')} />
              ${isAdmin && p.participant_id !== currentUserId() && html`
                <button class="chat-participants__remove" onClick=${() => removeUser(p.participant_id)} title="Remove">\u00d7</button>`}
            </div>`)}
        </div>

        <${Dialog} open=${addOpen} title="Add Participant" onClose=${() => setAddOpen(false)}>
          <${UserPicker} onSelect=${addUser} placeholder="Search users\u2026" autoFocus />
        <//>
      </div>`;
  }


  // ═══════════════════════════════════════════
  //  NewConversationDialog
  // ═══════════════════════════════════════════

  function NewConversationDialog({ open, onClose, onCreated }) {
    var [type, setType] = useState('group');
    var [title, setTitle] = useState('');
    var [selected, setSelected] = useState([]);
    var [creating, setCreating] = useState(false);

    function reset() {
      setType('group');
      setTitle('');
      setSelected([]);
      setCreating(false);
    }

    function addUser(user) {
      // Avoid duplicates
      if (selected.find(u => u.id === user.id)) return;
      // For DM, only allow one user
      if (type === 'direct' && selected.length >= 1) {
        setSelected([user]);
        return;
      }
      setSelected(prev => [...prev, user]);
    }

    function removeSelected(id) {
      setSelected(prev => prev.filter(u => u.id !== id));
    }

    async function handleCreate() {
      if (selected.length === 0) return;
      setCreating(true);
      try {
        var convTitle = type === 'direct'
          ? ''
          : (title.trim() || selected.map(u => u.display_name || u.username).join(', '));
        var participants = selected.map(u => ({
          id: u.id,
          display_name: u.display_name || u.username,
        }));
        var res = await api.post('/conversations', {
          title: convTitle,
          type: type,
          participants: participants,
          creator_display_name: currentDisplayName(),
        });
        reset();
        onClose();
        onCreated(res.id || res.data?.id);
      } catch (e) {
        console.error('[chat] create failed:', e);
      }
      setCreating(false);
    }

    var actions = [
      { label: 'Cancel', variant: 'secondary', onClick: () => { reset(); onClose(); } },
      { label: 'Create', variant: 'primary', onClick: handleCreate, disabled: creating || selected.length === 0 },
    ];

    return html`
      <${Dialog} open=${open} title="New Conversation" onClose=${() => { reset(); onClose(); }} actions=${actions}>
        <div class="chat-new">
          <div class="chat-new__type">
            <label>
              <input type="radio" name="convType" value="group"
                     checked=${type === 'group'} onChange=${() => { setType('group'); setSelected([]); }} />
              Group
            </label>
            <label>
              <input type="radio" name="convType" value="direct"
                     checked=${type === 'direct'} onChange=${() => { setType('direct'); setSelected([]); }} />
              Direct Message
            </label>
          </div>
          ${type === 'group' && html`
            <input class="chat-new__title" type="text" value=${title}
                   placeholder="Conversation title (optional)"
                   onInput=${e => setTitle(e.target.value)} />`}
          <${UserPicker} onSelect=${addUser} placeholder=${type === 'direct' ? 'Search for a user\u2026' : 'Add participants\u2026'} />
          ${selected.length > 0 && html`
            <div class="chat-new__selected">
              ${selected.map(u => html`
                <span key=${u.id} class="chat-new__chip">
                  ${u.display_name || u.username}
                  <button onClick=${() => removeSelected(u.id)}>\u00d7</button>
                </span>`)}
            </div>`}
        </div>
      <//>`;
  }


  // ═══════════════════════════════════════════
  //  ChatApp — root component
  // ═══════════════════════════════════════════

  function ChatApp() {
    var [conversations, setConversations] = useState([]);
    var [unread, setUnread] = useState({});
    var [selectedId, setSelectedId] = useState(null);
    var [participants, setParticipants] = useState([]);
    var [showParticipants, setShowParticipants] = useState(false);
    var [showNew, setShowNew] = useState(false);
    var [loading, setLoading] = useState(true);
    var userId = currentUserId();

    // Load conversations
    // Note: SDK auto-unwraps {data: [...]} envelopes → res is the array directly
    function loadConversations() {
      return api.get('/conversations').then(res => {
        setConversations(Array.isArray(res) ? res : (res && res.data) || []);
      }).catch(() => {});
    }

    // Load unread
    // Note: SDK returns {data: {id: count}} — NOT auto-unwrapped (data is object, not array)
    function loadUnread() {
      return api.get('/unread').then(res => {
        setUnread((res && res.data) || (typeof res === 'object' && !Array.isArray(res) ? res : {}));
      }).catch(() => {});
    }

    // Initial load
    useEffect(() => {
      Promise.all([loadConversations(), loadUnread()]).then(() => setLoading(false));
    }, []);

    // Load participants when conversation changes
    useEffect(() => {
      if (!selectedId) { setParticipants([]); return; }
      api.get('/participants/' + selectedId).then(res => {
        setParticipants(Array.isArray(res) ? res : (res && res.data) || []);
      }).catch(() => {});
    }, [selectedId]);

    // Realtime: refresh conversation list on activity
    useEffect(() => {
      // Subscribe to all active conversations
      var unsubs = [];
      conversations.forEach(c => {
        unsubs.push(sw.realtime.subscribe('conversation:' + c.id, 'message', () => {
          loadConversations();
          loadUnread();
        }));
        unsubs.push(sw.realtime.subscribe('conversation:' + c.id, 'participant.added', () => {
          loadConversations();
          if (c.id === selectedId) {
            api.get('/participants/' + selectedId).then(res => {
              setParticipants(Array.isArray(res) ? res : (res && res.data) || []);
            }).catch(() => {});
          }
        }));
        unsubs.push(sw.realtime.subscribe('conversation:' + c.id, 'participant.removed', () => {
          loadConversations();
          if (c.id === selectedId) {
            api.get('/participants/' + selectedId).then(res => {
              setParticipants(Array.isArray(res) ? res : (res && res.data) || []);
            }).catch(() => {});
          }
        }));
      });
      return () => unsubs.forEach(fn => fn());
    }, [conversations.map(c => c.id).join(','), selectedId]);

    // Select conversation
    function selectConversation(id) {
      setSelectedId(id);
      setShowParticipants(false);
      // Mark read
      setUnread(prev => { var next = { ...prev }; delete next[id]; return next; });
    }

    // After creating a new conversation
    function onCreated(id) {
      loadConversations().then(() => {
        if (id) setSelectedId(id);
      });
    }

    // Is current user admin in selected conversation?
    var isAdmin = useMemo(() => {
      return participants.some(p => p.participant_id === userId && p.role === 'admin');
    }, [participants, userId]);

    // Refresh participants
    function refreshParticipants() {
      if (!selectedId) return;
      api.get('/participants/' + selectedId).then(res => {
        setParticipants(Array.isArray(res) ? res : (res && res.data) || []);
      }).catch(() => {});
    }

    // Conversation title for topbar
    var selectedConv = conversations.find(c => c.id === selectedId);
    var threadTitle = selectedConv ? (selectedConv.title || 'Direct Message') : '';

    if (loading) {
      return html`<div class="chat-loading"><${Spinner} /></div>`;
    }

    return html`
      <div class="chat-app">
        <${Topbar} title="Chat">
          ${selectedId && html`
            <span class="chat-topbar__thread-title">${threadTitle}</span>
            <${Button} size="sm" variant="secondary"
                       onClick=${() => setShowParticipants(!showParticipants)}>
              ${showParticipants ? 'Hide' : 'People'}
            <//>`}
        <//>
        <div class="chat-body">
          <${ConversationList}
            selected=${selectedId}
            onSelect=${selectConversation}
            onNew=${() => setShowNew(true)}
            conversations=${conversations}
            unread=${unread} />
          <div class="chat-main">
            <${MessageThread}
              conversationId=${selectedId}
              participants=${participants} />
            <${ComposeBar} conversationId=${selectedId} />
          </div>
          ${showParticipants && selectedId && html`
            <${ParticipantSidebar}
              conversationId=${selectedId}
              participants=${participants}
              onRefresh=${refreshParticipants}
              isAdmin=${isAdmin} />`}
        </div>
        <${NewConversationDialog}
          open=${showNew}
          onClose=${() => setShowNew(false)}
          onCreated=${onCreated} />
      </div>`;
  }


  // ── Mount ──────────────────────────────────
  render(html`<${ChatApp} />`, mount);

})();
