use gonidium::diag::{DiagCode, DiagCollector};
use gonidium::{
    ComposeError, RenameError, Value, compose, compose_with_diags, interp_eval, lower, parse_dsl,
    rename_params,
};

#[test]
fn rename_params_updates_signature_and_body_refs() {
    let func = parse_dsl("|x0: i64, x1: i64|\nx0 + x1").expect("parse");
    let renamed = rename_params(
        &func,
        [
            ("x0".to_string(), "x2".to_string()),
            ("x1".to_string(), "x3".to_string()),
        ],
    )
    .expect("rename");

    let param_names: Vec<String> = renamed
        .params
        .iter()
        .map(|(name, _)| name.clone())
        .collect();
    assert_eq!(param_names, vec!["x2".to_string(), "x3".to_string()]);

    let dag = lower(&renamed).expect("lower").dag;
    let out = interp_eval(
        &dag,
        &[
            Value::SInt {
                bits: 64,
                value: 10,
            },
            Value::SInt { bits: 64, value: 7 },
        ],
    )
    .expect("interp");
    assert_eq!(
        out,
        vec![Value::SInt {
            bits: 64,
            value: 17
        }]
    );
}

#[test]
fn rename_params_rejects_name_conflict_with_existing_let() {
    let func = parse_dsl("|x0: i64|\ny = x0\ny + 1").expect("parse");
    let err = rename_params(&func, [("x0".to_string(), "y".to_string())]).expect_err("conflict");
    assert!(matches!(err, RenameError::NameConflict { name } if name == "y"));
}

#[test]
fn compose_replaces_parameter_with_inner_expr() {
    let outer = parse_dsl("|a: i64, b: i64|\na + b").expect("parse outer");
    let inner = parse_dsl("|c: i64|\nc + 1").expect("parse inner");
    let composed = compose(&outer, "a", &inner).expect("compose");

    let param_names: Vec<String> = composed
        .params
        .iter()
        .map(|(name, _)| name.clone())
        .collect();
    assert_eq!(param_names, vec!["b".to_string(), "c".to_string()]);

    let dag = lower(&composed).expect("lower").dag;
    let out = interp_eval(
        &dag,
        &[
            Value::SInt { bits: 64, value: 2 },
            Value::SInt { bits: 64, value: 3 },
        ],
    )
    .expect("interp");
    assert_eq!(out, vec![Value::SInt { bits: 64, value: 6 }]);
}

#[test]
fn compose_emits_symbol_reuse_warning_for_same_named_params() {
    let outer = parse_dsl("|a: i64, b: i64|\na + b").expect("parse outer");
    let inner = parse_dsl("|a: i64, c: i64|\na + c").expect("parse inner");
    let mut collector = DiagCollector::default();

    let _ = compose_with_diags(&outer, "b", &inner, &mut collector).expect("compose");
    let has_reuse_warning = collector
        .diags
        .iter()
        .any(|diag| diag.code == DiagCode::ComposeSymbolReuse);
    assert!(has_reuse_warning, "expected ComposeSymbolReuse warning");
}

#[test]
fn compose_rejects_conflicting_constraints_for_same_symbol() {
    let outer = parse_dsl("|a: i64, b: i64|\na + b").expect("parse outer");
    let inner = parse_dsl("|a: i32, c: i32|\na + c").expect("parse inner");
    let mut collector = DiagCollector::default();

    let err = compose_with_diags(&outer, "b", &inner, &mut collector).expect_err("conflict");
    assert!(matches!(
        err,
        ComposeError::ConstraintConflict { symbol, .. } if symbol == "a"
    ));
}

#[test]
fn compose_avoids_capturing_inner_param_by_outer_let() {
    let outer = parse_dsl("|a: i64, b: i64|\na = 100\na + b").expect("parse outer");
    let inner = parse_dsl("|a: i64|\na + 1").expect("parse inner");
    let composed = compose(&outer, "b", &inner).expect("compose");

    let dag = lower(&composed).expect("lower").dag;
    let out = interp_eval(&dag, &[Value::SInt { bits: 64, value: 5 }]).expect("interp");
    assert_eq!(
        out,
        vec![Value::SInt {
            bits: 64,
            value: 106
        }]
    );
}
