//! Operator AST nodes.
//!
//! This module defines the prefix, postfix, and infix operator kinds used by
//! expression AST nodes.
//!
//! The enums here represent only the *syntactic operator category* recorded in
//! the AST. They do not encode precedence, associativity, overload behavior,
//! or token spelling details beyond the normalized operator kind itself.
//! Those concerns are handled by the expression parser and precedence tables.

use crate::lexer::{Keyword, Token, TokenPosition};

use core::convert::TryFrom;

/// Prefix unary operators.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum PrefixOperator {
    /// Logical negation: `!expr`.
    Not,
    /// Arithmetic negation: `-expr`.
    Minus,
    /// Unary plus: `+expr`.
    Plus,
    /// Bitwise negation: `~expr`.
    BitwiseNot,
    /// Prefix increment: `++expr`.
    Increment,
    /// Prefix decrement: `--expr`.
    Decrement,
}

/// Postfix unary operators.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum PostfixOperator {
    /// Postfix increment: `expr++`.
    Increment,
    /// Postfix decrement: `expr--`.
    Decrement,
}

/// Binary / infix operators.
///
/// These operators appear between left-hand side and right-hand side operands.
/// This enum stores only the normalized AST-level operator kind.
///
/// The parser assigns precedence and associativity separately.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum InfixOperator {
    /// Simple assignment: `left_hand_side = right_hand_side`.
    Assign,
    /// Multiplicative assignment: `left_hand_side *= right_hand_side`.
    MultiplyAssign,
    /// Division assignment: `left_hand_side /= right_hand_side`.
    DivideAssign,
    /// Modulo assignment: `left_hand_side %= right_hand_side`.
    ModuloAssign,
    /// Additive assignment: `left_hand_side += right_hand_side`.
    PlusAssign,
    /// Subtractive assignment: `left_hand_side -= right_hand_side`.
    MinusAssign,
    /// String concatenation assignment: `left_hand_side $= right_hand_side`.
    ConcatAssign,
    /// Space-concatenation assignment: `left_hand_side @= right_hand_side`.
    ConcatSpaceAssign,

    /// String concatenation without inserted whitespace:
    /// `left_hand_side $ right_hand_side`.
    Concat,
    /// String concatenation with an inserted space:
    /// `left_hand_side @ right_hand_side`.
    ConcatSpace,

    /// Logical conjunction: `left_hand_side && right_hand_side`.
    And,
    /// Logical exclusive-or: `left_hand_side ^^ right_hand_side`.
    Xor,
    /// Logical disjunction: `left_hand_side || right_hand_side`.
    Or,

    /// Bitwise AND: `left_hand_side & right_hand_side`.
    BitwiseAnd,
    /// Bitwise OR: `left_hand_side | right_hand_side`.
    BitwiseOr,
    /// Bitwise XOR: `left_hand_side ^ right_hand_side`.
    BitwiseXor,

    /// Inequality test: `left_hand_side != right_hand_side`.
    NotEqual,
    /// Equality test: `left_hand_side == right_hand_side`.
    Equal,
    /// Approximate equality test: `left_hand_side ~= right_hand_side`.
    ApproximatelyEqual,
    /// Less-than comparison: `left_hand_side < right_hand_side`.
    Less,
    /// Less-than-or-equal comparison: `left_hand_side <= right_hand_side`.
    LessEqual,
    /// Greater-than comparison: `left_hand_side > right_hand_side`.
    Greater,
    /// Greater-than-or-equal comparison: `left_hand_side >= right_hand_side`.
    GreaterEqual,
    /// UnrealScript-specific directional comparison:
    /// `left_hand_side ClockwiseFrom right_hand_side`.
    ClockwiseFrom,

    /// Left shift: `left_hand_side << right_hand_side`.
    LeftShift,
    /// Logical right shift: `left_hand_side >>> right_hand_side`.
    LogicalRightShift,
    /// Arithmetic / ordinary right shift: `left_hand_side >> right_hand_side`.
    RightShift,

    /// Addition: `left_hand_side + right_hand_side`.
    Plus,
    /// Subtraction: `left_hand_side - right_hand_side`.
    Minus,

    /// Remainder / modulo: `left_hand_side % right_hand_side`.
    Modulo,
    /// Multiplication: `left_hand_side * right_hand_side`.
    Multiply,
    /// Division: `left_hand_side / right_hand_side`.
    Divide,

    /// Dot product: `left_hand_side Dot right_hand_side`.
    ///
    /// This is spelled as a keyword-level operator in source.
    Dot,
    /// Cross product: `left_hand_side Cross right_hand_side`.
    ///
    /// This is spelled as a keyword-level operator in source.
    Cross,

    /// Exponentiation: `left_hand_side ** right_hand_side`.
    Exponentiation,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PrefixOperatorName {
    pub kind: PrefixOperator,
    pub position: TokenPosition,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct InfixOperatorName {
    pub kind: InfixOperator,
    pub position: TokenPosition,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PostfixOperatorName {
    pub kind: PostfixOperator,
    pub position: TokenPosition,
}

impl TryFrom<Token> for PostfixOperator {
    type Error = ();

    fn try_from(token: Token) -> Result<Self, Self::Error> {
        use PostfixOperator::{Decrement, Increment};

        match token {
            Token::Increment => Ok(Increment),
            Token::Decrement => Ok(Decrement),
            _ => Err(()),
        }
    }
}

impl TryFrom<Token> for PrefixOperator {
    type Error = ();

    fn try_from(token: Token) -> Result<Self, Self::Error> {
        use PrefixOperator::{BitwiseNot, Decrement, Increment, Minus, Not, Plus};

        match token {
            Token::Not => Ok(Not),
            Token::Minus => Ok(Minus),
            Token::Plus => Ok(Plus),
            Token::BitwiseNot => Ok(BitwiseNot),
            Token::Increment => Ok(Increment),
            Token::Decrement => Ok(Decrement),
            _ => Err(()),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct InfixOperatorInfo {
    pub operator: InfixOperator,
    pub right_precedence_rank: u8,
}

pub(crate) const fn infix_operator_info(token: Token) -> Option<InfixOperatorInfo> {
    use InfixOperator::{
        And, ApproximatelyEqual, Assign, BitwiseAnd, BitwiseOr, BitwiseXor, ClockwiseFrom, Concat,
        ConcatAssign, ConcatSpace, ConcatSpaceAssign, Cross, Divide, DivideAssign, Dot, Equal,
        Exponentiation, Greater, GreaterEqual, LeftShift, Less, LessEqual, LogicalRightShift,
        Minus, MinusAssign, Modulo, ModuloAssign, Multiply, MultiplyAssign, NotEqual, Or, Plus,
        PlusAssign, RightShift, Xor,
    };

    let (precedence_rank, operator) = match token {
        Token::Exponentiation => (12, Exponentiation),

        Token::Multiply => (16, Multiply),
        Token::Divide => (16, Divide),
        Token::Keyword(Keyword::Cross) => (16, Cross),
        Token::Keyword(Keyword::Dot) => (16, Dot),

        Token::Modulo => (18, Modulo),

        Token::Plus => (20, Plus),
        Token::Minus => (20, Minus),

        Token::LeftShift => (22, LeftShift),
        Token::RightShift => (22, RightShift),
        Token::LogicalRightShift => (22, LogicalRightShift),

        Token::Less => (24, Less),
        Token::LessEqual => (24, LessEqual),
        Token::Greater => (24, Greater),
        Token::GreaterEqual => (24, GreaterEqual),
        Token::Equal => (24, Equal),
        Token::ApproximatelyEqual => (24, ApproximatelyEqual),
        Token::Keyword(Keyword::ClockwiseFrom) => (24, ClockwiseFrom),

        Token::NotEqual => (26, NotEqual),

        Token::BitwiseAnd => (28, BitwiseAnd),
        Token::BitwiseXor => (28, BitwiseXor),
        Token::BitwiseOr => (28, BitwiseOr),

        Token::LogicalAnd => (30, And),
        Token::LogicalXor => (30, Xor),

        Token::LogicalOr => (32, Or),

        Token::MultiplyAssign => (34, MultiplyAssign),
        Token::DivideAssign => (34, DivideAssign),
        Token::PlusAssign => (34, PlusAssign),
        Token::MinusAssign => (34, MinusAssign),
        Token::Assign => (34, Assign),
        Token::ModuloAssign => (34, ModuloAssign),

        Token::Concat => (40, Concat),
        Token::ConcatSpace => (40, ConcatSpace),

        Token::ConcatAssign => (44, ConcatAssign),
        Token::ConcatSpaceAssign => (44, ConcatSpaceAssign),

        _ => return None,
    };

    Some(InfixOperatorInfo {
        operator,
        right_precedence_rank: precedence_rank,
    })
}

impl TryFrom<Token> for InfixOperator {
    type Error = ();

    fn try_from(token: Token) -> Result<Self, Self::Error> {
        infix_operator_info(token)
            .map(|info| info.operator)
            .ok_or(())
    }
}
