// tests/m8_5_acceptance.rs --- T M8.5 magit-class foldable section view.

//! Acceptance for the M8.5 deliverable: a foldable section view
//! built on the two-buffer projection pattern (Plan A from the
//! M8.5 architecture decision). The package source-of-record is
//! the in-tree fixture at `tests/fixtures/pmacs-magit/`.
//!
//! The four spec acceptance bullets:
//!
//! 1. Section rendering: a buffer with three top-level sections,
//!    each containing nested subsections, renders correctly.
//! 2. Folding state survives buffer redraw and view repaint.
//! 3. Folding does not modify the rope; the underlying text is
//!    unchanged regardless of fold state.
//! 4. Cursor navigation respects fold state (folded sections
//!    skipped on `C-n` / `C-p`).
//!
//! Plus regressions:
//!
//! * Visible buffer rejects user edits (intercept).
//! * Toggle-fold via TAB binding through the dispatch path.
//! * fold-all / unfold-all commands.
//! * Nested fold: collapsing a parent hides every descendant.
//! * Re-open is reload-safe (`on_unload` hook drops handles +
//!   commands cleanly).

use std::path::PathBuf;
use std::time::{Duration, Instant};

use pmacs::editor::EditorState;
use pmacs::lua_bindings::PackageInstallOverride;
use tempfile::TempDir;

fn magit_package_path() -> PathBuf {
    let here = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    here.join("tests").join("fixtures").join("pmacs-magit")
}

#[allow(dead_code)]
fn pump_until<F: Fn(&EditorState) -> bool>(state: &mut EditorState, predicate: F) {
    let deadline = Instant::now() + Duration::from_secs(5);
    while !predicate(state) {
        assert!(
            Instant::now() < deadline,
            "async pump deadline exceeded after 5s"
        );
        state.tick_async();
        std::thread::sleep(Duration::from_millis(2));
    }
}

fn editor_with_magit() -> (EditorState, TempDir, TempDir) {
    let cache = tempfile::tempdir().expect("cache tempdir");
    let user_root = tempfile::tempdir().expect("user-root tempdir");
    let mut state = EditorState::new();
    state.lua_host.reopen_init_phase_for_testing();
    state.lua_host.set_package_install_override(
        PackageInstallOverride::new()
            .with_cache_dir(cache.path().to_path_buf())
            .with_user_install_root(user_root.path().to_path_buf()),
    );
    let pkg = magit_package_path();
    let pkg_str = pkg.display().to_string();
    let install = format!(
        r#"
        pmacs.packages.install_local("{pkg_str}")
        require("pmacs-magit")
    "#
    );
    state
        .lua_host
        .eval(Some("magit-install"), &install)
        .unwrap_or_else(|e| panic!("install_local + require failed: {e}"));
    (state, cache, user_root)
}

/// Standard test spec: three top-level sections, each with two
/// nested subsections. Used by most of the acceptance tests.
const STANDARD_SPEC: &str = r#"
    require("pmacs-magit").open {
        {
            id = "a", title = "Section A",
            body = "a-body-1\na-body-2",
            children = {
                { id = "a1", title = "Subsection A1", body = "a1-body" },
                { id = "a2", title = "Subsection A2", body = "a2-body" },
            },
        },
        {
            id = "b", title = "Section B",
            body = "b-body",
            children = {
                { id = "b1", title = "Subsection B1", body = "b1-body" },
                { id = "b2", title = "Subsection B2", body = "b2-body" },
            },
        },
        {
            id = "c", title = "Section C",
            body = "c-body",
            children = {
                { id = "c1", title = "Subsection C1", body = "c1-body" },
                { id = "c2", title = "Subsection C2", body = "c2-body" },
            },
        },
    }
"#;

fn open_standard(state: &mut EditorState) {
    state
        .lua_host
        .eval(Some("magit-open-standard"), STANDARD_SPEC)
        .unwrap_or_else(|e| panic!("magit.open failed: {e}"));
}

fn active_buffer_text(state: &mut EditorState) -> String {
    state
        .lua_host
        .lua()
        .load(
            r"
            local buf = pmacs.window.buffer()
            return buf:slice(0, buf:len())
        ",
        )
        .eval::<String>()
        .expect("read active buffer")
}

fn source_text(state: &mut EditorState) -> String {
    state
        .lua_host
        .lua()
        .load(
            r#"
            local te = require("pmacs-magit").__pmacs_magit_test_seam_DO_NOT_USE
            local h = te.active_handle()
            return h.source:slice(0, h.source:len())
        "#,
        )
        .eval::<String>()
        .expect("read source buffer text")
}

// ---------------------------------------------------------------------------
// Bullet 1 --- section rendering
// ---------------------------------------------------------------------------

#[test]
fn magit_three_top_level_with_nested_subsections_render_correctly() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);
    let text = active_buffer_text(&mut state);
    let lines: Vec<&str> = text.lines().collect();

    // Default: everything expanded. Each top-level section has:
    //   1 header + 1-or-2 body lines + (2 children * (1 header + 1 body)).
    // Three top-levels with the standard spec produces 21 lines:
    //   A header (1) + 2 body + A1 header + a1 body + A2 header + a2 body
    //   = 7 lines for A (and same for B/C).
    // But B and C have 1 body each (not 2), so they're 6 lines each.
    // Total: 7 + 6 + 6 = 19.
    assert_eq!(
        lines.len(),
        19,
        "expected 19 lines (7 + 6 + 6); got {}: {}",
        lines.len(),
        text
    );

    // Top-level headers (depth 0) start with the fold marker.
    assert!(
        lines[0].starts_with("v Section A") || lines[0].starts_with("> Section A"),
        "line 0 must be Section A header: {:?}",
        lines[0]
    );
    // Body indented under header (2 spaces past header indent + 0 marker).
    assert_eq!(lines[1], "  a-body-1");
    assert_eq!(lines[2], "  a-body-2");
    // Subsection header indented 2 spaces.
    assert!(
        lines[3].starts_with("  ") && lines[3].contains("Subsection A1"),
        "line 3 must be Subsection A1: {:?}",
        lines[3]
    );
    // Subsection body indented 4 spaces (2 levels deep + 1).
    assert_eq!(lines[4], "    a1-body");
}

// ---------------------------------------------------------------------------
// Bullet 2 --- folding state survives buffer redraw
// ---------------------------------------------------------------------------

#[test]
fn magit_fold_state_survives_explicit_repaint() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);

    // Collapse section "a" via direct fold-state manipulation (the
    // test seam exposes the helpers); then call repaint twice and
    // verify the projection is the same both times.
    state
        .lua_host
        .eval(
            Some("collapse-and-repaint"),
            r#"
                local te = require("pmacs-magit").__pmacs_magit_test_seam_DO_NOT_USE
                local h = te.active_handle()
                te.fold.toggle(h.fold_state, "a")
                te.repaint_visible(h)
                _G.M8_5_AFTER_FIRST = h.visible:slice(0, h.visible:len())
                te.repaint_visible(h)
                _G.M8_5_AFTER_SECOND = h.visible:slice(0, h.visible:len())
                _G.M8_5_FOLD_A = h.fold_state.a
            "#,
        )
        .expect("collapse + repaint");
    let after_first: String = state
        .lua_host
        .lua()
        .globals()
        .get("M8_5_AFTER_FIRST")
        .expect("first projection");
    let after_second: String = state
        .lua_host
        .lua()
        .globals()
        .get("M8_5_AFTER_SECOND")
        .expect("second projection");
    let fold_a: String = state
        .lua_host
        .lua()
        .globals()
        .get("M8_5_FOLD_A")
        .expect("fold state of a");
    assert_eq!(fold_a, "collapsed", "section a must be collapsed");
    assert_eq!(
        after_first, after_second,
        "two consecutive repaints with the same fold state must be byte-identical"
    );

    // Section A's body and subsections must not appear in the projection.
    assert!(
        !after_first.contains("a-body-1") && !after_first.contains("Subsection A1"),
        "collapsed Section A must hide its body and children: {after_first}"
    );
    // Header line for A is still present (with collapsed marker).
    assert!(
        after_first.contains("Section A"),
        "collapsed Section A must still show its header: {after_first}"
    );
}

// ---------------------------------------------------------------------------
// Bullet 3 --- source rope unchanged across fold ops
// ---------------------------------------------------------------------------

#[test]
fn magit_source_rope_unchanged_across_fold_operations() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);

    let source_before = source_text(&mut state);
    assert!(!source_before.is_empty(), "source buffer must have content");

    // A sequence of fold operations: collapse a few, expand them,
    // collapse all, expand all. After all of this the source rope
    // must be byte-for-byte identical to its initial state.
    state
        .lua_host
        .eval(
            Some("fold-churn"),
            r#"
                pmacs.command.invoke("pmacs-magit.fold-all")
                pmacs.command.invoke("pmacs-magit.unfold-all")
                local te = require("pmacs-magit").__pmacs_magit_test_seam_DO_NOT_USE
                local h = te.active_handle()
                te.fold.toggle(h.fold_state, "b1")
                te.repaint_visible(h)
                te.fold.toggle(h.fold_state, "b1")
                te.repaint_visible(h)
                te.fold.toggle(h.fold_state, "a")
                te.repaint_visible(h)
            "#,
        )
        .expect("fold churn");

    let source_after = source_text(&mut state);
    assert_eq!(
        source_before, source_after,
        "source rope must be byte-for-byte unchanged after fold churn"
    );
}

// ---------------------------------------------------------------------------
// Bullet 4 --- cursor navigation respects fold state
// ---------------------------------------------------------------------------
//
// In Plan A, "respects fold state" is a structural property of the
// visible buffer: folded content isn't in it at all, so any cursor
// movement (`C-n` / `C-p`, page-down, word-skip, ...) within the
// visible buffer cannot land on a folded line. The strongest
// version of this test verifies that no byte position in the
// visible buffer corresponds to text that the source contains
// inside a folded section.

#[test]
fn magit_visible_buffer_excludes_folded_content_after_collapse() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);

    // Collapse Section B.
    state
        .lua_host
        .eval(
            Some("collapse-b"),
            r#"
                local te = require("pmacs-magit").__pmacs_magit_test_seam_DO_NOT_USE
                local h = te.active_handle()
                te.fold.toggle(h.fold_state, "b")
                te.repaint_visible(h)
            "#,
        )
        .expect("collapse b");

    let visible = active_buffer_text(&mut state);
    let source = source_text(&mut state);

    // Source still has every body line.
    assert!(source.contains("b-body"));
    assert!(source.contains("b1-body"));
    assert!(source.contains("b2-body"));
    assert!(source.contains("Subsection B1"));
    assert!(source.contains("Subsection B2"));

    // Visible buffer omits B's body and all of B's descendants.
    assert!(
        !visible.contains("b-body"),
        "collapsed Section B's body must not be in visible: {visible}"
    );
    assert!(
        !visible.contains("Subsection B1"),
        "collapsed Section B's child header must be hidden: {visible}"
    );
    assert!(
        !visible.contains("b1-body") && !visible.contains("b2-body"),
        "collapsed Section B's grandchildren bodies must be hidden: {visible}"
    );
    // B's own header is still visible (collapsed marker, not its body).
    assert!(
        visible.contains("Section B"),
        "Section B's header must still be in visible: {visible}"
    );

    // Cursor in visible buffer: the line count after Section B's
    // header is the start of Section C. C-n / C-p respect this
    // because there's nothing in between.
    let visible_lines: Vec<&str> = visible.lines().collect();
    let b_idx = visible_lines
        .iter()
        .position(|l| l.contains("Section B"))
        .expect("Section B header in visible");
    let c_idx = visible_lines
        .iter()
        .position(|l| l.contains("Section C"))
        .expect("Section C header in visible");
    assert_eq!(
        c_idx,
        b_idx + 1,
        "with B collapsed, C must be the line immediately after B's header in the visible buffer"
    );
}

// ---------------------------------------------------------------------------
// Regression: visible buffer rejects user edits
// ---------------------------------------------------------------------------

#[test]
fn magit_visible_buffer_rejects_user_edits() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);

    let msg: String = state
        .lua_host
        .lua()
        .load(
            r#"
                local buf = pmacs.window.buffer()
                local ok, err = pcall(function() buf:insert(0, "x") end)
                assert(not ok, "user edit must be rejected by intercept")
                return tostring(err)
            "#,
        )
        .eval()
        .expect("user-edit eval");
    assert!(
        msg.contains("section view") && msg.contains("not supported"),
        "rejection must explain why; got: {msg}"
    );
}

// ---------------------------------------------------------------------------
// Regression: TAB binding invokes toggle-fold
// ---------------------------------------------------------------------------

#[test]
fn magit_tab_binding_toggles_fold_at_cursor() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);

    // Cursor lands at byte 0 by default → line 0 = Section A header.
    // Invoke the bound TAB command: this is a buffer-scope keymap
    // entry that resolves to pmacs-magit.toggle-fold.
    //
    // Since we don't have a clean dispatch_key seam in tests yet,
    // we exercise the same code path by invoking the command
    // directly (M8.4 finding 4: `pmacs.editor.move_cursor` and
    // friends will let us drive through the actual keymap path).
    state
        .lua_host
        .eval(
            Some("toggle-via-command"),
            r#"pmacs.command.invoke("pmacs-magit.toggle-fold")"#,
        )
        .expect("toggle-fold invoke");
    let after = active_buffer_text(&mut state);
    assert!(
        !after.contains("a-body-1"),
        "after toggling Section A, its body must be hidden: {after}"
    );
}

// ---------------------------------------------------------------------------
// Regression: fold-all / unfold-all
// ---------------------------------------------------------------------------

#[test]
fn magit_fold_all_collapses_every_section() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);
    state
        .lua_host
        .eval(
            Some("fold-all"),
            r#"pmacs.command.invoke("pmacs-magit.fold-all")"#,
        )
        .expect("fold-all");
    let visible = active_buffer_text(&mut state);
    // No body lines should be visible.
    for body in [
        "a-body-1", "a-body-2", "a1-body", "a2-body", "b-body", "b1-body", "b2-body", "c-body",
        "c1-body", "c2-body",
    ] {
        assert!(
            !visible.contains(body),
            "fold-all: body {body} must not be in visible: {visible}"
        );
    }
    // Three top-level headers still show.
    assert!(visible.contains("Section A"));
    assert!(visible.contains("Section B"));
    assert!(visible.contains("Section C"));
    // Subsection headers are hidden because their parents are folded.
    assert!(!visible.contains("Subsection A1"));
    assert!(!visible.contains("Subsection B2"));
}

#[test]
fn magit_unfold_all_after_fold_all_restores_full_view() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);
    let before = active_buffer_text(&mut state);
    state
        .lua_host
        .eval(
            Some("fold-then-unfold"),
            r#"
                pmacs.command.invoke("pmacs-magit.fold-all")
                pmacs.command.invoke("pmacs-magit.unfold-all")
            "#,
        )
        .expect("fold-then-unfold");
    let after = active_buffer_text(&mut state);
    assert_eq!(
        before, after,
        "unfold-all after fold-all must restore the original view"
    );
}

// ---------------------------------------------------------------------------
// Regression: nested fold cascade
// ---------------------------------------------------------------------------

#[test]
fn magit_collapsing_parent_hides_descendant_subtree() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);
    state
        .lua_host
        .eval(
            Some("collapse-a-only"),
            r#"
                local te = require("pmacs-magit").__pmacs_magit_test_seam_DO_NOT_USE
                local h = te.active_handle()
                te.fold.toggle(h.fold_state, "a")
                te.repaint_visible(h)
            "#,
        )
        .expect("collapse a");
    let v = active_buffer_text(&mut state);
    // Collapsing A hides every descendant: A's body, A's children's
    // headers, A's children's bodies. None of "a-body-1", "a-body-2",
    // "Subsection A1", "a1-body", "Subsection A2", "a2-body" should appear.
    for hidden in [
        "a-body-1",
        "a-body-2",
        "Subsection A1",
        "Subsection A2",
        "a1-body",
        "a2-body",
    ] {
        assert!(
            !v.contains(hidden),
            "collapsed A must hide descendant {hidden}: {v}"
        );
    }
    // B and C are unaffected.
    assert!(v.contains("Subsection B1"));
    assert!(v.contains("Subsection C2"));
    assert!(v.contains("c2-body"));
}

// ---------------------------------------------------------------------------
// Regression: parse errors surface usefully
// ---------------------------------------------------------------------------

#[test]
fn magit_open_with_duplicate_id_errors() {
    let (state, _c, _u) = editor_with_magit();
    let msg: String = state
        .lua_host
        .lua()
        .load(
            r#"
                local ok, err = pcall(function()
                    require("pmacs-magit").open {
                        { id = "x", title = "X", body = "" },
                        { id = "x", title = "X-again", body = "" },
                    }
                end)
                assert(not ok, "duplicate id must error")
                return tostring(err)
            "#,
        )
        .eval()
        .expect("dup-id eval");
    assert!(
        msg.contains("duplicate section id"),
        "error must name the duplicate-id problem: {msg}"
    );
}

#[test]
fn magit_open_with_no_sections_errors() {
    let (state, _c, _u) = editor_with_magit();
    let msg: String = state
        .lua_host
        .lua()
        .load(
            r#"
                local ok, err = pcall(function()
                    require("pmacs-magit").open {}
                end)
                assert(not ok, "empty spec must error")
                return tostring(err)
            "#,
        )
        .eval()
        .expect("empty-spec eval");
    assert!(
        msg.contains("at least one"),
        "error must explain the empty-spec problem: {msg}"
    );
}

// ---------------------------------------------------------------------------
// Regression: section-at lookup via test seam
// ---------------------------------------------------------------------------

#[test]
fn magit_section_at_lookup_resolves_header_and_body_lines() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);
    let answers: String = state
        .lua_host
        .lua()
        .load(
            r#"
                local te = require("pmacs-magit").__pmacs_magit_test_seam_DO_NOT_USE
                local h = te.active_handle()
                local proj = h.projection
                -- Line 0: A header. Line 1: a-body-1 (belongs to A).
                -- Line 3: A1 header. Line 4: a1-body (belongs to A1).
                local s0 = te.fold.section_at(proj, 0)
                local s1 = te.fold.section_at(proj, 1)
                local s3 = te.fold.section_at(proj, 3)
                local s4 = te.fold.section_at(proj, 4)
                return s0 .. "|" .. s1 .. "|" .. s3 .. "|" .. s4
            "#,
        )
        .eval()
        .expect("section_at eval");
    assert_eq!(answers, "a|a|a1|a1");
}

// ---------------------------------------------------------------------------
// Pass-2 review High1 --- source-of-truth is read-only
// ---------------------------------------------------------------------------
//
// Earlier draft created a leading-space-named source buffer; pmacs's
// listing / cycling does not filter leading-space names, so the
// canonical source rope was reachable via C-x <left> / C-x C-b /
// pmacs.window.switch_buffer(source). Plan A's invariant requires
// the source-of-truth to be unwritable by the user. Fix: keep the
// source as a real rope-backed buffer, but attach a read-only
// intercept to reject direct edits.

#[test]
fn magit_source_of_truth_is_a_readonly_buffer() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);

    let source_names: Vec<String> = state
        .lua_host
        .lua()
        .load(
            r"
                local out = {}
                for _, id in ipairs(pmacs.buffer.list()) do
                    local d = pmacs.describe.buffer(id)
                    if d ~= nil and d.name:find('magit%-source') then
                        out[#out + 1] = d.name
                    end
                end
                return out
            ",
        )
        .eval::<Vec<String>>()
        .expect("buffer list eval");

    assert_eq!(
        source_names.len(),
        1,
        "source rope must be present as exactly one buffer; got: {source_names:?}"
    );

    let msg: String = state
        .lua_host
        .lua()
        .load(
            r#"
                local te = require("pmacs-magit").__pmacs_magit_test_seam_DO_NOT_USE
                local h = te.active_handle()
                local before = h.source:slice(0, h.source:len())
                local ok, err = pcall(function() h.source:insert(0, "x") end)
                assert(not ok, "source edit must be rejected")
                assert(h.source:slice(0, h.source:len()) == before,
                    "rejected source edit must leave source rope unchanged")
                return tostring(err)
            "#,
        )
        .eval()
        .expect("source edit eval");
    assert!(
        msg.contains("magit source view") && msg.contains("not supported"),
        "source rejection must explain why; got: {msg}"
    );
}

// ---------------------------------------------------------------------------
// Pass-2 review Med2 --- body-only sections show fold marker
// ---------------------------------------------------------------------------
//
// Earlier draft considered a section foldable only if it had
// children, so a body-only leaf could be collapsed (body hidden)
// while its header rendered with a blank "  " marker, visually
// indistinguishable from a never-foldable leaf. Fix: foldable =
// body OR children. The marker shows whenever folding has any
// effect.

#[test]
fn magit_body_only_section_shows_fold_marker() {
    let (mut state, _c, _u) = editor_with_magit();
    state
        .lua_host
        .eval(
            Some("body-only-spec"),
            r#"
                require("pmacs-magit").open {
                    { id = "leaf", title = "Leaf", body = "single body line" },
                }
            "#,
        )
        .expect("open body-only");

    let initial = active_buffer_text(&mut state);
    let lines: Vec<&str> = initial.lines().collect();
    // Default expanded -> "v Leaf" header.
    assert!(
        lines[0].starts_with("v Leaf"),
        "body-only section must render with expanded marker; got: {:?}",
        lines[0]
    );
    assert_eq!(lines[1], "  single body line");

    // Collapse via toggle-fold.
    state
        .lua_host
        .eval(
            Some("collapse-leaf"),
            r#"pmacs.command.invoke("pmacs-magit.toggle-fold")"#,
        )
        .expect("toggle");

    let after = active_buffer_text(&mut state);
    let after_lines: Vec<&str> = after.lines().collect();
    assert_eq!(after_lines.len(), 1, "body must be hidden: {after}");
    assert!(
        after_lines[0].starts_with("> Leaf"),
        "collapsed body-only section must render with collapsed marker; \
         got: {:?}",
        after_lines[0]
    );
}

// True leaves (no body, no children) still get no marker.
#[test]
fn magit_true_leaf_section_renders_without_fold_marker() {
    let (mut state, _c, _u) = editor_with_magit();
    state
        .lua_host
        .eval(
            Some("leaf-spec"),
            r#"
                require("pmacs-magit").open {
                    { id = "empty", title = "Empty Section" },
                }
            "#,
        )
        .expect("open leaf");
    let text = active_buffer_text(&mut state);
    let lines: Vec<&str> = text.lines().collect();
    assert_eq!(lines.len(), 1, "leaf must render exactly one line");
    // "  " (no marker) prefix.
    assert!(
        lines[0].starts_with("  Empty Section"),
        "true-leaf must render with no fold marker; got: {:?}",
        lines[0]
    );
}

// ---------------------------------------------------------------------------
// Pass-2 review Med3 --- cursor reseats to same section after repaint
// ---------------------------------------------------------------------------
//
// Wholesale `:replace` of the visible buffer leaves the engine's
// window cursor at a stale byte offset. After a fold-all or a
// body-line toggle, the cursor would land on whatever happened to
// be at the same byte index in the new projection. Fix:
// repaint_visible captures the section under cursor first, then
// after repaint walks the cursor (via move_up/move_down, the same
// pattern builtin/commands/default.lua's buffer-list refresh uses)
// to that section's new header line.

#[test]
fn magit_cursor_stays_on_same_section_across_fold_all_unfold_all() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);

    // Move the cursor onto Section B's header (line 7 in the
    // default projection: line 0=A, 1-2=A body, 3=A1 hdr, 4=a1 body,
    // 5=A2 hdr, 6=a2 body, 7=B hdr).
    state
        .lua_host
        .eval(
            Some("seek-to-b"),
            r"
                for _ = 1, 7 do pmacs.editor.move_down() end
            ",
        )
        .expect("seek to B");
    let line_at_b: i64 = state
        .lua_host
        .lua()
        .load("return pmacs.editor.cursor_line()")
        .eval()
        .expect("cursor at B");
    assert_eq!(line_at_b, 7, "setup: cursor must be on Section B header");

    // fold-all + unfold-all. After this churn the projection is the
    // same as the start (per the existing unfold-after-fold-all
    // test); cursor should be back on Section B's header.
    state
        .lua_host
        .eval(
            Some("fold-then-unfold"),
            r#"
                pmacs.command.invoke("pmacs-magit.fold-all")
                pmacs.command.invoke("pmacs-magit.unfold-all")
            "#,
        )
        .expect("fold-then-unfold");
    let line_after: i64 = state
        .lua_host
        .lua()
        .load("return pmacs.editor.cursor_line()")
        .eval()
        .expect("cursor after");
    assert_eq!(
        line_after, 7,
        "cursor must be back on Section B header after fold-all + unfold-all"
    );
}

// Cursor on a body line should also follow the section: after the
// section's body is collapsed, cursor goes to the section's header.
#[test]
fn magit_cursor_from_body_line_reseats_to_header_after_collapse() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);

    // Move cursor to line 1 (Section A, body line 1).
    state
        .lua_host
        .eval(Some("to-a-body"), "pmacs.editor.move_down()")
        .expect("to A body");
    let baseline_line: i64 = state
        .lua_host
        .lua()
        .load("return pmacs.editor.cursor_line()")
        .eval()
        .expect("baseline");
    assert_eq!(baseline_line, 1, "setup: cursor on A's first body line");

    // Toggle Section A collapsed (cursor on body -> belongs to A).
    state
        .lua_host
        .eval(
            Some("toggle-from-body"),
            r#"pmacs.command.invoke("pmacs-magit.toggle-fold")"#,
        )
        .expect("toggle");

    let after_line: i64 = state
        .lua_host
        .lua()
        .load("return pmacs.editor.cursor_line()")
        .eval()
        .expect("after");
    assert_eq!(
        after_line, 0,
        "cursor must reseat onto A's header (line 0) after A collapses"
    );
}

// Cursor on a section that gets hidden by a parent fold reseats to
// the nearest visible ancestor, not to byte 0.
#[test]
fn magit_cursor_on_hidden_descendant_reseats_to_visible_ancestor() {
    let (mut state, _c, _u) = editor_with_magit();
    open_standard(&mut state);

    // Move cursor onto Subsection A1's header (line 3).
    state
        .lua_host
        .eval(
            Some("to-a1-hdr"),
            r"for _ = 1, 3 do pmacs.editor.move_down() end",
        )
        .expect("to A1");
    let on_a1: i64 = state
        .lua_host
        .lua()
        .load("return pmacs.editor.cursor_line()")
        .eval()
        .expect("on A1");
    assert_eq!(on_a1, 3, "setup: cursor on A1 header");

    // Collapse Section A (the parent of A1). A1 is no longer in the
    // projection. Cursor should reseat onto A's header (line 0),
    // not stay at byte offset 3 / line 3 (which would now be the
    // start of Section B's body region).
    state
        .lua_host
        .eval(
            Some("collapse-a"),
            r#"
                local te = require("pmacs-magit").__pmacs_magit_test_seam_DO_NOT_USE
                local h = te.active_handle()
                te.fold.toggle(h.fold_state, "a")
                te.repaint_visible(h)
            "#,
        )
        .expect("collapse a");

    let after: i64 = state
        .lua_host
        .lua()
        .load("return pmacs.editor.cursor_line()")
        .eval()
        .expect("after");
    assert_eq!(
        after, 0,
        "cursor on hidden A1 must reseat to A's header (line 0)"
    );
}
