/**
 * chat.mjs — AI-Do v4.0 chat component
 * Manages the central chat view with streaming support, markdown rendering,
 * code block copy, voice/upload buttons, and glassmorphic welcome screen.
 */

import store from '../store.mjs';
import eventBus, { EVENTS } from '../event-bus.mjs';
import api from '../api.mjs';
import { $, $$, createElement, icon, escapeHtml, empty } from '../utils/dom.mjs';
import { relativeTime } from '../utils/format.mjs';
import { renderMarkdown } from '../utils/markdown.mjs';
import { openVoiceMode } from './voice-mode.mjs';

import { createPanel as createPreviewPanel, toggle as togglePreview } from './preview-panel.mjs';

// ── DOM references ──
let container = null;
let welcomeEl = null;
let messagesEl = null;
let inputArea = null;
let textarea = null;
let sendBtn = null;
let fileInput = null;
let streamingBubble = null;
let chatHeader = null;
let attachmentStrip = null;

// ── Pending file attachments ──
let pendingFiles = []; // Array of { file: File, id: string, previewUrl?: string }

// ── Quick action cards definition ──
const QUICK_ACTIONS = [
  {
    id: 'ask',
    icon: 'message-circle',
    title: 'Ask a Question',
    desc: 'Get answers and explanations on any topic',
    prompt: 'Can you help me understand '
  },
  {
    id: 'create',
    icon: 'plus-circle',
    title: 'Create Something',
    desc: 'Build pages, documents, or code files',
    prompt: 'Create a '
  },
  {
    id: 'analyze',
    icon: 'bar-chart-2',
    title: 'Analyze Data',
    desc: 'Examine, interpret, and summarize data',
    prompt: 'Analyze '
  },
  {
    id: 'research',
    icon: 'search',
    title: 'Learn & Research',
    desc: 'Explore topics and gather knowledge',
    prompt: 'Research '
  }
];

// ──────────────────────────────────────
// Public init
// ──────────────────────────────────────

export function init() {
  container = $('#view-chat');
  if (!container) {
    console.warn('[chat] #view-chat not found');
    return;
  }

  buildDOM();
  subscribeToStore();
  setupInputListeners();

  // Render existing messages from store
  const { messages } = store.getState().chat;
  if (messages.length > 0) {
    hideWelcome();
    renderAllMessages(messages);
  }

  // Start task polling
  refreshTaskBar();
  setInterval(refreshTaskBar, 5000);
}

// ──────────────────────────────────────
// DOM construction
// ──────────────────────────────────────

function buildDOM() {
  empty(container);
  container.classList.add('chat-with-preview');

  // Chat main area (left side)
  const chatMain = createElement('div', { className: 'chat-main chat-container' });

  // Drag-and-drop overlay
  const dropOverlay = createElement('div', { className: 'chat-drop-overlay' }, ['Drop file to upload']);
  chatMain.appendChild(dropOverlay);

  // Welcome screen
  welcomeEl = buildWelcomeScreen();
  chatMain.appendChild(welcomeEl);

  // Chat header (shown when messages are visible)
  chatHeader = createElement('div', { className: 'chat-header' });
  chatHeader.style.display = 'none';

  const backBtn = createElement('button', {
    className: 'glass-button-icon chat-back-btn',
    'aria-label': 'New conversation',
    title: 'New Chat'
  });
  backBtn.appendChild(icon('plus-circle', { size: 18 }));
  backBtn.addEventListener('click', () => showWelcome());
  chatHeader.appendChild(backBtn);

  // Preview toggle button
  const previewToggleBtn = createElement('button', {
    className: 'glass-button-icon preview-toggle-btn',
    'aria-label': 'Toggle preview panel',
    title: 'Preview Panel',
    type: 'button'
  });
  previewToggleBtn.appendChild(icon('eye', { size: 16 }));
  previewToggleBtn.addEventListener('click', () => togglePreview());
  chatHeader.appendChild(previewToggleBtn);

  // Export conversation button
  const exportBtn = createElement('button', {
    className: 'glass-button-icon chat-export-btn',
    'aria-label': 'Export conversation',
    title: 'Export Chat',
    type: 'button'
  });
  exportBtn.appendChild(icon('download', { size: 16 }));
  exportBtn.addEventListener('click', exportConversation);
  chatHeader.appendChild(exportBtn);

  // Focus mode toggle button
  const focusBtn = createElement('button', {
    className: 'glass-button-icon chat-focus-btn',
    'aria-label': 'Toggle focus mode',
    title: 'Focus Mode',
    type: 'button'
  });
  focusBtn.appendChild(icon('maximize-2', { size: 16 }));
  focusBtn.addEventListener('click', () => {
    store.dispatch('SET_FOCUS_MODE', !store.getState().ui.focusMode);
  });
  chatHeader.appendChild(focusBtn);

  chatMain.appendChild(chatHeader);

  // Task status bar (always visible, shows running/queued tasks)
  const taskBar = createElement('div', { className: 'chat-task-bar' });
  taskBar.id = 'chat-task-bar';
  chatMain.appendChild(taskBar);

  // Messages area
  messagesEl = createElement('div', { className: 'chat-messages' });
  messagesEl.style.display = 'none';
  chatMain.appendChild(messagesEl);

  // Connection status banner
  const connBanner = createElement('div', { className: 'chat-connection-banner' });
  connBanner.style.display = 'none';
  connBanner.appendChild(icon('warning', { size: 16 }));
  connBanner.appendChild(createElement('span', {}, ['Connection lost. Reconnecting...']));
  chatMain.appendChild(connBanner);

  // Input area
  inputArea = buildInputArea();
  chatMain.appendChild(inputArea);

  // Drag-and-drop handlers
  chatMain.addEventListener('dragover', (e) => {
    e.preventDefault();
    chatMain.classList.add('drag-over');
  });
  chatMain.addEventListener('dragleave', (e) => {
    if (!chatMain.contains(e.relatedTarget)) {
      chatMain.classList.remove('drag-over');
    }
  });
  chatMain.addEventListener('drop', (e) => {
    e.preventDefault();
    chatMain.classList.remove('drag-over');
    const files = e.dataTransfer?.files;
    if (files) {
      for (const file of files) {
        addPendingFile(file);
      }
    }
  });

  container.appendChild(chatMain);

  // Preview panel (right side)
  container.appendChild(createPreviewPanel());
}

function buildWelcomeScreen() {
  const persona = store.getState().persona;

  const greeting = createElement('div', { className: 'chat-welcome-greeting' }, [
    createElement('h2', {}, [persona.name || 'AI-Do']),
    createElement('p', {}, [persona.greeting || 'Hello! How can I help you today?'])
  ]);

  const cardsGrid = createElement('div', { className: 'chat-quick-actions' });

  for (const action of QUICK_ACTIONS) {
    const card = createElement('div', { className: 'glass-card', dataset: { action: action.id } });

    const cardIcon = createElement('div', { className: 'card-icon' });
    cardIcon.appendChild(icon(action.icon, { size: 18 }));

    const cardTitle = createElement('div', { className: 'card-title' }, [action.title]);
    const cardDesc = createElement('div', { className: 'card-desc' }, [action.desc]);

    card.appendChild(cardIcon);
    card.appendChild(cardTitle);
    card.appendChild(cardDesc);

    card.addEventListener('click', () => {
      if (textarea) {
        textarea.value = action.prompt;
        textarea.focus();
        autoResizeTextarea();
        updateSendButton();
      }
    });

    cardsGrid.appendChild(card);
  }

  const el = createElement('div', { className: 'chat-welcome' });
  el.appendChild(greeting);
  el.appendChild(cardsGrid);
  return el;
}

function buildInputArea() {
  const wrapper = createElement('div', { className: 'chat-input-wrapper' });

  // Attachment strip (shown above the input row when files are pending)
  attachmentStrip = createElement('div', { className: 'chat-attachment-strip' });
  attachmentStrip.style.display = 'none';
  wrapper.appendChild(attachmentStrip);

  // Input row
  const area = createElement('div', { className: 'chat-input-area' });

  // Upload button
  const uploadBtn = createElement('button', {
    className: 'btn-upload',
    'aria-label': 'Upload file'
  });
  uploadBtn.appendChild(icon('upload', { size: 18 }));

  fileInput = createElement('input', {
    className: 'file-input-hidden',
    type: 'file',
    multiple: 'true',
    'aria-hidden': 'true'
  });

  uploadBtn.addEventListener('click', () => fileInput.click());
  fileInput.addEventListener('change', handleFileUpload);

  // Mic button — opens full voice mode overlay (handles Grok/OpenAI or fallback)
  const micBtn = createElement('button', {
    className: 'btn-mic',
    'aria-label': 'Voice mode',
    title: 'Voice Mode',
    type: 'button'
  });
  micBtn.appendChild(icon('mic', { size: 18 }));
  micBtn.addEventListener('click', (e) => {
    e.preventDefault();
    openVoiceMode();
  });

  // Textarea
  textarea = createElement('textarea', {
    className: 'glass-textarea',
    placeholder: 'Type a message...',
    rows: '1',
    'aria-label': 'Chat message input'
  });

  // Send button
  sendBtn = createElement('button', {
    className: 'btn-send',
    'aria-label': 'Send message',
    disabled: 'true'
  });
  sendBtn.appendChild(icon('send', { size: 18 }));

  area.appendChild(uploadBtn);
  area.appendChild(fileInput);
  area.appendChild(micBtn);
  area.appendChild(textarea);
  area.appendChild(sendBtn);

  wrapper.appendChild(area);
  inputArea = wrapper;
  return wrapper;
}

// ──────────────────────────────────────
// Store subscriptions
// ──────────────────────────────────────

function subscribeToStore() {
  // Messages changed (full replacement, e.g. initial load)
  store.subscribe(
    (s) => s.chat.messages,
    (messages) => {
      if (messages.length > 0) {
        hideWelcome();
      }
      renderAllMessages(messages);
    }
  );

  // Streaming state
  store.subscribe(
    (s) => s.chat.streaming,
    (streaming) => {
      if (streaming) {
        hideWelcome();
        showTypingIndicator();
        swapToStopButton();
      } else {
        removeTypingIndicator();
        finalizeStreamingBubble();
        swapToSendButton();
      }
      // Announce streaming state to screen readers
      const srEl = document.getElementById('sr-announcements');
      if (srEl) srEl.textContent = streaming ? 'AI is responding...' : 'AI response complete';
    }
  );

  // Stream text accumulation
  store.subscribe(
    (s) => s.chat.streamText,
    (text) => {
      if (text && store.getState().chat.streaming) {
        updateStreamingBubble(text);
      }
    }
  );

  // Focus mode — update toggle button icon
  store.subscribe(
    (s) => s.ui.focusMode,
    (focusMode) => {
      const btn = container?.querySelector('.chat-focus-btn');
      if (!btn) return;
      empty(btn);
      btn.appendChild(icon(focusMode ? 'minimize-2' : 'maximize-2', { size: 16 }));
      btn.setAttribute('aria-label', focusMode ? 'Exit focus mode' : 'Toggle focus mode');
      btn.setAttribute('title', focusMode ? 'Exit Focus Mode' : 'Focus Mode');
    }
  );

  // Persona update (refresh welcome greeting)
  store.subscribe(
    (s) => s.persona.greeting,
    () => {
      if (welcomeEl && welcomeEl.style.display !== 'none') {
        const persona = store.getState().persona;
        const h2 = $('h2', welcomeEl);
        const p = $('p', welcomeEl);
        if (h2) h2.textContent = persona.name || 'AI-Do';
        if (p) p.textContent = persona.greeting || 'Hello! How can I help you today?';
      }
    }
  );

  // New chat message from WebSocket (agent replies)
  eventBus.on(EVENTS.CHAT_MESSAGE, (data) => {
    // Already dispatched to store by api.mjs _handleWsMessage
    // We scroll to bottom after the store subscriber renders it
    requestAnimationFrame(() => scrollToBottom());
  });

  // Stream end — finalize the streaming bubble with the real message
  eventBus.on(EVENTS.CHAT_STREAM_END, (data) => {
    if (data?.text) {
      finalizeStreamingBubbleWithContent(data.text, data.id);
    }
  });

  // Stream error — show user-friendly error in chat
  eventBus.on(EVENTS.CHAT_STREAM_ERROR, async (data) => {
    const errMsg = data?.error || 'Unknown error';
    // Finalize streaming bubble with error message
    const isRateLimit = /429|usage limit|rate limit|budget/i.test(errMsg);
    const userMessage = isRateLimit
      ? `⚠️ **Rate limit reached.** The AI provider has temporarily limited requests. You can:\n\n- **Wait a few minutes** and try again\n- **Add your own API key** in Settings → AI to remove limits\n- Check the [Activity](#) tab for details`
      : `⚠️ **Something went wrong:** ${errMsg}`;
    finalizeStreamingBubbleWithContent(userMessage, null);
    // Also show a toast
    try {
      const { warning } = await import('./toast.mjs');
      warning(isRateLimit ? 'Rate limit reached — configure your API key in Settings' : errMsg);
    } catch {}
  });

  // Connection status banner
  eventBus.on(EVENTS.WS_DISCONNECTED, () => {
    const banner = container?.querySelector('.chat-connection-banner');
    if (banner) banner.style.display = 'flex';
  });

  eventBus.on(EVENTS.WS_CONNECTED, () => {
    const banner = container?.querySelector('.chat-connection-banner');
    if (banner) banner.style.display = 'none';
  });

  // Refresh task bar on skill start/complete and activity events
  eventBus.on(EVENTS.SKILL_START, () => setTimeout(refreshTaskBar, 300));
  eventBus.on(EVENTS.SKILL_COMPLETE, () => setTimeout(refreshTaskBar, 300));
  eventBus.on(EVENTS.ACTIVITY_EVENT, (data) => {
    const kind = data?.kind || data?.type;
    if (kind === 'skill' || kind === 'system') setTimeout(refreshTaskBar, 500);
  });
}

// ──────────────────────────────────────
// Input handling
// ──────────────────────────────────────

function setupInputListeners() {
  textarea.addEventListener('input', () => {
    autoResizeTextarea();
    updateSendButton();
  });

  textarea.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      if (store.getState().chat.streaming) return;
      const text = textarea.value.trim();
      if (text || pendingFiles.length > 0) sendMessage(text);
    }
  });

  // Paste handler — images and files from clipboard
  textarea.addEventListener('paste', (e) => {
    const items = e.clipboardData?.items;
    if (!items) return;
    for (const item of items) {
      if (item.kind === 'file') {
        e.preventDefault();
        const file = item.getAsFile();
        if (file) addPendingFile(file);
      }
    }
  });

  sendBtn.addEventListener('click', () => {
    if (store.getState().chat.streaming) {
      api.cancelStream();
      return;
    }
    const text = textarea.value.trim();
    if (text || pendingFiles.length > 0) sendMessage(text);
  });
}

function autoResizeTextarea() {
  textarea.style.height = 'auto';
  const lineHeight = parseInt(getComputedStyle(textarea).lineHeight, 10) || 24;
  const maxRows = 6;
  const maxHeight = lineHeight * maxRows;
  textarea.style.height = Math.min(textarea.scrollHeight, maxHeight) + 'px';
}

function updateSendButton() {
  const hasText = textarea.value.trim().length > 0;
  const hasFiles = pendingFiles.length > 0;
  sendBtn.disabled = !hasText && !hasFiles;
}

async function sendMessage(text) {
  // Upload any pending files first
  let uploadedNames = [];
  if (pendingFiles.length > 0) {
    uploadedNames = await uploadPendingFiles();
  }

  // Build message text
  let messageText = text;
  if (uploadedNames.length > 0 && !text) {
    messageText = `I uploaded ${uploadedNames.length} file${uploadedNames.length > 1 ? 's' : ''}: ${uploadedNames.join(', ')}. Please acknowledge.`;
  } else if (uploadedNames.length > 0 && text) {
    messageText = `[Attached: ${uploadedNames.join(', ')}]\n\n${text}`;
  }

  if (!messageText) return;

  // Add user message to store
  store.dispatch('ADD_MESSAGE', {
    role: 'user',
    content: messageText,
    timestamp: Date.now()
  });

  // Clear input
  textarea.value = '';
  autoResizeTextarea();
  updateSendButton();

  // Hide welcome, show messages
  hideWelcome();

  // Scroll to bottom after render
  requestAnimationFrame(() => scrollToBottom());

  // Send via streaming API
  try {
    await api.streamMessage(messageText);
  } catch (err) {
    console.error('[chat] Send error:', err);
  }
}

function handleFileUpload() {
  if (!fileInput.files || fileInput.files.length === 0) return;
  for (const file of fileInput.files) {
    addPendingFile(file);
  }
  fileInput.value = '';
}

function addPendingFile(file) {
  const id = `att-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
  const entry = { file, id, previewUrl: null };

  // Generate thumbnail for images
  if (file.type.startsWith('image/')) {
    entry.previewUrl = URL.createObjectURL(file);
  }

  pendingFiles.push(entry);
  renderAttachmentStrip();
  updateSendButton();
}

function removePendingFile(id) {
  const idx = pendingFiles.findIndex(f => f.id === id);
  if (idx === -1) return;
  const entry = pendingFiles[idx];
  if (entry.previewUrl) URL.revokeObjectURL(entry.previewUrl);
  pendingFiles.splice(idx, 1);
  renderAttachmentStrip();
  updateSendButton();
}

function renderAttachmentStrip() {
  if (!attachmentStrip) return;
  empty(attachmentStrip);

  if (pendingFiles.length === 0) {
    attachmentStrip.style.display = 'none';
    return;
  }

  attachmentStrip.style.display = 'flex';

  for (const entry of pendingFiles) {
    const chip = createElement('div', { className: 'attachment-chip' });

    if (entry.previewUrl) {
      const thumb = createElement('img', {
        className: 'attachment-thumb',
        src: entry.previewUrl
      });
      thumb.alt = entry.file.name;
      // Click thumbnail to preview in a new window
      thumb.addEventListener('click', (e) => {
        e.stopPropagation();
        window.open(entry.previewUrl, '_blank');
      });
      chip.appendChild(thumb);
    } else {
      const fileIcon = createElement('span', { className: 'attachment-icon' });
      fileIcon.appendChild(icon('file', { size: 14 }));
      chip.appendChild(fileIcon);
    }

    const nameEl = createElement('span', { className: 'attachment-name' }, [
      entry.file.name.length > 20 ? entry.file.name.slice(0, 17) + '...' : entry.file.name
    ]);
    nameEl.title = entry.file.name;
    chip.appendChild(nameEl);

    const removeBtn = createElement('button', {
      className: 'attachment-remove',
      'aria-label': `Remove ${entry.file.name}`,
      type: 'button'
    });
    removeBtn.appendChild(icon('x', { size: 12 }));
    removeBtn.addEventListener('click', (e) => {
      e.stopPropagation();
      removePendingFile(entry.id);
    });
    chip.appendChild(removeBtn);

    attachmentStrip.appendChild(chip);
  }
}

function clearPendingFiles() {
  for (const entry of pendingFiles) {
    if (entry.previewUrl) URL.revokeObjectURL(entry.previewUrl);
  }
  pendingFiles = [];
  renderAttachmentStrip();
}

async function uploadPendingFiles() {
  const names = [];
  for (const entry of pendingFiles) {
    try {
      const base64 = await readFileAsBase64(entry.file);
      await api.post('/api/upload', { filename: entry.file.name, content: base64, encoding: 'base64' });
      names.push(entry.file.name);
    } catch (err) {
      console.error('[chat] Upload error:', err);
      eventBus.emit(EVENTS.NOTIFICATION, {
        message: `Upload failed: ${entry.file.name} — ${err.message}`,
        type: 'error'
      });
    }
  }
  clearPendingFiles();
  return names;
}

function readFileAsBase64(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result.split(',')[1]);
    reader.onerror = () => reject(new Error('FileReader error'));
    reader.readAsDataURL(file);
  });
}

// ──────────────────────────────────────
// Send / Stop button swap
// ──────────────────────────────────────

function swapToStopButton() {
  empty(sendBtn);
  sendBtn.className = 'btn-stop';
  sendBtn.disabled = false;
  sendBtn.setAttribute('aria-label', 'Stop generation');
  sendBtn.appendChild(icon('square', { size: 16 }));

  // Rebind click to cancel
  sendBtn.onclick = () => api.cancelStream();
}

function swapToSendButton() {
  empty(sendBtn);
  sendBtn.className = 'btn-send';
  sendBtn.setAttribute('aria-label', 'Send message');
  sendBtn.appendChild(icon('send', { size: 18 }));
  updateSendButton();

  // Rebind click to send
  sendBtn.onclick = () => {
    const text = textarea.value.trim();
    if (text && !store.getState().chat.streaming) sendMessage(text);
  };
}

// ──────────────────────────────────────
// Message rendering
// ──────────────────────────────────────

function renderAllMessages(messages) {
  if (!messagesEl) return;
  empty(messagesEl);
  for (const msg of messages) {
    const el = renderMessage(msg, false);
    messagesEl.appendChild(el);
  }
  scrollToBottom();
}

function renderMessage(msg, animate = true) {
  const isUser = msg.role === 'user';

  const row = createElement('div', {
    className: `chat-message ${isUser ? 'user' : 'agent'}${animate ? ' animate-in' : ''}`,
    dataset: { id: String(msg.id || '') }
  });

  // Avatar
  const avatar = createElement('div', { className: 'avatar' });
  if (isUser) {
    avatar.textContent = 'U';
  } else {
    avatar.textContent = 'A';
  }

  // Bubble
  const bubble = createElement('div', { className: 'bubble' });
  const content = createElement('div', { className: 'content' });

  if (isUser) {
    content.textContent = msg.content || '';
  } else {
    // Render markdown async, set innerHTML
    content.innerHTML = '<div class="skeleton skeleton-text" style="width:80%"></div><div class="skeleton skeleton-text" style="width:60%"></div>';
    renderMarkdown(msg.content || '').then((html) => {
      content.innerHTML = html;
      addCodeCopyButtons(content);
    });
  }

  // Timestamp
  const ts = createElement('div', { className: 'timestamp' });
  ts.textContent = msg.timestamp ? relativeTime(msg.timestamp) : '';

  bubble.appendChild(content);

  bubble.appendChild(ts);
  row.appendChild(avatar);
  row.appendChild(bubble);

  return row;
}

// ──────────────────────────────────────
// Streaming bubble
// ──────────────────────────────────────

function showTypingIndicator() {
  if (!messagesEl) return;

  // Create a streaming message row
  streamingBubble = createElement('div', {
    className: 'chat-message agent animate-in',
    dataset: { streaming: 'true' }
  });

  const avatar = createElement('div', { className: 'avatar' });
  avatar.textContent = 'A';

  const bubble = createElement('div', { className: 'bubble' });
  const content = createElement('div', { className: 'content' });

  // Typing indicator dots
  const typingEl = createElement('div', { className: 'chat-typing-indicator' }, [
    createElement('span'),
    createElement('span'),
    createElement('span')
  ]);
  content.appendChild(typingEl);

  bubble.appendChild(content);
  streamingBubble.appendChild(avatar);
  streamingBubble.appendChild(bubble);

  messagesEl.style.display = 'flex';
  messagesEl.appendChild(streamingBubble);

  // Add shimmer bar
  const shimmer = createElement('div', { className: 'chat-streaming' });
  shimmer.dataset.streamingBar = 'true';
  messagesEl.parentNode.insertBefore(shimmer, messagesEl.nextSibling);

  scrollToBottom();
}

function updateStreamingBubble(text) {
  if (!streamingBubble) return;

  const content = $('.content', streamingBubble);
  if (!content) return;

  // Replace typing indicator with rendered text
  renderMarkdown(text).then((html) => {
    content.innerHTML = html;
    addCodeCopyButtons(content);
    scrollToBottom();
  });
}

function removeTypingIndicator() {
  // Remove shimmer bar
  const shimmer = container?.querySelector('[data-streaming-bar]');
  if (shimmer) shimmer.remove();
}

function finalizeStreamingBubble() {
  if (!streamingBubble) return;

  // Remove the streaming attribute
  delete streamingBubble.dataset.streaming;
  streamingBubble.classList.remove('animate-in');

  // Add timestamp
  const bubble = $('.bubble', streamingBubble);
  if (bubble && !$('.timestamp', bubble)) {
    const ts = createElement('div', { className: 'timestamp' });
    ts.textContent = relativeTime(Date.now());
    bubble.appendChild(ts);
  }

  streamingBubble = null;
}

function finalizeStreamingBubbleWithContent(text, id) {
  if (!streamingBubble) return;

  const content = $('.content', streamingBubble);
  if (content) {
    renderMarkdown(text).then((html) => {
      content.innerHTML = html;
      addCodeCopyButtons(content);
    });
  }

  if (id) {
    streamingBubble.dataset.id = String(id);
  }

  finalizeStreamingBubble();
}

// ──────────────────────────────────────
// Collapsible code blocks with copy buttons
// ──────────────────────────────────────

function addCodeCopyButtons(parentEl) {
  const preBlocks = $$('pre', parentEl);
  for (const pre of preBlocks) {
    // Skip if already wrapped in a collapsible container
    if (pre.parentElement?.classList.contains('code-collapse-body')) continue;

    // Extract language from the code element's class (e.g. "language-javascript")
    const codeEl = $('code', pre);
    let lang = '';
    if (codeEl) {
      const langClass = [...codeEl.classList].find(c => c.startsWith('language-'));
      if (langClass) lang = langClass.replace('language-', '');
    }
    const langLabel = lang || 'code';

    // Build the collapsible wrapper using <details>
    const details = createElement('details', { className: 'code-collapse' });

    // Summary = clickable header row
    const summary = createElement('summary', { className: 'code-collapse-header' });

    // Language badge
    const langBadge = createElement('span', { className: 'code-collapse-lang' }, [langLabel]);

    // Toggle label text (updates on open/close)
    const toggleLabel = createElement('span', { className: 'code-collapse-toggle' }, ['Show code']);

    // Chevron icon
    const chevron = createElement('span', { className: 'code-collapse-chevron' });
    chevron.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>';

    // Copy button (works even when collapsed)
    const copyBtn = createElement('button', { className: 'code-collapse-copy' }, ['Copy']);
    copyBtn.addEventListener('click', (e) => {
      e.stopPropagation();
      e.preventDefault();
      const code = $('code', pre);
      const text = code ? code.textContent : pre.textContent;
      navigator.clipboard.writeText(text).then(() => {
        copyBtn.textContent = 'Copied!';
        copyBtn.classList.add('copied');
        setTimeout(() => {
          copyBtn.textContent = 'Copy';
          copyBtn.classList.remove('copied');
        }, 2000);
      }).catch(() => {
        copyBtn.textContent = 'Failed';
        setTimeout(() => { copyBtn.textContent = 'Copy'; }, 2000);
      });
    });

    summary.appendChild(langBadge);
    summary.appendChild(toggleLabel);
    summary.appendChild(copyBtn);
    summary.appendChild(chevron);

    // Body wrapper holds the pre block
    const body = createElement('div', { className: 'code-collapse-body' });

    // Insert the details element where the pre was
    pre.parentNode.insertBefore(details, pre);
    body.appendChild(pre);
    details.appendChild(summary);
    details.appendChild(body);

    // Update toggle label on open/close
    details.addEventListener('toggle', () => {
      toggleLabel.textContent = details.open ? 'Hide code' : 'Show code';
    });
  }
}

// ──────────────────────────────────────
// Helpers
// ──────────────────────────────────────

function hideWelcome() {
  if (welcomeEl) welcomeEl.style.display = 'none';
  if (messagesEl) messagesEl.style.display = 'flex';
  if (chatHeader) chatHeader.style.display = 'flex';
}

function showWelcome() {
  if (welcomeEl) welcomeEl.style.display = '';
  if (messagesEl) {
    messagesEl.style.display = 'none';
    empty(messagesEl);
  }
  if (chatHeader) chatHeader.style.display = 'none';

  // Clear messages from store and server so they don't reload on restart
  store.dispatch('CLEAR_MESSAGES');
  api.clearChatSession();
  api.clearAllChat().catch(err => console.warn('[chat] Clear failed:', err));
}

function scrollToBottom() {
  if (!messagesEl) return;
  requestAnimationFrame(() => {
    messagesEl.scrollTop = messagesEl.scrollHeight;
  });
}

function exportConversation() {
  const { messages } = store.getState().chat;
  if (!messages.length) return;

  const lines = [];
  for (const msg of messages) {
    const role = msg.role === 'user' ? 'User' : 'Agent';
    lines.push(`## ${role}\n\n${msg.content || ''}\n`);
  }

  const md = lines.join('\n');
  const date = new Date().toISOString().slice(0, 10);
  const blob = new Blob([md], { type: 'text/markdown' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `conversation-${date}.md`;
  a.click();
  URL.revokeObjectURL(url);
}

// ──────────────────────────────────────
// Task status bar
// ──────────────────────────────────────

let lastTaskBarHTML = '';

async function refreshTaskBar() {
  const bar = document.getElementById('chat-task-bar');
  if (!bar) return;

  try {
    const tasks = await api.getTasks();
    if (!Array.isArray(tasks)) { bar.innerHTML = ''; bar.style.display = 'none'; return; }

    const running = tasks.filter(t => t.state === 'running');
    const queued = tasks.filter(t => t.state === 'queued');

    if (running.length === 0 && queued.length === 0) {
      if (bar.style.display !== 'none') {
        bar.style.display = 'none';
        bar.innerHTML = '';
        lastTaskBarHTML = '';
      }
      return;
    }

    const parts = [];

    for (const t of running) {
      const skillName = t.skill || t.kind || 'task';
      const elapsed = t.started_at ? formatElapsed(t.started_at) : '';
      parts.push(`<div class="task-bar-item running">
        <span class="task-bar-dot running"></span>
        <span class="task-bar-label">${escapeHtml(skillName)}</span>
        ${elapsed ? `<span class="task-bar-elapsed">${elapsed}</span>` : ''}
      </div>`);
    }

    if (queued.length > 0) {
      parts.push(`<div class="task-bar-item queued">
        <span class="task-bar-dot queued"></span>
        <span class="task-bar-label">${queued.length} queued</span>
      </div>`);
    }

    const html = parts.join('');
    if (html !== lastTaskBarHTML) {
      bar.innerHTML = html;
      lastTaskBarHTML = html;
    }
    bar.style.display = 'flex';
  } catch {
    // Silently ignore task fetch failures
  }
}

function formatElapsed(isoString) {
  const diff = Date.now() - new Date(isoString).getTime();
  const seconds = Math.floor(diff / 1000);
  if (seconds < 60) return `${seconds}s`;
  const minutes = Math.floor(seconds / 60);
  if (minutes < 60) return `${minutes}m`;
  return `${Math.floor(minutes / 60)}h`;
}
