use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum OpPriority {
    Or,
    And,
    Comparison,
    AddOrSub,
    MulOrDiv,
    Power,
    Negation,
    Call,
}
impl OpPriority {
    pub const fn max_priority() -> Self {
        Self::Call
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum UnaryOp {
    Neg,
    Not,
}
impl fmt::Display for UnaryOp {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            UnaryOp::Neg => formatter.write_str("negation"),
            UnaryOp::Not => formatter.write_str("logical negation"),
        }
    }
}
impl UnaryOp {
    pub fn priority(self) -> OpPriority {
        match self {
            Self::Neg | Self::Not => OpPriority::Negation,
        }
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BinaryOp {
    Add,
    Sub,
    Mul,
    Div,
    Power,
    Eq,
    NotEq,
    And,
    Or,
    Gt,
    Lt,
    Ge,
    Le,
}
impl fmt::Display for BinaryOp {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(match self {
            Self::Add => "addition",
            Self::Sub => "subtraction",
            Self::Mul => "multiplication",
            Self::Div => "division",
            Self::Power => "exponentiation",
            Self::Eq => "equality comparison",
            Self::NotEq => "non-equality comparison",
            Self::And => "AND",
            Self::Or => "OR",
            Self::Gt => "greater comparison",
            Self::Lt => "lesser comparison",
            Self::Ge => "greater-or-equal comparison",
            Self::Le => "lesser-or-equal comparison",
        })
    }
}
impl BinaryOp {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Add => "+",
            Self::Sub => "-",
            Self::Mul => "*",
            Self::Div => "/",
            Self::Power => "^",
            Self::Eq => "==",
            Self::NotEq => "!=",
            Self::And => "&&",
            Self::Or => "||",
            Self::Gt => ">",
            Self::Lt => "<",
            Self::Ge => ">=",
            Self::Le => "<=",
        }
    }
    pub fn priority(self) -> OpPriority {
        match self {
            Self::Or => OpPriority::Or,
            Self::And => OpPriority::And,
            Self::Eq | Self::NotEq | Self::Gt | Self::Lt | Self::Le | Self::Ge => {
                OpPriority::Comparison
            }
            Self::Add | Self::Sub => OpPriority::AddOrSub,
            Self::Mul | Self::Div => OpPriority::MulOrDiv,
            Self::Power => OpPriority::Power,
        }
    }
    pub fn is_arithmetic(self) -> bool {
        matches!(
            self,
            Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Power
        )
    }
    pub fn is_comparison(self) -> bool {
        matches!(
            self,
            Self::Eq | Self::NotEq | Self::Gt | Self::Lt | Self::Le | Self::Ge
        )
    }
    pub fn is_order_comparison(self) -> bool {
        matches!(self, Self::Gt | Self::Lt | Self::Le | Self::Ge)
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Op {
    Unary(UnaryOp),
    Binary(BinaryOp),
}
impl fmt::Display for Op {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Unary(inner) => fmt::Display::fmt(inner, formatter),
            Self::Binary(inner) => fmt::Display::fmt(inner, formatter),
        }
    }
}
impl From<UnaryOp> for Op {
    fn from(value: UnaryOp) -> Self {
        Self::Unary(value)
    }
}
impl From<BinaryOp> for Op {
    fn from(value: BinaryOp) -> Self {
        Self::Binary(value)
    }
}