//! Built-in Cypher functions.

pub mod aggregate;
mod list;
mod math;
mod scalar;
mod string;
mod temporal;

// Re-export the temporal cache clearing function for use by the executor
pub use temporal::clear_temporal_cache;

use std::collections::HashMap;

use crate::error::{ExecutionError, ExecutionResult};
use crate::result::CypherValue;

/// A function that can be called from Cypher.
pub type CypherFunction = fn(Vec<CypherValue>) -> ExecutionResult<CypherValue>;

/// Registry of built-in functions.
#[derive(Debug)]
pub struct FunctionRegistry {
    functions: HashMap<String, CypherFunction>,
}

impl FunctionRegistry {
    /// Create a new registry with all built-in functions.
    pub fn new() -> Self {
        let mut functions = HashMap::new();

        // Scalar functions
        functions.insert("id".to_string(), scalar::id as CypherFunction);
        functions.insert("type".to_string(), scalar::type_fn as CypherFunction);
        functions.insert("labels".to_string(), scalar::labels as CypherFunction);
        functions.insert("keys".to_string(), scalar::keys as CypherFunction);
        functions.insert("properties".to_string(), scalar::properties as CypherFunction);
        functions.insert("coalesce".to_string(), scalar::coalesce as CypherFunction);
        functions.insert("timestamp".to_string(), scalar::timestamp as CypherFunction);
        functions.insert("tointeger".to_string(), scalar::to_integer as CypherFunction);
        functions.insert("tofloat".to_string(), scalar::to_float as CypherFunction);
        functions.insert("toboolean".to_string(), scalar::to_boolean as CypherFunction);
        functions.insert("startnode".to_string(), scalar::start_node as CypherFunction);
        functions.insert("endnode".to_string(), scalar::end_node as CypherFunction);
        functions.insert("between".to_string(), scalar::between as CypherFunction);

        // String functions
        functions.insert("tostring".to_string(), string::to_string as CypherFunction);
        functions.insert("toupper".to_string(), string::to_upper as CypherFunction);
        functions.insert("tolower".to_string(), string::to_lower as CypherFunction);
        functions.insert("trim".to_string(), string::trim as CypherFunction);
        functions.insert("ltrim".to_string(), string::ltrim as CypherFunction);
        functions.insert("rtrim".to_string(), string::rtrim as CypherFunction);
        functions.insert("replace".to_string(), string::replace as CypherFunction);
        functions.insert("substring".to_string(), string::substring as CypherFunction);
        functions.insert("left".to_string(), string::left as CypherFunction);
        functions.insert("right".to_string(), string::right as CypherFunction);
        functions.insert("split".to_string(), string::split as CypherFunction);
        functions.insert("startswith".to_string(), string::starts_with as CypherFunction);
        functions.insert("endswith".to_string(), string::ends_with as CypherFunction);
        functions.insert("contains".to_string(), string::contains_string as CypherFunction);
        // reverse() works for both strings and lists
        functions.insert("reverse".to_string(), |args: Vec<CypherValue>| {
            if args.is_empty() {
                return Err(ExecutionError::InvalidArgument(
                    "reverse() requires exactly 1 argument".to_string(),
                ));
            }
            match &args[0] {
                CypherValue::String(_) => string::reverse_string(args),
                CypherValue::List(_) | CypherValue::Null => list::reverse_list(args),
                other => Err(ExecutionError::Type(format!(
                    "reverse() requires a string or list, got {}",
                    other.type_name()
                ))),
            }
        });

        // Math functions
        functions.insert("abs".to_string(), math::abs as CypherFunction);
        functions.insert("ceil".to_string(), math::ceil as CypherFunction);
        functions.insert("floor".to_string(), math::floor as CypherFunction);
        functions.insert("round".to_string(), math::round as CypherFunction);
        functions.insert("sign".to_string(), math::sign as CypherFunction);
        functions.insert("sqrt".to_string(), math::sqrt as CypherFunction);
        functions.insert("log".to_string(), math::log as CypherFunction);
        functions.insert("log10".to_string(), math::log10 as CypherFunction);
        functions.insert("exp".to_string(), math::exp as CypherFunction);
        functions.insert("sin".to_string(), math::sin as CypherFunction);
        functions.insert("cos".to_string(), math::cos as CypherFunction);
        functions.insert("tan".to_string(), math::tan as CypherFunction);
        functions.insert("asin".to_string(), math::asin as CypherFunction);
        functions.insert("acos".to_string(), math::acos as CypherFunction);
        functions.insert("atan".to_string(), math::atan as CypherFunction);
        functions.insert("atan2".to_string(), math::atan2 as CypherFunction);
        functions.insert("pi".to_string(), math::pi as CypherFunction);
        functions.insert("e".to_string(), math::e as CypherFunction);
        functions.insert("rand".to_string(), math::rand as CypherFunction);

        // List functions
        functions.insert("size".to_string(), list::size as CypherFunction);
        functions.insert("length".to_string(), list::length as CypherFunction);
        functions.insert("head".to_string(), list::head as CypherFunction);
        functions.insert("tail".to_string(), list::tail as CypherFunction);
        functions.insert("last".to_string(), list::last as CypherFunction);
        functions.insert("range".to_string(), list::range as CypherFunction);
        functions.insert("nodes".to_string(), list::nodes as CypherFunction);
        functions.insert("relationships".to_string(), list::relationships as CypherFunction);
        functions.insert("extract".to_string(), list::extract as CypherFunction);

        // Temporal functions
        functions.insert("date".to_string(), temporal::date as CypherFunction);
        functions.insert("time".to_string(), temporal::time as CypherFunction);
        functions.insert("localtime".to_string(), temporal::localtime as CypherFunction);
        functions.insert("datetime".to_string(), temporal::datetime as CypherFunction);
        functions.insert("localdatetime".to_string(), temporal::localdatetime as CypherFunction);
        functions.insert("duration".to_string(), temporal::duration as CypherFunction);
        functions.insert("truncate".to_string(), temporal::truncate as CypherFunction);
        functions.insert("inseconds".to_string(), temporal::inseconds as CypherFunction);
        functions.insert("inmonths".to_string(), temporal::inmonths as CypherFunction);
        functions.insert("indays".to_string(), temporal::indays as CypherFunction);

        // Register namespaced temporal methods (e.g., date.truncate, datetime.truncate)
        // Each has specific type-preserving behavior
        functions.insert("date.truncate".to_string(), temporal::truncate_for_date as CypherFunction);
        functions.insert("datetime.truncate".to_string(), temporal::truncate_for_datetime as CypherFunction);
        functions.insert("localdatetime.truncate".to_string(), temporal::truncate_for_localdatetime as CypherFunction);
        functions.insert("time.truncate".to_string(), temporal::truncate_for_time as CypherFunction);
        functions.insert("localtime.truncate".to_string(), temporal::truncate_for_localtime as CypherFunction);

        functions.insert("duration.inseconds".to_string(), temporal::inseconds as CypherFunction);
        functions.insert("duration.inmonths".to_string(), temporal::inmonths as CypherFunction);
        functions.insert("duration.indays".to_string(), temporal::indays as CypherFunction);
        functions.insert("duration.between".to_string(), temporal::duration_between as CypherFunction);

        // Register temporal transaction/statement/realtime functions
        functions.insert("date.transaction".to_string(), temporal::date_transaction as CypherFunction);
        functions.insert("date.statement".to_string(), temporal::date_statement as CypherFunction);
        functions.insert("date.realtime".to_string(), temporal::date_realtime as CypherFunction);

        functions.insert("time.transaction".to_string(), temporal::time_transaction as CypherFunction);
        functions.insert("time.statement".to_string(), temporal::time_statement as CypherFunction);
        functions.insert("time.realtime".to_string(), temporal::time_realtime as CypherFunction);

        functions.insert("localtime.transaction".to_string(), temporal::localtime_transaction as CypherFunction);
        functions.insert("localtime.statement".to_string(), temporal::localtime_statement as CypherFunction);
        functions.insert("localtime.realtime".to_string(), temporal::localtime_realtime as CypherFunction);

        functions.insert("datetime.transaction".to_string(), temporal::datetime_transaction as CypherFunction);
        functions.insert("datetime.statement".to_string(), temporal::datetime_statement as CypherFunction);
        functions.insert("datetime.realtime".to_string(), temporal::datetime_realtime as CypherFunction);
        functions.insert("datetime.fromepoch".to_string(), temporal::datetime_fromepoch as CypherFunction);
        functions.insert("datetime.fromepochmillis".to_string(), temporal::datetime_fromepochmillis as CypherFunction);

        functions.insert("localdatetime.transaction".to_string(), temporal::localdatetime_transaction as CypherFunction);
        functions.insert("localdatetime.statement".to_string(), temporal::localdatetime_statement as CypherFunction);
        functions.insert("localdatetime.realtime".to_string(), temporal::localdatetime_realtime as CypherFunction);

        Self { functions }
    }

    /// Call a function by name.
    pub fn call(
        &self,
        name: &str,
        args: Vec<CypherValue>,
        _distinct: bool,
    ) -> ExecutionResult<CypherValue> {
        let lowercase_name = name.to_lowercase();
        if let Some(func) = self.functions.get(&lowercase_name) {
            func(args)
        } else {
            Err(ExecutionError::UnknownFunction(name.to_string()))
        }
    }

    /// Check if a function exists.
    pub fn exists(&self, name: &str) -> bool {
        self.functions.contains_key(name)
    }

    /// Check if a function is an aggregate function.
    pub fn is_aggregate(&self, name: &str) -> bool {
        matches!(
            name.to_lowercase().as_str(),
            "count" | "sum" | "avg" | "min" | "max" | "collect" | "stdev" | "stdevp" | "percentilecont" | "percentiledisc"
        )
    }
}

impl Default for FunctionRegistry {
    fn default() -> Self {
        Self::new()
    }
}

pub use aggregate::AggregateAccumulator;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_function_registry() {
        let registry = FunctionRegistry::new();

        assert!(registry.exists("abs"));
        assert!(registry.exists("toupper"));
        assert!(!registry.exists("nonexistent"));

        let result = registry.call("abs", vec![CypherValue::Integer(-5)], false);
        assert_eq!(result.unwrap(), CypherValue::Integer(5));
    }

    #[test]
    fn test_is_aggregate() {
        let registry = FunctionRegistry::new();

        assert!(registry.is_aggregate("count"));
        assert!(registry.is_aggregate("sum"));
        assert!(registry.is_aggregate("collect"));
        assert!(!registry.is_aggregate("abs"));
        assert!(!registry.is_aggregate("toupper"));
    }
}
