use std::any::TypeId;
use std::collections::HashMap;

use hamelin_lib::func::def::ParameterBinding;

use crate::defs::register;
use crate::reverse_eval::domain::Constraint;
use crate::value::Value;

/// Type alias for eval functions.
///
/// Eval functions take bound parameter values and compute the result.
pub type EvalFn = Box<dyn Fn(ParameterBinding<Value>) -> anyhow::Result<Value> + Send + Sync>;

/// Type alias for reverse eval functions.
///
/// Reverse eval functions take:
/// - `constraint`: The constraint on the output value
/// - `bindings`: The bound parameter values (Some for constants, None for variables)
///
/// And return the constraint on the variable input.
pub type ReverseFn = Box<
    dyn Fn(&Constraint, ParameterBinding<Option<Value>>) -> anyhow::Result<Constraint>
        + Send
        + Sync,
>;

/// Registry containing eval and reverse eval implementations for functions.
///
/// Both concerns are tightly coupled (both operate on runtime values)
/// and are registered together per function.
pub struct EvalRegistry {
    eval_impls: HashMap<TypeId, EvalFn>,
    reverse_impls: HashMap<TypeId, ReverseFn>,
}

impl std::fmt::Debug for EvalRegistry {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("EvalRegistry")
            .field(
                "eval_impls",
                &format!("{} functions", self.eval_impls.len()),
            )
            .field(
                "reverse_impls",
                &format!("{} functions", self.reverse_impls.len()),
            )
            .finish()
    }
}

impl EvalRegistry {
    pub fn new() -> Self {
        Self {
            eval_impls: HashMap::new(),
            reverse_impls: HashMap::new(),
        }
    }

    /// Register eval implementation for a function type.
    pub fn register_eval<F: 'static>(
        &mut self,
        f: impl Fn(ParameterBinding<Value>) -> anyhow::Result<Value> + Send + Sync + 'static,
    ) {
        self.eval_impls.insert(TypeId::of::<F>(), Box::new(f));
    }

    /// Register reverse eval implementation for a function type.
    pub fn register_reverse<F: 'static>(
        &mut self,
        f: impl Fn(&Constraint, ParameterBinding<Option<Value>>) -> anyhow::Result<Constraint>
            + Send
            + Sync
            + 'static,
    ) {
        self.reverse_impls.insert(TypeId::of::<F>(), Box::new(f));
    }

    /// Look up and execute eval for a function.
    ///
    /// Returns None if no eval implementation is registered.
    pub fn eval(
        &self,
        type_id: TypeId,
        bindings: ParameterBinding<Value>,
    ) -> Option<anyhow::Result<Value>> {
        self.eval_impls.get(&type_id).map(|f| f(bindings))
    }

    /// Look up and execute reverse eval for a function.
    ///
    /// Returns None if no reverse eval implementation is registered.
    pub fn reverse(
        &self,
        type_id: TypeId,
        constraint: &Constraint,
        bindings: ParameterBinding<Option<Value>>,
    ) -> Option<anyhow::Result<Constraint>> {
        self.reverse_impls
            .get(&type_id)
            .map(|f| f(constraint, bindings))
    }
}

impl Default for EvalRegistry {
    fn default() -> Self {
        let mut registry = Self::new();

        register(&mut registry);
        registry
    }
}

/// Macro for ergonomic null propagation in eval functions.
/// If the value is null, returns early with Ok(Value::Null).
///
/// Usage: `let value = null_propagate!(some_value);`
#[macro_export]
macro_rules! null_propagate {
    ($value:expr) => {
        match $value {
            $crate::value::Value::Null => return Ok($crate::value::Value::Null),
            v => v,
        }
    };
}

/// Macro for null handling in reverse evaluation.
/// If the constant value is NULL:
/// - If output constraint is Equals(Value::Null), return Universal (any input could produce NULL)
/// - Otherwise, return Empty (non-NULL output is impossible with NULL input)
///
/// Usage: `let value = null_propagate_reverse!(constant_value, output_constraint);`
#[macro_export]
macro_rules! null_propagate_reverse {
    ($value:expr, $output_constraint:expr) => {
        match $value {
            $crate::value::Value::Null => {
                return Ok(match $output_constraint {
                    $crate::reverse_eval::domain::Constraint::Equals(
                        $crate::value::Value::Null,
                    ) => $crate::reverse_eval::domain::Constraint::Universal,
                    _ => $crate::reverse_eval::domain::Constraint::Empty,
                });
            }
            v => v,
        }
    };
}
