// Codex CLI renderer — renders CodexParser output to DOM
// Reuses cc-* CSS classes from claude.css (body.codex-mode scope)
// No status bar, no mode switching, no selection options

class CodexRenderer {
  constructor(outputEl) {
    this.outputEl = outputEl;
    this._lastSignature = null;
    this._blockEls = null;
    this._blockSigs = null;
  }

  render(doc) {
    const sig = this._signature(doc.blocks);
    if (sig !== this._lastSignature) {
      this._renderBlocks(doc.blocks);
      this._lastSignature = sig;
    }
  }

  clear() {
    this.outputEl.innerHTML = '';
    this._lastSignature = null;
    this._blockEls = null;
    this._blockSigs = null;
  }

  // ── Signature-based DOM diffing ──────────────────────────────────────────

  _blockSig(block) {
    return `${block.type}:${block.lines.length}:${block.lines[0] || ''}`;
  }

  _signature(blocks) {
    return blocks.map(b => this._blockSig(b)).join(',');
  }

  _renderBlocks(blocks) {
    const newSigs = blocks.map(b => this._blockSig(b));

    if (!this._blockEls || this._blockEls.length !== blocks.length) {
      this._fullRebuild(blocks, newSigs);
      return;
    }

    for (let i = 0; i < blocks.length; i++) {
      if (newSigs[i] === this._blockSigs[i]) continue;
      const newEl = this._renderBlock(blocks[i]);
      const oldEl = this._blockEls[i];
      if (newEl && oldEl && oldEl.parentNode) {
        oldEl.parentNode.replaceChild(newEl, oldEl);
        this._blockEls[i] = newEl;
      } else {
        this._fullRebuild(blocks, newSigs);
        return;
      }
      this._blockSigs[i] = newSigs[i];
    }
  }

  _fullRebuild(blocks, sigs) {
    const frag = document.createDocumentFragment();
    const els = [];
    for (const block of blocks) {
      const el = this._renderBlock(block);
      els.push(el);
      if (el) frag.appendChild(el);
    }
    this.outputEl.innerHTML = '';
    this.outputEl.appendChild(frag);
    this._blockEls = els;
    this._blockSigs = sigs;
  }

  // ── Block dispatcher ─────────────────────────────────────────────────────

  _renderBlock(block) {
    switch (block.type) {
      case 'prompt':      return this._renderPrompt(block);
      case 'thinking':    return this._renderThinking(block);
      case 'tool-use':    return this._renderToolUse(block);
      case 'tool-result': return this._renderToolResult(block);
      case 'response':    return this._renderResponse(block);
      case 'table':       return this._renderTable(block);
      case 'diff':        return this._renderDiff(block);
      case 'code':        return this._renderCode(block);
      case 'text':        return this._renderText(block);
      case 'empty':       return this._renderEmpty();
      default:            return null;
    }
  }

  // ── Shared utilities ─────────────────────────────────────────────────────

  _stripAnsi(s) {
    return s.replace(/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g, '');
  }

  _esc(text) {
    return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  }

  _addCopyButton(container, text) {
    container.classList.add('cc-copy-wrap');
    const btn = document.createElement('button');
    btn.className = 'cc-copy-btn';
    btn.innerHTML = '&#x2398;';
    btn.title = 'Copy';
    btn.addEventListener('click', (e) => {
      e.stopPropagation();
      navigator.clipboard.writeText(text).then(() => {
        btn.classList.add('copied');
        btn.textContent = '\u2713';
        setTimeout(() => {
          btn.classList.remove('copied');
          btn.innerHTML = '&#x2398;';
        }, 1500);
      });
    });
    container.appendChild(btn);
  }

  // ── Block renderers ───────────────────────────────────────────────────────

  _renderPrompt(block) {
    const div = document.createElement('div');
    div.className = 'cc-prompt';

    const firstLine = block.lines[0];
    const stripped = this._stripAnsi(firstLine);
    // Remove › marker
    const markerMatch = stripped.match(/^(\s*\u203A\s*)/);
    const text = markerMatch ? firstLine.slice(markerMatch[1].length) : firstLine;

    const marker = document.createElement('span');
    marker.className = 'cc-prompt-marker';
    marker.textContent = '›';

    const textEl = document.createElement('span');
    textEl.className = 'cc-prompt-text';
    textEl.innerHTML = ansiToHtml(text);

    div.appendChild(marker);
    div.appendChild(textEl);

    for (let i = 1; i < block.lines.length; i++) {
      const cont = document.createElement('div');
      cont.className = 'cc-prompt-cont';
      cont.innerHTML = ansiToHtml(block.lines[i]);
      div.appendChild(cont);
    }

    return div;
  }

  _renderThinking(block) {
    const div = document.createElement('div');
    div.className = 'cc-thinking';
    // Strip • prefix and show content
    const text = this._stripAnsi(block.lines[0]).replace(/^\s*\u2022\s*/, '');
    div.textContent = text;
    return div;
  }

  _renderToolUse(block) {
    const div = document.createElement('div');
    div.className = 'cc-tool-use';

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

    const icon = document.createElement('span');
    icon.className = 'cc-tool-icon';
    icon.textContent = '•';

    // Extract verb from "• Explored" etc.
    const firstStripped = this._stripAnsi(block.lines[0]).replace(/^\s*\u2022\s*/, '');
    const verbMatch = firstStripped.match(/^(\w+)(.*)/);

    const name = document.createElement('span');
    name.className = 'cc-tool-name';
    name.textContent = verbMatch ? verbMatch[1] : firstStripped;

    const args = document.createElement('span');
    args.className = 'cc-tool-args';
    args.textContent = verbMatch ? (verbMatch[2] || '').trim() : '';

    header.appendChild(icon);
    header.appendChild(name);
    if (args.textContent) header.appendChild(args);
    div.appendChild(header);

    // Sub-bullets (└ ├ lines)
    const subLines = block.lines.slice(1);
    if (subLines.length > 0) {
      const body = document.createElement('div');
      body.className = 'cc-tool-body';
      for (const line of subLines) {
        const lineEl = document.createElement('div');
        lineEl.className = 'cc-line';
        lineEl.innerHTML = ansiToHtml(line);
        body.appendChild(lineEl);
      }
      div.appendChild(body);
    }

    return div;
  }

  _renderToolResult(block) {
    const div = document.createElement('div');
    div.className = 'cc-tool-result';

    const scrollable = document.createElement('div');
    scrollable.className = 'cc-scrollable';

    const pre = document.createElement('pre');

    // Strip ╭/╰ border lines; for │ content lines, remove the │ markers
    const contentLines = [];
    for (const line of block.lines) {
      const s = this._stripAnsi(line).trimStart();
      if (/^[│┃║]/.test(s)) {
        let cleaned = line.replace(/^(?:\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.))*[│┃║]\s?/, '');
        cleaned = cleaned.replace(/\s*[│┃║](?:\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.))*\s*$/, '');
        contentLines.push(cleaned.trimEnd());
      }
      // Skip ╭/╰ border lines
    }

    pre.innerHTML = contentLines.map(l => ansiToHtml(l)).join('\n');
    scrollable.appendChild(pre);
    div.appendChild(scrollable);

    if (contentLines.length > 8) {
      div.classList.add('cc-collapsible');
      const toggle = document.createElement('button');
      toggle.className = 'cc-collapse-toggle';
      toggle.textContent = `Show all (${contentLines.length} lines)`;
      toggle.addEventListener('click', () => {
        const expanded = div.classList.toggle('cc-expanded');
        toggle.textContent = expanded ? 'Show less' : `Show all (${contentLines.length} lines)`;
      });
      div.appendChild(toggle);
    }

    this._addCopyButton(div, contentLines.map(l => this._stripAnsi(l)).join('\n'));
    return div;
  }

  _renderResponse(block) {
    const div = document.createElement('div');
    div.className = 'cc-response';
    for (const line of block.lines) {
      const lineEl = document.createElement('div');
      lineEl.className = 'cc-line';
      // Strip • prefix from first line
      const content = line === block.lines[0]
        ? line.replace(/^(\x1b\[[0-9;]*m)*\s*\u2022\s?/, '')
        : line;
      lineEl.innerHTML = ansiToHtml(content);
      div.appendChild(lineEl);
    }
    return div;
  }

  // Markdown pipe table: | col | col |
  _renderTable(block) {
    const div = document.createElement('div');
    div.className = 'cc-table-wrap';

    const scrollable = document.createElement('div');
    scrollable.className = 'cc-scrollable';

    const table = document.createElement('table');
    table.className = 'cc-table';

    const thead = document.createElement('thead');
    const tbody = document.createElement('tbody');

    // Detect separator row: |---|---:| or |:---:|
    const isSepRow = s => /^\s*\|[\s|:=-]+\|\s*$/.test(s.replace(/[^|:\-= ]/g, ' '));

    let headerDone = false;
    for (let i = 0; i < block.lines.length; i++) {
      const stripped = this._stripAnsi(block.lines[i]).trim();
      if (!stripped.startsWith('|')) continue;

      // Check if NEXT line is a separator → this is header
      const nextStripped = i + 1 < block.lines.length
        ? this._stripAnsi(block.lines[i + 1]).trim()
        : '';
      const nextIsSep = isSepRow(nextStripped);

      if (isSepRow(stripped)) {
        headerDone = true;
        continue;
      }

      const cells = stripped.split('|').slice(1, -1);
      const isHeader = !headerDone && nextIsSep;

      const tr = document.createElement('tr');
      for (const cell of cells) {
        const cellEl = document.createElement(isHeader ? 'th' : 'td');
        cellEl.innerHTML = ansiToHtml(cell.trim());
        tr.appendChild(cellEl);
      }

      if (isHeader) {
        thead.appendChild(tr);
      } else {
        tbody.appendChild(tr);
      }
    }

    if (thead.children.length > 0) table.appendChild(thead);
    table.appendChild(tbody);
    scrollable.appendChild(table);
    div.appendChild(scrollable);
    return div;
  }

  _renderDiff(block) {
    const div = document.createElement('div');
    div.className = 'cc-diff';

    const scrollable = document.createElement('div');
    scrollable.className = 'cc-scrollable';

    const content = document.createElement('div');
    content.className = 'cc-diff-content';

    for (const line of block.lines) {
      const span = document.createElement('span');
      span.className = 'cc-diff-line';
      if (/^\+(?!\+)/.test(line))    span.classList.add('cc-added');
      else if (/^-(?!-)/.test(line)) span.classList.add('cc-removed');
      else if (/^@@/.test(line))     span.classList.add('cc-hunk');
      span.innerHTML = ansiToHtml(line);
      content.appendChild(span);
      content.appendChild(document.createTextNode('\n'));
    }

    scrollable.appendChild(content);
    div.appendChild(scrollable);
    this._addCopyButton(div, block.lines.map(l => this._stripAnsi(l)).join('\n'));
    return div;
  }

  _renderCode(block) {
    const div = document.createElement('div');
    div.className = 'cc-code';

    const scrollable = document.createElement('div');
    scrollable.className = 'cc-scrollable';

    const pre = document.createElement('pre');
    pre.className = 'cc-code-content';
    pre.innerHTML = block.lines.map(l => ansiToHtml(l)).join('\n');

    scrollable.appendChild(pre);
    div.appendChild(scrollable);
    this._addCopyButton(div, block.lines.map(l => this._stripAnsi(l)).join('\n'));
    return div;
  }

  _renderText(block) {
    const div = document.createElement('div');
    div.className = 'cc-text';
    for (const line of block.lines) {
      const lineEl = document.createElement('div');
      lineEl.className = 'cc-line';
      lineEl.innerHTML = ansiToHtml(line);
      div.appendChild(lineEl);
    }
    return div;
  }

  _renderEmpty() {
    const div = document.createElement('div');
    div.className = 'cc-empty';
    return div;
  }
}

window.CodexRenderer = CodexRenderer;
