  // ── Chatbox wired to /api/command-center/stream ──────────────
  // Mirrors the full dashboard's command center: SSE streaming, tool-call
  // progress, abort, and per-tab session continuity.
  var SLIM_STORAGE_KEY = 'slim-cc-state-v1';
  var SLIM_TAB_KEY     = 'slim-cc-tabid-v1';
  var SLIM_PROJECT_KEY = 'slim-current-project-v1';
  var SLIM_MAX_MESSAGES = 30;
  var STREAM_TIMEOUT_MS = (60 * 60 * 1000) + 60000;

  var sessionId = null;
  var messages = [];
  var sending = false;
  var abortController = null;
  var currentProject = null;
  try { currentProject = localStorage.getItem(SLIM_PROJECT_KEY) || null; } catch (_e) { /* private mode */ }

  var tabId = (function() {
    try {
      var t = sessionStorage.getItem(SLIM_TAB_KEY);
      if (t) return t;
      t = 'slim-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
      sessionStorage.setItem(SLIM_TAB_KEY, t);
      return t;
    } catch (_e) {
      return 'slim-' + Date.now().toString(36);
    }
  })();

  var msgsEl  = document.getElementById('chat-messages');
  var inputEl = document.getElementById('chat-input');
  var sendBtn = document.getElementById('chat-send');
  var stopBtn = document.getElementById('chat-stop');

  function loadState() {
    try {
      var raw = localStorage.getItem(SLIM_STORAGE_KEY);
      if (!raw) return;
      var data = JSON.parse(raw) || {};
      sessionId = data.sessionId || null;
      messages = Array.isArray(data.messages) ? data.messages.slice(-SLIM_MAX_MESSAGES) : [];
    } catch (_e) { /* ignore */ }
  }
  var saveDebounce = null;
  function saveState() {
    if (saveDebounce) return;
    saveDebounce = setTimeout(function() {
      saveDebounce = null;
      try {
        localStorage.setItem(SLIM_STORAGE_KEY, JSON.stringify({
          sessionId: sessionId,
          messages: messages.slice(-SLIM_MAX_MESSAGES),
        }));
      } catch (_e) { /* localStorage full */ }
    }, 300);
  }

  function escHtmlChat(s) {
    return String(s == null ? '' : s)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  }
  function renderMarkdown(text) {
    if (!text) return '';
    var html = escHtmlChat(text);
    var codeSlots = [];
    html = html.replace(/```(\w*)\n?([\s\S]*?)```/g, function(_, _lang, code) {
      codeSlots.push('<pre><code>' + code + '</code></pre>');
      return '\u0001CB' + (codeSlots.length - 1) + '\u0001';
    });
    html = html.replace(/`([^`\n]+)`/g, function(_, code) {
      codeSlots.push('<code>' + code + '</code>');
      return '\u0001CB' + (codeSlots.length - 1) + '\u0001';
    });
    html = html.replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>');
    html = html.replace(/(^|[^*\w])\*([^*\n]+)\*(?!\w)/g, '$1<em>$2</em>');
    html = html.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, function(_, label, href) {
      if (!/^(https?:|mailto:|\/)/i.test(href)) return label;
      return '<a href="' + href + '" target="_blank" rel="noopener">' + label + '</a>';
    });
    html = html.replace(/\u0001CB(\d+)\u0001/g, function(_, i) { return codeSlots[+i] || ''; });
    return html;
  }

  // Reconcile a streamed `chunk` frame against the text accumulated so far.
  // The CC server emits the FULL accumulated text in each chunk (not a delta),
  // but the pool path and re-attach snapshots can resend or partially overlap.
  // Naive `+=` therefore duplicates ("Let me…Let me locate…"); this collapses
  // every case: identical, cumulative extension, stale prefix, or a tail/head
  // overlap, falling back to append only when there's no overlap.
  function mergeStreamText(prev, incoming) {
    var current = prev || '';
    var next = incoming || '';
    if (!current) return next;
    if (!next) return current;
    if (next === current) return current;
    if (next.indexOf(current) === 0) return next;        // incoming extends prev → replace
    if (current.indexOf(next) === 0) return current;      // stale/short frame → keep
    for (var overlap = Math.min(current.length, next.length); overlap > 0; overlap--) {
      if (current.slice(-overlap) === next.slice(0, overlap)) return current + next.slice(overlap);
    }
    return current + next;
  }

  function clearEmpty() {
    var empty = msgsEl.querySelector('.chat-empty');
    if (empty) empty.remove();
  }
  function scrollToBottom() {
    msgsEl.scrollTop = msgsEl.scrollHeight;
  }
  function appendBubble(role, text, toolCalls) {
    clearEmpty();
    var div = document.createElement('div');
    div.className = 'chat-msg ' + role;
    if (role === 'assistant') {
      var body = document.createElement('div');
      body.className = 'chat-body';
      // eslint-disable-next-line no-unsanitized/property -- reason: renderMarkdown() escapes input via escHtmlChat() first; later substitutions only re-insert pre-escaped <pre><code>/<a>/<strong>/<em> spans with scheme-validated hrefs
      body.innerHTML = renderMarkdown(text);
      div.appendChild(body);
      // Replay the collapsed tool panel for responses that ran tools.
      if (toolCalls && toolCalls.length) {
        var toolbar = document.createElement('div');
        toolbar.className = 'chat-toolbar';
        var tp = buildToolsPanel(toolCalls);
        toolbar.appendChild(tp.panel);
        div.appendChild(toolbar);
        tp.scroll.scrollTop = tp.scroll.scrollHeight;
      }
    } else {
      div.textContent = text;
    }
    msgsEl.appendChild(div);
    scrollToBottom();
    return div;
  }
  function appendActionStatus(severity, label) {
    var div = document.createElement('div');
    div.className = 'chat-action ' + (severity || '');
    div.textContent = label;
    msgsEl.appendChild(div);
    scrollToBottom();
    return div;
  }

  // Render a tool invocation as a one-line string. `full` keeps the complete
  // command/args (used by the tool-calls modal); otherwise long values are
  // clipped for the collapsed 3-line panel.
  function formatTool(name, input, full) {
    var inp = input || {};
    function clip(s, n) {
      s = String(s == null ? '' : s);
      return (!full && s.length > n) ? s.slice(0, n - 3) + '...' : s;
    }
    switch (name) {
      case 'Bash':  return '$ ' + clip(inp.command, 80);
      case 'Read':  return 'Reading ' + (inp.file_path || '');
      case 'Edit':  return 'Editing ' + (inp.file_path || '');
      case 'Write': return 'Writing ' + (inp.file_path || '');
      case 'Grep':  return 'Searching `' + (inp.pattern || '') + '` in ' + (inp.path || '.');
      case 'Glob':  return 'Glob ' + (inp.pattern || '');
      case 'WebFetch':  return 'Fetch ' + (inp.url || '');
      case 'WebSearch': return 'Search "' + (inp.query || '') + '"';
      case 'TodoWrite': {
        var items = Array.isArray(inp.todos) ? inp.todos : [];
        return 'Update todos (' + items.length + ' items)';
      }
      default: {
        var keys = Object.keys(inp);
        if (keys.length === 0) return name + '()';
        if (full) {
          return name + '(' + keys.map(function(k) { return k + ': ' + String(inp[k]); }).join(', ') + ')';
        }
        return name + '(' + keys[0] + ': ' + clip(inp[keys[0]], 40) + ')';
      }
    }
  }

  // A stored tool call is { name, input }; tolerate legacy plain-string entries.
  function toolLineText(item, full) {
    if (typeof item === 'string') return item;
    return formatTool(item && item.name, item && item.input, full);
  }

  function makeToolLine(text) {
    var line = document.createElement('div');
    line.className = 'chat-tool';
    line.textContent = text;
    return line;
  }

  // Open the tool-calls modal showing the full list (long commands wrapped).
  function openToolsModal(list) {
    var modal = document.getElementById('slim-tools-modal');
    var body = document.getElementById('slim-tools-body');
    var titleEl = document.getElementById('slim-tools-title');
    if (!modal || !body) return;
    if (titleEl) titleEl.textContent = 'Tool calls (' + (list ? list.length : 0) + ')';
    body.textContent = '';
    if (!list || !list.length) {
      var none = document.createElement('div');
      none.className = 'tools-modal-empty';
      none.textContent = 'No tool calls.';
      body.appendChild(none);
    } else {
      list.forEach(function(t) {
        var row = document.createElement('div');
        row.className = 'tools-modal-line';
        row.textContent = toolLineText(t, true);
        body.appendChild(row);
      });
    }
    modal.classList.add('open');
  }

  // Build the collapsed tool panel (max 3 visible lines, scrolls, click -> modal).
  // `list` is the backing array of formatted summaries — shared by reference so
  // a live stream can push into it and the modal always shows the latest set.
  function buildToolsPanel(list) {
    var panel = document.createElement('div');
    panel.className = 'chat-tools-panel';
    panel.setAttribute('role', 'button');
    panel.setAttribute('tabindex', '0');
    panel.title = 'View all tool calls';
    var scroll = document.createElement('div');
    scroll.className = 'chat-tools';
    list.forEach(function(t) { scroll.appendChild(makeToolLine(toolLineText(t, false))); });
    panel.appendChild(scroll);
    function open() { openToolsModal(list); }
    panel.addEventListener('click', open);
    panel.addEventListener('keydown', function(ev) {
      if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); open(); }
    });
    return { panel: panel, scroll: scroll };
  }

  function buildStreamBubble() {
    clearEmpty();
    var div = document.createElement('div');
    div.className = 'chat-msg assistant streaming';

    var body = document.createElement('div');
    body.className = 'chat-body';
    div.appendChild(body);

    // Bottom toolbar: thinking indicator (left) + tool panel (right). The panel
    // is hidden until the first tool call arrives.
    var toolbar = document.createElement('div');
    toolbar.className = 'chat-toolbar';
    div.appendChild(toolbar);

    var thinking = document.createElement('div');
    thinking.className = 'chat-thinking';
    thinking.innerHTML = 'Thinking<span class="chat-thinking-dots"><span></span><span></span><span></span></span>';
    toolbar.appendChild(thinking);

    var toolList = [];
    var tp = buildToolsPanel(toolList);
    tp.panel.style.display = 'none';
    toolbar.appendChild(tp.panel);

    msgsEl.appendChild(div);
    scrollToBottom();

    return {
      root: div,
      body: body,
      thinking: thinking,
      getToolList: function() { return toolList; },
      addTool: function(name, input) {
        toolList.push({ name: name, input: input });
        tp.scroll.appendChild(makeToolLine(formatTool(name, input, false)));
        tp.panel.style.display = '';
        tp.scroll.scrollTop = tp.scroll.scrollHeight;  // newest at bottom; older scroll off the top
        scrollToBottom();
      },
      setText: function(text) {
        // eslint-disable-next-line no-unsanitized/property -- reason: renderMarkdown() escapes input via escHtmlChat() first; later substitutions only re-insert pre-escaped <pre><code>/<a>/<strong>/<em> spans with scheme-validated hrefs
        body.innerHTML = renderMarkdown(text || '');
        scrollToBottom();
      },
      finalize: function(text) {
        if (thinking && thinking.parentNode) thinking.parentNode.removeChild(thinking);
        // eslint-disable-next-line no-unsanitized/property -- reason: renderMarkdown() escapes input via escHtmlChat() first; later substitutions only re-insert pre-escaped <pre><code>/<a>/<strong>/<em> spans with scheme-validated hrefs
        body.innerHTML = renderMarkdown(text || '');
        div.classList.remove('streaming');
        // No tools ran — drop the now-empty toolbar entirely.
        if (!toolList.length && toolbar.parentNode) toolbar.parentNode.removeChild(toolbar);
        scrollToBottom();
      },
      replaceWithError: function(message) {
        if (thinking && thinking.parentNode) thinking.parentNode.removeChild(thinking);
        div.className = 'chat-msg error';
        div.innerHTML = '';
        div.textContent = message;
        scrollToBottom();
      },
    };
  }

  function setSending(on) {
    sending = on;
    sendBtn.disabled = on;
    sendBtn.textContent = on ? 'Sending…' : 'Send';
    stopBtn.style.display = on ? 'block' : 'none';
    inputEl.disabled = on;
  }

  function rerenderHistory() {
    msgsEl.innerHTML = '';
    if (!messages.length) {
      var empty = document.createElement('div');
      empty.className = 'chat-empty';
      empty.textContent = 'No messages yet — say hi to Command Center.';
      msgsEl.appendChild(empty);
      return;
    }
    for (var i = 0; i < messages.length; i++) {
      var m = messages[i];
      if (m.role === 'action') appendActionStatus(m.severity || '', m.text || '');
      else appendBubble(m.role, m.text || '', m.toolCalls);
    }
  }
  loadState();
  rerenderHistory();

