use std::sync::Arc;

use rex::{
    Rex,
    ast::{Expr, Symbol},
    engine::{
        Context, Engine, EngineError, FromRex, Handle, Heap, IntoRex, Module, RexDefault, Value,
        virtual_export_name,
    },
    parser::parse as parse_rex,
    typesystem::{BuiltinTypeId, Scheme, Type, TypeError, TypeKind},
};
use uuid::Uuid;

#[derive(Clone, Debug, PartialEq, Rex)]
enum Side {
    Left,
    Right,
}

#[derive(Clone, Debug, PartialEq, Rex)]
enum Correctness {
    Right,
    Wrong,
}

#[derive(Clone, Debug, PartialEq, Rex)]
struct Label {
    text: String,
    side: Side,
}

fn render_label(label: Label) -> String {
    match label.side {
        Side::Left => format!("{:<12}", label.text),
        Side::Right => format!("{:>12}", label.text),
    }
}

fn inject_globals<State: Clone + Send + Sync + 'static>(
    engine: &mut Engine<State>,
    build: impl FnOnce(&mut Module<State>) -> Result<(), EngineError>,
) -> Result<(), EngineError> {
    let mut module = Module::global();
    build(&mut module)?;
    engine.inject_module(module)
}

#[tokio::test]
async fn module_render_label_with_module_scoped_adts_left_and_right() {
    let mut engine: Engine<()> = Engine::with_prelude(()).unwrap();
    engine.add_default_resolvers();

    let mut module = Module::new("sample");
    module.add_rex_adt::<Side>().unwrap();
    module.add_rex_adt::<Correctness>().unwrap();
    module.add_rex_adt::<Label>().unwrap();
    module
        .export("render_label", |_: &(), label: Label| {
            Ok::<String, EngineError>(render_label(label))
        })
        .unwrap();
    engine.inject_module(module).unwrap();
    let (value, ty) = engine
        .into_evaluator()
        .eval_snippet(
            r#"
            import sample (Label, Left, Right, Wrong, render_label);
            import sample as Sample;
            (
                render_label (Label { text = "left", side = Left }),
                render_label (Label { text = "right", side = (Right is Sample.Side) }),
                (Right is Sample.Correctness),
                (Wrong is Sample.Correctness)
            )
            "#,
        )
        .await
        .unwrap();

    // `Side` and `Correctness` both provide a `Right` constructor in the same module.
    // This ensures Rex keeps them distinct via explicit type ascription (`is Side` vs `is Sample.Correctness`).
    let correctness_ty = Type::con(virtual_export_name("sample", "Correctness"), 0);
    assert_eq!(
        ty,
        Type::tuple(vec![
            Type::builtin(BuiltinTypeId::String),
            Type::builtin(BuiltinTypeId::String),
            correctness_ty.clone(),
            correctness_ty,
        ])
    );
    let items = tuple_items(&value);
    assert_eq!(items.len(), 4);
    assert_eq!(
        items[0].to_rust::<String>().unwrap(),
        format!("{:<12}", "left")
    );
    assert_eq!(
        items[1].to_rust::<String>().unwrap(),
        format!("{:>12}", "right")
    );
    match items[2].value().unwrap() {
        Value::Adt(tag, args) => {
            assert_eq!(tag.as_ref(), "Right");
            assert!(args.is_empty());
        }
        _ => panic!("expected ADT value for Correctness.Right"),
    }
    match items[3].value().unwrap() {
        Value::Adt(tag, args) => {
            assert_eq!(tag.as_ref(), "Wrong");
            assert!(args.is_empty());
        }
        _ => panic!("expected ADT value for Correctness.Wrong"),
    }
}

#[tokio::test]
async fn module_inject_rex_adt_registers_acyclic_dependency_closure() {
    let mut engine: Engine<()> = Engine::with_prelude(()).unwrap();
    engine.add_default_resolvers();

    let mut module = Module::new("sample");
    module.add_rex_adt::<Label>().unwrap();
    module
        .export("render_label", |_: &(), label: Label| {
            Ok::<String, EngineError>(render_label(label))
        })
        .unwrap();
    engine.inject_module(module).unwrap();
    let (value, ty) = engine
        .into_evaluator()
        .eval_snippet(
            r#"
            import sample (Label, Left, render_label);
            render_label (Label { text = "left", side = Left })
        "#,
        )
        .await
        .unwrap();

    assert_eq!(ty, Type::builtin(BuiltinTypeId::String));
    assert_eq!(
        value.to_rust::<String>().unwrap(),
        format!("{:<12}", "left")
    );
}

#[tokio::test]
async fn match_ascribed_module_type_with_overlapping_constructor_is_ambiguous_regression() {
    // Regression guard: when two module ADTs expose overlapping constructor names
    // (e.g. both have `Right`), `match` arms that use the bare constructor after an
    // `is Sample.Correctness` ascription currently remain ambiguous. This test ensures
    // we keep surfacing that ambiguity instead of silently picking one constructor.
    let mut engine: Engine<()> = Engine::with_prelude(()).unwrap();
    engine.add_default_resolvers();

    let mut module = Module::new("sample");
    module.add_rex_adt::<Side>().unwrap();
    module.add_rex_adt::<Correctness>().unwrap();
    engine.inject_module(module).unwrap();
    let err = engine
        .into_evaluator()
        .eval_snippet(
            r#"
            import sample (Right, Wrong);
            import sample as Sample;
            let x = (Right is Sample.Correctness) in
            match (x is Sample.Correctness) with {
              case Right -> true;
              case Wrong -> false;
            }
            "#,
        )
        .await
        .expect_err("expected ambiguity error for overlapping constructor in match pattern");

    match err.into_engine_error() {
        EngineError::Type(mut e) => {
            while let TypeError::Spanned { error, .. } = e {
                e = *error;
            }
            match e {
                TypeError::AmbiguousOverload(name) => {
                    assert!(name.as_ref().ends_with(".Right"));
                }
                other => panic!("expected ambiguous overload error, got {other:?}"),
            }
        }
        other => panic!("expected type error, got {other:?}"),
    }
}

#[derive(Clone, Debug, PartialEq, Rex)]
struct Entity1 {
    account_id: Uuid,
    project_id: Uuid,
    name: String,
    description: Option<String>,
    tags: Option<Vec<String>>,
    numbers: Vec<u32>,
}

#[derive(Clone, Debug, PartialEq, Rex)]
struct Entity2 {
    account_id: Uuid,
    project_id: Uuid,
    name: String,
    description: Option<String>,
    tags: Option<Vec<String>>,
    numbers: Vec<u32>,
}

impl Entity2 {
    fn rex_new(state: &HostState, name: String, numbers: Vec<u32>) -> Result<Entity2, EngineError> {
        Ok(Entity2 {
            account_id: state.account_id,
            project_id: state.project_id,
            name,
            description: None,
            tags: None,
            numbers,
        })
    }
}

impl RexDefault<HostState> for Entity1 {
    fn rex_default(engine: Context<HostState>) -> Result<Handle, EngineError> {
        let entity = Entity1 {
            account_id: engine.state().account_id,
            project_id: engine.state().project_id,
            name: "".to_string(),
            description: None,
            tags: None,
            numbers: vec![],
        };
        entity.into_rex(engine.heap())
    }
}

#[derive(Clone)]
struct HostState {
    account_id: Uuid,
    project_id: Uuid,
    is_admin: bool,
    roles: Vec<String>,
}

fn current_account_id(state: &HostState) -> Result<Uuid, EngineError> {
    Ok(state.account_id)
}

fn current_project_id(state: &HostState) -> Result<Uuid, EngineError> {
    Ok(state.project_id)
}

fn is_admin(state: &HostState) -> Result<bool, EngineError> {
    Ok(state.is_admin)
}

fn have_role(state: &HostState, role: String) -> Result<bool, EngineError> {
    Ok(state.roles.iter().any(|r| r == &role))
}

async fn have_role_async(state: HostState, role: String) -> Result<bool, EngineError> {
    Ok(state.roles.iter().any(|r| r == &role))
}

fn parse(code: &str) -> Arc<Expr> {
    parse_rex(code).unwrap().body.unwrap()
}
fn tuple_items(value: &Handle) -> Vec<Handle> {
    let Value::Tuple(items) = value.value().unwrap() else {
        panic!("expected tuple, got {}", value.type_name().unwrap());
    };
    items
}

fn list_from_handles(heap: &Heap, values: Vec<Handle>) -> Result<Handle, EngineError> {
    let mut list = heap.alloc_adt(Symbol::intern("Empty"), vec![])?;
    for value in values.into_iter().rev() {
        list = heap.alloc_adt(Symbol::intern("Cons"), vec![value, list])?;
    }
    Ok(list)
}

fn handle_as_list(handle: &Handle) -> Result<Vec<Handle>, EngineError> {
    let mut out = Vec::new();
    let mut cursor = handle.clone();
    loop {
        let Value::Adt(tag, args) = cursor.value()? else {
            return Err(EngineError::NativeType {
                expected: "List a".into(),
                got: cursor.type_name()?.into(),
            });
        };
        if tag == Symbol::intern("Empty") {
            return Ok(out);
        }
        if tag == Symbol::intern("Cons") && args.len() == 2 {
            out.push(args[0].clone());
            cursor = args[1].clone();
            continue;
        }
        return Err(EngineError::NativeType {
            expected: "List a".into(),
            got: cursor.type_name()?.into(),
        });
    }
}

fn is_i32_or_var(ty: &Type) -> bool {
    matches!(ty.as_ref(), TypeKind::Con(tc) if tc.name_str() == "i32")
        || matches!(ty.as_ref(), TypeKind::Var(_))
}

fn assert_overload_tuple_type_shape(ty: &Type) {
    let TypeKind::Tuple(items) = ty.as_ref() else {
        panic!("expected tuple type, got {ty}");
    };
    assert_eq!(items.len(), 6);
    assert!(
        is_i32_or_var(&items[0]),
        "expected i32/var at index 0, got {}",
        items[0]
    );
    assert_eq!(items[1], Type::builtin(BuiltinTypeId::String));
    assert_eq!(items[2], Type::builtin(BuiltinTypeId::Bool));
    assert!(
        is_i32_or_var(&items[3]),
        "expected i32/var at index 3, got {}",
        items[3]
    );
    assert_eq!(items[4], Type::builtin(BuiltinTypeId::Bool));
    assert_eq!(items[5], Type::builtin(BuiltinTypeId::String));
}

#[derive(Clone, Debug, PartialEq, Rex)]
struct EmbedRecord {
    n: i32,
}

#[tokio::test]
async fn injected_functions_can_read_shared_state_fields() {
    let account_id = uuid::uuid!("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa");
    let project_id = uuid::uuid!("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb");
    let mut engine: Engine<HostState> = Engine::with_prelude(HostState {
        account_id,
        project_id,
        is_admin: true,
        roles: vec!["admin".to_string(), "editor".to_string()],
    })
    .unwrap();

    inject_globals(&mut engine, |module| {
        module.export("current_account_id", current_account_id)?;
        module.export("current_project_id", current_project_id)?;
        module.export("is_admin", is_admin)?;
        module.export("have_role", have_role)?;
        Ok(())
    })
    .unwrap();

    let expr = parse(
        "(current_account_id, current_project_id, is_admin, have_role \"admin\", have_role \"viewer\")",
    );
    let (value, ty) = engine.into_evaluator().eval(expr.as_ref()).await.unwrap();
    assert_eq!(
        ty,
        Type::tuple(vec![
            Type::builtin(BuiltinTypeId::Uuid),
            Type::builtin(BuiltinTypeId::Uuid),
            Type::builtin(BuiltinTypeId::Bool),
            Type::builtin(BuiltinTypeId::Bool),
            Type::builtin(BuiltinTypeId::Bool),
        ])
    );

    let items = tuple_items(&value);
    assert_eq!(items.len(), 5);
    assert_eq!(items[0].to_rust::<Uuid>().unwrap(), account_id);
    assert_eq!(items[1].to_rust::<Uuid>().unwrap(), project_id);
    assert!(items[2].to_rust::<bool>().unwrap());
    assert!(items[3].to_rust::<bool>().unwrap());
    assert!(!items[4].to_rust::<bool>().unwrap());
}

#[tokio::test]
async fn derived_rex_default_can_read_host_state() {
    let account_id = uuid::uuid!("11111111-1111-4111-8111-111111111111");
    let project_id = uuid::uuid!("22222222-2222-4222-8222-222222222222");
    let mut engine: Engine<HostState> = Engine::with_prelude(HostState {
        account_id,
        project_id,
        is_admin: true,
        roles: vec!["admin".to_string()],
    })
    .unwrap();

    Entity1::inject_rex_with_default(&mut engine).unwrap();

    let expr = parse("let e: Entity1 = default in e");
    let (value, ty) = engine.into_evaluator().eval(expr.as_ref()).await.unwrap();
    assert_eq!(ty, Type::con("Entity1", 0));

    let decoded = Entity1::from_rex(&value).unwrap();
    assert_eq!(
        decoded,
        Entity1 {
            account_id,
            project_id,
            name: String::new(),
            description: None,
            tags: None,
            numbers: vec![],
        }
    );
}

#[tokio::test]
async fn derived_rex_default_record_update_can_override_fields() {
    let account_id = uuid::uuid!("33333333-3333-4333-8333-333333333333");
    let project_id = uuid::uuid!("44444444-4444-4444-8444-444444444444");
    let mut engine: Engine<HostState> = Engine::with_prelude(HostState {
        account_id,
        project_id,
        is_admin: false,
        roles: vec!["reader".to_string()],
    })
    .unwrap();

    Entity1::inject_rex_with_default(&mut engine).unwrap();

    let expr = parse(
        r#"let e: Entity1 = { default with { name = "sample", tags = Some (to_array ["x", "y"]), numbers = to_array [7, 11] } } in e"#,
    );
    let (value, ty) = engine.into_evaluator().eval(expr.as_ref()).await.unwrap();
    assert_eq!(ty, Type::con("Entity1", 0));

    let decoded = Entity1::from_rex(&value).unwrap();
    assert_eq!(
        decoded,
        Entity1 {
            account_id,
            project_id,
            name: "sample".to_string(),
            description: None,
            tags: Some(vec!["x".to_string(), "y".to_string()]),
            numbers: vec![7, 11],
        }
    );
}

#[tokio::test]
async fn entity2_constructor_defaults_from_host_state_with_required_fields() {
    let account_id = uuid::uuid!("55555555-5555-4555-8555-555555555555");
    let project_id = uuid::uuid!("66666666-6666-4666-8666-666666666666");
    let mut engine: Engine<HostState> = Engine::with_prelude(HostState {
        account_id,
        project_id,
        is_admin: false,
        roles: vec!["reader".to_string()],
    })
    .unwrap();

    Entity2::inject_rex_with_constructor(&mut engine, Entity2::rex_new).unwrap();

    let expr = parse(r#"Entity2 "sample" [7, 11]"#);
    let (value, ty) = engine.into_evaluator().eval(expr.as_ref()).await.unwrap();
    assert_eq!(ty, Type::con("Entity2", 0));

    let decoded = Entity2::from_rex(&value).unwrap();
    assert_eq!(
        decoded,
        Entity2 {
            account_id,
            project_id,
            name: "sample".to_string(),
            description: None,
            tags: None,
            numbers: vec![7, 11],
        }
    );
}

#[tokio::test]
async fn entity2_constructor_result_can_be_record_updated() {
    let account_id = uuid::uuid!("77777777-7777-4777-8777-777777777777");
    let project_id = uuid::uuid!("88888888-8888-4888-8888-888888888888");
    let mut engine: Engine<HostState> = Engine::with_prelude(HostState {
        account_id,
        project_id,
        is_admin: true,
        roles: vec!["admin".to_string()],
    })
    .unwrap();

    Entity2::inject_rex_with_constructor(&mut engine, Entity2::rex_new).unwrap();

    let expr = parse(
        r#"{
            (Entity2 "sample" [7, 11])
            with {
                description = Some "desc",
                tags = Some (to_array ["x", "y"])
            }
        }"#,
    );
    let (value, ty) = engine.into_evaluator().eval(expr.as_ref()).await.unwrap();
    assert_eq!(ty, Type::con("Entity2", 0));

    let decoded = Entity2::from_rex(&value).unwrap();
    assert_eq!(
        decoded,
        Entity2 {
            account_id,
            project_id,
            name: "sample".to_string(),
            description: Some("desc".to_string()),
            tags: Some(vec!["x".to_string(), "y".to_string()]),
            numbers: vec![7, 11],
        }
    );
}

#[tokio::test]
async fn async_injected_functions_can_read_shared_state_fields() {
    let mut engine: Engine<HostState> = Engine::with_prelude(HostState {
        account_id: uuid::uuid!("cccccccc-cccc-4ccc-8ccc-cccccccccccc"),
        project_id: uuid::uuid!("dddddddd-dddd-4ddd-8ddd-dddddddddddd"),
        is_admin: false,
        roles: vec!["reader".to_string(), "editor".to_string()],
    })
    .unwrap();

    inject_globals(&mut engine, |module| {
        module.export_async("have_role_async", |state: &HostState, role: String| {
            have_role_async(state.clone(), role)
        })
    })
    .unwrap();

    let expr = parse("(have_role_async \"editor\", have_role_async \"admin\")");
    let (value, ty) = engine.into_evaluator().eval(expr.as_ref()).await.unwrap();
    assert_eq!(
        ty,
        Type::tuple(vec![
            Type::builtin(BuiltinTypeId::Bool),
            Type::builtin(BuiltinTypeId::Bool)
        ])
    );

    let items = tuple_items(&value);
    assert_eq!(items.len(), 2);
    assert!(items[0].to_rust::<bool>().unwrap());
    assert!(!items[1].to_rust::<bool>().unwrap());
}

#[tokio::test]
async fn generic_export_can_repeat_a_value_into_a_list() {
    let mut engine: Engine<()> = Engine::with_prelude(()).unwrap();

    // This demonstrates how to write a generic function by declaring a fresh
    // type variable `T` and using it in the exported Rex type scheme.
    let t_var = engine.type_system.fresh_type_var(Some(Symbol::intern("T")));
    let t = Type::var(t_var.clone());
    let scheme = Scheme::new(
        vec![t_var],
        vec![],
        Type::fun(
            t.clone(),
            Type::fun(Type::builtin(BuiltinTypeId::I32), Type::list(t)),
        ),
    );
    inject_globals(&mut engine, |module| {
        module.export_native("repeat_value", scheme, 2, |engine, _, args| {
            let value = args[0].clone();
            let len = args[1].to_rust::<i32>()?;
            let copies = (0..len.max(0)).map(|_| value.clone()).collect();
            list_from_handles(engine.heap(), copies)
        })
    })
    .unwrap();

    let expr = parse(r#"(repeat_value "rex" 3, repeat_value true 2)"#);
    let (value, ty) = engine.into_evaluator().eval(expr.as_ref()).await.unwrap();
    assert_eq!(
        ty,
        Type::tuple(vec![
            Type::list(Type::builtin(BuiltinTypeId::String)),
            Type::list(Type::builtin(BuiltinTypeId::Bool)),
        ])
    );

    let items = tuple_items(&value);
    let repeated_strings = handle_as_list(&items[0]).unwrap();
    assert_eq!(repeated_strings.len(), 3);
    assert_eq!(repeated_strings[0].to_rust::<String>().unwrap(), "rex");
    assert_eq!(repeated_strings[1].to_rust::<String>().unwrap(), "rex");
    assert_eq!(repeated_strings[2].to_rust::<String>().unwrap(), "rex");

    let repeated_bools = handle_as_list(&items[1]).unwrap();
    assert_eq!(repeated_bools.len(), 2);
    assert!(repeated_bools[0].to_rust::<bool>().unwrap());
    assert!(repeated_bools[1].to_rust::<bool>().unwrap());
}

#[tokio::test]
async fn generic_export_can_swap_two_values_of_different_types() {
    let mut engine: Engine<()> = Engine::with_prelude(()).unwrap();

    // This is another example of writing a generic function: it introduces
    // independent type variables `P` and `Q` and returns them in swapped order.
    let p_var = engine.type_system.fresh_type_var(Some(Symbol::intern("P")));
    let q_var = engine.type_system.fresh_type_var(Some(Symbol::intern("Q")));
    let p = Type::var(p_var.clone());
    let q = Type::var(q_var.clone());
    let scheme = Scheme::new(
        vec![p_var, q_var],
        vec![],
        Type::fun(p.clone(), Type::fun(q.clone(), Type::tuple(vec![q, p]))),
    );
    inject_globals(&mut engine, |module| {
        module.export_native("swap_pair", scheme, 2, |engine, _, args| {
            engine
                .heap()
                .alloc_tuple(vec![args[1].clone(), args[0].clone()])
        })
    })
    .unwrap();

    let expr = parse(r#"(swap_pair "left" 7, swap_pair true "right")"#);
    let (value, ty) = engine.into_evaluator().eval(expr.as_ref()).await.unwrap();
    assert_eq!(
        ty,
        Type::tuple(vec![
            Type::tuple(vec![
                Type::builtin(BuiltinTypeId::I32),
                Type::builtin(BuiltinTypeId::String),
            ]),
            Type::tuple(vec![
                Type::builtin(BuiltinTypeId::String),
                Type::builtin(BuiltinTypeId::Bool),
            ]),
        ])
    );

    let items = tuple_items(&value);

    let first_swap = tuple_items(&items[0]);
    assert_eq!(first_swap[0].to_rust::<i32>().unwrap(), 7);
    assert_eq!(first_swap[1].to_rust::<String>().unwrap(), "left");

    let second_swap = tuple_items(&items[1]);
    assert_eq!(second_swap[0].to_rust::<String>().unwrap(), "right");
    assert!(second_swap[1].to_rust::<bool>().unwrap());
}

#[tokio::test]
async fn overloaded_exports_types_and_values() {
    let mut engine: Engine<()> = Engine::with_prelude(()).unwrap();

    EmbedRecord::inject_rex(&mut engine).unwrap();

    inject_globals(&mut engine, |module| {
        module.export("over1", |_state: &(), x: i32| Ok(x + 1))?;
        module.export("over1", |_state: &(), x: bool| {
            Ok(if x {
                "bool:true".to_string()
            } else {
                "bool:false".to_string()
            })
        })?;
        module.export("over1", |_state: &(), rec: EmbedRecord| Ok(rec.n > 10))?;
        module.export("over3", |_state: &(), a: i32, b: i32, c: i32| Ok(a + b + c))?;
        module.export("over3", |_state: &(), a: String, b: String, c: String| {
            Ok(a.len() < b.len() + c.len())
        })?;
        module.export(
            "over3",
            |_state: &(), a: EmbedRecord, b: EmbedRecord, c: EmbedRecord| {
                Ok(format!("records:{}:{}:{}", a.n, b.n, c.n))
            },
        )?;
        Ok(())
    })
    .unwrap();

    let expr = r#"
    (
        over1 41,
        over1 true,
        over1 (EmbedRecord { n = 9 }),
        over3 1 2 3,
        over3 "a" "bb" "ccc",
        over3 (EmbedRecord { n = 1 }) (EmbedRecord { n = 2 }) (EmbedRecord { n = 3 })
    )
    "#;

    let (_, inferred) = engine.infer_snippet(expr).unwrap();
    assert_overload_tuple_type_shape(&inferred);

    let value = engine.into_evaluator().eval(parse(expr).as_ref()).await;
    assert!(value.is_ok(), "evaluation failed: {value:?}");
    let (value, ty) = value.unwrap();
    assert_overload_tuple_type_shape(&ty);

    let items = tuple_items(&value);
    assert_eq!(items.len(), 6);
    assert_eq!(items[0].to_rust::<i32>().unwrap(), 42);
    assert_eq!(items[1].to_rust::<String>().unwrap(), "bool:true");
    assert!(!items[2].to_rust::<bool>().unwrap());
    assert_eq!(items[3].to_rust::<i32>().unwrap(), 6);
    assert!(items[4].to_rust::<bool>().unwrap());
    assert_eq!(items[5].to_rust::<String>().unwrap(), "records:1:2:3");
}

#[tokio::test]
async fn overloaded_async_exports_types_and_values() {
    let mut engine: Engine<()> = Engine::with_prelude(()).unwrap();
    EmbedRecord::inject_rex(&mut engine).unwrap();

    inject_globals(&mut engine, |module| {
        module.export_async("a1", |_state: &(), x: i32| async move { Ok(x + 1) })?;
        module.export_async("a1", |_state: &(), x: bool| async move {
            Ok(if x {
                "bool:true".to_string()
            } else {
                "bool:false".to_string()
            })
        })?;
        module.export_async("a1", |_state: &(), rec: EmbedRecord| async move {
            Ok(rec.n > 10)
        })?;
        module.export_async("a3", |_state: &(), a: i32, b: i32, c: i32| async move {
            Ok(a + b + c)
        })?;
        module.export_async(
            "a3",
            |_state: &(), a: String, b: String, c: String| async move {
                Ok(a.len() < b.len() + c.len())
            },
        )?;
        module.export_async(
            "a3",
            |_state: &(), a: EmbedRecord, b: EmbedRecord, c: EmbedRecord| async move {
                Ok(format!("records:{}:{}:{}", a.n, b.n, c.n))
            },
        )?;
        Ok(())
    })
    .unwrap();

    let expr = r#"
    (
        a1 41,
        a1 true,
        a1 (EmbedRecord { n = 9 }),
        a3 1 2 3,
        a3 "a" "bb" "ccc",
        a3 (EmbedRecord { n = 1 }) (EmbedRecord { n = 2 }) (EmbedRecord { n = 3 })
    )
    "#;

    let (_, inferred) = engine.infer_snippet(expr).unwrap();
    assert_overload_tuple_type_shape(&inferred);

    let value = engine.into_evaluator().eval(parse(expr).as_ref()).await;
    assert!(value.is_ok(), "evaluation failed: {value:?}");
    let (value, ty) = value.unwrap();
    assert_overload_tuple_type_shape(&ty);

    let items = tuple_items(&value);
    assert_eq!(items.len(), 6);
    assert_eq!(items[0].to_rust::<i32>().unwrap(), 42);
    assert_eq!(items[1].to_rust::<String>().unwrap(), "bool:true");
    assert!(!items[2].to_rust::<bool>().unwrap());
    assert_eq!(items[3].to_rust::<i32>().unwrap(), 6);
    assert!(items[4].to_rust::<bool>().unwrap());
    assert_eq!(items[5].to_rust::<String>().unwrap(), "records:1:2:3");
}
