//! Pins the Float-aware `NoInstance` diagnostic added in milestone 23.
//!
//! Property protected: a polymorphic Eq/Ord helper invoked at Float
//! fires `no-instance` (since Float has neither Eq nor Ord instance
//! per design/contracts/0005-float-semantics.md) AND the diagnostic message
//! cross-references the canonical float-semantics contract so the
//! LLM author immediately learns the partial-Float story rather
//! than seeing a bare "no instance" message.

use ailang_check::check_workspace;
use ailang_surface::load_workspace;
use std::path::PathBuf;

fn fixture(name: &str) -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("../../examples")
        .join(name)
}

#[test]
fn eq_at_float_fires_float_aware_noinstance() {
    let ws = load_workspace(&fixture("eq_float_noinstance.ail")).expect("load");
    let diags = check_workspace(&ws);

    assert!(!diags.is_empty(), "expected NoInstance diagnostic, got none");

    let no_inst = diags
        .iter()
        .find(|d| d.code == "no-instance")
        .unwrap_or_else(|| panic!("expected diagnostic with code 'no-instance', got: {diags:#?}"));

    assert!(
        no_inst.message.contains("Float"),
        "expected Float-aware NoInstance diagnostic, got message: {:?}",
        no_inst.message
    );

    // Cross-ref to the float-semantics contract — the LLM author should
    // be pointed at the canonical explanation of partial Float orderability.
    assert!(
        no_inst.message.contains("design/contracts/0005-float-semantics.md"),
        "expected NoInstance message to cross-reference the float-semantics contract, got: {:?}",
        no_inst.message
    );
    // 2026-05-21 operator-routing-eq-ord: the addendum names the
    // explicit IEEE-aware alternative `float_eq` (and siblings)
    // alongside the contract cross-reference, so the LLM author
    // learns the named-fn surface from the diagnostic itself.
    assert!(
        no_inst.message.contains("float_eq"),
        "expected NoInstance message to name `float_eq` as the explicit IEEE-aware alternative, got: {:?}",
        no_inst.message
    );
}

/// #25: the Float-aware `NoInstance` addendum branches on the called
/// method. `compare` returns a three-way `Ordering`, but no `float_compare`
/// ships — a deliberate non-ship per the operator-routing-eq-ord spec. The
/// `eq`/`ne`/`lt`/`le`/`gt`/`ge` arm keeps the existing wording (the boolean
/// `float_*` siblings are drop-in replacements); the `compare` arm must
/// instead acknowledge the omission and point the author at building a
/// three-way result from `float_lt` + `float_eq`, rather than listing the
/// boolean siblings as if a `float_compare` existed.
#[test]
fn compare_at_float_names_deliberate_no_float_compare() {
    let ws = load_workspace(&fixture("compare_float_noinstance.ail")).expect("load");
    let diags = check_workspace(&ws);

    let no_inst = diags
        .iter()
        .find(|d| d.code == "no-instance")
        .unwrap_or_else(|| panic!("expected diagnostic with code 'no-instance', got: {diags:#?}"));

    assert!(
        no_inst.message.contains("Float"),
        "expected Float-aware NoInstance diagnostic, got: {:?}",
        no_inst.message
    );
    assert!(
        no_inst.message.contains("design/contracts/0005-float-semantics.md"),
        "expected the float-semantics contract cross-reference, got: {:?}",
        no_inst.message
    );
    // The compare-specific branch: name the deliberate `float_compare`
    // non-ship and the `float_lt` + `float_eq` building blocks.
    assert!(
        no_inst.message.contains("float_compare"),
        "expected the compare addendum to name the deliberate `float_compare` non-ship, got: {:?}",
        no_inst.message
    );
    assert!(
        no_inst.message.contains("float_lt") && no_inst.message.contains("float_eq"),
        "expected the compare addendum to name float_lt + float_eq as the three-way building blocks, got: {:?}",
        no_inst.message
    );
}
