//! RED-pin for #51 (raw-buf fieldtest F7): the explicit element
//! type-arg of `(new RawBuf <elem> <size>)` must be honoured.
//!
//! Property protected: the author's `(con Elem)` annotation on a
//! `(new RawBuf ...)` is the binding declaration of the buffer's
//! element type — it must NOT depend on a downstream `get`/`set` to
//! be recovered, and it must be subject to RawBuf's `param-in`
//! restricted set {Int, Float, Bool} at check time.
//!
//! Two legs, both stemming from the same defect: `(new T <type>
//! <values>)` desugars to `(app T.new <values>)` and DROPS the
//! leading `NewArg::Type` (crates/ailang-core/src/desugar.rs:1053).
//!
//!  - Leg 1 (legitimate, must build+run): `(new RawBuf (con Int) 3)`
//!    used only via `RawBuf.size`. With the type-arg dropped, the
//!    result's forall var `a` is never pinned by use, monomorph
//!    defaults it to Unit (mono.rs free-fn-call arm / subst.rs), and
//!    codegen mints `RawBuf_new__Unit`, which has no registered
//!    intercept (intercepts.rs registers only Int/Float/Bool). Today
//!    `ail build --alloc=rc` aborts with
//!    `intrinsic fn RawBuf_new__Unit has no registered intercept`.
//!    Expected post-fix: build succeeds, program prints `3`.
//!
//!  - Leg 2 (forbidden, must reject at check): `(new RawBuf (con
//!    Unit) 3)`. Unit is not in RawBuf's param-in set, but because
//!    the `(con Unit)` type-arg is dropped before check, the
//!    `param-in` enforcement in `check_type_well_formed`
//!    (crates/ailang-check/src/lib.rs:2084) never sees a `RawBuf<Unit>`
//!    type, so `ail check` wrongly passes (exit 0) and the program
//!    crashes at `ail build` with the same `RawBuf_new__Unit` error.
//!    Expected post-fix: `ail check` fails with
//!    `param-not-in-restricted-set` naming `Unit`; codegen unreached.
//!
//! These are CLI-boundary E2E tests because the defect spans the
//! desugar -> check/codegen pipeline and the symptoms are the exit
//! behaviour of `ail check` / `ail build` themselves.

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

fn repo_root() -> PathBuf {
    let manifest_dir = env!("CARGO_MANIFEST_DIR");
    Path::new(manifest_dir)
        .parent()
        .unwrap()
        .parent()
        .unwrap()
        .to_path_buf()
}

/// Leg 1: a `(new RawBuf (con Int) 3)` buffer used only via
/// `RawBuf.size` must build under `--alloc=rc` and print its size.
#[test]
fn rawbuf_new_int_size_only_builds_and_runs() {
    let fixture = repo_root().join("examples/raw_buf_new_type_arg_intsize.ail");
    let tmp = std::env::temp_dir().join(format!(
        "ailang_rawbuf_new_type_arg_intsize_{}",
        std::process::id()
    ));
    std::fs::create_dir_all(&tmp).unwrap();
    let out = tmp.join("bin");

    let build = Command::new(env!("CARGO_BIN_EXE_ail"))
        .args(["build", fixture.to_str().unwrap(), "--alloc=rc", "-o"])
        .arg(&out)
        .output()
        .expect("ail build failed to spawn");
    assert!(
        build.status.success(),
        "ail build --alloc=rc failed for size-only RawBuf<Int>: stderr={}",
        String::from_utf8_lossy(&build.stderr)
    );

    let run = Command::new(&out).output().expect("execute binary");
    assert!(
        run.status.success(),
        "binary exited non-zero: {:?}",
        run.status
    );
    let stdout = String::from_utf8_lossy(&run.stdout);
    assert!(
        stdout.trim() == "3",
        "expected the program to print the buffer size `3`, got: {stdout:?}"
    );
}

/// Leg 2: a `(new RawBuf (con Unit) 3)` names a forbidden element
/// type and must be rejected at `ail check` with
/// `param-not-in-restricted-set` naming `Unit`.
#[test]
fn rawbuf_new_unit_element_rejected_at_check() {
    let fixture = repo_root().join("examples/raw_buf_new_type_arg_unit_reject.ail");

    let out = Command::new(env!("CARGO_BIN_EXE_ail"))
        .arg("check")
        .arg(&fixture)
        .output()
        .expect("ail check failed to spawn");

    assert!(
        !out.status.success(),
        "expected `ail check` to REJECT RawBuf<Unit>, but it passed: stdout={}",
        String::from_utf8_lossy(&out.stdout)
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("param-not-in-restricted-set"),
        "expected diagnostic code `param-not-in-restricted-set`, got: {stderr}"
    );
    assert!(
        stderr.contains("Unit"),
        "expected the diagnostic to name the forbidden element `Unit`, got: {stderr}"
    );
}
