use std::rc::Rc;

use crate::environment::Environment;

use super::{Node, NodeEnum, Precedence, symbol::Symbol};

#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum FunctionType {
    Native(
        &'static str,
        fn(&Vec<Rc<NodeEnum>>) -> Result<Rc<NodeEnum>, String>,
    ),
    UserFunction(Rc<NodeEnum>),
}

#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct Function {
    function: FunctionType,
    // TODO: Finish reasoning about whether or not this is
    // the right way to implement functions
    arguments: Vec<Symbol>,
}

impl Node for Function {
    fn evaluate(&self, _: &mut Environment) -> Result<Rc<NodeEnum>, String> {
        Ok(Rc::new(self.clone().into()))
    }

    fn as_string(&self, env: Option<&Environment>) -> String {
        let args = self
            .arguments
            .iter()
            .map(|x| Node::as_string(x, None))
            .reduce(|acc, e| format!("{acc}, {e}"))
            .unwrap_or("()".to_owned());

        match &self.function {
            FunctionType::Native(name, _) => name.to_owned().to_owned(),
            FunctionType::UserFunction(body) => format!("({args} -> {})", body.as_string(env)),
        }
    }

    fn precedence(&self) -> Precedence {
        Precedence::Call
    }
}

impl Function {
    // TODO: Implement new.
    // pub fn new(t: FunctionType, ) -> Self {
    //
    // }

    pub fn get_body(&self) -> &FunctionType {
        &self.function
    }

    pub fn get_arguments(&self) -> &Vec<Symbol> {
        &self.arguments
    }
}
