//! Variadic list operations: min, max, gcd, lcm.
//!
//! All accept 1 or more arguments. `gcd` and `lcm` require integer arguments.

use super::{FunctionError, FunctionRegistry};

fn min_fn(args: &[f64]) -> Result<f64, FunctionError> {
    // We already know args.len() >= 1 from the variadic guard.
    let mut m = args[0];
    for &v in &args[1..] {
        if v < m {
            m = v;
        }
    }
    Ok(m)
}

fn max_fn(args: &[f64]) -> Result<f64, FunctionError> {
    let mut m = args[0];
    for &v in &args[1..] {
        if v > m {
            m = v;
        }
    }
    Ok(m)
}

/// GCD of two non-negative integers using Euclidean algorithm.
fn gcd_pair(mut a: i64, mut b: i64) -> i64 {
    a = a.abs();
    b = b.abs();
    while b != 0 {
        let t = b;
        b = a % b;
        a = t;
    }
    a
}

/// LCM of two non-negative integers.
fn lcm_pair(a: i64, b: i64) -> i64 {
    if a == 0 || b == 0 {
        return 0;
    }
    (a.abs() / gcd_pair(a, b)) * b.abs()
}

fn gcd_fn(args: &[f64]) -> Result<f64, FunctionError> {
    for &v in args {
        if v.fract() != 0.0 {
            return Err(FunctionError::new("gcd requires integer arguments"));
        }
    }
    let mut result = args[0] as i64;
    for &v in &args[1..] {
        result = gcd_pair(result, v as i64);
    }
    Ok(result as f64)
}

fn lcm_fn(args: &[f64]) -> Result<f64, FunctionError> {
    for &v in args {
        if v.fract() != 0.0 {
            return Err(FunctionError::new("lcm requires integer arguments"));
        }
    }
    let mut result = args[0] as i64;
    for &v in &args[1..] {
        result = lcm_pair(result, v as i64);
    }
    Ok(result as f64)
}

/// Register list-operation functions.
pub fn register(reg: &mut FunctionRegistry) {
    reg.register_variadic("min", 1, min_fn);
    reg.register_variadic("max", 1, max_fn);
    reg.register_variadic("gcd", 1, gcd_fn);
    reg.register_variadic("lcm", 1, lcm_fn);
}

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

    fn reg() -> FunctionRegistry {
        FunctionRegistry::new()
    }

    // --- min ---

    #[test]
    fn min_single() {
        let v = reg().call("min", &[42.0]).unwrap();
        assert!((v - 42.0).abs() < 1e-10);
    }

    #[test]
    fn min_two() {
        let v = reg().call("min", &[3.0, 7.0]).unwrap();
        assert!((v - 3.0).abs() < 1e-10);
    }

    #[test]
    fn min_many() {
        let v = reg().call("min", &[10.0, 3.0, 7.0, 1.0, 5.0]).unwrap();
        assert!((v - 1.0).abs() < 1e-10);
    }

    #[test]
    fn min_negative() {
        let v = reg().call("min", &[-5.0, -2.0, -10.0]).unwrap();
        assert!((v - (-10.0)).abs() < 1e-10);
    }

    #[test]
    fn min_no_args_error() {
        let err = reg().call("min", &[]).unwrap_err();
        assert!(err.message.contains("at least 1"));
    }

    // --- max ---

    #[test]
    fn max_single() {
        let v = reg().call("max", &[42.0]).unwrap();
        assert!((v - 42.0).abs() < 1e-10);
    }

    #[test]
    fn max_two() {
        let v = reg().call("max", &[3.0, 7.0]).unwrap();
        assert!((v - 7.0).abs() < 1e-10);
    }

    #[test]
    fn max_many() {
        let v = reg().call("max", &[10.0, 3.0, 7.0, 1.0, 50.0]).unwrap();
        assert!((v - 50.0).abs() < 1e-10);
    }

    #[test]
    fn max_no_args_error() {
        let err = reg().call("max", &[]).unwrap_err();
        assert!(err.message.contains("at least 1"));
    }

    // --- gcd ---

    #[test]
    fn gcd_two_numbers() {
        let v = reg().call("gcd", &[12.0, 8.0]).unwrap();
        assert!((v - 4.0).abs() < 1e-10);
    }

    #[test]
    fn gcd_three_numbers() {
        let v = reg().call("gcd", &[12.0, 8.0, 6.0]).unwrap();
        assert!((v - 2.0).abs() < 1e-10);
    }

    #[test]
    fn gcd_coprime() {
        let v = reg().call("gcd", &[7.0, 13.0]).unwrap();
        assert!((v - 1.0).abs() < 1e-10);
    }

    #[test]
    fn gcd_with_zero() {
        let v = reg().call("gcd", &[0.0, 5.0]).unwrap();
        assert!((v - 5.0).abs() < 1e-10);
    }

    #[test]
    fn gcd_single() {
        let v = reg().call("gcd", &[42.0]).unwrap();
        assert!((v - 42.0).abs() < 1e-10);
    }

    #[test]
    fn gcd_non_integer_error() {
        let err = reg().call("gcd", &[3.5, 2.0]).unwrap_err();
        assert!(err.message.contains("integer"));
    }

    // --- lcm ---

    #[test]
    fn lcm_two_numbers() {
        let v = reg().call("lcm", &[4.0, 6.0]).unwrap();
        assert!((v - 12.0).abs() < 1e-10);
    }

    #[test]
    fn lcm_three_numbers() {
        let v = reg().call("lcm", &[4.0, 6.0, 10.0]).unwrap();
        assert!((v - 60.0).abs() < 1e-10);
    }

    #[test]
    fn lcm_with_zero() {
        let v = reg().call("lcm", &[0.0, 5.0]).unwrap();
        assert!((v - 0.0).abs() < 1e-10);
    }

    #[test]
    fn lcm_single() {
        let v = reg().call("lcm", &[42.0]).unwrap();
        assert!((v - 42.0).abs() < 1e-10);
    }

    #[test]
    fn lcm_coprime() {
        let v = reg().call("lcm", &[7.0, 13.0]).unwrap();
        assert!((v - 91.0).abs() < 1e-10);
    }

    #[test]
    fn lcm_non_integer_error() {
        let err = reg().call("lcm", &[3.5, 2.0]).unwrap_err();
        assert!(err.message.contains("integer"));
    }
}
