use once_cell::sync::OnceCell;

use crate::sql::expression::apply::{BinaryOperatorApply, UnaryOperatorApply};
use crate::sql::expression::operator::Operator;
use crate::sql::expression::SQLExpression;

enum PrecedenceEntry {
    BinaryPrecedenceEntry(Vec<Operator>),
    UnaryPrecedenceEntry(Vec<Operator>),
}

static PRECEDENCE: OnceCell<Vec<PrecedenceEntry>> = OnceCell::new();
pub fn precedence(exp: &SQLExpression) -> Option<usize> {
    PRECEDENCE
        .get_or_init(|| {
            vec![
                PrecedenceEntry::UnaryPrecedenceEntry(vec![Operator::Plus, Operator::Minus]),
                PrecedenceEntry::BinaryPrecedenceEntry(vec![
                    Operator::Asterisk,
                    Operator::Slash,
                    Operator::Percent,
                ]),
                PrecedenceEntry::BinaryPrecedenceEntry(vec![Operator::Plus, Operator::Minus]),
                PrecedenceEntry::BinaryPrecedenceEntry(vec![
                    Operator::Lt,
                    Operator::Neq,
                    Operator::Lt,
                    Operator::Lte,
                    Operator::Gt,
                    Operator::Gte,
                ]),
                PrecedenceEntry::UnaryPrecedenceEntry(vec![Operator::Not]),
                PrecedenceEntry::BinaryPrecedenceEntry(vec![Operator::And]),
                PrecedenceEntry::BinaryPrecedenceEntry(vec![Operator::Or]),
            ]
        })
        .iter()
        .enumerate()
        .filter(|(_, entry)| match (exp, entry) {
            (
                SQLExpression::BinaryOperatorApply(BinaryOperatorApply {
                    operator: this_operator,
                    ..
                }),
                PrecedenceEntry::BinaryPrecedenceEntry(operators),
            ) => operators.contains(this_operator),
            (
                SQLExpression::UnaryOperatorApply(UnaryOperatorApply {
                    operator: this_operator,
                    ..
                }),
                PrecedenceEntry::UnaryPrecedenceEntry(operators),
            ) => operators.contains(this_operator),
            _ => false,
        })
        .map(|(i, _)| i)
        .next()
}
