use std::path::Path;

use jcv::canvas::{parse, Node};

fn fixture(name: &str) -> std::path::PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures")
        .join(name)
}

#[test]
fn loads_empty_canvas() {
    let canvas = parse::load(&fixture("empty.canvas")).expect("empty.canvas should parse");
    assert!(canvas.nodes.is_empty());
    assert!(canvas.edges.is_empty());
}

#[test]
fn loads_single_text() {
    let canvas = parse::load(&fixture("single-text.canvas")).expect("single-text.canvas");
    assert_eq!(canvas.nodes.len(), 1);
    match &canvas.nodes[0] {
        Node::Text(t) => {
            assert_eq!(t.id, "n1");
            assert_eq!(t.text, "Hello, canvas!");
            assert_eq!(t.width, 200);
        }
        other => panic!("expected text node, got {other:?}"),
    }
}

#[test]
fn loads_all_node_types() {
    let canvas = parse::load(&fixture("all-node-types.canvas")).expect("all-node-types");
    assert_eq!(canvas.nodes.len(), 4);
    assert_eq!(canvas.edges.len(), 2);
    let kinds: Vec<&'static str> = canvas
        .nodes
        .iter()
        .map(|n| match n {
            Node::Text(_) => "text",
            Node::File(_) => "file",
            Node::Link(_) => "link",
            Node::Group(_) => "group",
        })
        .collect();
    assert_eq!(kinds, vec!["text", "file", "link", "group"]);
}

#[test]
fn loads_obsidian_export() {
    let canvas = parse::load(&fixture("obsidian-export.canvas")).expect("obsidian-export");
    assert!(canvas.nodes.len() > 5);
    assert!(!canvas.edges.is_empty());
}

#[test]
fn malformed_json_reports_path_and_position() {
    let dir = tempdir();
    let path = dir.join("bad.canvas");
    std::fs::write(&path, "{\n  \"nodes\": [ { invalid \n}").unwrap();
    let err = parse::load(&path).unwrap_err();
    let msg = format!("{err:#}");
    assert!(msg.contains("bad.canvas"), "msg should mention path: {msg}");
    assert!(
        msg.contains("line") || msg.contains("column"),
        "msg should locate error: {msg}"
    );
}

#[test]
fn missing_required_field_reports_field() {
    let dir = tempdir();
    let path = dir.join("missing-field.canvas");
    // text node missing "text"
    std::fs::write(
        &path,
        r#"{"nodes":[{"id":"x","type":"text","x":0,"y":0,"width":1,"height":1}],"edges":[]}"#,
    )
    .unwrap();
    let err = parse::load(&path).unwrap_err();
    let msg = format!("{err:#}");
    assert!(
        msg.contains("text") || msg.contains("missing"),
        "msg should mention missing field: {msg}"
    );
}

fn tempdir() -> std::path::PathBuf {
    let mut p = std::env::temp_dir();
    let unique = format!(
        "jcv-parse-test-{}-{}",
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    );
    p.push(unique);
    std::fs::create_dir_all(&p).unwrap();
    p
}
