//! Byte goldens for the line-diff transport.
//!
//! Every golden was captured from the oracle by driving ink's
//! `createIncremental` (`ink/src/log-update.ts`) against a fake capturing
//! stream inside the ink repo, via `tsx`. Capture configuration:
//! `logUpdate.create(stream, { showCursor: true, incremental: true })` with
//! **no** `setCursorPosition` call. `showCursor: true` is load-bearing: it
//! suppresses the `cli-cursor.hide()` terminal-IO write that the pure
//! transport intentionally omits (NO terminal IO); omitting
//! `setCursorPosition` keeps `activeCursor` undefined, so the cursor suffix /
//! return-to-bottom prefix collapse to `""`. The result is exactly the
//! pure `&str -> bytes` content this crate models.
//!
//! Each `\u{1b}[...` literal below is the verbatim oracle write.

use super::*;

fn diff_str(d: &mut LineDiff, s: &str) -> String {
    String::from_utf8(d.diff(s)).expect("ascii/utf8 escapes")
}

// Oracle: run('bootstrap single', ['Hello\n']) -> writes[0] = "Hello\n"
#[test]
fn bootstrap_single_frame() {
    let mut d = LineDiff::new();
    assert_eq!(diff_str(&mut d, "Hello\n"), "Hello\n");
}

// Oracle: run('unchanged frame', ['Hello\n','Hello\n']) -> writes.length == 1
// i.e. the SECOND diff emits ZERO bytes.
#[test]
fn unchanged_frame_emits_zero_bytes() {
    let mut d = LineDiff::new();
    let first = d.diff("Hello\n");
    assert_eq!(first, b"Hello\n");
    let second = d.diff("Hello\n");
    assert!(second.is_empty(), "unchanged frame must emit zero bytes");
}

// Oracle: run('single changed line',
//   ['Line 1\nLine 2\nLine 3\n','Line 1\nUpdated\nLine 3\n'])
//   writes[1] = "[3A[E[1GUpdated[K\n[E"
#[test]
fn single_changed_line() {
    let mut d = LineDiff::new();
    let _ = d.diff("Line 1\nLine 2\nLine 3\n");
    let out = diff_str(&mut d, "Line 1\nUpdated\nLine 3\n");
    assert_eq!(out, "\u{1b}[3A\u{1b}[E\u{1b}[1GUpdated\u{1b}[K\n\u{1b}[E");
}

// Oracle: run('shrink 3->1', ['Line 1\nLine 2\nLine 3\n','Line 1\n'])
//   writes[1] = "[2K[1A[2K[1A[2K[G[1A[E"
#[test]
fn shrink_three_to_one() {
    let mut d = LineDiff::new();
    let _ = d.diff("Line 1\nLine 2\nLine 3\n");
    let out = diff_str(&mut d, "Line 1\n");
    assert_eq!(
        out,
        "\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[G\u{1b}[1A\u{1b}[E"
    );
}

// Oracle: run('grow 1->3', ['Line 1\n','Line 1\nLine 2\nLine 3\n'])
//   writes[1] = "[1A[E[1GLine 2[K\n[1GLine 3[K\n"
#[test]
fn grow_one_to_three() {
    let mut d = LineDiff::new();
    let _ = d.diff("Line 1\n");
    let out = diff_str(&mut d, "Line 1\nLine 2\nLine 3\n");
    assert_eq!(
        out,
        "\u{1b}[1A\u{1b}[E\u{1b}[1GLine 2\u{1b}[K\n\u{1b}[1GLine 3\u{1b}[K\n"
    );
}

// Oracle: run('fullscreen no trailing changed first', ['A\nB','X\nB'])
//   writes[1] = "[1A[1GX[K\n"
// Last line B is unchanged AND last with no trailing newline: no overshoot,
// no cursorNextLine emitted past it.
#[test]
fn fullscreen_no_trailing_changed_first_line() {
    let mut d = LineDiff::new();
    let _ = d.diff("A\nB");
    let out = diff_str(&mut d, "X\nB");
    assert_eq!(out, "\u{1b}[1A\u{1b}[1GX\u{1b}[K\n");
}

// Oracle: run('fullscreen no trailing newline grow', ['A','A\nB\nC'])
//   writes[1] = "[0A[E[1GB[K\n[1GC[K"
// Final line C is changed AND last with no trailing newline: written WITHOUT
// a trailing '\n' (no overshoot past last line).
#[test]
fn fullscreen_no_trailing_newline_grow() {
    let mut d = LineDiff::new();
    let _ = d.diff("A");
    let out = diff_str(&mut d, "A\nB\nC");
    assert_eq!(
        out,
        "\u{1b}[0A\u{1b}[E\u{1b}[1GB\u{1b}[K\n\u{1b}[1GC\u{1b}[K"
    );
}

// Oracle: run('shrink no trailing', ['A\nB','A'])
//   writes[1] = "[2K[G[1A"
#[test]
fn shrink_no_trailing_newline() {
    let mut d = LineDiff::new();
    let _ = d.diff("A\nB");
    let out = diff_str(&mut d, "A");
    assert_eq!(out, "\u{1b}[2K\u{1b}[G\u{1b}[1A");
}

// Oracle: run('render to newline only', ['Line 1\nLine 2\nLine 3\n','\n'])
//   writes[1] = "[2K[1A[2K[1A[2K[1A[2K[G\n"
// Bootstrap branch (str === '\n'): full eraseLines(4) + the newline.
#[test]
fn render_to_newline_only() {
    let mut d = LineDiff::new();
    let _ = d.diff("Line 1\nLine 2\nLine 3\n");
    let out = diff_str(&mut d, "\n");
    assert_eq!(
        out,
        "\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[G\n"
    );
    // Oracle: rendering '\n' again is identical -> zero bytes.
    assert!(d.diff("\n").is_empty());
}

// Oracle: run('shrinking tight 3->2->1',
//   ['Line 1\nLine 2\nLine 3\n','Line 1\nLine 2\n','Line 1\n'])
//   writes[1] = "[2K[1A[2K[G[2A[E[E"
//   writes[2] = "[2K[1A[2K[G[1A[E"
// The writes[2] golden matches ink's `test/log-update.tsx` third-call pin
// (eraseLines(2) + cursorUp(1) + cursorNextLine).
#[test]
fn shrinking_tight_three_two_one() {
    let mut d = LineDiff::new();
    let _ = d.diff("Line 1\nLine 2\nLine 3\n");
    let second = diff_str(&mut d, "Line 1\nLine 2\n");
    assert_eq!(
        second,
        "\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[G\u{1b}[2A\u{1b}[E\u{1b}[E"
    );
    let third = diff_str(&mut d, "Line 1\n");
    assert_eq!(
        third,
        "\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[G\u{1b}[1A\u{1b}[E"
    );
}

// reset() restores bootstrap behavior for the next frame (done()/clear()).
#[test]
fn reset_restores_bootstrap() {
    let mut d = LineDiff::new();
    let _ = d.diff("Line 1\nLine 2\nLine 3\n");
    d.reset();
    // After reset, previous_output is empty -> bootstrap branch:
    // eraseLines(0) (== "") + the frame.
    assert_eq!(diff_str(&mut d, "Line 1\n"), "Line 1\n");
}

// Escape builders match the captured ansi-escapes bytes.
#[test]
fn escape_builders_match_oracle() {
    assert_eq!(cursor_up(0), "\u{1b}[0A");
    assert_eq!(cursor_up(3), "\u{1b}[3A");
    assert_eq!(cursor_to(0), "\u{1b}[1G");
    assert_eq!(cursor_to(5), "\u{1b}[6G");
    assert_eq!(cursor_next_line(), "\u{1b}[E");
    assert_eq!(erase_end_line(), "\u{1b}[K");
    assert_eq!(erase_lines(0), "");
    assert_eq!(erase_lines(1), "\u{1b}[2K\u{1b}[G");
    assert_eq!(erase_lines(2), "\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[G");
}

// `LineDiff` views lines through `line_ranges` slices where it previously used
// `split('\n')` — the diff covenant rests on the two never disagreeing. Pin
// the equivalence on every corner shape: empty input (split yields one empty
// element), lone newline, trailing newline, leading newline, consecutive
// newlines, and multibyte content around the separators.
#[test]
fn line_ranges_match_split_on_corner_shapes() {
    for s in ["", "\n", "a\n", "\na", "a\n\nb", "é\n😀\n", "no newline"] {
        let via_ranges: Vec<&str> = line_ranges(s).into_iter().map(|r| &s[r]).collect();
        let via_split: Vec<&str> = s.split('\n').collect();
        assert_eq!(
            via_ranges, via_split,
            "line_ranges vs split('\\n') for {s:?}"
        );
    }
}

// ── `last_changed_lines` telemetry discriminator (additive; byte-inert).
//
// These pin that the counter reflects the REAL number of lines a frame
// rewrote. They are mutation-discriminating: a counter hardwired to 0, to a
// constant, or to the wrong branch's value fails at least one assertion.
// `new()` initializes the counter to 0.
#[test]
fn changed_lines_is_zero_on_construction() {
    let d = LineDiff::new();
    assert_eq!(d.last_changed_lines(), 0);
}

// Two identical consecutive frames: the second diff emits zero bytes AND
// rewrites zero lines. Discriminates a counter hardwired to a non-zero constant
// or to the visible line count.
#[test]
fn changed_lines_zero_on_unchanged_frame() {
    let mut d = LineDiff::new();
    let _ = d.diff("Line 1\nLine 2\nLine 3\n");
    // bootstrap of 3 visible lines -> full repaint count.
    assert_eq!(d.last_changed_lines(), 3, "bootstrap repaints all 3 lines");
    let second = d.diff("Line 1\nLine 2\nLine 3\n");
    assert!(second.is_empty(), "unchanged frame emits zero bytes");
    assert_eq!(
        d.last_changed_lines(),
        0,
        "unchanged frame rewrites zero lines"
    );
}

// Exactly one line differs between consecutive frames: exactly one line is
// rewritten. Mirrors `single_changed_line`'s setup. Discriminates a counter
// hardwired to 0, to 3 (all lines), or to the bootstrap value.
#[test]
fn changed_lines_one_on_single_line_change() {
    let mut d = LineDiff::new();
    let _ = d.diff("Line 1\nLine 2\nLine 3\n");
    let out = diff_str(&mut d, "Line 1\nUpdated\nLine 3\n");
    // Byte output is unchanged from the golden — the counter rides alongside.
    assert_eq!(out, "\u{1b}[3A\u{1b}[E\u{1b}[1GUpdated\u{1b}[K\n\u{1b}[E");
    assert_eq!(
        d.last_changed_lines(),
        1,
        "only the middle line was rewritten"
    );
}

// Two lines differ in a 3-line frame: counts exactly 2 (not 1, not 3).
// Tightens discrimination between "one changed line" and "all lines".
#[test]
fn changed_lines_counts_each_changed_line() {
    let mut d = LineDiff::new();
    let _ = d.diff("Line 1\nLine 2\nLine 3\n");
    let _ = d.diff("Line 1\nAlpha\nBravo\n");
    assert_eq!(
        d.last_changed_lines(),
        2,
        "lines 2 and 3 changed, line 1 unchanged"
    );
}

// First/bootstrap frame of N visible lines repaints all N. Parameterized over N
// so a constant can't satisfy it. A trailing newline yields N visible lines
// (the trailing empty slot is not counted).
#[test]
fn changed_lines_full_repaint_on_bootstrap() {
    for n in 1u32..=5 {
        let mut d = LineDiff::new();
        let frame: String = (1..=n).map(|i| format!("Line {i}\n")).collect();
        let _ = d.diff(&frame);
        assert_eq!(
            d.last_changed_lines(),
            n,
            "bootstrap of {n} visible lines repaints all {n}"
        );
    }
}
