/**
 * Flynn Chat Page
 *
 * Session selector, message input, streaming tool events,
 * markdown-rendered responses, slash commands, and web search.
 */

/* global marked, hljs */

let _currentSession = null;
let _sending = false;
let _searchMode = false;
let _slashPopupIndex = -1;
let _elements = {};
let _pendingAttachments = [];

// ── Slash Command Definitions ───────────────────────────────

const SLASH_COMMANDS = [
  { name: '/help',    desc: 'Show available commands' },
  { name: '/reset',   desc: 'Reset session' },
  { name: '/compact', desc: 'Compact context' },
  { name: '/usage',   desc: 'Show token usage' },
  { name: '/status',  desc: 'Show system health' },
  { name: '/model',   desc: 'Show current model' },
];

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

function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

function renderMarkdown(text) {
  try {
    if (typeof marked !== 'undefined') {
      return marked.parse(text);
    }
  } catch {
    // Fall through to plain text
  }
  return `<p>${escapeHtml(text)}</p>`;
}

function highlightCode() {
  if (typeof hljs !== 'undefined') {
    document.querySelectorAll('.chat-messages pre code').forEach(block => {
      hljs.highlightElement(block);
    });
  }
}

function createMessageEl(role, content) {
  const wrapper = document.createElement('div');
  wrapper.className = 'message-wrapper';

  const div = document.createElement('div');
  div.className = `message ${role}`;

  if (role === 'assistant' || role === 'system') {
    div.innerHTML = renderMarkdown(content);
    setTimeout(highlightCode, 0);
  } else {
    div.textContent = content;
  }
  wrapper.appendChild(div);

  // Add action buttons (copy for all, edit for user) outside the message box
  if (role !== 'system') {
    wrapper.appendChild(createMessageActions(role));
  }

  return wrapper;
}

function createToolEventEl(event, data) {
  const group = document.createElement('div');
  group.className = 'tool-event-group';

  const header = document.createElement('div');
  header.className = 'tool-event-header';

  if (event === 'tool_start') {
    header.innerHTML = `<span class="spinner"></span> <strong>${escapeHtml(data.tool)}</strong>`;
  } else if (event === 'tool_end') {
    const icon = data.result?.success ? '&#10003;' : '&#10007;';
    const cls = data.result?.success ? 'status-ok' : 'status-error';
    header.innerHTML = `<span class="${cls}">${icon}</span> <strong>${escapeHtml(data.tool)}</strong>`;
  }

  header.addEventListener('click', () => {
    body.classList.toggle('open');
  });

  const body = document.createElement('div');
  body.className = 'tool-event-body';

  if (event === 'tool_start' && data.args) {
    body.textContent = JSON.stringify(data.args, null, 2);
  } else if (event === 'tool_end' && data.result) {
    body.textContent = data.result.output || data.result.error || '(no output)';
  }

  group.appendChild(header);
  group.appendChild(body);
  return group;
}

function scrollToBottom() {
  const msgs = _elements.messages;
  if (msgs) {
    msgs.scrollTop = msgs.scrollHeight;
  }
}

function isSupportedImageMime(mimeType) {
  return mimeType === 'image/jpeg'
    || mimeType === 'image/png'
    || mimeType === 'image/gif'
    || mimeType === 'image/webp';
}

function readFileAsBase64(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = () => reject(new Error('Failed to read file'));
    reader.onload = () => {
      const result = String(reader.result || '');
      // result is like: data:<mime>;base64,<payload>
      const comma = result.indexOf(',');
      if (comma === -1) {
        reject(new Error('Unexpected file encoding'));
        return;
      }
      resolve(result.slice(comma + 1));
    };
    reader.readAsDataURL(file);
  });
}

function renderPendingAttachments() {
  const el = _elements.attachments;
  if (!el) {return;}

  el.innerHTML = '';
  if (!_pendingAttachments.length) {
    el.classList.add('hidden');
    return;
  }

  el.classList.remove('hidden');
  for (let i = 0; i < _pendingAttachments.length; i++) {
    const att = _pendingAttachments[i];
    const chip = document.createElement('div');
    chip.className = 'attachment-chip';

    const name = document.createElement('span');
    name.className = 'attachment-name';
    name.textContent = att.filename || 'attachment';

    const rm = document.createElement('button');
    rm.className = 'attachment-remove';
    rm.type = 'button';
    rm.title = 'Remove attachment';
    rm.textContent = '×';
    rm.addEventListener('click', () => {
      _pendingAttachments.splice(i, 1);
      renderPendingAttachments();
    });

    chip.appendChild(name);
    chip.appendChild(rm);
    el.appendChild(chip);
  }
}

function clearPendingAttachments() {
  _pendingAttachments = [];
  if (_elements.fileInput) {
    _elements.fileInput.value = '';
  }
  renderPendingAttachments();
}

// ── Message Action Buttons ──────────────────────────────────

function getMessageText(el) {
  // For user messages, textContent is the raw text.
  // For assistant/system messages rendered as markdown, extract plain text.
  return (el.textContent || '').trim();
}

function createMessageActions(role) {
  const bar = document.createElement('div');
  bar.className = 'message-actions';

  // Copy button — all messages
  const copyBtn = document.createElement('button');
  copyBtn.className = 'msg-action-btn';
  copyBtn.title = 'Copy';
  copyBtn.innerHTML = COPY_ICON;
  copyBtn.addEventListener('click', () => {
    const msg = bar.closest('.message');
    if (!msg) {return;}
    const text = getMessageText(msg);
    navigator.clipboard.writeText(text).then(() => {
      copyBtn.innerHTML = CHECK_ICON;
      copyBtn.classList.add('copied');
      setTimeout(() => {
        copyBtn.innerHTML = COPY_ICON;
        copyBtn.classList.remove('copied');
      }, 1500);
    });
  });
  bar.appendChild(copyBtn);

  // Edit button — user messages only
  if (role === 'user') {
    const editBtn = document.createElement('button');
    editBtn.className = 'msg-action-btn';
    editBtn.title = 'Edit';
    editBtn.innerHTML = EDIT_ICON;
    editBtn.addEventListener('click', () => {
      const msg = bar.closest('.message');
      if (!msg) {return;}
      const text = getMessageText(msg);
      const input = _elements.input;
      if (input) {
        input.value = text;
        input.focus();
        // Trigger auto-resize
        input.style.height = 'auto';
        input.style.height = Math.min(input.scrollHeight, 150) + 'px';
      }
    });
    bar.appendChild(editBtn);
  }

  return bar;
}

// ── Search Mode ─────────────────────────────────────────────

function setSearchMode(active) {
  _searchMode = active;
  const btn = _elements.searchBtn;
  const input = _elements.input;
  if (!btn || !input) {return;}

  if (active) {
    btn.classList.add('active');
    input.placeholder = 'What do you want to search for?';
  } else {
    btn.classList.remove('active');
    input.placeholder = 'Type a message...';
  }
  input.focus();
}

// ── Slash Popup ─────────────────────────────────────────────

function getFilteredCommands(text) {
  const prefix = text.toLowerCase();
  return SLASH_COMMANDS.filter(c => c.name.startsWith(prefix));
}

function showSlashPopup(filtered) {
  const popup = _elements.slashPopup;
  if (!popup) {return;}

  popup.innerHTML = '';
  if (filtered.length === 0) {
    hideSlashPopup();
    return;
  }

  for (let i = 0; i < filtered.length; i++) {
    const item = document.createElement('div');
    item.className = 'slash-popup-item' + (i === _slashPopupIndex ? ' selected' : '');
    item.innerHTML = `<span class="cmd-name">${escapeHtml(filtered[i].name)}</span><span class="cmd-desc">${escapeHtml(filtered[i].desc)}</span>`;
    item.addEventListener('click', () => {
      selectSlashCommand(filtered[i].name);
    });
    // Touch-friendly: handle pointerenter for hover on mobile
    item.addEventListener('pointerenter', () => {
      _slashPopupIndex = i;
      updatePopupSelection(filtered);
    });
    popup.appendChild(item);
  }
  popup.classList.remove('hidden');
}

function hideSlashPopup() {
  const popup = _elements.slashPopup;
  if (popup) {popup.classList.add('hidden');}
  _slashPopupIndex = -1;
}

function updatePopupSelection(filtered) {
  const popup = _elements.slashPopup;
  if (!popup) {return;}
  const items = popup.querySelectorAll('.slash-popup-item');
  items.forEach((el, i) => {
    el.classList.toggle('selected', i === _slashPopupIndex);
  });
}

function selectSlashCommand(name) {
  const input = _elements.input;
  if (!input) {return;}
  input.value = name;
  hideSlashPopup();
  input.focus();
}

function handleSlashPopupInput() {
  const input = _elements.input;
  if (!input) {return;}
  const text = input.value;

  // Show popup only when text starts with / and is at most a single word (the command itself)
  if (text.startsWith('/') && !text.includes(' ')) {
    const filtered = getFilteredCommands(text);
    // Clamp selection index
    if (_slashPopupIndex >= filtered.length) {_slashPopupIndex = filtered.length - 1;}
    showSlashPopup(filtered);
  } else {
    hideSlashPopup();
  }
}

// ── Slash Command Handlers ──────────────────────────────────

function parseSlashCommand(text) {
  const trimmed = text.trim();
  if (!trimmed.startsWith('/')) {return null;}

  const parts = trimmed.split(/\s+/);
  const cmd = parts[0].toLowerCase();
  const args = parts.slice(1).join(' ');

  switch (cmd) {
    case '/help':    return { type: 'help' };
    case '/reset':   return { type: 'reset' };
    case '/compact': return { type: 'compact' };
    case '/usage':   return { type: 'usage' };
    case '/status':  return { type: 'status' };
    case '/model':   return { type: 'model', args };
    default:         return null;
  }
}

function showSystemMessage(content) {
  _elements.messages.appendChild(createMessageEl('system', content));
  scrollToBottom();
}

async function handleSlashCommand(cmd, client) {
  switch (cmd.type) {
    case 'help': {
      const lines = [
        '**Available Commands**',
        '',
        '| Command | Description |',
        '|---------|-------------|',
        '| `/help` | Show this help |',
        '| `/reset` | Reset the current session |',
        '| `/compact` | Ask the agent to compact context |',
        '| `/usage` | Show token usage stats |',
        '| `/status` | Show system health |',
        '| `/model` | Show current model info |',
        '',
        'Type `/` to see autocomplete suggestions.',
      ];
      showSystemMessage(lines.join('\n'));
      return true;
    }

    case 'reset': {
      try {
        // Send reset command via metadata
        const stream = client.stream('agent.send', {
          message: '/reset',
          metadata: { isCommand: true, command: 'reset' },
        });
        await stream.result;
        _elements.messages.innerHTML = '';
        showSystemMessage('Session reset.');
      } catch (err) {
        showSystemMessage(`Failed to reset: ${err.message}`);
      }
      return true;
    }

    case 'compact': {
      // Send as a regular message — the agent will interpret the request
      showSystemMessage('Requesting context compaction...');
      return false; // Let it pass through as a normal message
    }

    case 'usage': {
      try {
        const result = await client.call('system.tokenUsage');
        const sessions = result.sessions ?? [];
        if (sessions.length === 0) {
          showSystemMessage('No usage data available.');
        } else {
          const lines = ['**Token Usage**', ''];
          let totalIn = 0, totalOut = 0, totalCalls = 0;
          for (const s of sessions) {
            totalIn += s.total?.inputTokens ?? 0;
            totalOut += s.total?.outputTokens ?? 0;
            totalCalls += s.total?.calls ?? 0;
          }
          lines.push(`**Input:** ${totalIn.toLocaleString()} tokens`);
          lines.push(`**Output:** ${totalOut.toLocaleString()} tokens`);
          lines.push(`**API Calls:** ${totalCalls}`);
          if (sessions.length > 1) {
            lines.push(`**Sessions:** ${sessions.length}`);
          }
          showSystemMessage(lines.join('\n'));
        }
      } catch (err) {
        showSystemMessage(`Failed to fetch usage: ${err.message}`);
      }
      return true;
    }

    case 'status': {
      try {
        const result = await client.call('system.health');
        const lines = [
          '**System Status**',
          '',
          `**Uptime:** ${result.uptime ?? 'unknown'}`,
          `**Status:** ${result.status ?? 'unknown'}`,
        ];
        if (result.channels) {
          lines.push('', '**Channels:**');
          for (const ch of result.channels) {
            const dot = ch.status === 'connected' ? '\\*' : '-';
            lines.push(`  ${dot} ${ch.name}: ${ch.status}`);
          }
        }
        if (result.model) {
          lines.push('', `**Model:** ${result.model}`);
        }
        showSystemMessage(lines.join('\n'));
      } catch (err) {
        showSystemMessage(`Failed to fetch status: ${err.message}`);
      }
      return true;
    }

    case 'model': {
      try {
        const result = await client.call('system.health');
        const model = result.model ?? result.config?.model ?? 'unknown';
        showSystemMessage(`**Current Model:** ${model}`);
      } catch (err) {
        showSystemMessage(`Failed to fetch model info: ${err.message}`);
      }
      return true;
    }

    default:
      return false;
  }
}

// ── Session Management ──────────────────────────────────────

async function loadSessions(client) {
  const select = _elements.sessionSelect;
  if (!select) {return;}

  try {
    const result = await client.call('sessions.list');
    const sessions = result.sessions ?? [];

    // Preserve current selection
    const current = _currentSession;
    select.innerHTML = '';

    if (sessions.length === 0) {
      const opt = document.createElement('option');
      opt.value = '';
      opt.textContent = 'No sessions';
      select.appendChild(opt);
    } else {
      for (const s of sessions) {
        const opt = document.createElement('option');
        opt.value = s.id;
        opt.textContent = `${s.id} (${s.messageCount} msgs)`;
        if (s.id === current) {opt.selected = true;}
        select.appendChild(opt);
      }
    }

    // Update current session
    _currentSession = select.value || null;
  } catch {
    // Ignore — sessions may not be available
  }
}

async function loadHistory(client) {
  const msgs = _elements.messages;
  if (!msgs || !_currentSession) {return;}

  msgs.innerHTML = '';

  try {
    const result = await client.call('sessions.history', { sessionId: _currentSession });
    const messages = result.messages ?? [];

    for (const msg of messages) {
      const role = msg.role ?? 'assistant';
      const content = msg.content ?? msg.text ?? '';
      msgs.appendChild(createMessageEl(role, content));
    }

    scrollToBottom();
  } catch {
    msgs.innerHTML = '<div class="empty-state">Could not load history</div>';
  }
}

// ── Send Message ────────────────────────────────────────────

async function sendMessage(client, overrideText) {
  const input = _elements.input;
  const rawText = (overrideText ?? input?.value ?? '').trim();
  const hasText = Boolean(rawText);
  const hasAttachments = Boolean(_pendingAttachments.length > 0);
  if ((!hasText && !hasAttachments) || _sending) {return;}

  // Check for slash commands first (text only)
  if (hasText) {
    const cmd = parseSlashCommand(rawText);
    if (cmd) {
      if (!overrideText) {input.value = '';}
      hideSlashPopup();
      const handled = await handleSlashCommand(cmd, client);
      if (handled) {return;}
      // If not fully handled (e.g. /compact), fall through to send as message
    }
  }

  _sending = true;
  _elements.sendBtn.disabled = true;
  if (!overrideText && input) {input.value = '';}

  // Apply search mode prefix
  let messageText = rawText;
  if (_searchMode && hasText && !rawText.startsWith('/')) {
    messageText = `Search the web for: ${rawText}`;
    setSearchMode(false);
  }

  const userDisplay = hasText
    ? rawText
    : `Sent ${_pendingAttachments.length} attachment(s)`;
  _elements.messages.appendChild(createMessageEl('user', userDisplay));
  scrollToBottom();

  // Create placeholder for assistant response
  const placeholder = document.createElement('div');
  placeholder.className = 'message assistant streaming-cursor';
  placeholder.innerHTML = '<span class="text-muted">Thinking...</span>';
  _elements.messages.appendChild(placeholder);
  scrollToBottom();

  try {
    const stream = client.stream('agent.send', { message: messageText, attachments: _pendingAttachments });

    stream.on('tool_start', (data) => {
      const el = createToolEventEl('tool_start', data);
      _elements.messages.insertBefore(el, placeholder);
      scrollToBottom();
    });

    stream.on('tool_end', (data) => {
      // Replace the last tool_start spinner with completion marker
      const events = _elements.messages.querySelectorAll('.tool-event-group');
      const last = events[events.length - 1];
      if (last) {
        const header = last.querySelector('.tool-event-header');
        if (header && data.tool) {
          const icon = data.result?.success !== false ? '&#10003;' : '&#10007;';
          const cls = data.result?.success !== false ? 'status-ok' : 'status-error';
          header.innerHTML = `<span class="${cls}">${icon}</span> <strong>${escapeHtml(data.tool)}</strong>`;
        }
        // Add result body
        const body = last.querySelector('.tool-event-body');
        if (body && data.result) {
          body.textContent = data.result.output || data.result.error || '(no output)';
        }
      }
      scrollToBottom();
    });

    const done = await stream.result;
    // Replace placeholder with actual response
    placeholder.classList.remove('streaming-cursor');
    const content = done?.content ?? done?.text ?? '(no response)';
    placeholder.innerHTML = renderMarkdown(content);
    placeholder.appendChild(createMessageActions('assistant'));
    setTimeout(highlightCode, 0);
  } catch (err) {
    placeholder.classList.remove('streaming-cursor');
    placeholder.className = 'message error';
    placeholder.textContent = `Error: ${err.message}`;
  } finally {
    _sending = false;
    if (_elements.sendBtn) {_elements.sendBtn.disabled = false;}
    clearPendingAttachments();
    scrollToBottom();
  }
}

// ── Search SVG Icon ─────────────────────────────────────────

const SEARCH_ICON = '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 3a5.5 5.5 0 0 1 4.38 8.82l4.15 4.15a.75.75 0 0 1-1.06 1.06l-4.15-4.15A5.5 5.5 0 1 1 8.5 3zm0 1.5a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="currentColor"/></svg>';
const ATTACH_ICON = '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M7.5 4.75A3.75 3.75 0 0 1 11.25 1h.5A3.25 3.25 0 0 1 15 4.25V12a4.5 4.5 0 0 1-9 0V6.75a2.75 2.75 0 0 1 5.5 0V12a1.25 1.25 0 0 1-2.5 0V6.75a.75.75 0 0 0-1.5 0V12a2.75 2.75 0 0 0 5.5 0V6.75A4.25 4.25 0 0 0 5 6.75V12a6 6 0 0 0 12 0V4.25A4.75 4.75 0 0 0 11.75-.5h-.5A5.25 5.25 0 0 0 6 4.75v8a.75.75 0 0 0 1.5 0z" fill="currentColor"/></svg>';
const COPY_ICON = '<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z" fill="currentColor"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" fill="currentColor"/></svg>';
const CHECK_ICON = '<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z" fill="currentColor"/></svg>';
const EDIT_ICON = '<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm1.414 1.06a.25.25 0 0 0-.354 0L3.463 11.1l-.47 1.64 1.64-.47 8.61-8.61a.25.25 0 0 0 0-.354Z" fill="currentColor"/></svg>';

// ── Page Export ──────────────────────────────────────────────

export const ChatPage = {
  async render(el, client) {
    el.innerHTML = `
      <div class="chat-layout">
        <div class="chat-header">
          <select id="chat-session-select"></select>
          <button id="chat-new-session" class="btn btn-secondary">+ New</button>
          <button id="chat-load-history" class="btn btn-secondary">History</button>
        </div>
        <div class="chat-messages" id="chat-messages"></div>
        <div class="chat-actions">
          <button id="chat-search" class="btn-action" title="Search the web">
            ${SEARCH_ICON}
            <span class="btn-action-label">Search</span>
          </button>
          <button id="chat-attach" class="btn-action" title="Attach image">
            ${ATTACH_ICON}
            <span class="btn-action-label">Attach</span>
          </button>
          <input id="chat-file" type="file" accept="image/png,image/jpeg,image/gif,image/webp" multiple class="hidden" />
          <div id="chat-attachments" class="chat-attachments hidden"></div>
        </div>
        <div class="chat-input-wrapper">
          <div id="slash-popup" class="slash-popup hidden"></div>
          <div class="chat-input">
            <textarea id="chat-input" placeholder="Type a message..." rows="1"></textarea>
            <button id="chat-send" class="btn btn-primary">Send</button>
          </div>
        </div>
      </div>
    `;

    _elements = {
      sessionSelect: el.querySelector('#chat-session-select'),
      messages: el.querySelector('#chat-messages'),
      input: el.querySelector('#chat-input'),
      sendBtn: el.querySelector('#chat-send'),
      searchBtn: el.querySelector('#chat-search'),
      attachBtn: el.querySelector('#chat-attach'),
      fileInput: el.querySelector('#chat-file'),
      attachments: el.querySelector('#chat-attachments'),
      slashPopup: el.querySelector('#slash-popup'),
    };

    // Load sessions into dropdown
    await loadSessions(client);

    // Event: session change
    _elements.sessionSelect.addEventListener('change', () => {
      _currentSession = _elements.sessionSelect.value || null;
    });

    // Event: new session
    el.querySelector('#chat-new-session').addEventListener('click', async () => {
      try {
        const result = await client.call('sessions.create');
        _currentSession = result.sessionId;
        await loadSessions(client);
        _elements.messages.innerHTML = '';
      } catch (err) {
        _elements.messages.innerHTML = `<div class="empty-state">Failed to create session: ${err.message}</div>`;
      }
    });

    // Event: load history
    el.querySelector('#chat-load-history').addEventListener('click', () => {
      loadHistory(client);
    });

    // Event: search button toggle
    _elements.searchBtn.addEventListener('click', () => {
      setSearchMode(!_searchMode);
    });

    // Event: attach button
    _elements.attachBtn.addEventListener('click', () => {
      if (_elements.fileInput) {_elements.fileInput.click();}
    });

    // Event: file input change
    _elements.fileInput.addEventListener('change', async () => {
      const files = Array.from(_elements.fileInput.files ?? []);
      if (files.length === 0) {return;}

      const MAX_BYTES = 5 * 1024 * 1024;
      for (const file of files) {
        if (!isSupportedImageMime(file.type)) {
          showSystemMessage(`Unsupported file type: ${file.type || file.name}`);
          continue;
        }
        if (file.size > MAX_BYTES) {
          showSystemMessage(`File too large (max 5MB): ${file.name}`);
          continue;
        }

        try {
          const data = await readFileAsBase64(file);
          _pendingAttachments.push({ mimeType: file.type, data, filename: file.name });
        } catch (err) {
          showSystemMessage(`Failed to attach ${file.name}: ${err.message}`);
        }
      }

      renderPendingAttachments();
    });

    // Event: send message
    _elements.sendBtn.addEventListener('click', () => sendMessage(client));

    // Event: keyboard in textarea
    _elements.input.addEventListener('keydown', (e) => {
      const popup = _elements.slashPopup;
      const isPopupVisible = popup && !popup.classList.contains('hidden');

      // Handle slash popup navigation
      if (isPopupVisible) {
        const text = _elements.input.value;
        const filtered = getFilteredCommands(text);

        if (e.key === 'ArrowDown') {
          e.preventDefault();
          _slashPopupIndex = Math.min(_slashPopupIndex + 1, filtered.length - 1);
          updatePopupSelection(filtered);
          return;
        }
        if (e.key === 'ArrowUp') {
          e.preventDefault();
          _slashPopupIndex = Math.max(_slashPopupIndex - 1, 0);
          updatePopupSelection(filtered);
          return;
        }
        if ((e.key === 'Enter' || e.key === 'Tab') && _slashPopupIndex >= 0 && _slashPopupIndex < filtered.length) {
          e.preventDefault();
          selectSlashCommand(filtered[_slashPopupIndex].name);
          return;
        }
        if (e.key === 'Escape') {
          e.preventDefault();
          hideSlashPopup();
          return;
        }
      }

      // Enter to send (Shift+Enter for newline)
      if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        hideSlashPopup();
        sendMessage(client);
      }
    });

    // Event: input changes for slash popup + auto-resize
    _elements.input.addEventListener('input', () => {
      // Auto-resize textarea
      const ta = _elements.input;
      ta.style.height = 'auto';
      ta.style.height = Math.min(ta.scrollHeight, 150) + 'px';

      // Slash command popup
      handleSlashPopupInput();
    });

    // Dismiss slash popup on outside click
    el.addEventListener('click', (e) => {
      if (!e.target.closest('.chat-input-wrapper')) {
        hideSlashPopup();
      }
    });

    // If there's a current session, show welcome
    if (!_currentSession) {
      _elements.messages.innerHTML = '<div class="empty-state">Select a session or create a new one to start chatting</div>';
    }
  },

  teardown() {
    _currentSession = null;
    _sending = false;
    _searchMode = false;
    _slashPopupIndex = -1;
    _elements = {};
    _pendingAttachments = [];
  },
};
