use crate::internal::as2::ast::{BinaryOperator, Expr, ExprKind, UnaryOperator};
use crate::internal::as2::lexer::operator::Operator;
use crate::internal::as2::lexer::tokens::{Token, TokenKind};
use crate::internal::as2::parser::Tokens;
use crate::internal::span::Span;
use serde::Serialize;
use std::cmp::Ordering;
use winnow::error::ParserError;
use winnow::token::any;
use winnow::{ModalResult, Parser};

#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, PartialOrd, Ord)]
enum OperatorPrecedence {
    MulDivMod,
    AddSub,
    BitShifts,
    Comparison,
    Equality,
    BitMath,
    Logic,
    Other,
    Assignment,
}

impl BinaryOperator {
    pub(crate) fn should_swap(left: BinaryOperator, right: BinaryOperator) -> bool {
        match right.precedence().cmp(&left.precedence()) {
            Ordering::Greater => true,
            Ordering::Equal if left.precedence() < OperatorPrecedence::Other => true,
            _ => false,
        }
    }
}

impl BinaryOperator {
    fn precedence(&self) -> OperatorPrecedence {
        match self {
            BinaryOperator::Multiply | BinaryOperator::Divide | BinaryOperator::Modulo => {
                OperatorPrecedence::MulDivMod
            }
            BinaryOperator::Add | BinaryOperator::Sub | BinaryOperator::StringAdd => {
                OperatorPrecedence::AddSub
            }
            BinaryOperator::BitShiftRight
            | BinaryOperator::BitShiftLeft
            | BinaryOperator::BitShiftRightUnsigned => OperatorPrecedence::BitShifts,
            BinaryOperator::LessThan
            | BinaryOperator::LessThanEqual
            | BinaryOperator::GreaterThan
            | BinaryOperator::GreaterThanEqual
            | BinaryOperator::StringLessThan
            | BinaryOperator::StringGreaterThan
            | BinaryOperator::StringGreaterThanEqual
            | BinaryOperator::StringLessThanEqual => OperatorPrecedence::Comparison,
            BinaryOperator::Equal
            | BinaryOperator::StrictEqual
            | BinaryOperator::NotEqual
            | BinaryOperator::StrictNotEqual
            | BinaryOperator::StringEqual
            | BinaryOperator::StringNotEqual => OperatorPrecedence::Equality,
            BinaryOperator::BitAnd | BinaryOperator::BitOr | BinaryOperator::BitXor => {
                OperatorPrecedence::BitMath
            }
            BinaryOperator::LogicalOr
            | BinaryOperator::LogicalAnd
            | BinaryOperator::BooleanAnd
            | BinaryOperator::BooleanOr => OperatorPrecedence::Logic,
            BinaryOperator::Assign
            | BinaryOperator::AddAssign
            | BinaryOperator::SubAssign
            | BinaryOperator::DivideAssign
            | BinaryOperator::MultiplyAssign
            | BinaryOperator::ModuloAssign
            | BinaryOperator::BitAndAssign
            | BinaryOperator::BitOrAssign
            | BinaryOperator::BitXorAssign
            | BinaryOperator::BitShiftLeftAssign
            | BinaryOperator::BitShiftRightAssign
            | BinaryOperator::BitShiftRightUnsignedAssign => OperatorPrecedence::Assignment,
            BinaryOperator::InstanceOf => OperatorPrecedence::Comparison,
        }
    }
}

pub(crate) fn expr_for_binary_operator<'i>(
    op: BinaryOperator,
    a: Box<Expr<'i>>,
    b: Box<Expr<'i>>,
) -> Expr<'i> {
    match *b {
        Expr {
            value: ExprKind::Ternary { condition, yes, no },
            ..
        } if op.precedence() != OperatorPrecedence::Assignment => Expr::new(
            Span::encompassing(condition.span, no.span),
            ExprKind::Ternary {
                condition: Box::new(expr_for_binary_operator(op, a, condition)),
                yes,
                no,
            },
        ),
        Expr {
            value: ExprKind::BinaryOperator(bop, ba, bb),
            ..
        } if BinaryOperator::should_swap(op, bop) => {
            let new_left = Box::new(expr_for_binary_operator(op, a, ba));
            Expr::new(
                Span::encompassing(new_left.span, bb.span),
                ExprKind::BinaryOperator(bop, new_left, bb),
            )
        }
        _ => Expr::new(
            Span::encompassing(a.span, b.span),
            ExprKind::BinaryOperator(op, a, b),
        ),
    }
}

pub(crate) fn expr_for_unary_operator(op: UnaryOperator, expr: Box<Expr>, op_span: Span) -> Expr {
    match *expr {
        Expr {
            value: ExprKind::BinaryOperator(binary_op, a, b),
            ..
        } => {
            let new_left = Box::new(expr_for_unary_operator(op, a, op_span));
            Expr::new(
                Span::encompassing(new_left.span, b.span),
                ExprKind::BinaryOperator(binary_op, new_left, b),
            )
        }
        Expr {
            value: ExprKind::Ternary { condition, yes, no },
            ..
        } => {
            let new_condition = Box::new(expr_for_unary_operator(op, condition, op_span));
            Expr::new(
                Span::encompassing(new_condition.span, no.span),
                ExprKind::Ternary {
                    condition: new_condition,
                    yes,
                    no,
                },
            )
        }
        _ => Expr::new(
            Span::encompassing(op_span, expr.span),
            ExprKind::UnaryOperator(op, expr),
        ),
    }
}

pub fn binary_operator(i: &mut Tokens<'_>) -> ModalResult<BinaryOperator> {
    let Token {
        kind: TokenKind::Operator(operator),
        ..
    } = any.parse_next(i)?
    else {
        return Err(ParserError::from_input(i));
    };
    Ok(match operator {
        Operator::Add => BinaryOperator::Add,
        Operator::Assign => BinaryOperator::Assign,
        Operator::AddAssign => BinaryOperator::AddAssign,
        Operator::Sub => BinaryOperator::Sub,
        Operator::SubAssign => BinaryOperator::SubAssign,
        Operator::Divide => BinaryOperator::Divide,
        Operator::DivideAssign => BinaryOperator::DivideAssign,
        Operator::Multiply => BinaryOperator::Multiply,
        Operator::MultiplyAssign => BinaryOperator::MultiplyAssign,
        Operator::Modulo => BinaryOperator::Modulo,
        Operator::ModuloAssign => BinaryOperator::ModuloAssign,
        Operator::BitAnd => BinaryOperator::BitAnd,
        Operator::BitOr => BinaryOperator::BitOr,
        Operator::BitXor => BinaryOperator::BitXor,
        Operator::BitShiftLeft => BinaryOperator::BitShiftLeft,
        Operator::BitShiftRight => BinaryOperator::BitShiftRight,
        Operator::BitShiftRightUnsigned => BinaryOperator::BitShiftRightUnsigned,
        Operator::BitAndAssign => BinaryOperator::BitAndAssign,
        Operator::BitOrAssign => BinaryOperator::BitOrAssign,
        Operator::BitXorAssign => BinaryOperator::BitXorAssign,
        Operator::BitShiftLeftAssign => BinaryOperator::BitShiftLeftAssign,
        Operator::BitShiftRightAssign => BinaryOperator::BitShiftRightAssign,
        Operator::BitShiftRightUnsignedAssign => BinaryOperator::BitShiftRightUnsignedAssign,
        Operator::Equal => BinaryOperator::Equal,
        Operator::StrictEqual => BinaryOperator::StrictEqual,
        Operator::NotEqual => BinaryOperator::NotEqual,
        Operator::StrictNotEqual => BinaryOperator::StrictNotEqual,
        Operator::LessThan => BinaryOperator::LessThan,
        Operator::LessThanEqual => BinaryOperator::LessThanEqual,
        Operator::GreaterThan => BinaryOperator::GreaterThan,
        Operator::GreaterThanEqual => BinaryOperator::GreaterThanEqual,
        Operator::LogicalAnd => BinaryOperator::LogicalAnd,
        Operator::LogicalOr => BinaryOperator::LogicalOr,
        _ => return Err(ParserError::from_input(i)),
    })
}
