use std::{
    fs,
    path::Path,
    process::{Command, Output},
};

#[test]
fn array_struct_fields_fixture_emits_array_field_struct_llvm_shapes() {
    let output = run_glagol(["../examples/array-struct-fields.slo"]);
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    assert!(
        output.status.success(),
        "compiler rejected array-struct-fields fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("define { [3 x i32], [2 x i64], [3 x double], [3 x i1], [3 x ptr] } @make_record(i32 %base, ptr %head)")
            && stdout.contains("define i32 @int_at(")
            && stdout.contains("define i64 @wide_at(")
            && stdout.contains("define double @ratio_at(")
            && stdout.contains("define i1 @flag_at(")
            && stdout.contains("define ptr @word_at(")
            && stdout.contains("extractvalue { [3 x i32], [2 x i64], [3 x double], [3 x i1], [3 x ptr] }")
            && stdout.contains("call void @__glagol_array_bounds_trap()")
            && stdout.contains("getelementptr inbounds [3 x ptr]"),
        "LLVM output did not contain expected array-struct-field shape\nstdout:\n{}",
        stdout
    );
    assert!(
        !stdout.contains("struct array i32 field access")
            && !stdout.contains("struct array local param return call flow"),
        "compiler emitted test metadata into LLVM output\nstdout:\n{}",
        stdout
    );
    assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
}

#[test]
fn array_struct_fields_fixture_runs_tests_and_formats_stably() {
    let run = run_glagol(["--run-tests", "../examples/array-struct-fields.slo"]);
    assert_success_stdout(
        run,
        concat!(
            "test \"struct array i32 field access\" ... ok\n",
            "test \"struct array i64 field access\" ... ok\n",
            "test \"struct array f64 field access\" ... ok\n",
            "test \"struct array bool field access\" ... ok\n",
            "test \"struct array string field access\" ... ok\n",
            "test \"struct array local param return call flow\" ... ok\n",
            "6 test(s) passed\n",
        ),
        "array-struct-fields test runner output",
    );

    let expected = fs::read_to_string("../tests/array-struct-fields.slo").expect("read fixture");
    let format = run_glagol(["--format", "../tests/array-struct-fields.slo"]);
    assert_success_stdout(format, &expected, "array-struct-fields formatter output");
}

#[test]
fn array_struct_fields_fixture_lowering_snapshots_are_stable() {
    let surface = run_glagol([
        "--inspect-lowering=surface",
        "../tests/array-struct-fields.slo",
    ]);
    assert_success_stdout(
        surface,
        &fs::read_to_string("../tests/array-struct-fields.surface.lower")
            .expect("read surface snapshot"),
        "array-struct-fields surface lowering output",
    );

    let checked = run_glagol([
        "--inspect-lowering=checked",
        "../tests/array-struct-fields.slo",
    ]);
    assert_success_stdout(
        checked,
        &fs::read_to_string("../tests/array-struct-fields.checked.lower")
            .expect("read checked snapshot"),
        "array-struct-fields checked lowering output",
    );
}

fn run_glagol<const N: usize>(args: [&str; N]) -> Output {
    Command::new(env!("CARGO_BIN_EXE_glagol"))
        .args(args)
        .current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
        .output()
        .expect("run glagol")
}

fn assert_success_stdout(output: Output, expected: &str, context: &str) {
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        output.status.success(),
        "{} failed\nstdout:\n{}\nstderr:\n{}",
        context,
        stdout,
        stderr
    );
    assert_eq!(stdout, expected, "{} drifted", context);
    assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
}
