// Codex CLI terminal output parser
// Markers (confirmed from actual output):
//   › U+203A  — user prompt
//   • U+2022  — working indicator / tool-use / assistant response
//   ╭ U+256D  — box-drawing box (splash screen)
//   | pipe    — markdown pipe table

class CodexParser {
  constructor() {
    this._lastLines = null;
    this._lastDoc = null;
  }

  parse(lines) {
    if (lines === this._lastLines) return this._lastDoc;
    this._lastLines = lines;
    this._lastDoc = this._doParse(lines);
    return this._lastDoc;
  }

  _doParse(lines) {
    if (!lines || lines.length === 0) return { blocks: [] };
    const blocks = this._parseBlocks(lines);
    return { blocks };
  }

  _parseBlocks(lines) {
    const blocks = [];
    let i = 0;

    while (i < lines.length) {
      const line = lines[i];
      const s = this._stripAnsi(line);

      // Empty
      if (/^\s*$/.test(s)) {
        blocks.push({ type: 'empty', lines: [line] });
        i++;
        continue;
      }

      // Separator: long run of ─ (U+2500) — skip
      if (this._isSeparatorLine(line)) {
        i++;
        continue;
      }

      // Box-drawing box ╭...╰ (splash screen / info box)
      if (/^\s*\u256D/.test(s) || s.trimStart().startsWith('╭')) {
        const bl = [line]; i++;
        while (i < lines.length) {
          const l = lines[i];
          bl.push(l); i++;
          if (/[\u2570\u256F]/.test(this._stripAnsi(l))) break;
        }
        blocks.push({ type: 'tool-result', lines: bl });
        continue;
      }

      // Markdown pipe table: line starting with |
      if (/^\s*\|/.test(s)) {
        const bl = [line]; i++;
        while (i < lines.length && /^\s*\|/.test(this._stripAnsi(lines[i]))) {
          bl.push(lines[i++]);
        }
        blocks.push({ type: 'table', lines: bl });
        continue;
      }

      // › Prompt (U+203A user input)
      if (/^\s*\u203A/.test(s)) {
        const bl = [line]; i++;
        while (i < lines.length) {
          const ns = this._stripAnsi(lines[i]);
          if (/^\s*$/.test(ns)) break;
          if (this._isBlockStart(lines[i])) break;
          bl.push(lines[i++]);
        }
        // Skip empty prompts
        const firstText = s.replace(/^\s*\u203A\s*/, '').trim();
        if (!firstText) { i = i; continue; }
        blocks.push({ type: 'prompt', lines: bl });
        continue;
      }

      // • Working indicator (U+2022 + "Working" pattern)
      if (/^\s*\u2022/.test(s)) {
        const content = s.replace(/^\s*\u2022\s*/, '');

        // Thinking/working spinner: "Working(Xs • esc to interrupt)"
        if (/^Working[\s(]/.test(content)) {
          const bl = [line]; i++;
          while (i < lines.length) {
            const ns = this._stripAnsi(lines[i]);
            if (/^\s*\u2022\s*Working[\s(]/.test(ns) || /^\s*$/.test(ns)) {
              if (/^\s*$/.test(ns)) break;
              bl.push(lines[i++]);
            } else break;
          }
          blocks.push({ type: 'thinking', lines: bl });
          continue;
        }

        // Tool-use completed: "• Explored" / "• Completed" etc. with └ sub-bullet
        const isToolUse = /^(Explored|Completed|Applied|Created|Updated|Deleted|Read|Listed|Searched|Ran|Executed)\b/.test(content);
        const nextLine = i + 1 < lines.length ? this._stripAnsi(lines[i + 1]) : '';
        const hasSubBullet = /^\s+[└├]/.test(nextLine);

        if (isToolUse || hasSubBullet) {
          const bl = [line]; i++;
          // Collect sub-bullets (indented └ ├ lines)
          while (i < lines.length) {
            const ns = this._stripAnsi(lines[i]);
            if (/^\s+[└├─]/.test(ns)) {
              bl.push(lines[i++]);
            } else break;
          }
          blocks.push({ type: 'tool-use', lines: bl });
          continue;
        }

        // Response text: • plain prose
        const bl = [line]; i++;
        while (i < lines.length) {
          const ns = this._stripAnsi(lines[i]);
          if (/^\s*$/.test(ns)) break;
          if (this._isBlockStart(lines[i])) break;
          // Continuation: indented lines (list items, etc.)
          if (/^ {2,}/.test(ns) || /^\s*[-*]/.test(ns)) {
            bl.push(lines[i++]);
            continue;
          }
          // Another • line → new block
          if (/^\s*\u2022/.test(ns)) break;
          bl.push(lines[i++]);
        }
        blocks.push({ type: 'response', lines: bl });
        continue;
      }

      // Diff: +/- lines
      if (/^[+-](?![+-])/.test(s) || /^@@/.test(s)) {
        const bl = [line]; i++;
        while (i < lines.length) {
          const cs = this._stripAnsi(lines[i]);
          if (/^[+\- @]/.test(cs) && !this._isBlockStart(lines[i])) {
            bl.push(lines[i++]);
          } else break;
        }
        blocks.push({ type: 'diff', lines: bl });
        continue;
      }

      // Code: 4+ space indent with code-like content
      if (/^ {4,}/.test(s) && this._isCodeLike(s)) {
        const bl = [line]; i++;
        while (i < lines.length) {
          const cs = this._stripAnsi(lines[i]);
          if (/^ {4,}/.test(cs)) {
            bl.push(lines[i++]);
          } else if (/^\s*$/.test(lines[i]) && i + 1 < lines.length && /^ {4,}/.test(this._stripAnsi(lines[i + 1]))) {
            bl.push(lines[i++]);
          } else {
            break;
          }
        }
        while (bl.length > 0 && /^\s*$/.test(bl[bl.length - 1])) bl.pop();
        if (bl.length > 0) blocks.push({ type: 'code', lines: bl });
        continue;
      }

      // Text: fallback
      const bl = [line]; i++;
      while (i < lines.length && !this._isBlockStart(lines[i]) && !/^\s*$/.test(this._stripAnsi(lines[i]))) {
        bl.push(lines[i++]);
      }
      blocks.push({ type: 'text', lines: bl });
    }

    return blocks;
  }

  _isBlockStart(line) {
    if (!line || /^\s*$/.test(line)) return false;
    const s = this._stripAnsi(line);
    return (
      /^\s*\u203A/.test(s) ||   // › prompt
      /^\s*\u2022/.test(s) ||   // • tool/response
      /^\s*\u256D/.test(s) ||   // ╭ box
      s.trimStart().startsWith('╭') ||
      /^\s*\|/.test(s) ||       // | table
      /^[+-](?![+-])/.test(s) || // diff
      /^@@/.test(s)
    );
  }

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

  _isSeparatorLine(line) {
    const stripped = this._stripAnsi(line).trim();
    if (stripped.length < 3) return false;
    const sepChars = (stripped.match(/[\u2500\u2501\u2550\u254C\u254D]/g) || []).length;
    if (sepChars / stripped.length > 0.5) return true;
    const uniqueChars = new Set(stripped.replace(/\s/g, '')).size;
    return uniqueChars === 1 && stripped.length >= 3 && /[^\w\s]/.test(stripped);
  }

  _isCodeLike(line) {
    return /[{}\[\]();=]/.test(line) ||
      /\b(const|let|var|function|class|import|export|if|else|for|while|return|def|async)\b/.test(line) ||
      /^\s+\w[\w.]*\s*[({]/.test(line);
  }
}

window.CodexParser = CodexParser;
