//! Gitea #7 RED pin — whole-valued Float must render with a `.0` suffix.
//!
//! A Float literal whose value is whole (e.g. `2.0`), printed via the
//! polymorphic `print` builtin, monomorphises to the Show-Float path:
//! `Show Float.show` → `float_to_str` → runtime/str.c::
//! `ailang_float_to_str` → libc `snprintf(buf, sizeof(buf), "%g", x)`.
//!
//! The surface printer at
//! `crates/ailang-surface/src/print.rs::write_float_lit` (lines 624-637)
//! already enforces the property that a finite Float whose `to_string`
//! contains neither `.` nor `e/E` gets a `.0` suffix appended, so the
//! lex round-trip stays on the Float branch. The runtime path lacks
//! that fallback and therefore drifts: a Float-typed `2.0` prints as
//! `2`, which is Int-shaped output and corrupts the LLM author's
//! mental model of typing — a Float in the source can produce
//! Int-looking text in stdout.
//!
//! This test pins the desired property (post-fix): stdout for a single
//! `print 2.0` must be `"2.0\n"`. Pre-fix the test fails with `"2\n"`.
//!
//! Fix site: `runtime/str.c::ailang_float_to_str` — after the `%g`
//! snprintf, if the buffer is finite and contains neither `.` nor
//! `e/E`, append `.0` (mirroring the surface printer). Out of scope
//! for this RED.

use std::path::Path;
use std::process::Command;

fn ail_bin() -> &'static str {
    env!("CARGO_BIN_EXE_ail")
}

fn build_and_run(example: &str) -> String {
    let manifest_dir = env!("CARGO_MANIFEST_DIR");
    let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
    let src = workspace.join("examples").join(example);
    let tmp = std::env::temp_dir().join(format!(
        "ailang_e2e_{}_{}",
        example.replace('.', "_"),
        std::process::id()
    ));
    std::fs::create_dir_all(&tmp).unwrap();
    let out = tmp.join("bin");
    let status = Command::new(ail_bin())
        .args(["build", src.to_str().unwrap(), "-o"])
        .arg(&out)
        .status()
        .expect("ail build failed to run");
    assert!(status.success(), "ail build failed for {example}");
    let output = Command::new(&out).output().expect("execute binary");
    assert!(
        output.status.success(),
        "binary {} exited non-zero",
        out.display()
    );
    String::from_utf8(output.stdout).expect("stdout utf8")
}

/// A whole-valued Float printed through `Show Float.show` keeps its
/// Float shape on stdout — i.e. the rendering contains a `.0` (or
/// equivalent `.` / `e/E` form) so a re-lex of the output would land
/// back on the Float branch, not on the Int branch. This is the
/// runtime mirror of `write_float_lit`'s surface-side `.0` fallback.
#[test]
fn print_whole_valued_float_renders_with_dot_zero_suffix() {
    let out = build_and_run("print_float_whole_smoke.ail");
    assert_eq!(
        out, "2.0\n",
        "whole-valued Float `2.0` must render with `.0` suffix \
         (mirroring write_float_lit); got `{out}` — the runtime \
         `ailang_float_to_str` is missing the `.0` fallback that \
         the surface printer applies (see Gitea #7).",
    );
}
