use gonidium::diag::DiagCode;
use gonidium::ir::DType;
use gonidium::parse::ast::Expr;
use gonidium::{lower, parse_dsl};

#[test]
fn parse_dsl_basic() {
    let text = r#"
    |a: f32, b: f64|
    q1 = a+1
    q2 = b+1
    q1*q2
    "#;
    let func = parse_dsl(text).expect("parse should succeed");
    assert_eq!(
        func.params,
        vec![("a".to_string(), DType::F32), ("b".to_string(), DType::F64)]
    );
    match func.body.node {
        Expr::Let(_, _, _) => {}
        _ => panic!("expected let-chain body"),
    }
}

#[test]
fn parse_dsl_empty_params() {
    let text = r#"
    ||
    x * 2
    "#;
    let func = parse_dsl(text).expect("parse should succeed");
    assert!(func.params.is_empty());
}

#[test]
fn parse_dsl_unary_and_pow() {
    let text = r#"
    |x: f64|
    -x ** 2
    "#;
    let func = parse_dsl(text).expect("parse should succeed");
    match func.body.node {
        Expr::Unary(_, _) => {}
        _ => panic!("expected unary expression"),
    }
}

#[test]
fn parse_dsl_missing_result_raises() {
    let text = r#"
    |a: f32|
    q = a+1
    "#;
    let err = parse_dsl(text).expect_err("parse should fail");
    assert_eq!(err.code, DiagCode::ParseSyntax);
}

#[test]
fn parse_dsl_int_imaginary_raises() {
    let text = r#"
    |z: f64|
    z = 2j + 1.0
    z
    "#;
    let err = parse_dsl(text).expect_err("parse should fail");
    assert_eq!(err.code, DiagCode::ParseIntImaginary);
    assert!(err.message.contains("`2j`"));
}

#[test]
fn parse_dsl_annotated_literal() {
    let text = r#"
    |x: f32|
    -8@i8 + x
    "#;
    let func = parse_dsl(text).expect("parse should succeed");
    match func.body.node {
        Expr::Binary(_, _, _) => {}
        _ => panic!("expected binary expression"),
    }
}

#[test]
fn parse_dsl_postfix_cast_on_expr() {
    let text = r#"
    |a: i64, b: i64|
    -a + (3//2)@f32 + b
    "#;
    let func = parse_dsl(text).expect("parse should succeed");
    // Walk the body to find the casted `(3//2)@f8`.
    fn find_cast(expr: &Expr) -> bool {
        match expr {
            Expr::Cast(_, _) => true,
            Expr::Binary(_, l, r) => find_cast(&l.node) || find_cast(&r.node),
            _ => false,
        }
    }
    assert!(
        find_cast(&func.body.node),
        "expected a Cast node produced by `(3//2)@f8`"
    );
}

#[test]
fn parse_dsl_chained_postfix_casts() {
    let text = r#"
    ||
    1@i8@i16
    "#;
    let func = parse_dsl(text).expect("parse should succeed");
    let Expr::Cast(outer_dt, _inner) = func.body.node else {
        panic!("expected outer Cast");
    };
    assert_eq!(outer_dt, DType::I16);
    // The inner node is a literal annotated with i8; we only assert that parsing succeeds
    // and produces an outer cast to i16 here.
}

#[test]
fn lower_warns_on_u64_plus_sint() {
    let text = r#"
    |a: u64, b: i64|
    a + b
    "#;
    let func = parse_dsl(text).expect("parse should succeed");
    let r = lower(&func).expect("lower should succeed");
    assert!(
        r.diags
            .iter()
            .any(|d| d.code == DiagCode::LowerUnsafeUIntToSInt),
        "expected LowerUnsafeUIntToSInt diagnostic"
    );
}
