use std::{
    env,
    ffi::OsStr,
    fs,
    path::{Path, PathBuf},
    process::{Command, Output},
    sync::atomic::{AtomicUsize, Ordering},
};

static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);

#[test]
fn vec_i32_fixture_emits_runtime_owned_vector_calls_and_tests_pass() {
    let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-i32.slo");
    let compile = run_glagol([fixture.as_os_str()]);
    let stdout = String::from_utf8_lossy(&compile.stdout);
    let stderr = String::from_utf8_lossy(&compile.stderr);

    assert!(
        compile.status.success(),
        "compiler rejected vec-i32 fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("declare ptr @__glagol_vec_i32_empty()")
            && stdout.contains("declare ptr @__glagol_vec_i32_append(ptr, i32)")
            && stdout.contains("declare i32 @__glagol_vec_i32_len(ptr)")
            && stdout.contains("declare i32 @__glagol_vec_i32_index(ptr, i32)")
            && stdout.contains("declare i1 @__glagol_vec_i32_eq(ptr, ptr)")
            && stdout.contains("define ptr @empty_values()")
            && stdout.contains("define ptr @pair(i32 %base)")
            && stdout.contains("define ptr @echo(ptr %values)")
            && stdout.contains("define i32 @at(ptr %values, i32 %i)")
            && stdout.contains("call ptr @__glagol_vec_i32_empty()")
            && stdout.contains("call ptr @__glagol_vec_i32_append(ptr %")
            && stdout.contains("call i32 @__glagol_vec_i32_len(ptr %")
            && stdout.contains("call i32 @__glagol_vec_i32_index(ptr %")
            && !stdout.contains("@std."),
        "LLVM output did not contain expected vec runtime shape\nstdout:\n{}",
        stdout
    );
    assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);

    let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
    assert_success_stdout(
        run,
        concat!(
            "test \"vec i32 empty length\" ... ok\n",
            "test \"vec i32 append length\" ... ok\n",
            "test \"vec i32 index\" ... ok\n",
            "test \"vec i32 append is immutable\" ... ok\n",
            "test \"vec i32 equality\" ... ok\n",
            "5 test(s) passed\n",
        ),
        "vec-i32 test runner output",
    );
}

#[test]
fn vec_i32_equality_lowers_to_runtime_helper() {
    let fixture = write_fixture(
        "vec-i32-equality",
        r#"
(module main)

(fn same () -> i32
  (if (= (std.vec.i32.append (std.vec.i32.empty) 1)
         (std.vec.i32.append (std.vec.i32.empty) 1))
    1
    0))

(fn main () -> i32
  (same))
"#,
    );
    let compile = run_glagol([fixture.as_os_str()]);
    let stdout = String::from_utf8_lossy(&compile.stdout);
    let stderr = String::from_utf8_lossy(&compile.stderr);

    assert!(
        compile.status.success(),
        "compiler rejected vec equality fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("call i1 @__glagol_vec_i32_eq(ptr %"),
        "LLVM output did not lower vec equality through helper\nstdout:\n{}",
        stdout
    );
}

#[test]
fn vec_i32_formatter_and_lowering_inspector_are_visible() {
    let fixture = write_fixture(
        "vec-i32-tooling",
        r#"
(module main)

(fn make () -> (vec i32)
  (let values (vec i32) (std.vec.i32.empty))
  (std.vec.i32.append values 1))

(fn main () -> i32
  (std.vec.i32.len (make)))
"#,
    );

    let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
    assert_success_stdout(
        formatted,
        concat!(
            "(module main)\n",
            "\n",
            "(fn make () -> (vec i32)\n",
            "  (let values (vec i32) (std.vec.i32.empty))\n",
            "  (std.vec.i32.append values 1))\n",
            "\n",
            "(fn main () -> i32\n",
            "  (std.vec.i32.len (make)))\n",
        ),
        "vec-i32 formatter output",
    );

    let surface = run_glagol([
        OsStr::new("--inspect-lowering=surface"),
        fixture.as_os_str(),
    ]);
    assert_success("inspect vec-i32 surface lowering", &surface);
    assert!(
        String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec i32)"),
        "surface lowering did not show vec return type\nstdout:\n{}",
        String::from_utf8_lossy(&surface.stdout)
    );

    let checked = run_glagol([
        OsStr::new("--inspect-lowering=checked"),
        fixture.as_os_str(),
    ]);
    assert_success("inspect vec-i32 checked lowering", &checked);
    assert!(
        String::from_utf8_lossy(&checked.stdout).contains("call std.vec.i32.append : (vec i32)"),
        "checked lowering did not show typed vec append\nstdout:\n{}",
        String::from_utf8_lossy(&checked.stdout)
    );
}

#[test]
fn vec_i32_runtime_smoke_when_clang_is_available() {
    let Some(clang) = find_clang() else {
        eprintln!("skipping vec-i32 runtime smoke: set GLAGOL_CLANG or install clang");
        return;
    };

    let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-i32.slo");
    let compile = run_glagol([fixture.as_os_str()]);
    assert_success("compile vec-i32 runtime smoke", &compile);

    let run = compile_and_run_with_runtime(&clang, "vec-i32", &compile.stdout);
    assert_eq!(
        run.status.code(),
        Some(41),
        "vec-i32 runtime exit code drifted\nstdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&run.stdout),
        String::from_utf8_lossy(&run.stderr)
    );
    assert_eq!(
        String::from_utf8_lossy(&run.stdout),
        "2\n",
        "vec-i32 runtime stdout drifted"
    );
    assert!(
        run.stderr.is_empty(),
        "vec-i32 runtime wrote stderr:\n{}",
        String::from_utf8_lossy(&run.stderr)
    );
}

#[test]
fn vec_i32_runtime_traps_have_contract_messages_when_clang_is_available() {
    let Some(clang) = find_clang() else {
        eprintln!("skipping vec-i32 trap smoke: set GLAGOL_CLANG or install clang");
        return;
    };

    let cases = [
        (
            "vec-index-bounds",
            r#"
(module main)

(fn main () -> i32
  (std.vec.i32.index (std.vec.i32.empty) 0))
"#,
            "slovo runtime error: vector index out of bounds\n",
        ),
        (
            "vec-index-negative",
            r#"
(module main)

(fn main () -> i32
  (std.vec.i32.index (std.vec.i32.append (std.vec.i32.empty) 1) -1))
"#,
            "slovo runtime error: vector index out of bounds\n",
        ),
        (
            "vec-allocation",
            r#"#include <stddef.h>
#include <stdint.h>

typedef struct __glagol_vec_i32 __glagol_vec_i32;

__glagol_vec_i32 *__glagol_vec_i32_empty(void);

void *__wrap_malloc(size_t size) {
    (void)size;
    return NULL;
}

int main(void) {
    (void)__glagol_vec_i32_empty();
    return 0;
}
"#,
            "slovo runtime error: vector allocation failed\n",
        ),
    ];

    let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
    let runtime = manifest.join("../runtime/runtime.c");

    for (name, source, expected_stderr) in cases {
        let temp_dir = env::temp_dir().join(format!(
            "glagol-vec-runtime-{}-{}",
            std::process::id(),
            NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
        ));
        fs::create_dir_all(&temp_dir)
            .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));

        let exe = temp_dir.join(name);
        let run = if name.starts_with("vec-index-") {
            let fixture = write_fixture(name, source);
            let compile = run_glagol([fixture.as_os_str()]);
            assert_success("compile vec index trap", &compile);
            compile_and_run_with_runtime(&clang, name, &compile.stdout)
        } else {
            let probe = temp_dir.join(format!("{}.c", name));
            fs::write(&probe, source)
                .unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err));
            let mut clang_command = Command::new(&clang);
            clang_command
                .arg(&runtime)
                .arg(&probe)
                .arg("-Wl,--wrap=malloc")
                .arg("-o")
                .arg(&exe)
                .current_dir(manifest);
            configure_clang_runtime_env(&mut clang_command, &clang);
            let clang_output = clang_command
                .output()
                .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
            assert_success("clang vec allocation probe", &clang_output);
            Command::new(&exe)
                .output()
                .unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err))
        };

        assert_eq!(
            run.status.code(),
            Some(1),
            "trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
            name,
            String::from_utf8_lossy(&run.stdout),
            String::from_utf8_lossy(&run.stderr)
        );
        assert_eq!(
            String::from_utf8_lossy(&run.stderr),
            expected_stderr,
            "trap `{}` stderr drifted",
            name
        );
        assert!(
            run.stdout.is_empty(),
            "trap `{}` wrote stdout:\n{}",
            name,
            String::from_utf8_lossy(&run.stdout)
        );
    }
}

#[test]
fn vec_i32_test_runner_reports_index_trap() {
    let cases = [
        (
            "vec-index-test-trap",
            r#"
(module main)

(test "vec index trap"
  (= (std.vec.i32.index (std.vec.i32.empty) 0) 0))
"#,
        ),
        (
            "vec-negative-index-test-trap",
            r#"
(module main)

(test "vec negative index trap"
  (= (std.vec.i32.index (std.vec.i32.append (std.vec.i32.empty) 1) -1) 0))
"#,
        ),
    ];

    for (name, source) in cases {
        let fixture = write_fixture(name, source);
        let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let stderr = String::from_utf8_lossy(&output.stderr);

        assert_eq!(
            output.status.code(),
            Some(1),
            "test runner vec trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
            name,
            stdout,
            stderr
        );
        assert!(
            stdout.is_empty(),
            "test runner vec trap `{}` wrote stdout:\n{}",
            name,
            stdout
        );
        assert!(
            stderr.contains("TestRuntimeTrap")
                && stderr.contains("slovo runtime error: vector index out of bounds"),
            "test runner vec trap `{}` diagnostic drifted\nstderr:\n{}",
            name,
            stderr
        );
    }
}

#[test]
fn vec_i32_project_mode_rewrites_imported_vector_symbols() {
    let project = write_vec_project();

    let check = run_glagol([OsStr::new("check"), project.as_os_str()]);
    assert_success("check vec-i32 project", &check);

    let run = run_glagol([OsStr::new("test"), project.as_os_str()]);
    assert_success_stdout(
        run,
        "test \"project vector import\" ... ok\n1 test(s) passed\n",
        "vec-i32 project test output",
    );
}

#[test]
fn vec_i64_fixture_emits_runtime_owned_vector_calls_and_tests_pass() {
    let fixture = write_fixture("vec-i64", vec_i64_fixture_source());
    let compile = run_glagol([fixture.as_os_str()]);
    let stdout = String::from_utf8_lossy(&compile.stdout);
    let stderr = String::from_utf8_lossy(&compile.stderr);

    assert!(
        compile.status.success(),
        "compiler rejected vec-i64 fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("declare ptr @__glagol_vec_i64_empty()")
            && stdout.contains("declare ptr @__glagol_vec_i64_append(ptr, i64)")
            && stdout.contains("declare i32 @__glagol_vec_i64_len(ptr)")
            && stdout.contains("declare i64 @__glagol_vec_i64_index(ptr, i32)")
            && stdout.contains("declare i1 @__glagol_vec_i64_eq(ptr, ptr)")
            && stdout.contains("define ptr @empty_values()")
            && stdout.contains("define ptr @pair(i64 %base)")
            && stdout.contains("define ptr @echo(ptr %values)")
            && stdout.contains("define i64 @at(ptr %values, i32 %i)")
            && stdout.contains("call ptr @__glagol_vec_i64_empty()")
            && stdout.contains("call ptr @__glagol_vec_i64_append(ptr %")
            && stdout.contains("call i32 @__glagol_vec_i64_len(ptr %")
            && stdout.contains("call i64 @__glagol_vec_i64_index(ptr %")
            && stdout.contains("call i1 @__glagol_vec_i64_eq(ptr %")
            && !stdout.contains("@std."),
        "LLVM output did not contain expected vec i64 runtime shape\nstdout:\n{}",
        stdout
    );
    assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);

    let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
    assert_success_stdout(
        run,
        concat!(
            "test \"vec i64 empty length\" ... ok\n",
            "test \"vec i64 append length\" ... ok\n",
            "test \"vec i64 index\" ... ok\n",
            "test \"vec i64 append is immutable\" ... ok\n",
            "test \"vec i64 equality\" ... ok\n",
            "5 test(s) passed\n",
        ),
        "vec-i64 test runner output",
    );
}

#[test]
fn vec_i64_formatter_and_lowering_inspector_are_visible() {
    let fixture = write_fixture(
        "vec-i64-tooling",
        r#"
(module main)

(fn make () -> (vec i64)
  (let values (vec i64) (std.vec.i64.empty))
  (std.vec.i64.append values 1i64))

(fn main () -> i32
  (std.vec.i64.len (make)))
"#,
    );

    let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
    assert_success_stdout(
        formatted,
        concat!(
            "(module main)\n",
            "\n",
            "(fn make () -> (vec i64)\n",
            "  (let values (vec i64) (std.vec.i64.empty))\n",
            "  (std.vec.i64.append values 1i64))\n",
            "\n",
            "(fn main () -> i32\n",
            "  (std.vec.i64.len (make)))\n",
        ),
        "vec-i64 formatter output",
    );

    let surface = run_glagol([
        OsStr::new("--inspect-lowering=surface"),
        fixture.as_os_str(),
    ]);
    assert_success("inspect vec-i64 surface lowering", &surface);
    assert!(
        String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec i64)"),
        "surface lowering did not show vec i64 return type\nstdout:\n{}",
        String::from_utf8_lossy(&surface.stdout)
    );

    let checked = run_glagol([
        OsStr::new("--inspect-lowering=checked"),
        fixture.as_os_str(),
    ]);
    assert_success("inspect vec-i64 checked lowering", &checked);
    assert!(
        String::from_utf8_lossy(&checked.stdout).contains("call std.vec.i64.append : (vec i64)"),
        "checked lowering did not show typed vec i64 append\nstdout:\n{}",
        String::from_utf8_lossy(&checked.stdout)
    );
}

#[test]
fn vec_i64_runtime_smoke_when_clang_is_available() {
    let Some(clang) = find_clang() else {
        eprintln!("skipping vec-i64 runtime smoke: set GLAGOL_CLANG or install clang");
        return;
    };

    let fixture = write_fixture("vec-i64-runtime", vec_i64_fixture_source());
    let compile = run_glagol([fixture.as_os_str()]);
    assert_success("compile vec-i64 runtime smoke", &compile);

    let run = compile_and_run_with_runtime(&clang, "vec-i64", &compile.stdout);
    assert_eq!(
        run.status.code(),
        Some(0),
        "vec-i64 runtime exit code drifted\nstdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&run.stdout),
        String::from_utf8_lossy(&run.stderr)
    );
    assert_eq!(
        String::from_utf8_lossy(&run.stdout),
        "21\n",
        "vec-i64 runtime stdout drifted"
    );
    assert!(
        run.stderr.is_empty(),
        "vec-i64 runtime wrote stderr:\n{}",
        String::from_utf8_lossy(&run.stderr)
    );
}

#[test]
fn vec_i64_runtime_traps_have_contract_messages_when_clang_is_available() {
    let Some(clang) = find_clang() else {
        eprintln!("skipping vec-i64 trap smoke: set GLAGOL_CLANG or install clang");
        return;
    };

    let cases = [
        (
            "vec-i64-index-bounds",
            r#"
(module main)

(fn main () -> i32
  (std.io.print_i64 (std.vec.i64.index (std.vec.i64.empty) 0))
  0)
"#,
            "slovo runtime error: vector index out of bounds\n",
        ),
        (
            "vec-i64-index-negative",
            r#"
(module main)

(fn main () -> i32
  (std.io.print_i64
    (std.vec.i64.index (std.vec.i64.append (std.vec.i64.empty) 1i64) -1))
  0)
"#,
            "slovo runtime error: vector index out of bounds\n",
        ),
        (
            "vec-i64-allocation",
            r#"#include <stddef.h>
#include <stdint.h>

typedef struct __glagol_vec_i64 __glagol_vec_i64;

__glagol_vec_i64 *__glagol_vec_i64_empty(void);

void *__wrap_malloc(size_t size) {
    (void)size;
    return NULL;
}

int main(void) {
    (void)__glagol_vec_i64_empty();
    return 0;
}
"#,
            "slovo runtime error: vector allocation failed\n",
        ),
    ];

    let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
    let runtime = manifest.join("../runtime/runtime.c");

    for (name, source, expected_stderr) in cases {
        let temp_dir = env::temp_dir().join(format!(
            "glagol-vec-i64-runtime-{}-{}",
            std::process::id(),
            NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
        ));
        fs::create_dir_all(&temp_dir)
            .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));

        let exe = temp_dir.join(name);
        let run = if name.starts_with("vec-i64-index-") {
            let fixture = write_fixture(name, source);
            let compile = run_glagol([fixture.as_os_str()]);
            assert_success("compile vec i64 index trap", &compile);
            compile_and_run_with_runtime(&clang, name, &compile.stdout)
        } else {
            let probe = temp_dir.join(format!("{}.c", name));
            fs::write(&probe, source)
                .unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err));
            let mut clang_command = Command::new(&clang);
            clang_command
                .arg(&runtime)
                .arg(&probe)
                .arg("-Wl,--wrap=malloc")
                .arg("-o")
                .arg(&exe)
                .current_dir(manifest);
            configure_clang_runtime_env(&mut clang_command, &clang);
            let clang_output = clang_command
                .output()
                .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
            assert_success("clang vec i64 allocation probe", &clang_output);
            Command::new(&exe)
                .output()
                .unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err))
        };

        assert_eq!(
            run.status.code(),
            Some(1),
            "trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
            name,
            String::from_utf8_lossy(&run.stdout),
            String::from_utf8_lossy(&run.stderr)
        );
        assert_eq!(
            String::from_utf8_lossy(&run.stderr),
            expected_stderr,
            "trap `{}` stderr drifted",
            name
        );
        assert!(
            run.stdout.is_empty(),
            "trap `{}` wrote stdout:\n{}",
            name,
            String::from_utf8_lossy(&run.stdout)
        );
    }
}

#[test]
fn vec_i64_test_runner_reports_index_trap() {
    let cases = [
        (
            "vec-i64-index-test-trap",
            r#"
(module main)

(test "vec i64 index trap"
  (= (std.vec.i64.index (std.vec.i64.empty) 0) 0i64))
"#,
        ),
        (
            "vec-i64-negative-index-test-trap",
            r#"
(module main)

(test "vec i64 negative index trap"
  (= (std.vec.i64.index (std.vec.i64.append (std.vec.i64.empty) 1i64) -1) 0i64))
"#,
        ),
    ];

    for (name, source) in cases {
        let fixture = write_fixture(name, source);
        let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let stderr = String::from_utf8_lossy(&output.stderr);

        assert_eq!(
            output.status.code(),
            Some(1),
            "test runner vec i64 trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
            name,
            stdout,
            stderr
        );
        assert!(
            stdout.is_empty(),
            "test runner vec i64 trap `{}` wrote stdout:\n{}",
            name,
            stdout
        );
        assert!(
            stderr.contains("TestRuntimeTrap")
                && stderr.contains("slovo runtime error: vector index out of bounds"),
            "test runner vec i64 trap `{}` diagnostic drifted\nstderr:\n{}",
            name,
            stderr
        );
    }
}

#[test]
fn vec_f64_fixture_emits_runtime_owned_vector_calls_and_tests_pass() {
    let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-f64.slo");
    let compile = run_glagol([fixture.as_os_str()]);
    let stdout = String::from_utf8_lossy(&compile.stdout);
    let stderr = String::from_utf8_lossy(&compile.stderr);

    assert!(
        compile.status.success(),
        "compiler rejected vec-f64 fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("declare ptr @__glagol_vec_f64_empty()")
            && stdout.contains("declare ptr @__glagol_vec_f64_append(ptr, double)")
            && stdout.contains("declare i32 @__glagol_vec_f64_len(ptr)")
            && stdout.contains("declare double @__glagol_vec_f64_index(ptr, i32)")
            && stdout.contains("declare i1 @__glagol_vec_f64_eq(ptr, ptr)")
            && stdout.contains("define ptr @empty_values()")
            && stdout.contains("define ptr @pair(double %base)")
            && stdout.contains("define ptr @echo(ptr %values)")
            && stdout.contains("define double @at(ptr %values, i32 %i)")
            && stdout.contains("call ptr @__glagol_vec_f64_empty()")
            && stdout.contains("call ptr @__glagol_vec_f64_append(ptr %")
            && stdout.contains("call i32 @__glagol_vec_f64_len(ptr %")
            && stdout.contains("call double @__glagol_vec_f64_index(ptr %")
            && !stdout.contains("@std."),
        "LLVM output did not contain expected vec f64 runtime shape\nstdout:\n{}",
        stdout
    );
    assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);

    let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
    assert_success_stdout(
        run,
        concat!(
            "test \"vec f64 empty length\" ... ok\n",
            "test \"vec f64 append length\" ... ok\n",
            "test \"vec f64 index\" ... ok\n",
            "test \"vec f64 append is immutable\" ... ok\n",
            "test \"vec f64 equality\" ... ok\n",
            "5 test(s) passed\n",
        ),
        "vec-f64 test runner output",
    );
}

#[test]
fn vec_f64_formatter_and_lowering_inspector_are_visible() {
    let fixture = write_fixture(
        "vec-f64-tooling",
        r#"
(module main)

(fn make () -> (vec f64)
  (let values (vec f64) (std.vec.f64.empty))
  (std.vec.f64.append values 1.0))

(fn main () -> i32
  (std.vec.f64.len (make)))
"#,
    );

    let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
    assert_success_stdout(
        formatted,
        concat!(
            "(module main)\n",
            "\n",
            "(fn make () -> (vec f64)\n",
            "  (let values (vec f64) (std.vec.f64.empty))\n",
            "  (std.vec.f64.append values 1.0))\n",
            "\n",
            "(fn main () -> i32\n",
            "  (std.vec.f64.len (make)))\n",
        ),
        "vec-f64 formatter output",
    );

    let surface = run_glagol([
        OsStr::new("--inspect-lowering=surface"),
        fixture.as_os_str(),
    ]);
    assert_success("inspect vec-f64 surface lowering", &surface);
    assert!(
        String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec f64)"),
        "surface lowering did not show vec f64 return type\nstdout:\n{}",
        String::from_utf8_lossy(&surface.stdout)
    );

    let checked = run_glagol([
        OsStr::new("--inspect-lowering=checked"),
        fixture.as_os_str(),
    ]);
    assert_success("inspect vec-f64 checked lowering", &checked);
    assert!(
        String::from_utf8_lossy(&checked.stdout).contains("call std.vec.f64.append : (vec f64)"),
        "checked lowering did not show typed vec f64 append\nstdout:\n{}",
        String::from_utf8_lossy(&checked.stdout)
    );
}

#[test]
fn vec_f64_runtime_smoke_when_clang_is_available() {
    let Some(clang) = find_clang() else {
        eprintln!("skipping vec-f64 runtime smoke: set GLAGOL_CLANG or install clang");
        return;
    };

    let fixture = write_fixture("vec-f64-runtime", vec_f64_fixture_source());
    let compile = run_glagol([fixture.as_os_str()]);
    assert_success("compile vec-f64 runtime smoke", &compile);

    let run = compile_and_run_with_runtime(&clang, "vec-f64", &compile.stdout);
    assert_eq!(
        run.status.code(),
        Some(0),
        "vec-f64 runtime exit code drifted\nstdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&run.stdout),
        String::from_utf8_lossy(&run.stderr)
    );
    assert_eq!(
        String::from_utf8_lossy(&run.stdout),
        "21\n",
        "vec-f64 runtime stdout drifted"
    );
    assert!(
        run.stderr.is_empty(),
        "vec-f64 runtime wrote stderr:\n{}",
        String::from_utf8_lossy(&run.stderr)
    );
}

#[test]
fn vec_f64_runtime_traps_have_contract_messages_when_clang_is_available() {
    let Some(clang) = find_clang() else {
        eprintln!("skipping vec-f64 trap smoke: set GLAGOL_CLANG or install clang");
        return;
    };

    let cases = [
        (
            "vec-f64-index-bounds",
            r#"
(module main)

(fn main () -> i32
  (std.io.print_f64 (std.vec.f64.index (std.vec.f64.empty) 0))
  0)
"#,
            "slovo runtime error: vector index out of bounds\n",
        ),
        (
            "vec-f64-index-negative",
            r#"
(module main)

(fn main () -> i32
  (std.io.print_f64
    (std.vec.f64.index (std.vec.f64.append (std.vec.f64.empty) 1.0) -1))
  0)
"#,
            "slovo runtime error: vector index out of bounds\n",
        ),
        (
            "vec-f64-allocation",
            r#"#include <stddef.h>
#include <stdint.h>

typedef struct __glagol_vec_f64 __glagol_vec_f64;

__glagol_vec_f64 *__glagol_vec_f64_empty(void);

void *__wrap_malloc(size_t size) {
    (void)size;
    return NULL;
}

int main(void) {
    (void)__glagol_vec_f64_empty();
    return 0;
}
"#,
            "slovo runtime error: vector allocation failed\n",
        ),
    ];

    let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
    let runtime = manifest.join("../runtime/runtime.c");

    for (name, source, expected_stderr) in cases {
        let temp_dir = env::temp_dir().join(format!(
            "glagol-vec-f64-runtime-{}-{}",
            std::process::id(),
            NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
        ));
        fs::create_dir_all(&temp_dir)
            .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));

        let exe = temp_dir.join(name);
        let run = if name.starts_with("vec-f64-index-") {
            let fixture = write_fixture(name, source);
            let compile = run_glagol([fixture.as_os_str()]);
            assert_success("compile vec f64 index trap", &compile);
            compile_and_run_with_runtime(&clang, name, &compile.stdout)
        } else {
            let probe = temp_dir.join(format!("{}.c", name));
            fs::write(&probe, source)
                .unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err));
            let mut clang_command = Command::new(&clang);
            clang_command
                .arg(&runtime)
                .arg(&probe)
                .arg("-Wl,--wrap=malloc")
                .arg("-o")
                .arg(&exe)
                .current_dir(manifest);
            configure_clang_runtime_env(&mut clang_command, &clang);
            let clang_output = clang_command
                .output()
                .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
            assert_success("clang vec f64 allocation probe", &clang_output);
            Command::new(&exe)
                .output()
                .unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err))
        };

        assert_eq!(
            run.status.code(),
            Some(1),
            "trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
            name,
            String::from_utf8_lossy(&run.stdout),
            String::from_utf8_lossy(&run.stderr)
        );
        assert_eq!(
            String::from_utf8_lossy(&run.stderr),
            expected_stderr,
            "trap `{}` stderr drifted",
            name
        );
        assert!(
            run.stdout.is_empty(),
            "trap `{}` wrote stdout:\n{}",
            name,
            String::from_utf8_lossy(&run.stdout)
        );
    }
}

#[test]
fn vec_f64_test_runner_reports_index_trap() {
    let cases = [
        (
            "vec-f64-index-test-trap",
            r#"
(module main)

(test "vec f64 index trap"
  (= (std.vec.f64.index (std.vec.f64.empty) 0) 0.0))
"#,
        ),
        (
            "vec-f64-negative-index-test-trap",
            r#"
(module main)

(test "vec f64 negative index trap"
  (= (std.vec.f64.index (std.vec.f64.append (std.vec.f64.empty) 1.0) -1) 0.0))
"#,
        ),
    ];

    for (name, source) in cases {
        let fixture = write_fixture(name, source);
        let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let stderr = String::from_utf8_lossy(&output.stderr);

        assert_eq!(
            output.status.code(),
            Some(1),
            "test runner vec f64 trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
            name,
            stdout,
            stderr
        );
        assert!(
            stdout.is_empty(),
            "test runner vec f64 trap `{}` wrote stdout:\n{}",
            name,
            stdout
        );
        assert!(
            stderr.contains("TestRuntimeTrap")
                && stderr.contains("slovo runtime error: vector index out of bounds"),
            "test runner vec f64 trap `{}` diagnostic drifted\nstderr:\n{}",
            name,
            stderr
        );
    }
}

#[test]
fn vec_bool_fixture_emits_runtime_owned_vector_calls_and_tests_pass() {
    let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-bool.slo");
    let compile = run_glagol([fixture.as_os_str()]);
    let stdout = String::from_utf8_lossy(&compile.stdout);
    let stderr = String::from_utf8_lossy(&compile.stderr);

    assert!(
        compile.status.success(),
        "compiler rejected vec-bool fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("declare ptr @__glagol_vec_bool_empty()")
            && stdout.contains("declare ptr @__glagol_vec_bool_append(ptr, i1)")
            && stdout.contains("declare i32 @__glagol_vec_bool_len(ptr)")
            && stdout.contains("declare i1 @__glagol_vec_bool_index(ptr, i32)")
            && stdout.contains("declare i1 @__glagol_vec_bool_eq(ptr, ptr)")
            && stdout.contains("define ptr @empty_values()")
            && stdout.contains("define ptr @pair()")
            && stdout.contains("define ptr @echo(ptr %values)")
            && stdout.contains("define i1 @at(ptr %values, i32 %i)")
            && stdout.contains("call ptr @__glagol_vec_bool_empty()")
            && stdout.contains("call ptr @__glagol_vec_bool_append(ptr %")
            && stdout.contains("call i32 @__glagol_vec_bool_len(ptr %")
            && stdout.contains("call i1 @__glagol_vec_bool_index(ptr %")
            && !stdout.contains("@std."),
        "LLVM output did not contain expected vec bool runtime shape\nstdout:\n{}",
        stdout
    );
    assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);

    let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
    assert_success_stdout(
        run,
        concat!(
            "test \"vec bool empty length\" ... ok\n",
            "test \"vec bool append length\" ... ok\n",
            "test \"vec bool index\" ... ok\n",
            "test \"vec bool append is immutable\" ... ok\n",
            "test \"vec bool equality\" ... ok\n",
            "5 test(s) passed\n",
        ),
        "vec-bool test runner output",
    );
}

#[test]
fn vec_bool_formatter_and_lowering_inspector_are_visible() {
    let fixture = write_fixture(
        "vec-bool-tooling",
        r#"
(module main)

(fn make () -> (vec bool)
  (let values (vec bool) (std.vec.bool.empty))
  (std.vec.bool.append values true))

(fn main () -> i32
  (if (= (std.vec.bool.index (make) 0) true)
    1
    0))
"#,
    );

    let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
    assert_success_stdout(
        formatted,
        concat!(
            "(module main)\n",
            "\n",
            "(fn make () -> (vec bool)\n",
            "  (let values (vec bool) (std.vec.bool.empty))\n",
            "  (std.vec.bool.append values true))\n",
            "\n",
            "(fn main () -> i32\n",
            "  (if (= (std.vec.bool.index (make) 0) true)\n",
            "    1\n",
            "    0))\n",
        ),
        "vec-bool formatter output",
    );

    let surface = run_glagol([
        OsStr::new("--inspect-lowering=surface"),
        fixture.as_os_str(),
    ]);
    assert_success("inspect vec-bool surface lowering", &surface);
    assert!(
        String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec bool)"),
        "surface lowering did not show vec bool return type\nstdout:\n{}",
        String::from_utf8_lossy(&surface.stdout)
    );

    let checked = run_glagol([
        OsStr::new("--inspect-lowering=checked"),
        fixture.as_os_str(),
    ]);
    assert_success("inspect vec-bool checked lowering", &checked);
    assert!(
        String::from_utf8_lossy(&checked.stdout).contains("call std.vec.bool.append : (vec bool)"),
        "checked lowering did not show typed vec bool append\nstdout:\n{}",
        String::from_utf8_lossy(&checked.stdout)
    );
}

#[test]
fn vec_bool_runtime_smoke_when_clang_is_available() {
    let Some(clang) = find_clang() else {
        eprintln!("skipping vec-bool runtime smoke: set GLAGOL_CLANG or install clang");
        return;
    };

    let fixture = write_fixture("vec-bool-runtime", vec_bool_fixture_source());
    let compile = run_glagol([fixture.as_os_str()]);
    assert_success("compile vec-bool runtime smoke", &compile);

    let run = compile_and_run_with_runtime(&clang, "vec-bool", &compile.stdout);
    assert_eq!(
        run.status.code(),
        Some(0),
        "vec-bool runtime exit code drifted\nstdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&run.stdout),
        String::from_utf8_lossy(&run.stderr)
    );
    assert_eq!(
        String::from_utf8_lossy(&run.stdout),
        "false\n",
        "vec-bool runtime stdout drifted"
    );
    assert!(
        run.stderr.is_empty(),
        "vec-bool runtime wrote stderr:\n{}",
        String::from_utf8_lossy(&run.stderr)
    );
}

#[test]
fn vec_bool_runtime_traps_have_contract_messages_when_clang_is_available() {
    let Some(clang) = find_clang() else {
        eprintln!("skipping vec-bool trap smoke: set GLAGOL_CLANG or install clang");
        return;
    };

    let cases = [
        (
            "vec-bool-index-bounds",
            r#"
(module main)

(fn main () -> i32
  (std.io.print_bool (std.vec.bool.index (std.vec.bool.empty) 0))
  0)
"#,
            "slovo runtime error: vector index out of bounds\n",
        ),
        (
            "vec-bool-index-negative",
            r#"
(module main)

(fn main () -> i32
  (std.io.print_bool
    (std.vec.bool.index (std.vec.bool.append (std.vec.bool.empty) true) -1))
  0)
"#,
            "slovo runtime error: vector index out of bounds\n",
        ),
        (
            "vec-bool-allocation",
            r#"#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

typedef struct __glagol_vec_bool __glagol_vec_bool;

__glagol_vec_bool *__glagol_vec_bool_empty(void);

void *__wrap_malloc(size_t size) {
    (void)size;
    return NULL;
}

int main(void) {
    (void)__glagol_vec_bool_empty();
    return 0;
}
"#,
            "slovo runtime error: vector allocation failed\n",
        ),
    ];

    let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
    let runtime = manifest.join("../runtime/runtime.c");

    for (name, source, expected_stderr) in cases {
        let temp_dir = env::temp_dir().join(format!(
            "glagol-vec-bool-runtime-{}-{}",
            std::process::id(),
            NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
        ));
        fs::create_dir_all(&temp_dir)
            .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));

        let exe = temp_dir.join(name);
        let run = if name.starts_with("vec-bool-index-") {
            let fixture = write_fixture(name, source);
            let compile = run_glagol([fixture.as_os_str()]);
            assert_success("compile vec bool index trap", &compile);
            compile_and_run_with_runtime(&clang, name, &compile.stdout)
        } else {
            let probe = temp_dir.join(format!("{}.c", name));
            fs::write(&probe, source)
                .unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err));
            let mut clang_command = Command::new(&clang);
            clang_command
                .arg(&runtime)
                .arg(&probe)
                .arg("-Wl,--wrap=malloc")
                .arg("-o")
                .arg(&exe)
                .current_dir(manifest);
            configure_clang_runtime_env(&mut clang_command, &clang);
            let clang_output = clang_command
                .output()
                .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
            assert_success("clang vec bool allocation probe", &clang_output);
            Command::new(&exe)
                .output()
                .unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err))
        };

        assert_eq!(
            run.status.code(),
            Some(1),
            "trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
            name,
            String::from_utf8_lossy(&run.stdout),
            String::from_utf8_lossy(&run.stderr)
        );
        assert_eq!(
            String::from_utf8_lossy(&run.stderr),
            expected_stderr,
            "trap `{}` stderr drifted",
            name
        );
        assert!(
            run.stdout.is_empty(),
            "trap `{}` wrote stdout:\n{}",
            name,
            String::from_utf8_lossy(&run.stdout)
        );
    }
}

#[test]
fn vec_bool_test_runner_reports_index_trap() {
    let cases = [
        (
            "vec-bool-index-test-trap",
            r#"
(module main)

(test "vec bool index trap"
  (= (std.vec.bool.index (std.vec.bool.empty) 0) false))
"#,
        ),
        (
            "vec-bool-negative-index-test-trap",
            r#"
(module main)

(test "vec bool negative index trap"
  (= (std.vec.bool.index (std.vec.bool.append (std.vec.bool.empty) true) -1) false))
"#,
        ),
    ];

    for (name, source) in cases {
        let fixture = write_fixture(name, source);
        let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let stderr = String::from_utf8_lossy(&output.stderr);

        assert_eq!(
            output.status.code(),
            Some(1),
            "test runner vec bool trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
            name,
            stdout,
            stderr
        );
        assert!(
            stdout.is_empty(),
            "test runner vec bool trap `{}` wrote stdout:\n{}",
            name,
            stdout
        );
        assert!(
            stderr.contains("TestRuntimeTrap")
                && stderr.contains("slovo runtime error: vector index out of bounds"),
            "test runner vec bool trap `{}` diagnostic drifted\nstderr:\n{}",
            name,
            stderr
        );
    }
}

#[test]
fn vec_string_fixture_emits_runtime_owned_vector_calls_and_tests_pass() {
    let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-string.slo");
    let compile = run_glagol([fixture.as_os_str()]);
    let stdout = String::from_utf8_lossy(&compile.stdout);
    let stderr = String::from_utf8_lossy(&compile.stderr);

    assert!(
        compile.status.success(),
        "compiler rejected vec-string fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("declare ptr @__glagol_vec_string_empty()")
            && stdout.contains("declare ptr @__glagol_vec_string_append(ptr, ptr)")
            && stdout.contains("declare i32 @__glagol_vec_string_len(ptr)")
            && stdout.contains("declare ptr @__glagol_vec_string_index(ptr, i32)")
            && stdout.contains("declare i1 @__glagol_vec_string_eq(ptr, ptr)")
            && stdout.contains("define ptr @empty_values()")
            && stdout.contains("define ptr @pair(ptr %first, ptr %second)")
            && stdout.contains("define ptr @echo(ptr %values)")
            && stdout.contains("define ptr @at(ptr %values, i32 %i)")
            && stdout.contains("call ptr @__glagol_vec_string_empty()")
            && stdout.contains("call ptr @__glagol_vec_string_append(ptr %")
            && stdout.contains("call i32 @__glagol_vec_string_len(ptr %")
            && stdout.contains("call ptr @__glagol_vec_string_index(ptr %")
            && !stdout.contains("@std."),
        "LLVM output did not contain expected vec string runtime shape\nstdout:\n{}",
        stdout
    );
    assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);

    let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
    assert_success_stdout(
        run,
        concat!(
            "test \"vec string empty length\" ... ok\n",
            "test \"vec string append length\" ... ok\n",
            "test \"vec string index\" ... ok\n",
            "test \"vec string append is immutable\" ... ok\n",
            "test \"vec string equality\" ... ok\n",
            "5 test(s) passed\n",
        ),
        "vec-string test runner output",
    );
}

#[test]
fn vec_string_equality_lowers_to_runtime_helper() {
    let fixture = write_fixture(
        "vec-string-equality",
        r#"
(module main)

(fn same () -> i32
  (if (= (std.vec.string.append (std.vec.string.empty) "same")
         (std.vec.string.append (std.vec.string.empty) "same"))
    1
    0))

(fn main () -> i32
  (same))
"#,
    );
    let compile = run_glagol([fixture.as_os_str()]);
    let stdout = String::from_utf8_lossy(&compile.stdout);
    let stderr = String::from_utf8_lossy(&compile.stderr);

    assert!(
        compile.status.success(),
        "compiler rejected vec string equality fixture\nstdout:\n{}\nstderr:\n{}",
        stdout,
        stderr
    );
    assert!(
        stdout.contains("call i1 @__glagol_vec_string_eq(ptr %"),
        "LLVM output did not lower vec string equality through helper\nstdout:\n{}",
        stdout
    );
}

#[test]
fn vec_string_formatter_and_lowering_inspector_are_visible() {
    let fixture = write_fixture(
        "vec-string-tooling",
        r#"
(module main)

(fn make () -> (vec string)
  (let values (vec string) (std.vec.string.empty))
  (std.vec.string.append values "alpha"))

(fn main () -> i32
  (std.vec.string.len (make)))
"#,
    );

    let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
    assert_success_stdout(
        formatted,
        concat!(
            "(module main)\n",
            "\n",
            "(fn make () -> (vec string)\n",
            "  (let values (vec string) (std.vec.string.empty))\n",
            "  (std.vec.string.append values \"alpha\"))\n",
            "\n",
            "(fn main () -> i32\n",
            "  (std.vec.string.len (make)))\n",
        ),
        "vec-string formatter output",
    );

    let surface = run_glagol([
        OsStr::new("--inspect-lowering=surface"),
        fixture.as_os_str(),
    ]);
    assert_success("inspect vec-string surface lowering", &surface);
    assert!(
        String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec string)"),
        "surface lowering did not show vec string return type\nstdout:\n{}",
        String::from_utf8_lossy(&surface.stdout)
    );

    let checked = run_glagol([
        OsStr::new("--inspect-lowering=checked"),
        fixture.as_os_str(),
    ]);
    assert_success("inspect vec-string checked lowering", &checked);
    assert!(
        String::from_utf8_lossy(&checked.stdout)
            .contains("call std.vec.string.append : (vec string)"),
        "checked lowering did not show typed vec string append\nstdout:\n{}",
        String::from_utf8_lossy(&checked.stdout)
    );
}

#[test]
fn vec_string_runtime_smoke_when_clang_is_available() {
    let Some(clang) = find_clang() else {
        eprintln!("skipping vec-string runtime smoke: set GLAGOL_CLANG or install clang");
        return;
    };

    let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-string.slo");
    let compile = run_glagol([fixture.as_os_str()]);
    assert_success("compile vec-string runtime smoke", &compile);

    let run = compile_and_run_with_runtime(&clang, "vec-string", &compile.stdout);
    assert_eq!(
        run.status.code(),
        Some(0),
        "vec-string runtime exit code drifted\nstdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&run.stdout),
        String::from_utf8_lossy(&run.stderr)
    );
    assert_eq!(
        String::from_utf8_lossy(&run.stdout),
        "tree\n",
        "vec-string runtime stdout drifted"
    );
    assert!(
        run.stderr.is_empty(),
        "vec-string runtime wrote stderr:\n{}",
        String::from_utf8_lossy(&run.stderr)
    );
}

#[test]
fn vec_string_runtime_traps_have_contract_messages_when_clang_is_available() {
    let Some(clang) = find_clang() else {
        eprintln!("skipping vec-string trap smoke: set GLAGOL_CLANG or install clang");
        return;
    };

    let cases = [
        (
            "vec-string-index-bounds",
            r#"
(module main)

(fn main () -> i32
  (std.io.print_string (std.vec.string.index (std.vec.string.empty) 0))
  0)
"#,
            "slovo runtime error: vector index out of bounds\n",
        ),
        (
            "vec-string-index-negative",
            r#"
(module main)

(fn main () -> i32
  (std.io.print_string
    (std.vec.string.index
      (std.vec.string.append (std.vec.string.empty) "only")
      -1))
  0)
"#,
            "slovo runtime error: vector index out of bounds\n",
        ),
        (
            "vec-string-allocation",
            r#"#include <stddef.h>
#include <stdint.h>

typedef struct __glagol_vec_string __glagol_vec_string;

__glagol_vec_string *__glagol_vec_string_empty(void);

void *__wrap_malloc(size_t size) {
    (void)size;
    return NULL;
}

int main(void) {
    (void)__glagol_vec_string_empty();
    return 0;
}
"#,
            "slovo runtime error: vector allocation failed\n",
        ),
    ];

    let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
    let runtime = manifest.join("../runtime/runtime.c");

    for (name, source, expected_stderr) in cases {
        let temp_dir = env::temp_dir().join(format!(
            "glagol-vec-string-runtime-{}-{}",
            std::process::id(),
            NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
        ));
        fs::create_dir_all(&temp_dir)
            .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));

        let exe = temp_dir.join(name);
        let run = if name.starts_with("vec-string-index-") {
            let fixture = write_fixture(name, source);
            let compile = run_glagol([fixture.as_os_str()]);
            assert_success("compile vec string index trap", &compile);
            compile_and_run_with_runtime(&clang, name, &compile.stdout)
        } else {
            let probe = temp_dir.join(format!("{}.c", name));
            fs::write(&probe, source)
                .unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err));
            let mut clang_command = Command::new(&clang);
            clang_command
                .arg(&runtime)
                .arg(&probe)
                .arg("-Wl,--wrap=malloc")
                .arg("-o")
                .arg(&exe)
                .current_dir(manifest);
            configure_clang_runtime_env(&mut clang_command, &clang);
            let clang_output = clang_command
                .output()
                .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
            assert_success("clang vec string allocation probe", &clang_output);
            Command::new(&exe)
                .output()
                .unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err))
        };

        assert_eq!(
            run.status.code(),
            Some(1),
            "trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
            name,
            String::from_utf8_lossy(&run.stdout),
            String::from_utf8_lossy(&run.stderr)
        );
        assert_eq!(
            String::from_utf8_lossy(&run.stderr),
            expected_stderr,
            "trap `{}` stderr drifted",
            name
        );
        assert!(
            run.stdout.is_empty(),
            "trap `{}` wrote stdout:\n{}",
            name,
            String::from_utf8_lossy(&run.stdout)
        );
    }
}

#[test]
fn vec_string_test_runner_reports_index_trap() {
    let cases = [
        (
            "vec-string-index-test-trap",
            r#"
(module main)

(test "vec string index trap"
  (= (std.vec.string.index (std.vec.string.empty) 0) "missing"))
"#,
        ),
        (
            "vec-string-negative-index-test-trap",
            r#"
(module main)

(test "vec string negative index trap"
  (= (std.vec.string.index (std.vec.string.append (std.vec.string.empty) "only") -1) "missing"))
"#,
        ),
    ];

    for (name, source) in cases {
        let fixture = write_fixture(name, source);
        let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let stderr = String::from_utf8_lossy(&output.stderr);

        assert_eq!(
            output.status.code(),
            Some(1),
            "test runner vec string trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
            name,
            stdout,
            stderr
        );
        assert!(
            stdout.is_empty(),
            "test runner vec string trap `{}` wrote stdout:\n{}",
            name,
            stdout
        );
        assert!(
            stderr.contains("TestRuntimeTrap")
                && stderr.contains("slovo runtime error: vector index out of bounds"),
            "test runner vec string trap `{}` diagnostic drifted\nstderr:\n{}",
            name,
            stderr
        );
    }
}

fn vec_i64_fixture_source() -> &'static str {
    r#"
(module main)

(fn empty_values () -> (vec i64)
  (std.vec.i64.empty))

(fn pair ((base i64)) -> (vec i64)
  (let values (vec i64) (std.vec.i64.empty))
  (let first (vec i64) (std.vec.i64.append values base))
  (std.vec.i64.append first (+ base 1i64)))

(fn echo ((values (vec i64))) -> (vec i64)
  values)

(fn length ((values (vec i64))) -> i32
  (std.vec.i64.len values))

(fn at ((values (vec i64)) (i i32)) -> i64
  (std.vec.i64.index values i))

(fn call_return_value () -> i64
  (std.vec.i64.index (echo (pair 20i64)) 1))

(fn original_len_after_append () -> i32
  (let values (vec i64) (std.vec.i64.empty))
  (let appended (vec i64) (std.vec.i64.append values 1i64))
  (std.vec.i64.len values))

(fn same_pair () -> bool
  (= (pair 5i64)
     (std.vec.i64.append (std.vec.i64.append (std.vec.i64.empty) 5i64) 6i64)))

(test "vec i64 empty length"
  (= (std.vec.i64.len (empty_values)) 0))

(test "vec i64 append length"
  (= (length (pair 40i64)) 2))

(test "vec i64 index"
  (= (at (pair 40i64) 1) 41i64))

(test "vec i64 append is immutable"
  (= (original_len_after_append) 0))

(test "vec i64 equality"
  (same_pair))

(fn main () -> i32
  (std.io.print_i64 (call_return_value))
  0)
"#
}

fn vec_f64_fixture_source() -> &'static str {
    r#"
(module main)

(fn empty_values () -> (vec f64)
  (std.vec.f64.empty))

(fn pair ((base f64)) -> (vec f64)
  (let values (vec f64) (std.vec.f64.empty))
  (let first (vec f64) (std.vec.f64.append values base))
  (std.vec.f64.append first (+ base 1.0)))

(fn echo ((values (vec f64))) -> (vec f64)
  values)

(fn length ((values (vec f64))) -> i32
  (std.vec.f64.len values))

(fn at ((values (vec f64)) (i i32)) -> f64
  (std.vec.f64.index values i))

(fn call_return_value () -> f64
  (std.vec.f64.index (echo (pair 20.0)) 1))

(fn original_len_after_append () -> i32
  (let values (vec f64) (std.vec.f64.empty))
  (let appended (vec f64) (std.vec.f64.append values 1.0))
  (std.vec.f64.len values))

(fn same_pair () -> bool
  (= (pair 5.0)
     (std.vec.f64.append (std.vec.f64.append (std.vec.f64.empty) 5.0) 6.0)))

(test "vec f64 empty length"
  (= (std.vec.f64.len (empty_values)) 0))

(test "vec f64 append length"
  (= (length (pair 40.0)) 2))

(test "vec f64 index"
  (= (at (pair 40.0) 1) 41.0))

(test "vec f64 append is immutable"
  (= (original_len_after_append) 0))

(test "vec f64 equality"
  (same_pair))

(fn main () -> i32
  (std.io.print_f64 (call_return_value))
  0)
"#
}

fn vec_bool_fixture_source() -> &'static str {
    r#"
(module main)

(fn empty_values () -> (vec bool)
  (std.vec.bool.empty))

(fn pair () -> (vec bool)
  (let values (vec bool) (std.vec.bool.empty))
  (let first (vec bool) (std.vec.bool.append values true))
  (std.vec.bool.append first false))

(fn echo ((values (vec bool))) -> (vec bool)
  values)

(fn length ((values (vec bool))) -> i32
  (std.vec.bool.len values))

(fn at ((values (vec bool)) (i i32)) -> bool
  (std.vec.bool.index values i))

(fn call_return_value () -> bool
  (std.vec.bool.index (echo (pair)) 1))

(fn original_len_after_append () -> i32
  (let values (vec bool) (std.vec.bool.empty))
  (let appended (vec bool) (std.vec.bool.append values true))
  (std.vec.bool.len values))

(fn same_pair () -> bool
  (= (pair)
     (std.vec.bool.append (std.vec.bool.append (std.vec.bool.empty) true) false)))

(test "vec bool empty length"
  (= (std.vec.bool.len (empty_values)) 0))

(test "vec bool append length"
  (= (length (pair)) 2))

(test "vec bool index"
  (= (at (pair) 1) false))

(test "vec bool append is immutable"
  (= (original_len_after_append) 0))

(test "vec bool equality"
  (same_pair))

(fn main () -> i32
  (std.io.print_bool (call_return_value))
  0)
"#
}

fn run_glagol<I, S>(args: I) -> Output
where
    I: IntoIterator<Item = S>,
    S: AsRef<OsStr>,
{
    Command::new(env!("CARGO_BIN_EXE_glagol"))
        .args(args)
        .current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
        .output()
        .expect("run glagol")
}

fn write_fixture(name: &str, source: &str) -> PathBuf {
    let mut path = env::temp_dir();
    path.push(format!(
        "glagol-collections-alpha-{}-{}-{}.slo",
        name,
        std::process::id(),
        NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
    ));
    fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
    path
}

fn write_vec_project() -> PathBuf {
    let root = env::temp_dir().join(format!(
        "glagol-collections-alpha-project-{}-{}",
        std::process::id(),
        NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
    ));
    let src = root.join("src");
    fs::create_dir_all(&src).unwrap_or_else(|err| panic!("create `{}`: {}", src.display(), err));
    fs::write(
        root.join("slovo.toml"),
        "[project]\nname = \"vecproj\"\nsource_root = \"src\"\nentry = \"main\"\n",
    )
    .unwrap_or_else(|err| panic!("write project manifest: {}", err));
    fs::write(
        src.join("math.slo"),
        r#"(module math (export singleton length))

(fn singleton ((value i32)) -> (vec i32)
  (std.vec.i32.append (std.vec.i32.empty) value))

(fn length ((values (vec i32))) -> i32
  (std.vec.i32.len values))
"#,
    )
    .unwrap_or_else(|err| panic!("write math module: {}", err));
    fs::write(
        src.join("main.slo"),
        r#"(module main)

(import math (singleton length))

(fn main () -> i32
  (length (singleton 7)))

(test "project vector import"
  (= (length (singleton 7)) 1))
"#,
    )
    .unwrap_or_else(|err| panic!("write main module: {}", err));
    root
}

fn assert_success(context: &str, output: &Output) {
    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
    );
}

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, "{} stdout drifted", context);
    assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
}

fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8]) -> Output {
    let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
    let temp_dir = env::temp_dir().join(format!(
        "glagol-vec-runtime-{}-{}",
        std::process::id(),
        NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
    ));
    fs::create_dir_all(&temp_dir)
        .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));

    let ir_path = temp_dir.join(format!("{}.ll", name));
    let exe_path = temp_dir.join(name);
    fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err));

    let runtime = manifest.join("../runtime/runtime.c");
    let mut clang_command = Command::new(clang);
    clang_command
        .arg(&runtime)
        .arg(&ir_path)
        .arg("-o")
        .arg(&exe_path)
        .current_dir(manifest);
    configure_clang_runtime_env(&mut clang_command, clang);
    let clang_output = clang_command
        .output()
        .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
    assert_success("clang vec runtime smoke", &clang_output);

    Command::new(&exe_path)
        .output()
        .unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err))
}

fn find_clang() -> Option<PathBuf> {
    if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) {
        return Some(PathBuf::from(path));
    }

    let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang");
    if hermetic_clang.is_file() {
        return Some(hermetic_clang);
    }

    find_on_path("clang")
}

fn find_on_path(program: &str) -> Option<PathBuf> {
    let path = env::var_os("PATH")?;
    env::split_paths(&path)
        .map(|dir| dir.join(program))
        .find(|candidate| candidate.is_file())
}

fn configure_clang_runtime_env(command: &mut Command, clang: &Path) {
    if !clang.starts_with("/tmp/glagol-clang-root") {
        return;
    }

    let root = Path::new("/tmp/glagol-clang-root");
    let lib64 = root.join("usr/lib64");
    let lib = root.join("usr/lib");
    let existing = env::var_os("LD_LIBRARY_PATH").unwrap_or_default();
    let mut paths = vec![lib64, lib];
    paths.extend(env::split_paths(&existing));
    let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH");
    command.env("LD_LIBRARY_PATH", joined);
}
