//! Round-trip stability for the `state.ncl` renderer (O1.5 / D9).
//!
//! Strategy: load the real `.ontology/state.ncl`, render it, re-parse the
//! rendered bytes, render again. The second render MUST produce the same
//! bytes as the first — confirming `render(parse(render(x)))` is a fixed
//! point. This is strictly stronger than a structural-equivalence check
//! because it inspects every byte of the canonical form.

use std::path::{Path, PathBuf};
use std::process::Command;

use ontoref_ontology::render::render_state_ncl;
use ontoref_ontology::types::StateConfig;

/// Project root that holds the `.ontoref/` spine.
///
/// Walks up from this crate until it finds the directory containing
/// `.ontoref/`. Post-ADR-048 (constellation) the spine sits at the
/// constellation parent, outside `code/`; pre-ADR-048 it was inside the
/// workspace. Searching ancestors resolves it under either layout instead of
/// hard-coding a hop count.
fn workspace_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .ancestors()
        .find(|dir| dir.join(".ontoref").is_dir())
        .map(Path::to_path_buf)
        .expect("a `.ontoref/` spine must exist in an ancestor of CARGO_MANIFEST_DIR")
}

fn nickel_export_state(path: &Path) -> StateConfig {
    let output = Command::new("nickel")
        .arg("export")
        .arg("--format")
        .arg("json")
        .arg(path)
        .output()
        .expect("nickel binary must be on PATH for this test");
    assert!(
        output.status.success(),
        "nickel export failed for {}: {}",
        path.display(),
        String::from_utf8_lossy(&output.stderr)
    );
    serde_json::from_slice(&output.stdout).expect("state.ncl JSON deserialises to StateConfig")
}

#[test]
fn render_then_reparse_then_render_is_idempotent() {
    let root = workspace_root();
    let source = ontoref_ontology::layout::resolve_section(&root, "ontology/state.ncl");
    let initial = nickel_export_state(&source);

    let bytes_a = render_state_ncl(&initial);

    let tmp = tempfile::tempdir().expect("tempdir");
    let staged = tmp.path().join("state.ncl");
    std::fs::write(&staged, &bytes_a).expect("write staged");

    let reparsed = nickel_export_state(&staged);
    let bytes_b = render_state_ncl(&reparsed);

    assert_eq!(
        bytes_a, bytes_b,
        "render(parse(render(state))) must produce identical bytes"
    );
}
