//! Built-in function registry for formula expressions.

use super::FormulaError;

#[derive(Debug, Clone)]
pub struct FunctionDef {
    pub min_args: usize,
    pub max_args: usize,
    pub evaluator: fn(&[f64]) -> Result<f64, FormulaError>,
}

#[must_use]
pub fn get_function(name: &str) -> Option<FunctionDef> {
    let upper = name.to_ascii_uppercase();
    match upper.as_str() {
        "SUM" => Some(FunctionDef {
            min_args: 1,
            max_args: usize::MAX,
            evaluator: |args| Ok(args.iter().sum()),
        }),
        "AVG" => Some(FunctionDef {
            min_args: 1,
            max_args: usize::MAX,
            evaluator: |args| Ok(args.iter().sum::<f64>() / args.len() as f64),
        }),
        "MIN" => Some(FunctionDef {
            min_args: 1,
            max_args: usize::MAX,
            evaluator: |args| Ok(args.iter().fold(f64::INFINITY, |a, b| a.min(*b))),
        }),
        "MAX" => Some(FunctionDef {
            min_args: 1,
            max_args: usize::MAX,
            evaluator: |args| Ok(args.iter().fold(f64::NEG_INFINITY, |a, b| a.max(*b))),
        }),
        "ABS" => Some(FunctionDef {
            min_args: 1,
            max_args: 1,
            evaluator: |args| Ok(args[0].abs()),
        }),
        "ROUND" => Some(FunctionDef {
            min_args: 1,
            max_args: 2,
            evaluator: |args| {
                let value = args[0];
                let decimals = if args.len() > 1 { args[1] } else { 0.0 };
                let multiplier = 10_f64.powi(decimals as i32);
                Ok((value * multiplier).round() / multiplier)
            },
        }),
        "IF" => Some(FunctionDef {
            min_args: 3,
            max_args: 3,
            evaluator: |args| {
                Ok(if args[0].abs() > f64::EPSILON {
                    args[1]
                } else {
                    args[2]
                })
            },
        }),
        "SQRT" => Some(FunctionDef {
            min_args: 1,
            max_args: 1,
            evaluator: |args| {
                if args[0] < 0.0 {
                    Err(FormulaError::MathError(
                        "SQRT of negative number".to_string(),
                    ))
                } else {
                    Ok(args[0].sqrt())
                }
            },
        }),
        "POW" => Some(FunctionDef {
            min_args: 2,
            max_args: 2,
            evaluator: |args| Ok(args[0].powf(args[1])),
        }),
        "FLOOR" => Some(FunctionDef {
            min_args: 1,
            max_args: 1,
            evaluator: |args| Ok(args[0].floor()),
        }),
        "CEIL" => Some(FunctionDef {
            min_args: 1,
            max_args: 1,
            evaluator: |args| Ok(args[0].ceil()),
        }),
        _ => None,
    }
}

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

    #[test]
    fn test_sum_function() {
        let func = get_function("SUM").unwrap();
        let result = (func.evaluator)(&[1.0, 2.0, 3.0, 4.0]);
        assert_eq!(result.unwrap(), 10.0);
    }

    #[test]
    fn test_avg_function() {
        let func = get_function("AVG").unwrap();
        let result = (func.evaluator)(&[2.0, 4.0, 6.0]);
        assert_eq!(result.unwrap(), 4.0);
    }

    #[test]
    fn test_min_function() {
        let func = get_function("MIN").unwrap();
        let result = (func.evaluator)(&[5.0, 2.0, 8.0, 1.0]);
        assert_eq!(result.unwrap(), 1.0);
    }

    #[test]
    fn test_max_function() {
        let func = get_function("MAX").unwrap();
        let result = (func.evaluator)(&[5.0, 2.0, 8.0, 1.0]);
        assert_eq!(result.unwrap(), 8.0);
    }

    #[test]
    fn test_abs_function() {
        let func = get_function("ABS").unwrap();
        assert_eq!((func.evaluator)(&[5.0]).unwrap(), 5.0);
        assert_eq!((func.evaluator)(&[-5.0]).unwrap(), 5.0);
    }

    #[test]
    fn test_round_function() {
        let func = get_function("ROUND").unwrap();
        assert_eq!((func.evaluator)(&[5.5678, 2.0]).unwrap(), 5.57);
        assert_eq!((func.evaluator)(&[5.5678]).unwrap(), 6.0);
    }

    #[test]
    fn test_if_function() {
        let func = get_function("IF").unwrap();
        assert_eq!((func.evaluator)(&[1.0, 10.0, 20.0]).unwrap(), 10.0);
        assert_eq!((func.evaluator)(&[0.0, 10.0, 20.0]).unwrap(), 20.0);
        assert_eq!((func.evaluator)(&[-5.0, 10.0, 20.0]).unwrap(), 10.0);
    }

    #[test]
    fn test_sqrt_function() {
        let func = get_function("SQRT").unwrap();
        assert_eq!((func.evaluator)(&[16.0]).unwrap(), 4.0);
        assert_eq!((func.evaluator)(&[2.0]).unwrap(), 2.0_f64.sqrt());
        assert!((func.evaluator)(&[-1.0]).is_err());
    }

    #[test]
    fn test_case_insensitive_lookup() {
        assert!(get_function("sum").is_some());
        assert!(get_function("Sum").is_some());
        assert!(get_function("SUM").is_some());
        assert!(get_function("unknown").is_none());
    }
}
