//! End-to-end coverage of the post-`MethodNameCollision`-
//! retirement multi-candidate dispatch path. Three positive fixtures
//! exercise the three trajectories from the milestone spec
//! §"Data flow":
//!
//! - Trajectory C — ambiguous bare-method call with both candidate
//!   classes shipping `Show Int`. Typecheck rejects with
//!   `ambiguous-method-resolution`.
//! - Trajectory E — explicit qualifier `<module>.<Class>.<method>`
//!   resolves cleanly to the named class.
//! - Class-fn shadow — bare-method call where both a class method and
//!   a free fn of the same name exist. Fn wins per lookup precedence;
//!   `class-method-shadowed-by-fn` warning fires so the LLM-author
//!   sees the shadow.
//!
//! Each test invokes `ail check --json <entry>` and asserts on the
//! diagnostic stream's `code` field. Stdout / runtime behaviour is
//! out of scope here — the dispatch path is a check-time concern.

use ailang_test_support::examples_dir;
use std::path::PathBuf;
use std::process::Command;

fn ail_bin() -> PathBuf {
    PathBuf::from(env!("CARGO_BIN_EXE_ail"))
}

fn run_ail_check_json(entry: &str) -> std::process::Output {
    Command::new(ail_bin())
        .args(["check", "--json", examples_dir().join(entry).to_str().unwrap()])
        .output()
        .expect("ail binary must launch")
}

/// Multi-class trajectory C: bare `show 42` in a workspace with two
/// `Show` classes (modA and modB) each shipping `Show Int` is
/// genuinely ambiguous. `ail check` rejects with
/// `ambiguous-method-resolution` and names both candidate classes.
#[test]
fn mq3_two_show_ambiguous_fires_ambiguous_method_resolution() {
    let out = run_ail_check_json("mq3_two_show_ambiguous.ail");
    assert!(
        !out.status.success(),
        "expected check to reject ambiguous dispatch; stdout={} stderr={}",
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr),
    );
    let stdout = String::from_utf8(out.stdout).expect("stdout is utf-8");
    let diags: serde_json::Value =
        serde_json::from_str(&stdout).expect("--json mode emits a JSON array on stdout");
    let arr = diags.as_array().expect("diagnostics is a JSON array");
    assert!(
        arr.iter().any(|d| d["code"] == "ambiguous-method-resolution"),
        "expected `ambiguous-method-resolution` diagnostic; got {stdout}",
    );
    // Both candidate classes named in the diagnostic so the author
    // knows which qualifiers to choose from.
    assert!(
        stdout.contains("mq3_two_show_ambiguous_a.Show"),
        "expected candidate A in diagnostic; got {stdout}",
    );
    assert!(
        stdout.contains("mq3_two_show_ambiguous_b.Show"),
        "expected candidate B in diagnostic; got {stdout}",
    );
}

/// Multi-class trajectory E: explicit qualifier
/// `mq3_two_show_ambiguous_a.Show.show 42` disambiguates the same
/// workspace as the ambiguous fixture. Typecheck succeeds.
#[test]
fn mq3_two_show_qualified_resolves_clean() {
    let out = run_ail_check_json("mq3_two_show_qualified.ail");
    assert!(
        out.status.success(),
        "expected check to succeed with explicit qualifier; stdout={} stderr={}",
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr),
    );
}

/// Class-fn shadow: bare `myeq 1 2` in a workspace with
/// `class MyEq { myeq }` in one module and `fn myeq` in another
/// resolves to the fn per lookup precedence. Typecheck succeeds AND
/// the structured warning `class-method-shadowed-by-fn` fires so the
/// LLM-author sees the shadow.
#[test]
fn mq3_class_eq_vs_fn_eq_fn_wins_with_warning() {
    let out = run_ail_check_json("mq3_class_eq_vs_fn_eq.ail");
    assert!(
        out.status.success(),
        "expected check to succeed (fn wins); stdout={} stderr={}",
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr),
    );
    let stdout = String::from_utf8(out.stdout).expect("stdout is utf-8");
    let diags: serde_json::Value =
        serde_json::from_str(&stdout).expect("--json mode emits a JSON array on stdout");
    let arr = diags.as_array().expect("diagnostics is a JSON array");
    assert!(
        arr.iter().any(|d| d["code"] == "class-method-shadowed-by-fn"),
        "expected `class-method-shadowed-by-fn` warning; got {stdout}",
    );
}
