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

#[test]
fn mutable_composite_locals_compile_lower_and_test() {
    let fixture = "../examples/composite-locals.slo";

    let compile = run_glagol([fixture]);
    let llvm_stdout = String::from_utf8_lossy(&compile.stdout);
    let llvm_stderr = String::from_utf8_lossy(&compile.stderr);
    assert!(
        compile.status.success(),
        "compiler rejected mutable composite locals fixture\nstdout:\n{}\nstderr:\n{}",
        llvm_stdout,
        llvm_stderr
    );
    assert!(
        llvm_stdout.contains("define ptr @swap_string(ptr %first, ptr %second)")
            && llvm_stdout.contains("define i64 @vec_i64_local_value()")
            && llvm_stdout.contains("define ptr @option_string_local_value()")
            && llvm_stdout.contains("define ptr @result_string_local_value()")
            && llvm_stdout.contains("define ptr @primitive_record_local_label()")
            && llvm_stdout.contains("define i32 @tagged_local_code()")
            && llvm_stdout.contains("alloca ptr")
            && llvm_stdout.contains("alloca { i1, ptr }")
            && llvm_stdout.contains("alloca { i1, ptr, i32 }")
            && llvm_stdout.contains("alloca { i32, i1, ptr }")
            && llvm_stdout.contains("alloca { i64, double }")
            && llvm_stdout.contains("alloca { i32, { i32, i32 } }"),
        "LLVM output lost mutable composite local storage shape\nstdout:\n{}",
        llvm_stdout
    );
    assert!(
        llvm_stderr.is_empty(),
        "compiler wrote stderr:\n{}",
        llvm_stderr
    );

    let tests = run_glagol(["--run-tests", fixture]);
    let tests_stdout = String::from_utf8_lossy(&tests.stdout);
    let tests_stderr = String::from_utf8_lossy(&tests.stderr);
    assert!(
        tests.status.success(),
        "test runner rejected mutable composite locals fixture\nstdout:\n{}\nstderr:\n{}",
        tests_stdout,
        tests_stderr
    );
    assert_eq!(
        tests_stdout,
        concat!(
            "test \"mutable string local set works\" ... ok\n",
            "test \"mutable vec i32 local set works\" ... ok\n",
            "test \"mutable vec i64 local set works\" ... ok\n",
            "test \"mutable vec f64 local set works\" ... ok\n",
            "test \"mutable vec bool local set works\" ... ok\n",
            "test \"mutable vec string local set works\" ... ok\n",
            "test \"mutable option i32 local set works\" ... ok\n",
            "test \"mutable option i64 local set works\" ... ok\n",
            "test \"mutable option f64 local set works\" ... ok\n",
            "test \"mutable option bool local set works\" ... ok\n",
            "test \"mutable option string local set works\" ... ok\n",
            "test \"mutable result i32 local set works\" ... ok\n",
            "test \"mutable result i64 local set works\" ... ok\n",
            "test \"mutable result f64 local set works\" ... ok\n",
            "test \"mutable result bool local set works\" ... ok\n",
            "test \"mutable result string local set works\" ... ok\n",
            "test \"mutable plain struct local set works\" ... ok\n",
            "test \"mutable primitive struct local set works\" ... ok\n",
            "test \"mutable numeric struct local set works\" ... ok\n",
            "test \"mutable payloadless enum local set works\" ... ok\n",
            "test \"mutable payload enum local set works\" ... ok\n",
            "test \"mutable enum struct local set works\" ... ok\n",
            "22 test(s) passed\n",
        ),
        "test runner output drifted"
    );
    assert!(
        tests_stderr.is_empty(),
        "test runner wrote stderr:\n{}",
        tests_stderr
    );

    let expected = fs::read_to_string("../examples/composite-locals.slo").expect("read fixture");
    let format = run_glagol(["--format", fixture]);
    let format_stdout = String::from_utf8_lossy(&format.stdout);
    let format_stderr = String::from_utf8_lossy(&format.stderr);
    assert!(
        format.status.success(),
        "formatter rejected mutable composite locals fixture\nstdout:\n{}\nstderr:\n{}",
        format_stdout,
        format_stderr
    );
    assert_eq!(format_stdout, expected, "formatter output drifted");
    assert!(
        format_stderr.is_empty(),
        "formatter wrote stderr:\n{}",
        format_stderr
    );

    let surface = run_glagol(["--inspect-lowering=surface", fixture]);
    let surface_stdout = String::from_utf8_lossy(&surface.stdout);
    let surface_stderr = String::from_utf8_lossy(&surface.stderr);
    assert!(
        surface.status.success(),
        "surface lowering rejected mutable composite locals fixture\nstdout:\n{}\nstderr:\n{}",
        surface_stdout,
        surface_stderr
    );
    assert!(
        surface_stdout.contains("local var current: string")
            && surface_stdout.contains("local var current: (vec i64)")
            && surface_stdout.contains("local var current: (option string)")
            && surface_stdout.contains("local var current: (result string i32)")
            && surface_stdout.contains("local var current: PrimitiveRecord")
            && surface_stdout.contains("local var current: Signal"),
        "surface lowering lost mutable composite local declarations\nstdout:\n{}",
        surface_stdout
    );
    assert!(
        surface_stderr.is_empty(),
        "surface lowering wrote stderr:\n{}",
        surface_stderr
    );

    let checked = run_glagol(["--inspect-lowering=checked", fixture]);
    let checked_stdout = String::from_utf8_lossy(&checked.stdout);
    let checked_stderr = String::from_utf8_lossy(&checked.stderr);
    assert!(
        checked.status.success(),
        "checked lowering rejected mutable composite locals fixture\nstdout:\n{}\nstderr:\n{}",
        checked_stdout,
        checked_stderr
    );
    assert!(
        checked_stdout.contains("var current : string")
            && checked_stdout.contains("var current : (vec i64)")
            && checked_stdout.contains("var current : (option string)")
            && checked_stdout.contains("var current : (result string i32)")
            && checked_stdout.contains("var current : PrimitiveRecord")
            && checked_stdout.contains("var current : Signal"),
        "checked lowering lost typed mutable composite local flow\nstdout:\n{}",
        checked_stdout
    );
    assert!(
        checked_stderr.is_empty(),
        "checked lowering wrote stderr:\n{}",
        checked_stderr
    );
}

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