//! Expression types for the AST.

use indexmap::IndexMap;

/// A Cypher expression.
#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
    /// A literal value.
    Literal(Literal),
    /// A parameter reference.
    Parameter(String),
    /// A variable reference.
    Variable(String),
    /// Property access: `expr.property`
    PropertyAccess(Box<Expression>, String),
    /// Index access: `expr[index]` (dynamic property or list index)
    IndexAccess(Box<Expression>, Box<Expression>),
    /// List slice: `expr[start..end]`
    ListSlice(Box<Expression>, Option<Box<Expression>>, Option<Box<Expression>>),
    /// Label check: `expr:Label`
    LabelCheck(Box<Expression>, Vec<String>),
    /// Binary operation.
    BinaryOp(Box<Expression>, BinaryOperator, Box<Expression>),
    /// Unary operation.
    UnaryOp(UnaryOperator, Box<Expression>),
    /// Function call.
    FunctionCall(FunctionCall),
    /// List literal.
    List(Vec<Expression>),
    /// Map literal.
    Map(IndexMap<String, Expression>),
    /// CASE expression.
    Case(CaseExpression),
    /// List comprehension: `[x IN list WHERE cond | expr]`
    ListComprehension(ListComprehension),
    /// Pattern comprehension: `[(n)-->(m) | expr]`
    PatternComprehension(PatternComprehension),
    /// Existential subquery: `EXISTS { pattern }`
    ExistsSubquery(Box<super::Query>),
    /// count(*)
    CountAll,
    /// String predicate (STARTS WITH, ENDS WITH, CONTAINS)
    StringPredicate(Box<Expression>, StringPredicateOp, Box<Expression>),
    /// List predicate (IN)
    InPredicate(Box<Expression>, Box<Expression>),
    /// Null predicate (IS NULL, IS NOT NULL)
    NullPredicate(Box<Expression>, bool), // bool = is_null (false means IS NOT NULL)
    /// Reduce expression
    Reduce(ReduceExpression),
    /// All/Any/None/Single quantifier
    Quantifier(QuantifierExpression),
    /// Pattern predicate in WHERE: `(a)-[:T]->(b)`
    PatternPredicate(super::Pattern),
    /// Parenthesized expression: `(expr)` - preserves column name formatting
    Parenthesized(Box<Expression>),
}

impl Expression {
    /// Create a variable expression.
    pub fn var(name: impl Into<String>) -> Self {
        Expression::Variable(name.into())
    }

    /// Create a property access expression.
    pub fn property(expr: Expression, name: impl Into<String>) -> Self {
        Expression::PropertyAccess(Box::new(expr), name.into())
    }

    /// Create a string literal expression.
    pub fn string(s: impl Into<String>) -> Self {
        Expression::Literal(Literal::String(s.into()))
    }

    /// Create an integer literal expression.
    pub fn integer(n: i64) -> Self {
        Expression::Literal(Literal::Integer(n))
    }

    /// Create a boolean literal expression.
    pub fn boolean(b: bool) -> Self {
        Expression::Literal(Literal::Boolean(b))
    }

    /// Create a null literal expression.
    pub fn null() -> Self {
        Expression::Literal(Literal::Null)
    }
}

/// A literal value.
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
    Null,
    Boolean(bool),
    Integer(i64),
    Float(f64),
    String(String),
}

/// Binary operators.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOperator {
    // Arithmetic
    Add,
    Subtract,
    Multiply,
    Divide,
    Modulo,
    Power,
    // Comparison
    Equal,
    NotEqual,
    LessThan,
    LessThanOrEqual,
    GreaterThan,
    GreaterThanOrEqual,
    // Logical
    And,
    Or,
    Xor,
    // String
    RegexMatch,
}

impl BinaryOperator {
    pub fn precedence(&self) -> u8 {
        match self {
            BinaryOperator::Or => 1,
            BinaryOperator::Xor => 2,
            BinaryOperator::And => 3,
            BinaryOperator::Equal
            | BinaryOperator::NotEqual
            | BinaryOperator::LessThan
            | BinaryOperator::LessThanOrEqual
            | BinaryOperator::GreaterThan
            | BinaryOperator::GreaterThanOrEqual
            | BinaryOperator::RegexMatch => 4,
            BinaryOperator::Add | BinaryOperator::Subtract => 5,
            BinaryOperator::Multiply | BinaryOperator::Divide | BinaryOperator::Modulo => 6,
            BinaryOperator::Power => 7,
        }
    }
}

/// Unary operators.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOperator {
    Not,
    Negate,
    Positive,
}

/// String predicate operators.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StringPredicateOp {
    StartsWith,
    EndsWith,
    Contains,
}

/// A function call expression.
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionCall {
    /// Namespace parts (e.g., ["db"] for db.labels())
    pub namespace: Vec<String>,
    /// Function name.
    pub name: String,
    /// Function arguments.
    pub arguments: Vec<Expression>,
    /// Whether DISTINCT is applied to arguments.
    pub distinct: bool,
}

impl FunctionCall {
    /// Create a new function call.
    pub fn new(name: impl Into<String>, arguments: Vec<Expression>) -> Self {
        Self {
            namespace: vec![],
            name: name.into(),
            arguments,
            distinct: false,
        }
    }

    /// Create a new function call with DISTINCT.
    pub fn with_distinct(name: impl Into<String>, arguments: Vec<Expression>) -> Self {
        Self {
            namespace: vec![],
            name: name.into(),
            arguments,
            distinct: true,
        }
    }

    /// Get the fully qualified name.
    pub fn full_name(&self) -> String {
        if self.namespace.is_empty() {
            self.name.clone()
        } else {
            format!("{}.{}", self.namespace.join("."), self.name)
        }
    }
}

/// CASE expression.
#[derive(Debug, Clone, PartialEq)]
pub struct CaseExpression {
    /// The expression to match (for simple CASE).
    pub operand: Option<Box<Expression>>,
    /// WHEN/THEN branches.
    pub alternatives: Vec<(Expression, Expression)>,
    /// ELSE clause.
    pub default: Option<Box<Expression>>,
}

/// List comprehension: `[x IN list WHERE cond | expr]`
#[derive(Debug, Clone, PartialEq)]
pub struct ListComprehension {
    /// The variable name.
    pub variable: String,
    /// The list expression.
    pub list: Box<Expression>,
    /// Optional WHERE condition.
    pub filter: Option<Box<Expression>>,
    /// Optional projection expression (after |).
    pub projection: Option<Box<Expression>>,
}

/// Pattern comprehension: `[(n)-->(m) | expr]`
#[derive(Debug, Clone, PartialEq)]
pub struct PatternComprehension {
    /// Optional variable to bind the pattern to.
    pub variable: Option<String>,
    /// The pattern.
    pub pattern: super::Pattern,
    /// Optional WHERE clause.
    pub filter: Option<Box<Expression>>,
    /// The projection expression.
    pub projection: Box<Expression>,
}

/// Reduce expression: `reduce(acc = init, x IN list | acc + x)`
#[derive(Debug, Clone, PartialEq)]
pub struct ReduceExpression {
    /// The accumulator variable.
    pub accumulator: String,
    /// Initial value for the accumulator.
    pub init: Box<Expression>,
    /// The iteration variable.
    pub variable: String,
    /// The list to iterate over.
    pub list: Box<Expression>,
    /// The expression to compute for each element.
    pub expression: Box<Expression>,
}

/// Quantifier type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuantifierType {
    All,
    Any,
    None,
    Single,
}

/// Quantifier expression: `all(x IN list WHERE cond)`
#[derive(Debug, Clone, PartialEq)]
pub struct QuantifierExpression {
    /// The quantifier type.
    pub quantifier: QuantifierType,
    /// The variable.
    pub variable: String,
    /// The list expression.
    pub list: Box<Expression>,
    /// The condition.
    pub condition: Box<Expression>,
}

/// Ordering direction.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OrderDirection {
    #[default]
    Ascending,
    Descending,
}

/// An item in ORDER BY.
#[derive(Debug, Clone, PartialEq)]
pub struct OrderItem {
    pub expression: Expression,
    pub direction: OrderDirection,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_expression_builders() {
        let var = Expression::var("n");
        assert!(matches!(var, Expression::Variable(ref s) if s == "n"));

        let prop = Expression::property(Expression::var("n"), "name");
        assert!(matches!(prop, Expression::PropertyAccess(_, ref s) if s == "name"));

        let str_lit = Expression::string("hello");
        assert!(matches!(str_lit, Expression::Literal(Literal::String(ref s)) if s == "hello"));
    }

    #[test]
    fn test_function_call_full_name() {
        let f1 = FunctionCall::new("count", vec![]);
        assert_eq!(f1.full_name(), "count");

        let f2 = FunctionCall {
            namespace: vec!["db".to_string()],
            name: "labels".to_string(),
            arguments: vec![],
            distinct: false,
        };
        assert_eq!(f2.full_name(), "db.labels");
    }
}
