panasyn 0.1.0

A lightweight GPU-accelerated terminal emulator for macOS and Linux.
use std::time::Instant;

use crate::ansi;
use crate::replay::format;
use crate::replay::recorder::*;
use crate::terminal::TerminalState;

/// Results from a headless replay run with detailed metrics.
#[derive(Debug, Clone)]
pub struct HeadlessResult {
    pub events_processed: u64,
    pub total_bytes: u64,
    pub parse_time_ms: f64,
    pub wall_time_ms: f64,
    pub parse_throughput_mbps: f64,
    pub peak_dirty_rows: usize,
    pub final_grid_hash: u64,
    pub final_cursor: (usize, usize),
    pub scrollback_lines: usize,
    pub scrollback_hash: u64,
    pub alt_screen_state: bool,
    pub grid_update_time_ms: f64,
    pub parser_latency_min_ms: f64,
    pub parser_latency_max_ms: f64,
    pub parser_latency_avg_ms: f64,
    pub total_events_counted: u64,
    pub errors: Vec<String>,
    // GridWriter breakdown
    pub cell_writes: u64,
    pub sgr_updates: u64,
    pub line_advances: u64,
}

/// Run a headless replay (no window, no renderer).
pub fn run_headless(events: &[ReplayEvent], cols: u16, rows: u16) -> HeadlessResult {
    let mut terminal = TerminalState::new(cols as usize, rows as usize, 10_000);
    let mut parser = ansi::Parser::new();
    let mut result = HeadlessResult {
        events_processed: 0,
        total_bytes: 0,
        parse_time_ms: 0.0,
        wall_time_ms: 0.0,
        parse_throughput_mbps: 0.0,
        peak_dirty_rows: 0,
        final_grid_hash: 0,
        final_cursor: (0, 0),
        scrollback_lines: 0,
        scrollback_hash: 0,
        alt_screen_state: false,
        grid_update_time_ms: 0.0,
        parser_latency_min_ms: f64::MAX,
        parser_latency_max_ms: f64::MIN,
        parser_latency_avg_ms: 0.0,
        total_events_counted: 0,
        errors: Vec::new(),
        cell_writes: 0,
        sgr_updates: 0,
        line_advances: 0,
    };

    let mut total_parser_latency = 0.0;
    let mut latency_count = 0u64;
    let start = Instant::now();

    for event in events {
        match &event.kind {
            ReplayEventKind::PtyBytes(data) => {
                let t0 = Instant::now();
                parser.advance(data, &mut terminal);
                let dt = t0.elapsed().as_secs_f64() * 1000.0;
                result.parse_time_ms += dt;
                result.events_processed += 1;
                result.total_bytes += data.len() as u64;

                // Capture parser profile
                let p = &parser.last_profile;
                result.cell_writes += p.cell_writes;
                result.sgr_updates += p.sgr_updates;
                result.line_advances += p.line_advances;

                if dt < result.parser_latency_min_ms {
                    result.parser_latency_min_ms = dt;
                }
                if dt > result.parser_latency_max_ms {
                    result.parser_latency_max_ms = dt;
                }
                total_parser_latency += dt;
                latency_count += 1;

                let dc = terminal.grid.dirty_count();
                if dc > result.peak_dirty_rows {
                    result.peak_dirty_rows = dc;
                }
            }
            ReplayEventKind::Resize { cols: c, rows: r } => {
                if *c as usize != terminal.grid.cols || *r as usize != terminal.grid.rows {
                    let t0 = Instant::now();
                    terminal.resize(*c as usize, *r as usize);
                    result.grid_update_time_ms += t0.elapsed().as_secs_f64() * 1000.0;
                }
            }
            ReplayEventKind::Snapshot(_) => {}
        }
    }

    let elapsed = start.elapsed().as_secs_f64();
    result.wall_time_ms = elapsed * 1000.0;
    let seconds = (result.parse_time_ms / 1000.0).max(f64::EPSILON);
    result.parse_throughput_mbps = (result.total_bytes as f64 / seconds) / (1024.0 * 1024.0);
    result.parser_latency_avg_ms = if latency_count > 0 {
        total_parser_latency / latency_count as f64
    } else {
        0.0
    };
    if result.parser_latency_min_ms == f64::MAX {
        result.parser_latency_min_ms = 0.0;
    }
    if result.parser_latency_max_ms == f64::MIN {
        result.parser_latency_max_ms = 0.0;
    }

    result.final_grid_hash = format::grid_hash(&terminal.grid);
    result.final_cursor = (terminal.grid.cursor_row(), terminal.grid.cursor_col());
    result.scrollback_lines = terminal.scrollback.len();
    result.scrollback_hash = format::scrollback_hash(&terminal.scrollback);
    result.alt_screen_state = terminal.in_alt_screen();
    result.total_events_counted = events.len() as u64;

    result
}

/// Print a human-readable headless replay report.
pub fn print_headless(r: &HeadlessResult) {
    println!("=== Headless Replay Report ===");
    println!("  Events:              {}", r.events_processed);
    println!(
        "  Total bytes:         {} ({:.2} KB)",
        r.total_bytes,
        r.total_bytes as f64 / 1024.0
    );
    println!("  Total parse time:    {:.2} ms", r.parse_time_ms);
    println!("  Wall time:           {:.2} ms", r.wall_time_ms);
    println!("  Grid update time:    {:.2} ms", r.grid_update_time_ms);
    println!("  Throughput:          {:.2} MB/s", r.parse_throughput_mbps);
    println!("  Peak dirty rows:     {}", r.peak_dirty_rows);
    println!("  Grid hash:           {:016x}", r.final_grid_hash);
    println!(
        "  Final cursor:        ({}, {})",
        r.final_cursor.0, r.final_cursor.1
    );
    println!("  Scrollback lines:    {}", r.scrollback_lines);
    println!("  Scrollback hash:     {:016x}", r.scrollback_hash);
    println!(
        "  Parser latency (ms): min={:.4} avg={:.4} max={:.4}",
        r.parser_latency_min_ms, r.parser_latency_avg_ms, r.parser_latency_max_ms
    );

    if r.cell_writes > 0 {
        println!("\n=== Parser Profile ===");
        println!("  Cell writes:       {}", r.cell_writes);
        println!("  SGR updates:       {}", r.sgr_updates);
        println!("  Line advances:     {}", r.line_advances);
    }

    if !r.errors.is_empty() {
        println!("\n=== Errors ===");
        for e in &r.errors {
            println!("  {}", e);
        }
    }
}

/// Print machine-readable headless report (one line, key=value).
pub fn print_headless_machine(r: &HeadlessResult) {
    println!(
        "HEADLESS_RESULT events={} bytes={} parse_ms={:.4} throughput_mbps={:.4} \
         peak_dirty={} grid_hash={:016x} cursor_row={} cursor_col={} \
         scrollback_lines={} scrollback_hash={:016x} \
         parser_latency_min_ms={:.4} parser_latency_avg_ms={:.4} parser_latency_max_ms={:.4} \
         grid_update_ms={:.4} \
         cell_writes={} sgr_updates={} line_advances={} wall_ms={:.4}",
        r.events_processed,
        r.total_bytes,
        r.parse_time_ms,
        r.parse_throughput_mbps,
        r.peak_dirty_rows,
        r.final_grid_hash,
        r.final_cursor.0,
        r.final_cursor.1,
        r.scrollback_lines,
        r.scrollback_hash,
        r.parser_latency_min_ms,
        r.parser_latency_avg_ms,
        r.parser_latency_max_ms,
        r.grid_update_time_ms,
        r.cell_writes,
        r.sgr_updates,
        r.line_advances,
        r.wall_time_ms,
    );
}