use std::rc::Rc;

use crate::environment::Environment;

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

pub type NativeFunctionType =
    fn(&Vec<Rc<NodeEnum>>, env: &mut Environment) -> Result<Rc<NodeEnum>, Error>;

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

#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct Function {
    function: FunctionType,
}

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

        // Ok(Rc::new(
        //     Self {
        //         function: match &self.function {
        //             FunctionType::Native(_, _) => self.function.clone(),
        //             FunctionType::UserFunction(node_enum, symbols) => {
        //                 env.disable_calls();
        //                 let evaluated = node_enum.evaluate(env)?;
        //                 env.enable_calls();
        //                 FunctionType::UserFunction(evaluated, symbols.clone())
        //             }
        //         },
        //     }
        //     .into(),
        // ))
    }

    fn as_string(&self, env: Option<&Environment>) -> String {
        match &self.function {
            FunctionType::Native(name, _) => format!("(Native Function `{name}`)"),
            FunctionType::UserFunction(body, args) => format!(
                "([{}] -> {})",
                args.iter()
                    .map(|x| x.as_string(env))
                    .reduce(|acc, e| format!("{acc}, {e}"))
                    .unwrap_or("[]".to_owned()),
                body.as_string(env)
            ),
        }
    }

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

impl Function {
    pub fn new(t: FunctionType) -> Rc<NodeEnum> {
        Rc::new(Self { function: t }.into())
    }

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