//! Pin tests on `resolve_method_dispatch` — the
//! dispatch-resolution helper that synth's `Term::Var` arm consults
//! per the spec's 5-step rule.
//!
//! Six cases:
//! 1. Unique candidate.
//! 2. Multi + explicit qualifier matching one.
//! 3. Multi + explicit qualifier matching none → UnknownClass.
//! 4. Multi + type-driven filter narrows to one.
//! 5. Multi + constraint-driven filter narrows to one (rigid var case).
//! 6. Multi + true ambiguity → AmbiguousMethodResolution.

use ailang_check::{resolve_method_dispatch, MethodDispatchOutcome};
use ailang_core::ast::{Constraint, Type};
use ailang_core::canonical;
use std::collections::{BTreeMap, BTreeSet};

fn registry_with(entries: &[(&str, &str)]) -> BTreeMap<(String, String), ()> {
    // Returns a synthetic registry: keys are (qualified_class, type_hash)
    // values are unit (instance presence flag).
    let mut reg = BTreeMap::new();
    for (cls, ty_name) in entries {
        let t = Type::Con { name: ty_name.to_string(), args: vec![] };
        let h = canonical::type_hash(&t);
        reg.insert((cls.to_string(), h), ());
    }
    reg
}

fn candidates_of(classes: &[&str]) -> BTreeSet<String> {
    classes.iter().map(|s| s.to_string()).collect()
}

/// Case 1: unique candidate → returns that class.
#[test]
fn case1_unique_candidate_resolves() {
    let candidates = candidates_of(&["prelude.Eq"]);
    let registry = registry_with(&[("prelude.Eq", "Int")]);
    let arg_ty = Type::Con { name: "Int".to_string(), args: vec![] };
    let result = resolve_method_dispatch(
        /*method*/ "eq",
        /*qualifier_prefix*/ None,
        /*candidates*/ &candidates,
        /*concrete_arg_type*/ Some(&arg_ty),
        /*declared_constraints*/ &[],
        /*registry*/ &registry,
    );
    assert_eq!(result, MethodDispatchOutcome::Resolved("prelude.Eq".to_string()));
}

/// Case 2: multi + explicit qualifier matching one → returns that class.
#[test]
fn case2_qualifier_matches_one() {
    let candidates = candidates_of(&["prelude.Show", "userlib.Show"]);
    let registry = registry_with(&[
        ("prelude.Show", "Int"),
        ("userlib.Show", "Int"),
    ]);
    let arg_ty = Type::Con { name: "Int".to_string(), args: vec![] };
    let result = resolve_method_dispatch(
        "show",
        Some("userlib.Show"),
        &candidates,
        Some(&arg_ty),
        &[],
        &registry,
    );
    assert_eq!(result, MethodDispatchOutcome::Resolved("userlib.Show".to_string()));
}

/// Case 3: multi + explicit qualifier matching none → UnknownClass.
#[test]
fn case3_qualifier_matches_none_unknown_class() {
    let candidates = candidates_of(&["prelude.Show", "userlib.Show"]);
    let registry = registry_with(&[("prelude.Show", "Int")]);
    let arg_ty = Type::Con { name: "Int".to_string(), args: vec![] };
    let result = resolve_method_dispatch(
        "show",
        Some("nonexistent.Show"),
        &candidates,
        Some(&arg_ty),
        &[],
        &registry,
    );
    assert_eq!(
        result,
        MethodDispatchOutcome::UnknownClass("nonexistent.Show".to_string()),
    );
}

/// Case 4: multi + type-driven filter narrows to one → returns that class.
#[test]
fn case4_type_driven_filter_narrows_to_one() {
    let candidates = candidates_of(&["prelude.Show", "userlib.Show"]);
    let registry = registry_with(&[("prelude.Show", "Int")]); // only prelude.Show has Show Int
    let arg_ty = Type::Con { name: "Int".to_string(), args: vec![] };
    let result = resolve_method_dispatch(
        "show",
        None,
        &candidates,
        Some(&arg_ty),
        &[],
        &registry,
    );
    assert_eq!(result, MethodDispatchOutcome::Resolved("prelude.Show".to_string()));
}

/// Case 5: multi + constraint-driven filter narrows to one (rigid var) → returns that class.
#[test]
fn case5_constraint_driven_filter_narrows_to_one() {
    let candidates = candidates_of(&["prelude.Show", "userlib.Show"]);
    let registry = registry_with(&[]);
    let rigid_a = Type::Var { name: "a".to_string() };
    let declared = vec![Constraint {
        class: "prelude.Show".to_string(),
        type_: Type::Var { name: "a".to_string() },
    }];
    let result = resolve_method_dispatch(
        "show",
        None,
        &candidates,
        Some(&rigid_a), // rigid var, can't drive registry lookup
        &declared,
        &registry,
    );
    assert_eq!(result, MethodDispatchOutcome::Resolved("prelude.Show".to_string()));
}

/// Case 6: multi + true ambiguity → AmbiguousMethodResolution.
#[test]
fn case6_true_ambiguity() {
    let candidates = candidates_of(&["prelude.Show", "userlib.Show"]);
    let registry = registry_with(&[
        ("prelude.Show", "Int"),
        ("userlib.Show", "Int"),
    ]);
    let arg_ty = Type::Con { name: "Int".to_string(), args: vec![] };
    let result = resolve_method_dispatch(
        "show",
        None,
        &candidates,
        Some(&arg_ty),
        &[],
        &registry,
    );
    assert_eq!(
        result,
        MethodDispatchOutcome::Ambiguous {
            method: "show".to_string(),
            at_type: "Int".to_string(),
            candidates: vec!["prelude.Show".to_string(), "userlib.Show".to_string()],
        },
    );
}
