use termichart_core::{Color, Style};

use crate::cell::CellGrid;

/// Converts a [`CellGrid`] into a string containing ANSI escape sequences
/// suitable for printing to a terminal.
///
/// The renderer tracks the previous style and only emits SGR changes when the
/// style differs, to minimise output size. At the end of each row, styles are
/// reset and a newline is appended.
pub fn render_to_string(grid: &CellGrid) -> String {
    // Pre-allocate a generous buffer. Each cell typically needs 1 char plus
    // occasional escape sequences.
    let mut buf = String::with_capacity((grid.width as usize) * (grid.height as usize) * 4);

    let mut prev_style = Style::default();

    for row in 0..grid.height {
        // Reset style at the start of each row so we begin from a known state.
        if row > 0 {
            buf.push('\n');
            prev_style = Style::default();
        }

        for col in 0..grid.width {
            let cell = match grid.get(col, row) {
                Some(c) => c,
                None => continue,
            };

            // Emit SGR sequences only for style changes.
            if cell.style != prev_style {
                emit_style_diff(&mut buf, &prev_style, &cell.style);
                prev_style = cell.style;
            }

            buf.push(cell.ch);
        }

        // Reset style and erase any leftover characters to end of line.
        // This prevents ghost content from previous frames in redrawn output.
        buf.push_str("\x1b[0m\x1b[K");
    }

    // Final newline after the last row.
    buf.push('\n');

    buf
}

/// Emits the minimal set of ANSI SGR escape sequences to transition from
/// `prev` to `next`.
fn emit_style_diff(buf: &mut String, prev: &Style, next: &Style) {
    // If the new style is default, just reset.
    if *next == Style::default() {
        buf.push_str("\x1b[0m");
        return;
    }

    // If any attribute is being *turned off* (bold or dim going from true to
    // false), we need a full reset followed by re-enabling what's still on,
    // because ANSI doesn't have a portable "un-bold" code.
    let needs_reset = (prev.bold && !next.bold) || (prev.dim && !next.dim);

    if needs_reset {
        buf.push_str("\x1b[0m");
        // After reset, everything needs re-emitting.
        if next.bold {
            buf.push_str("\x1b[1m");
        }
        if next.dim {
            buf.push_str("\x1b[2m");
        }
        emit_fg(buf, &next.fg);
        emit_bg(buf, &next.bg);
        return;
    }

    // Enable attributes that are newly turned on.
    if next.bold && !prev.bold {
        buf.push_str("\x1b[1m");
    }
    if next.dim && !prev.dim {
        buf.push_str("\x1b[2m");
    }

    // Update colours only if changed.
    if next.fg != prev.fg {
        emit_fg(buf, &next.fg);
    }
    if next.bg != prev.bg {
        emit_bg(buf, &next.bg);
    }
}

/// Emits the foreground colour escape sequence.
fn emit_fg(buf: &mut String, color: &Color) {
    match color {
        Color::Default => buf.push_str("\x1b[39m"),
        Color::Ansi(n) => {
            buf.push_str("\x1b[38;5;");
            buf.push_str(&n.to_string());
            buf.push('m');
        }
        Color::Rgb(r, g, b) => {
            buf.push_str("\x1b[38;2;");
            buf.push_str(&r.to_string());
            buf.push(';');
            buf.push_str(&g.to_string());
            buf.push(';');
            buf.push_str(&b.to_string());
            buf.push('m');
        }
    }
}

/// Emits the background colour escape sequence.
fn emit_bg(buf: &mut String, color: &Color) {
    match color {
        Color::Default => buf.push_str("\x1b[49m"),
        Color::Ansi(n) => {
            buf.push_str("\x1b[48;5;");
            buf.push_str(&n.to_string());
            buf.push('m');
        }
        Color::Rgb(r, g, b) => {
            buf.push_str("\x1b[48;2;");
            buf.push_str(&r.to_string());
            buf.push(';');
            buf.push_str(&g.to_string());
            buf.push(';');
            buf.push_str(&b.to_string());
            buf.push('m');
        }
    }
}
