use std::{fmt::Display, rc::Rc};

use add::Add;
use assign::Assign;
use call::Call;
use constant::Constant;
use divide::Divide;
use empty::Empty;
use enum_dispatch::enum_dispatch;
use function::Function;
use if_else::{Bool, IfElse};
use multiply::Multiply;
use subtract::Subtract;
use symbol::Symbol;

use crate::environment::Environment;

pub mod add;
pub mod constant;
pub mod divide;
pub mod multiply;
pub mod subtract;
pub mod symbol;
mod node_ref;
mod assign;
mod empty;
mod function;
mod call;
mod if_else;

#[enum_dispatch]
#[enum_dispatch(Debug, Clone, PartialEq, PartialOrd, ToString)]
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum NodeEnum {
    Constant,

    // Operators
    Add,
    Subtract,
    Multiply,
    Divide,

    Symbol,
    // NodeRef, // DEPRECATED, use Symbol
    Assign,
    Empty,
    Function,
    Call,

    Bool,
    IfElse,

    // Logical operators
    // In,
    // Where,
    // Not,
    // Or,
    // And
}

#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub enum Precedence {
    Empty,
    Assign,
    Compare,
    Equality,
    Term,
    Factor,
    Unary,
    Call,
    Primary,
}

#[enum_dispatch(NodeEnum)]
pub trait Node {
    fn evaluate(&self, _: &mut Environment) -> Result<Rc<NodeEnum>, String>;
    fn as_string(&self, _: Option<&Environment>) -> String;
    fn precedence(&self) -> Precedence;
}

impl Display for NodeEnum {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", Node::as_string(self, None))
    }
}
