use crate::eval::environment::Environment;
use crate::eval::evaluator::eval;
use crate::value::Value;
use hamelin_lib::tree::ast::identifier::Identifier;
use hamelin_lib::tree::ast::identifier::SimpleIdentifier;
use hamelin_lib::tree::builder::ExpressionBuilder;
use hamelin_lib::tree::options::ExpressionTypeCheckOptions;
use hamelin_lib::tree::typed_ast::environment::TypeEnvironment;
use hamelin_lib::type_check_expression;
use hamelin_lib::types::Type;
use std::sync::Arc;

/// Test context for evaluation tests
#[derive(Default)]
pub struct TestContext {
    pub env: Environment,
    pub translation_env: TypeEnvironment,
}

impl TestContext {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn set(&mut self, id: impl Into<String>, value: Value, typ: Type) {
        let si = SimpleIdentifier::new(&id.into());
        self.env.bind(si.clone(), value);
        let identifier: Identifier = si.into();
        self.translation_env.bind(identifier, typ);
    }

    /// Helper to evaluate an expression
    pub fn eval_expr(&self, expr: &impl ExpressionBuilder) -> Value {
        let typed = type_check_expression(
            expr.build(),
            ExpressionTypeCheckOptions::builder()
                .bindings(Arc::new(self.translation_env.clone()))
                .build(),
        )
        .output;
        eval(&typed, &self.env).unwrap()
    }

    /// Helper to try to evaluate an expression (returns Result for error testing)
    pub fn try_eval_expr(&self, expr: &impl ExpressionBuilder) -> Result<Value, String> {
        let typed = type_check_expression(
            expr.build(),
            ExpressionTypeCheckOptions::builder()
                .bindings(Arc::new(self.translation_env.clone()))
                .build(),
        )
        .output;
        eval(&typed, &self.env).map_err(|e| e.to_string())
    }
}

/// Helper function to create environment
pub fn setup_environment() -> Environment {
    Environment::new()
}

/// Helper to create a test context
pub fn test_context() -> TestContext {
    TestContext::new()
}
