//! Tests for numeric arithmetic reverse evaluation
use crate::eval::environment::Environment;
use crate::reverse_eval::domain::Constraint;
use crate::reverse_eval::reverse::reverse_eval;
use crate::value::Value;
use hamelin_lib::tree::builder::*;
use hamelin_lib::tree::options::ExpressionTypeCheckOptions;
use hamelin_lib::tree::typed_ast::environment::TypeEnvironment;
use hamelin_lib::type_check_expression;
use hamelin_lib::types::{DOUBLE, INT};
use pretty_assertions::assert_eq;
use rstest::rstest;

use std::sync::Arc;

#[rstest]
#[case::addition_equals(
    add(field_ref("x"), 5),
    Constraint::Equals(Value::Int(15)),
    Constraint::Equals(Value::Int(10))
)]
#[case::addition_range(
    add(field_ref("x"), 10),
    Constraint::Range { min: Some(Value::Int(20)), max: Some(Value::Int(30)) },
    Constraint::Range { min: Some(Value::Int(10)), max: Some(Value::Int(20)) }
)]
#[case::subtraction_equals(
    subtract(field_ref("x"), 3),
    Constraint::Equals(Value::Int(7)),
    Constraint::Equals(Value::Int(10))
)]
#[case::addition_double(
    add(field_ref("z"), 2.5),
    Constraint::Equals(Value::Double(10.0)),
    Constraint::Equals(Value::Double(7.5))
)]
#[case::parenthesized(
    add(field_ref("x"), 10),
    Constraint::Equals(Value::Int(25)),
    Constraint::Equals(Value::Int(15))
)]
fn test_reverse_eval_arithmetic(
    #[case] expr_builder: impl ExpressionBuilder,
    #[case] output_constraint: Constraint,
    #[case] expected_constraint: Constraint,
) {
    let env = Environment::new();

    // Setup translation environment with bindings for x, y, z
    let mut trans_env = TypeEnvironment::default();
    trans_env.bind_str("x", INT);
    trans_env.bind_str("y", INT);
    trans_env.bind_str("z", DOUBLE);
    let bindings = Arc::new(trans_env);

    let expr = type_check_expression(
        expr_builder.build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(bindings)
            .build(),
    )
    .output;
    let result = reverse_eval(&expr, output_constraint, &env).expect("Reverse eval failed");

    assert_eq!(result, Some(expected_constraint));
}
