#!/usr/bin/env node
'use strict';
// Capture raw PTY bytes from a child process for use as a rendering test
// fixture. Used when the desired sequence isn't already in scrollback_log
// (primarily Codex flows or freshly-released CC versions).
//
// Usage:
//   node bin/capture-pty.js --out fixture.bin [--cols 120] [--rows 40] \
//                           [--timing] [--max-bytes N] -- <cmd> [args...]
//
//   --timing      Also write <out>.timing.json: array of {offset, length, delayMs}
//                 so replay-fixture.js timed mode can preserve frame cadence.
//   --max-bytes   Stop capturing once N bytes have been recorded (default: 100000)
//
// Exit: send SIGINT (Ctrl+C) — for CC, send twice (it traps the first).
//       Capture also stops when the child exits.

const fs = require('fs');
const path = require('path');

function parseArgs(argv) {
  const out = { _: [], cols: 120, rows: 40, maxBytes: 100000, timing: false };
  let dashSeen = false;
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (dashSeen) { out._.push(a); continue; }
    if (a === '--') { dashSeen = true; continue; }
    if (a === '--timing') out.timing = true;
    else if (a.startsWith('--cols')) out.cols = parseInt(a.includes('=') ? a.split('=')[1] : argv[++i], 10);
    else if (a.startsWith('--rows')) out.rows = parseInt(a.includes('=') ? a.split('=')[1] : argv[++i], 10);
    else if (a.startsWith('--max-bytes')) out.maxBytes = parseInt(a.includes('=') ? a.split('=')[1] : argv[++i], 10);
    else if (a.startsWith('--out')) out.outPath = a.includes('=') ? a.split('=')[1] : argv[++i];
    else { console.error(`unknown option: ${a}`); process.exit(2); }
  }
  return out;
}

const opts = parseArgs(process.argv.slice(2));
if (!opts.outPath || opts._.length === 0) {
  console.error('usage: capture-pty.js --out file.bin [--cols N] [--rows N] [--timing] [--max-bytes N] -- <cmd> [args...]');
  process.exit(2);
}

// node-pty is a CTM dep; resolve from claude-task-manager/node_modules.
let pty;
try { pty = require('node-pty'); }
catch { pty = require(path.join(__dirname, '..', 'node_modules', 'node-pty')); }

const [cmd, ...args] = opts._;
const child = pty.spawn(cmd, args, {
  cols: opts.cols,
  rows: opts.rows,
  cwd: process.cwd(),
  env: { ...process.env, TERM: 'xterm-256color' },
});

const out = fs.openSync(opts.outPath, 'w');
const timing = opts.timing ? [] : null;
let total = 0;
let lastTs = Date.now();

child.onData((data) => {
  if (total >= opts.maxBytes) return;
  const buf = Buffer.from(data, 'utf8');
  const fit = Math.min(buf.length, opts.maxBytes - total);
  fs.writeSync(out, buf, 0, fit);
  if (timing) {
    const now = Date.now();
    timing.push({ offset: total, length: fit, delayMs: now - lastTs });
    lastTs = now;
  }
  total += fit;
  if (total >= opts.maxBytes) {
    console.error(`[capture-pty] reached max-bytes=${opts.maxBytes}, stopping child`);
    try { child.kill(); } catch {}
  }
});

child.onExit(({ exitCode }) => {
  fs.closeSync(out);
  if (timing) fs.writeFileSync(opts.outPath + '.timing.json', JSON.stringify(timing));
  console.error(`[capture-pty] wrote ${total} bytes → ${opts.outPath}` + (timing ? ` (+ ${timing.length} timing entries)` : ''));
  process.exit(exitCode || 0);
});

// Forward stdin to the child (so user can interact / Ctrl+C).
process.stdin.setRawMode?.(true);
process.stdin.resume();
process.stdin.on('data', (d) => { try { child.write(d.toString()); } catch {} });

// Forward output to our stdout too so user sees what's being captured.
child.onData((data) => process.stdout.write(data));
