//! Expression evaluator.


use chrono::Timelike;
use indexmap::IndexMap;
use regex::Regex;

use crate::ast::*;
use crate::error::{ExecutionError, ExecutionResult};
use crate::graph::GraphBackend;
use crate::result::{
    CypherValue, DateTimeValue, DateValue, DurationValue, ExtendedDateValue, LocalDateTimeValue, LocalTimeValue,
    NodeValue, TimeValue,
};

use super::context::ExecutionContext;
use super::functions::FunctionRegistry;
use super::pattern_matcher::PatternMatcher;

/// Evaluates Cypher expressions.
pub struct Evaluator<'a, G: GraphBackend> {
    graph: &'a G,
    functions: &'a FunctionRegistry,
}

impl<'a, G: GraphBackend> Evaluator<'a, G> {
    /// Create a new evaluator.
    pub fn new(graph: &'a G, functions: &'a FunctionRegistry) -> Self {
        Self { graph, functions }
    }

    /// Evaluate an expression in the given context.
    pub fn evaluate(
        &self,
        expr: &Expression,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        match expr {
            Expression::Literal(lit) => self.evaluate_literal(lit),
            Expression::Parameter(name) => ctx
                .get_parameter(name)
                .cloned()
                .ok_or_else(|| ExecutionError::VariableNotFound(format!("parameter ${}", name))),
            Expression::Variable(name) => ctx
                .get(name)
                .cloned()
                .ok_or_else(|| ExecutionError::VariableNotFound(name.clone())),
            Expression::PropertyAccess(base, prop) => {
                self.evaluate_property_access(base, prop, ctx)
            }
            Expression::LabelCheck(base, labels) => self.evaluate_label_check(base, labels, ctx),
            Expression::BinaryOp(left, op, right) => self.evaluate_binary_op(left, op, right, ctx),
            Expression::UnaryOp(op, expr) => self.evaluate_unary_op(op, expr, ctx),
            Expression::FunctionCall(call) => self.evaluate_function_call(call, ctx),
            Expression::List(items) => {
                let values: ExecutionResult<Vec<_>> =
                    items.iter().map(|item| self.evaluate(item, ctx)).collect();
                Ok(CypherValue::List(values?))
            }
            Expression::Map(entries) => {
                let mut map = IndexMap::new();
                for (k, v) in entries {
                    map.insert(k.clone(), self.evaluate(v, ctx)?);
                }
                Ok(CypherValue::Map(map))
            }
            Expression::Case(case) => self.evaluate_case(case, ctx),
            Expression::ListComprehension(comp) => self.evaluate_list_comprehension(comp, ctx),
            Expression::PatternComprehension(comp) => {
                self.evaluate_pattern_comprehension(comp, ctx)
            }
            Expression::ExistsSubquery(query) => {
                // EXISTS { query } - execute the full subquery and check if it returns any rows
                // Use SubqueryExecutor for read-only execution
                let subquery_executor = crate::executor::SubqueryExecutor::new(self.graph, self.functions);
                let has_results = subquery_executor.execute_exists_query(query, ctx)?;
                Ok(CypherValue::Boolean(has_results))
            }
            Expression::CountAll => {
                // count(*) is handled as aggregation at clause level
                Err(ExecutionError::Internal(
                    "count(*) not supported in basic evaluator".to_string(),
                ))
            }
            Expression::StringPredicate(base, op, pattern) => {
                self.evaluate_string_predicate(base, op, pattern, ctx)
            }
            Expression::InPredicate(value, list) => self.evaluate_in_predicate(value, list, ctx),
            Expression::NullPredicate(value, is_null) => {
                self.evaluate_null_predicate(value, *is_null, ctx)
            }
            Expression::Reduce(reduce) => self.evaluate_reduce(reduce, ctx),
            Expression::Quantifier(quant) => self.evaluate_quantifier(quant, ctx),
            Expression::PatternPredicate(pattern) => {
                // Pattern predicates in WHERE check if a pattern exists
                // Return true if the pattern matches at least once
                let matcher = PatternMatcher::new(self.graph, self.functions);
                let results = matcher.match_pattern(pattern, ctx, false)?;
                Ok(CypherValue::Boolean(!results.is_empty()))
            }
            Expression::IndexAccess(base, index) => {
                self.evaluate_index_access(base, index, ctx)
            }
            Expression::ListSlice(base, start, end) => {
                self.evaluate_list_slice(base, start.as_deref(), end.as_deref(), ctx)
            }
            Expression::Parenthesized(inner) => {
                // Just evaluate the inner expression
                self.evaluate(inner, ctx)
            }
        }
    }

    fn evaluate_literal(&self, lit: &Literal) -> ExecutionResult<CypherValue> {
        Ok(match lit {
            Literal::Null => CypherValue::Null,
            Literal::Boolean(b) => CypherValue::Boolean(*b),
            Literal::Integer(i) => CypherValue::Integer(*i),
            Literal::Float(f) => CypherValue::Float(*f),
            Literal::String(s) => CypherValue::String(s.clone()),
        })
    }

    fn evaluate_property_access(
        &self,
        base: &Expression,
        prop: &str,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        // First, check if there's a column with the full property access name
        // This handles cases like "a.x" being a projected column name after WITH
        if let Expression::Variable(var_name) = base {
            let column_name = format!("{}.{}", var_name, prop);
            if let Some(value) = ctx.get(&column_name) {
                return Ok(value.clone());
            }
        }

        let base_value = self.evaluate(base, ctx)?;

        match base_value {
            CypherValue::Node(node) => {
                // Check if node still exists in the graph (it may have been deleted)
                if self.graph.get_node(node.id).is_none() {
                    return Err(ExecutionError::NodeNotFound(node.id));
                }
                Ok(node
                    .properties
                    .get(prop)
                    .cloned()
                    .unwrap_or(CypherValue::Null))
            }
            CypherValue::Relationship(rel) => {
                // Check if relationship still exists in the graph (it may have been deleted)
                if self.graph.get_edge(rel.id).is_none() {
                    return Err(ExecutionError::RelationshipNotFound(rel.id));
                }
                Ok(rel
                    .properties
                    .get(prop)
                    .cloned()
                    .unwrap_or(CypherValue::Null))
            }
            CypherValue::Map(map) => Ok(map.get(prop).cloned().unwrap_or(CypherValue::Null)),
            CypherValue::Null => Ok(CypherValue::Null),
            CypherValue::Date(date) => self.evaluate_date_property(&date, prop),
            CypherValue::Time(time) => self.evaluate_time_property(&time, prop),
            CypherValue::LocalTime(localtime) => self.evaluate_localtime_property(&localtime, prop),
            CypherValue::DateTime(datetime) => self.evaluate_datetime_property(&datetime, prop),
            CypherValue::LocalDateTime(localdatetime) => self.evaluate_localdatetime_property(&localdatetime, prop),
            CypherValue::Duration(duration) => self.evaluate_duration_property(&duration, prop),
            _ => Err(ExecutionError::Type(format!(
                "Cannot access property '{}' on type {}",
                prop,
                base_value.type_name()
            ))),
        }
    }

    fn evaluate_label_check(
        &self,
        base: &Expression,
        labels: &[String],
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        let base_value = self.evaluate(base, ctx)?;

        match base_value {
            CypherValue::Node(node) => {
                let has_all = labels.iter().all(|l| node.labels.contains(l));
                Ok(CypherValue::Boolean(has_all))
            }
            CypherValue::Relationship(rel) => {
                // For relationships, check if the type matches any of the labels
                // In Cypher, r:T2 checks if relationship type is T2
                let has_all = labels.iter().all(|l| &rel.rel_type == l);
                Ok(CypherValue::Boolean(has_all))
            }
            CypherValue::Null => Ok(CypherValue::Null),
            // Other types don't have labels, return false
            _ => Ok(CypherValue::Boolean(false)),
        }
    }

    fn evaluate_date_property(
        &self,
        date: &crate::result::DateValue,
        prop: &str,
    ) -> ExecutionResult<CypherValue> {
        use chrono::Datelike;
        match prop {
            "year" => Ok(CypherValue::Integer(date.year() as i64)),
            "month" => Ok(CypherValue::Integer(date.month() as i64)),
            "day" => Ok(CypherValue::Integer(date.day() as i64)),
            "quarter" => {
                let quarter = (date.month() - 1) / 3 + 1;
                Ok(CypherValue::Integer(quarter as i64))
            }
            "week" => Ok(CypherValue::Integer(date.date.iso_week().week() as i64)),
            "weekYear" => Ok(CypherValue::Integer(date.date.iso_week().year() as i64)),
            "weekDay" => Ok(CypherValue::Integer(date.date.weekday().num_days_from_monday() as i64 + 1)),
            "ordinalDay" => Ok(CypherValue::Integer(date.date.ordinal() as i64)),
            "dayOfQuarter" => {
                let quarter = (date.month() - 1) / 3;
                let quarter_start_month = quarter * 3 + 1;
                let quarter_start = chrono::NaiveDate::from_ymd_opt(date.year(), quarter_start_month, 1)
                    .ok_or_else(|| ExecutionError::Internal("Invalid quarter start date".to_string()))?;
                let day_of_quarter = (date.date - quarter_start).num_days() + 1;
                Ok(CypherValue::Integer(day_of_quarter))
            }
            _ => Err(ExecutionError::PropertyNotFound(format!(
                "Date has no property '{}'",
                prop
            ))),
        }
    }

    fn evaluate_time_property(
        &self,
        time: &crate::result::TimeValue,
        prop: &str,
    ) -> ExecutionResult<CypherValue> {
        match prop {
            "hour" => Ok(CypherValue::Integer(time.hour() as i64)),
            "minute" => Ok(CypherValue::Integer(time.minute() as i64)),
            "second" => Ok(CypherValue::Integer(time.second() as i64)),
            "millisecond" => {
                let ms = time.nanosecond() / 1_000_000;
                Ok(CypherValue::Integer(ms as i64))
            }
            "microsecond" => {
                let us = time.nanosecond() / 1_000;
                Ok(CypherValue::Integer(us as i64))
            }
            "nanosecond" => Ok(CypherValue::Integer(time.nanosecond() as i64)),
            "timezone" => Ok(CypherValue::String(time.timezone())),
            "offset" => Ok(CypherValue::String(time.offset.to_string())),
            "offsetMinutes" => {
                let offset_seconds = time.offset.local_minus_utc();
                Ok(CypherValue::Integer((offset_seconds / 60) as i64))
            }
            "offsetSeconds" => {
                let offset_seconds = time.offset.local_minus_utc();
                Ok(CypherValue::Integer(offset_seconds as i64))
            }
            _ => Err(ExecutionError::PropertyNotFound(format!(
                "Time has no property '{}'",
                prop
            ))),
        }
    }

    fn evaluate_localtime_property(
        &self,
        localtime: &crate::result::LocalTimeValue,
        prop: &str,
    ) -> ExecutionResult<CypherValue> {
        match prop {
            "hour" => Ok(CypherValue::Integer(localtime.hour() as i64)),
            "minute" => Ok(CypherValue::Integer(localtime.minute() as i64)),
            "second" => Ok(CypherValue::Integer(localtime.second() as i64)),
            "millisecond" => {
                let ms = localtime.nanosecond() / 1_000_000;
                Ok(CypherValue::Integer(ms as i64))
            }
            "microsecond" => {
                let us = localtime.nanosecond() / 1_000;
                Ok(CypherValue::Integer(us as i64))
            }
            "nanosecond" => Ok(CypherValue::Integer(localtime.nanosecond() as i64)),
            _ => Err(ExecutionError::PropertyNotFound(format!(
                "LocalTime has no property '{}'",
                prop
            ))),
        }
    }

    fn evaluate_datetime_property(
        &self,
        datetime: &crate::result::DateTimeValue,
        prop: &str,
    ) -> ExecutionResult<CypherValue> {
        use chrono::Datelike;
        match prop {
            "year" => Ok(CypherValue::Integer(datetime.year() as i64)),
            "month" => Ok(CypherValue::Integer(datetime.month() as i64)),
            "day" => Ok(CypherValue::Integer(datetime.day() as i64)),
            "hour" => Ok(CypherValue::Integer(datetime.hour() as i64)),
            "minute" => Ok(CypherValue::Integer(datetime.minute() as i64)),
            "second" => Ok(CypherValue::Integer(datetime.second() as i64)),
            "quarter" => {
                let quarter = (datetime.month() - 1) / 3 + 1;
                Ok(CypherValue::Integer(quarter as i64))
            }
            "week" => Ok(CypherValue::Integer(datetime.datetime.iso_week().week() as i64)),
            "weekYear" => Ok(CypherValue::Integer(datetime.datetime.iso_week().year() as i64)),
            "weekDay" => Ok(CypherValue::Integer(datetime.datetime.weekday().num_days_from_monday() as i64 + 1)),
            "ordinalDay" => Ok(CypherValue::Integer(datetime.datetime.ordinal() as i64)),
            "dayOfQuarter" => {
                let quarter = (datetime.month() - 1) / 3;
                let quarter_start_month = quarter * 3 + 1;
                let quarter_start = chrono::NaiveDate::from_ymd_opt(datetime.year(), quarter_start_month, 1)
                    .ok_or_else(|| ExecutionError::Internal("Invalid quarter start date".to_string()))?;
                let day_of_quarter = (datetime.datetime.date_naive() - quarter_start).num_days() + 1;
                Ok(CypherValue::Integer(day_of_quarter))
            }
            "millisecond" => {
                let ms = datetime.nanosecond() / 1_000_000;
                Ok(CypherValue::Integer(ms as i64))
            }
            "microsecond" => {
                let us = datetime.nanosecond() / 1_000;
                Ok(CypherValue::Integer(us as i64))
            }
            "nanosecond" => Ok(CypherValue::Integer(datetime.nanosecond() as i64)),
            "timezone" => Ok(CypherValue::String(datetime.timezone())),
            "offset" => Ok(CypherValue::String(datetime.datetime.offset().to_string())),
            "offsetMinutes" => {
                let offset_seconds = datetime.datetime.offset().local_minus_utc();
                Ok(CypherValue::Integer((offset_seconds / 60) as i64))
            }
            "offsetSeconds" => {
                let offset_seconds = datetime.datetime.offset().local_minus_utc();
                Ok(CypherValue::Integer(offset_seconds as i64))
            }
            "epochSeconds" => {
                Ok(CypherValue::Integer(datetime.datetime.timestamp()))
            }
            "epochMillis" => {
                Ok(CypherValue::Integer(datetime.datetime.timestamp_millis()))
            }
            _ => Err(ExecutionError::PropertyNotFound(format!(
                "DateTime has no property '{}'",
                prop
            ))),
        }
    }

    fn evaluate_localdatetime_property(
        &self,
        localdatetime: &crate::result::LocalDateTimeValue,
        prop: &str,
    ) -> ExecutionResult<CypherValue> {
        use chrono::Datelike;
        match prop {
            "year" => Ok(CypherValue::Integer(localdatetime.year() as i64)),
            "month" => Ok(CypherValue::Integer(localdatetime.month() as i64)),
            "day" => Ok(CypherValue::Integer(localdatetime.day() as i64)),
            "hour" => Ok(CypherValue::Integer(localdatetime.hour() as i64)),
            "minute" => Ok(CypherValue::Integer(localdatetime.minute() as i64)),
            "second" => Ok(CypherValue::Integer(localdatetime.second() as i64)),
            "quarter" => {
                let quarter = (localdatetime.month() - 1) / 3 + 1;
                Ok(CypherValue::Integer(quarter as i64))
            }
            "week" => Ok(CypherValue::Integer(localdatetime.datetime.iso_week().week() as i64)),
            "weekYear" => Ok(CypherValue::Integer(localdatetime.datetime.iso_week().year() as i64)),
            "weekDay" => Ok(CypherValue::Integer(localdatetime.datetime.weekday().num_days_from_monday() as i64 + 1)),
            "ordinalDay" => Ok(CypherValue::Integer(localdatetime.datetime.ordinal() as i64)),
            "dayOfQuarter" => {
                let quarter = (localdatetime.month() - 1) / 3;
                let quarter_start_month = quarter * 3 + 1;
                let quarter_start = chrono::NaiveDate::from_ymd_opt(localdatetime.year(), quarter_start_month, 1)
                    .ok_or_else(|| ExecutionError::Internal("Invalid quarter start date".to_string()))?;
                let day_of_quarter = (localdatetime.datetime.date() - quarter_start).num_days() + 1;
                Ok(CypherValue::Integer(day_of_quarter))
            }
            "millisecond" => {
                let ms = localdatetime.nanosecond() / 1_000_000;
                Ok(CypherValue::Integer(ms as i64))
            }
            "microsecond" => {
                let us = localdatetime.nanosecond() / 1_000;
                Ok(CypherValue::Integer(us as i64))
            }
            "nanosecond" => Ok(CypherValue::Integer(localdatetime.nanosecond() as i64)),
            _ => Err(ExecutionError::PropertyNotFound(format!(
                "LocalDateTime has no property '{}'",
                prop
            ))),
        }
    }

    fn evaluate_duration_property(
        &self,
        duration: &crate::result::DurationValue,
        prop: &str,
    ) -> ExecutionResult<CypherValue> {
        match prop {
            "years" => Ok(CypherValue::Integer(duration.years())),
            "months" => Ok(CypherValue::Integer(duration.months)),
            "monthsOfYear" => Ok(CypherValue::Integer(duration.months_of_year())),
            "quarters" => Ok(CypherValue::Integer(duration.months / 3)),
            "quartersOfYear" => Ok(CypherValue::Integer((duration.months % 12) / 3)),
            "monthsOfQuarter" => Ok(CypherValue::Integer(duration.months % 3)),
            "weeks" => Ok(CypherValue::Integer(duration.weeks())),
            "days" => Ok(CypherValue::Integer(duration.days)),
            "daysOfWeek" => Ok(CypherValue::Integer(duration.days_of_week())),
            "hours" => Ok(CypherValue::Integer(duration.hours())),
            "minutes" => Ok(CypherValue::Integer(duration.seconds / 60)),
            "minutesOfHour" => Ok(CypherValue::Integer(duration.minutes())),
            "seconds" => Ok(CypherValue::Integer(duration.seconds)),
            "secondsOfMinute" => Ok(CypherValue::Integer(duration.seconds_of_minute())),
            "milliseconds" => Ok(CypherValue::Integer(duration.seconds * 1000 + duration.milliseconds())),
            "millisecondsOfSecond" => Ok(CypherValue::Integer(duration.milliseconds())),
            "microseconds" => Ok(CypherValue::Integer(duration.seconds * 1_000_000 + duration.microseconds())),
            "microsecondsOfSecond" => Ok(CypherValue::Integer(duration.microseconds())),
            "nanoseconds" => Ok(CypherValue::Integer(duration.seconds * 1_000_000_000 + duration.nanoseconds() as i64)),
            "nanosecondsOfSecond" => Ok(CypherValue::Integer(duration.nanoseconds() as i64)),
            _ => Err(ExecutionError::PropertyNotFound(format!(
                "Duration has no property '{}'",
                prop
            ))),
        }
    }

    fn evaluate_index_access(
        &self,
        base: &Expression,
        index: &Expression,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        let base_value = self.evaluate(base, ctx)?;
        let index_value = self.evaluate(index, ctx)?;

        match (&base_value, &index_value) {
            // List indexing: list[n]
            (CypherValue::List(list), CypherValue::Integer(idx)) => {
                let len = list.len() as i64;
                // Cypher supports negative indexing
                let actual_idx = if *idx < 0 { len + *idx } else { *idx };
                if actual_idx < 0 || actual_idx >= len {
                    Ok(CypherValue::Null)
                } else {
                    Ok(list[actual_idx as usize].clone())
                }
            }
            // Dynamic property access: node['propName'] or map['key']
            (CypherValue::Node(node), CypherValue::String(key)) => {
                Ok(node.properties.get(key).cloned().unwrap_or(CypherValue::Null))
            }
            (CypherValue::Relationship(rel), CypherValue::String(key)) => {
                Ok(rel.properties.get(key).cloned().unwrap_or(CypherValue::Null))
            }
            (CypherValue::Map(map), CypherValue::String(key)) => {
                Ok(map.get(key).cloned().unwrap_or(CypherValue::Null))
            }
            // Null propagation
            (CypherValue::Null, _) | (_, CypherValue::Null) => Ok(CypherValue::Null),
            _ => Err(ExecutionError::Type(format!(
                "Cannot index {} with {}",
                base_value.type_name(),
                index_value.type_name()
            ))),
        }
    }

    fn evaluate_list_slice(
        &self,
        base: &Expression,
        start: Option<&Expression>,
        end: Option<&Expression>,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        let base_value = self.evaluate(base, ctx)?;

        match base_value {
            CypherValue::List(list) => {
                let len = list.len() as i64;

                // Evaluate start index
                let start_idx = if let Some(start_expr) = start {
                    match self.evaluate(start_expr, ctx)? {
                        CypherValue::Integer(i) => {
                            let actual = if i < 0 { (len + i).max(0) } else { i.min(len) };
                            actual as usize
                        }
                        CypherValue::Null => return Ok(CypherValue::Null),
                        other => return Err(ExecutionError::Type(format!(
                            "Slice start must be integer, got {}",
                            other.type_name()
                        ))),
                    }
                } else {
                    0
                };

                // Evaluate end index
                let end_idx = if let Some(end_expr) = end {
                    match self.evaluate(end_expr, ctx)? {
                        CypherValue::Integer(i) => {
                            let actual = if i < 0 { (len + i).max(0) } else { i.min(len) };
                            actual as usize
                        }
                        CypherValue::Null => return Ok(CypherValue::Null),
                        other => return Err(ExecutionError::Type(format!(
                            "Slice end must be integer, got {}",
                            other.type_name()
                        ))),
                    }
                } else {
                    len as usize
                };

                // Handle edge cases
                if start_idx >= end_idx || start_idx >= list.len() {
                    Ok(CypherValue::List(vec![]))
                } else {
                    Ok(CypherValue::List(list[start_idx..end_idx.min(list.len())].to_vec()))
                }
            }
            CypherValue::String(s) => {
                let chars: Vec<char> = s.chars().collect();
                let len = chars.len() as i64;

                // Evaluate start index
                let start_idx = if let Some(start_expr) = start {
                    match self.evaluate(start_expr, ctx)? {
                        CypherValue::Integer(i) => {
                            let actual = if i < 0 { (len + i).max(0) } else { i.min(len) };
                            actual as usize
                        }
                        CypherValue::Null => return Ok(CypherValue::Null),
                        other => return Err(ExecutionError::Type(format!(
                            "Slice start must be integer, got {}",
                            other.type_name()
                        ))),
                    }
                } else {
                    0
                };

                // Evaluate end index
                let end_idx = if let Some(end_expr) = end {
                    match self.evaluate(end_expr, ctx)? {
                        CypherValue::Integer(i) => {
                            let actual = if i < 0 { (len + i).max(0) } else { i.min(len) };
                            actual as usize
                        }
                        CypherValue::Null => return Ok(CypherValue::Null),
                        other => return Err(ExecutionError::Type(format!(
                            "Slice end must be integer, got {}",
                            other.type_name()
                        ))),
                    }
                } else {
                    len as usize
                };

                // Handle edge cases
                if start_idx >= end_idx || start_idx >= chars.len() {
                    Ok(CypherValue::String(String::new()))
                } else {
                    let result: String = chars[start_idx..end_idx.min(chars.len())].iter().collect();
                    Ok(CypherValue::String(result))
                }
            }
            CypherValue::Null => Ok(CypherValue::Null),
            _ => Err(ExecutionError::Type(format!(
                "Cannot slice {}, expected list or string",
                base_value.type_name()
            ))),
        }
    }

    fn evaluate_binary_op(
        &self,
        left: &Expression,
        op: &BinaryOperator,
        right: &Expression,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        // Short-circuit evaluation for AND/OR with proper three-valued logic
        match op {
            BinaryOperator::And => {
                let left_val = self.evaluate(left, ctx)?;

                // Short-circuit: false AND anything = false
                match left_val {
                    CypherValue::Boolean(false) => return Ok(CypherValue::Boolean(false)),
                    CypherValue::Boolean(true) => {
                        // true AND x = x
                        let right_val = self.evaluate(right, ctx)?;
                        return Ok(match right_val {
                            CypherValue::Boolean(b) => CypherValue::Boolean(b),
                            CypherValue::Null => CypherValue::Null,
                            _ => CypherValue::Boolean(right_val.is_truthy()),
                        });
                    }
                    CypherValue::Null => {
                        // null AND x: if x is false, return false; otherwise null
                        let right_val = self.evaluate(right, ctx)?;
                        return Ok(match right_val {
                            CypherValue::Boolean(false) => CypherValue::Boolean(false),
                            _ => CypherValue::Null,
                        });
                    }
                    _ => {
                        // Coerce non-boolean left to boolean
                        if !left_val.is_truthy() {
                            return Ok(CypherValue::Boolean(false));
                        }
                        let right_val = self.evaluate(right, ctx)?;
                        return Ok(match right_val {
                            CypherValue::Boolean(b) => CypherValue::Boolean(b),
                            CypherValue::Null => CypherValue::Null,
                            _ => CypherValue::Boolean(right_val.is_truthy()),
                        });
                    }
                }
            }
            BinaryOperator::Or => {
                let left_val = self.evaluate(left, ctx)?;

                // Short-circuit: true OR anything = true
                match left_val {
                    CypherValue::Boolean(true) => return Ok(CypherValue::Boolean(true)),
                    CypherValue::Boolean(false) => {
                        // false OR x = x
                        let right_val = self.evaluate(right, ctx)?;
                        return Ok(match right_val {
                            CypherValue::Boolean(b) => CypherValue::Boolean(b),
                            CypherValue::Null => CypherValue::Null,
                            _ => CypherValue::Boolean(right_val.is_truthy()),
                        });
                    }
                    CypherValue::Null => {
                        // null OR x: if x is true, return true; otherwise null
                        let right_val = self.evaluate(right, ctx)?;
                        return Ok(match right_val {
                            CypherValue::Boolean(true) => CypherValue::Boolean(true),
                            _ => CypherValue::Null,
                        });
                    }
                    _ => {
                        // Coerce non-boolean left to boolean
                        if left_val.is_truthy() {
                            return Ok(CypherValue::Boolean(true));
                        }
                        let right_val = self.evaluate(right, ctx)?;
                        return Ok(match right_val {
                            CypherValue::Boolean(b) => CypherValue::Boolean(b),
                            CypherValue::Null => CypherValue::Null,
                            _ => CypherValue::Boolean(right_val.is_truthy()),
                        });
                    }
                }
            }
            _ => {}
        }

        let left_val = self.evaluate(left, ctx)?;
        let right_val = self.evaluate(right, ctx)?;

        // Handle null propagation
        if left_val.is_null() || right_val.is_null() {
            match op {
                BinaryOperator::Equal | BinaryOperator::NotEqual => {
                    // null = null is null, not true
                    return Ok(CypherValue::Null);
                }
                _ => return Ok(CypherValue::Null),
            }
        }

        // Handle NaN comparisons per openCypher spec:
        // - NaN compared to numbers (including NaN): returns false for >, >=, <, <=
        // - NaN compared to incompatible types: returns NULL
        // - NaN == NaN: returns NULL (handled below in equals_with_null_handling)
        let is_nan = |val: &CypherValue| match val {
            CypherValue::Float(f) => f.is_nan(),
            _ => false,
        };

        if is_nan(&left_val) || is_nan(&right_val) {
            match op {
                BinaryOperator::Equal => {
                    // NaN == anything returns false (not null)
                    return Ok(CypherValue::Boolean(false));
                }
                BinaryOperator::NotEqual => {
                    // NaN <> anything returns true (not null)
                    return Ok(CypherValue::Boolean(true));
                }
                BinaryOperator::LessThan | BinaryOperator::LessThanOrEqual |
                BinaryOperator::GreaterThan | BinaryOperator::GreaterThanOrEqual => {
                    // Check if both are numeric types
                    let both_numeric = matches!(left_val, CypherValue::Integer(_) | CypherValue::Float(_))
                        && matches!(right_val, CypherValue::Integer(_) | CypherValue::Float(_));

                    if both_numeric {
                        // NaN compared to numbers returns false
                        return Ok(CypherValue::Boolean(false));
                    } else {
                        // NaN compared to incompatible types returns NULL
                        return Ok(CypherValue::Null);
                    }
                }
                _ => {}
            }
        }

        match op {
            BinaryOperator::Add => self.add(left_val, right_val),
            BinaryOperator::Subtract => self.subtract(left_val, right_val),
            BinaryOperator::Multiply => self.multiply(left_val, right_val),
            BinaryOperator::Divide => self.divide(left_val, right_val),
            BinaryOperator::Modulo => self.modulo(left_val, right_val),
            BinaryOperator::Power => self.power(left_val, right_val),
            BinaryOperator::Equal => equals_with_null_handling(left_val, right_val),
            BinaryOperator::NotEqual => {
                match equals_with_null_handling(left_val, right_val)? {
                    CypherValue::Boolean(b) => Ok(CypherValue::Boolean(!b)),
                    CypherValue::Null => Ok(CypherValue::Null),
                    _ => unreachable!(),
                }
            }
            BinaryOperator::LessThan => match left_val.compare(&right_val) {
                Some(std::cmp::Ordering::Less) => Ok(CypherValue::Boolean(true)),
                Some(_) => Ok(CypherValue::Boolean(false)),
                None => Ok(CypherValue::Null), // NaN or incomparable types
            },
            BinaryOperator::LessThanOrEqual => match left_val.compare(&right_val) {
                Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) => {
                    Ok(CypherValue::Boolean(true))
                }
                Some(_) => Ok(CypherValue::Boolean(false)),
                None => Ok(CypherValue::Null), // NaN or incomparable types
            },
            BinaryOperator::GreaterThan => match left_val.compare(&right_val) {
                Some(std::cmp::Ordering::Greater) => Ok(CypherValue::Boolean(true)),
                Some(_) => Ok(CypherValue::Boolean(false)),
                None => Ok(CypherValue::Null), // NaN or incomparable types
            },
            BinaryOperator::GreaterThanOrEqual => match left_val.compare(&right_val) {
                Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) => {
                    Ok(CypherValue::Boolean(true))
                }
                Some(_) => Ok(CypherValue::Boolean(false)),
                None => Ok(CypherValue::Null), // NaN or incomparable types
            },
            BinaryOperator::Xor => {
                // XOR with NULL returns NULL
                match (&left_val, &right_val) {
                    (CypherValue::Null, _) | (_, CypherValue::Null) => Ok(CypherValue::Null),
                    (CypherValue::Boolean(a), CypherValue::Boolean(b)) => {
                        Ok(CypherValue::Boolean(a ^ b))
                    }
                    _ => Ok(CypherValue::Boolean(
                        left_val.is_truthy() ^ right_val.is_truthy(),
                    )),
                }
            }
            BinaryOperator::RegexMatch => self.regex_match(left_val, right_val),
            BinaryOperator::And | BinaryOperator::Or => unreachable!(),
        }
    }

    fn add(&self, left: CypherValue, right: CypherValue) -> ExecutionResult<CypherValue> {
        match (left, right) {
            (CypherValue::Integer(a), CypherValue::Integer(b)) => Ok(CypherValue::Integer(a + b)),
            (CypherValue::Float(a), CypherValue::Float(b)) => Ok(CypherValue::Float(a + b)),
            (CypherValue::Integer(a), CypherValue::Float(b)) => {
                Ok(CypherValue::Float(a as f64 + b))
            }
            (CypherValue::Float(a), CypherValue::Integer(b)) => {
                Ok(CypherValue::Float(a + b as f64))
            }
            (CypherValue::String(a), CypherValue::String(b)) => Ok(CypherValue::String(a + &b)),
            (CypherValue::List(mut a), CypherValue::List(b)) => {
                a.extend(b);
                Ok(CypherValue::List(a))
            }
            // List + Scalar: append scalar to list
            (CypherValue::List(mut a), scalar) if !matches!(scalar, CypherValue::Null) => {
                a.push(scalar);
                Ok(CypherValue::List(a))
            }
            // Scalar + List: prepend scalar to list
            (scalar, CypherValue::List(mut a)) if !matches!(scalar, CypherValue::Null) => {
                a.insert(0, scalar);
                Ok(CypherValue::List(a))
            }

            // Duration + Duration
            (CypherValue::Duration(d1), CypherValue::Duration(d2)) => {
                Ok(CypherValue::Duration(DurationValue::new(
                    d1.months + d2.months,
                    d1.days + d2.days,
                    d1.seconds + d2.seconds,
                    d1.nanos + d2.nanos,
                )))
            }

            // Date + Duration (or Duration + Date)
            (CypherValue::Date(date), CypherValue::Duration(dur))
            | (CypherValue::Duration(dur), CypherValue::Date(date)) => {
                use chrono::Datelike;
                let mut result_date = date.date;

                // Add months (with year overflow)
                if dur.months != 0 {
                    let total_months = result_date.month() as i64 + dur.months;
                    let years_to_add = if total_months > 0 {
                        (total_months - 1) / 12
                    } else {
                        // For negative months, floor division
                        (total_months - 12) / 12
                    };
                    // Compute month using Euclidean modulo (always positive result)
                    let final_month = if total_months > 0 {
                        ((total_months - 1) % 12 + 1) as u32
                    } else {
                        // For negative/zero: convert Rust's remainder to positive month
                        let rem = total_months % 12;
                        if rem == 0 {
                            12
                        } else {
                            (rem + 12) as u32
                        }
                    };

                    let new_year = result_date.year() + years_to_add as i32;
                    result_date = chrono::NaiveDate::from_ymd_opt(
                        new_year,
                        final_month,
                        std::cmp::min(result_date.day(), days_in_month(new_year, final_month)),
                    )
                    .ok_or_else(|| ExecutionError::InvalidArgument(
                        format!("Invalid date: year={}, month={}", new_year, final_month)
                    ))?;
                }

                // Add days
                if dur.days != 0 {
                    result_date += chrono::Duration::days(dur.days);
                }

                // When adding a duration to a date, the time components (seconds/nanos)
                // should contribute extra days. Convert total seconds to days.
                // 86400 seconds = 1 day. Use regular division (truncates towards zero)
                // rather than div_euclid (floor towards negative infinity).
                const SECONDS_PER_DAY: i64 = 86400;
                let total_nanos = dur.seconds * 1_000_000_000 + dur.nanos as i64;
                let extra_days = total_nanos / (SECONDS_PER_DAY * 1_000_000_000);
                if extra_days != 0 {
                    result_date += chrono::Duration::days(extra_days);
                }

                Ok(CypherValue::Date(DateValue::new(result_date)))
            }

            // ExtendedDate + Duration (or Duration + ExtendedDate)
            (CypherValue::ExtendedDate(date), CypherValue::Duration(dur))
            | (CypherValue::Duration(dur), CypherValue::ExtendedDate(date)) => {
                use crate::result::extended_temporal::ExtendedDate;

                // Create an ExtendedDate from the ExtendedDateValue
                let ext_date = ExtendedDate::from_ymd(date.year, date.month as u32, date.day as u32)
                    .ok_or_else(|| ExecutionError::InvalidArgument("Invalid extended date".to_string()))?;

                // Add months first
                let after_months = if dur.months != 0 {
                    ext_date.add_months(dur.months)
                        .ok_or_else(|| ExecutionError::InvalidArgument("Date overflow".to_string()))?
                } else {
                    ext_date
                };

                // Add days
                let mut result = if dur.days != 0 {
                    after_months.add_days(dur.days)
                        .ok_or_else(|| ExecutionError::InvalidArgument("Date overflow".to_string()))?
                } else {
                    after_months
                };

                // When adding a duration to a date, the time components (seconds/nanos)
                // should contribute extra days. 86400 seconds = 1 day
                const SECONDS_PER_DAY: i64 = 86400;
                // Use regular division (truncates towards zero) rather than div_euclid
                let total_nanos = dur.seconds * 1_000_000_000 + dur.nanos as i64;
                let extra_days = total_nanos / (SECONDS_PER_DAY * 1_000_000_000);
                if extra_days != 0 {
                    result = result.add_days(extra_days)
                        .ok_or_else(|| ExecutionError::InvalidArgument("Date overflow".to_string()))?;
                }

                // Check if result fits in chrono range - if so, return regular Date
                if let Some(naive) = result.to_chrono() {
                    Ok(CypherValue::Date(DateValue::new(naive)))
                } else {
                    Ok(CypherValue::ExtendedDate(ExtendedDateValue::new(
                        result.year(),
                        result.month() as u8,
                        result.day() as u8,
                    )))
                }
            }

            // LocalTime + Duration (or Duration + LocalTime)
            (CypherValue::LocalTime(time), CypherValue::Duration(dur))
            | (CypherValue::Duration(dur), CypherValue::LocalTime(time)) => {
                let total_seconds =
                    dur.hours() * 3600 + dur.minutes() * 60 + dur.seconds_of_minute();
                let total_nanos = total_seconds * 1_000_000_000 + dur.nanoseconds() as i64;

                let current_nanos = time.time.num_seconds_from_midnight() as i64 * 1_000_000_000
                    + time.time.nanosecond() as i64;

                let new_nanos = current_nanos + total_nanos;
                let seconds_in_day = 86400i64 * 1_000_000_000;

                // Handle wraparound
                let final_nanos = ((new_nanos % seconds_in_day) + seconds_in_day) % seconds_in_day;

                let new_time = chrono::NaiveTime::from_num_seconds_from_midnight_opt(
                    (final_nanos / 1_000_000_000) as u32,
                    (final_nanos % 1_000_000_000) as u32,
                )
                .ok_or_else(|| ExecutionError::Internal("Invalid time calculation in LocalTime + Duration".to_string()))?;

                Ok(CypherValue::LocalTime(LocalTimeValue::new(new_time)))
            }

            // Time + Duration (with timezone)
            (CypherValue::Time(time), CypherValue::Duration(dur))
            | (CypherValue::Duration(dur), CypherValue::Time(time)) => {
                // Same logic as LocalTime but preserve offset
                let total_seconds =
                    dur.hours() * 3600 + dur.minutes() * 60 + dur.seconds_of_minute();
                let total_nanos = total_seconds * 1_000_000_000 + dur.nanoseconds() as i64;

                let current_nanos = time.time.num_seconds_from_midnight() as i64 * 1_000_000_000
                    + time.time.nanosecond() as i64;

                let new_nanos = current_nanos + total_nanos;
                let seconds_in_day = 86400i64 * 1_000_000_000;
                let final_nanos = ((new_nanos % seconds_in_day) + seconds_in_day) % seconds_in_day;

                let new_time = chrono::NaiveTime::from_num_seconds_from_midnight_opt(
                    (final_nanos / 1_000_000_000) as u32,
                    (final_nanos % 1_000_000_000) as u32,
                )
                .ok_or_else(|| ExecutionError::Internal("Invalid time calculation in Time + Duration".to_string()))?;

                Ok(CypherValue::Time(TimeValue::new(new_time, time.offset)))
            }

            // LocalDateTime + Duration (or Duration + LocalDateTime)
            (CypherValue::LocalDateTime(dt), CypherValue::Duration(dur))
            | (CypherValue::Duration(dur), CypherValue::LocalDateTime(dt)) => {
                use chrono::Datelike;
                let mut result_dt = dt.datetime;

                // Add months (with year overflow)
                if dur.months != 0 {
                    let total_months = result_dt.month() as i64 + dur.months;
                    let years_to_add = if total_months > 0 {
                        (total_months - 1) / 12
                    } else {
                        (total_months - 12) / 12
                    };
                    let final_month = if total_months > 0 {
                        ((total_months - 1) % 12 + 1) as u32
                    } else {
                        let rem = total_months % 12;
                        if rem == 0 { 12 } else { (rem + 12) as u32 }
                    };

                    let new_year = result_dt.year() + years_to_add as i32;
                    let new_date = chrono::NaiveDate::from_ymd_opt(
                        new_year,
                        final_month,
                        std::cmp::min(result_dt.day(), days_in_month(new_year, final_month)),
                    )
                    .ok_or_else(|| ExecutionError::InvalidArgument(
                        format!("Invalid date: year={}, month={}", new_year, final_month)
                    ))?;
                    result_dt = new_date.and_time(result_dt.time());
                }

                // Add days
                if dur.days != 0 {
                    result_dt += chrono::Duration::days(dur.days);
                }

                // Add time components
                let total_seconds =
                    dur.hours() * 3600 + dur.minutes() * 60 + dur.seconds_of_minute();
                if total_seconds != 0 {
                    result_dt += chrono::Duration::seconds(total_seconds);
                }
                if dur.nanoseconds() != 0 {
                    result_dt += chrono::Duration::nanoseconds(dur.nanoseconds() as i64);
                }

                Ok(CypherValue::LocalDateTime(LocalDateTimeValue::new(
                    result_dt,
                )))
            }

            // DateTime + Duration (or Duration + DateTime)
            (CypherValue::DateTime(dt), CypherValue::Duration(dur))
            | (CypherValue::Duration(dur), CypherValue::DateTime(dt)) => {
                use chrono::{Datelike, TimeZone};
                let offset = *dt.datetime.offset();
                let mut result_dt = dt.datetime.naive_local();

                // Add months (with year overflow)
                if dur.months != 0 {
                    let total_months = result_dt.month() as i64 + dur.months;
                    let years_to_add = if total_months > 0 {
                        (total_months - 1) / 12
                    } else {
                        (total_months - 12) / 12
                    };
                    let final_month = if total_months > 0 {
                        ((total_months - 1) % 12 + 1) as u32
                    } else {
                        let rem = total_months % 12;
                        if rem == 0 { 12 } else { (rem + 12) as u32 }
                    };

                    let new_year = result_dt.year() + years_to_add as i32;
                    let new_date = chrono::NaiveDate::from_ymd_opt(
                        new_year,
                        final_month,
                        std::cmp::min(result_dt.day(), days_in_month(new_year, final_month)),
                    )
                    .ok_or_else(|| ExecutionError::InvalidArgument(
                        format!("Invalid date: year={}, month={}", new_year, final_month)
                    ))?;
                    result_dt = new_date.and_time(result_dt.time());
                }

                // Add days
                if dur.days != 0 {
                    result_dt += chrono::Duration::days(dur.days);
                }

                // Add time components
                let total_seconds =
                    dur.hours() * 3600 + dur.minutes() * 60 + dur.seconds_of_minute();
                if total_seconds != 0 {
                    result_dt += chrono::Duration::seconds(total_seconds);
                }
                if dur.nanoseconds() != 0 {
                    result_dt += chrono::Duration::nanoseconds(dur.nanoseconds() as i64);
                }

                // Convert back to DateTime<FixedOffset> with the same offset
                let final_dt = offset.from_local_datetime(&result_dt)
                    .earliest()
                    .ok_or_else(|| ExecutionError::Internal("DateTime conversion failed during DST transition".to_string()))?;
                Ok(CypherValue::DateTime(DateTimeValue::new(final_dt)))
            }

            // Type mismatches return NULL in Cypher semantics
            (_, _) => Ok(CypherValue::Null),
        }
    }

    fn subtract(&self, left: CypherValue, right: CypherValue) -> ExecutionResult<CypherValue> {
        match (left, right) {
            (CypherValue::Integer(a), CypherValue::Integer(b)) => Ok(CypherValue::Integer(a - b)),
            (CypherValue::Float(a), CypherValue::Float(b)) => Ok(CypherValue::Float(a - b)),
            (CypherValue::Integer(a), CypherValue::Float(b)) => {
                Ok(CypherValue::Float(a as f64 - b))
            }
            (CypherValue::Float(a), CypherValue::Integer(b)) => {
                Ok(CypherValue::Float(a - b as f64))
            }

            // Duration - Duration
            (CypherValue::Duration(d1), CypherValue::Duration(d2)) => {
                Ok(CypherValue::Duration(DurationValue::new(
                    d1.months - d2.months,
                    d1.days - d2.days,
                    d1.seconds - d2.seconds,
                    d1.nanos - d2.nanos,
                )))
            }

            // Date - Duration
            (CypherValue::Date(date), CypherValue::Duration(dur)) => {
                // Same as Date + Duration but negate the duration first
                let neg_dur =
                    DurationValue::new(-dur.months, -dur.days, -dur.seconds, -dur.nanos);
                self.add(
                    CypherValue::Date(date),
                    CypherValue::Duration(neg_dur),
                )
            }

            // ExtendedDate - Duration
            (CypherValue::ExtendedDate(date), CypherValue::Duration(dur)) => {
                // Same as ExtendedDate + Duration but negate the duration first
                let neg_dur =
                    DurationValue::new(-dur.months, -dur.days, -dur.seconds, -dur.nanos);
                self.add(
                    CypherValue::ExtendedDate(date),
                    CypherValue::Duration(neg_dur),
                )
            }

            // LocalTime - Duration
            (CypherValue::LocalTime(time), CypherValue::Duration(dur)) => {
                let neg_dur =
                    DurationValue::new(-dur.months, -dur.days, -dur.seconds, -dur.nanos);
                self.add(
                    CypherValue::LocalTime(time),
                    CypherValue::Duration(neg_dur),
                )
            }

            // Time - Duration
            (CypherValue::Time(time), CypherValue::Duration(dur)) => {
                let neg_dur =
                    DurationValue::new(-dur.months, -dur.days, -dur.seconds, -dur.nanos);
                self.add(CypherValue::Time(time), CypherValue::Duration(neg_dur))
            }

            // LocalDateTime - Duration
            (CypherValue::LocalDateTime(dt), CypherValue::Duration(dur)) => {
                let neg_dur =
                    DurationValue::new(-dur.months, -dur.days, -dur.seconds, -dur.nanos);
                self.add(
                    CypherValue::LocalDateTime(dt),
                    CypherValue::Duration(neg_dur),
                )
            }

            // DateTime - Duration
            (CypherValue::DateTime(dt), CypherValue::Duration(dur)) => {
                let neg_dur =
                    DurationValue::new(-dur.months, -dur.days, -dur.seconds, -dur.nanos);
                self.add(
                    CypherValue::DateTime(dt),
                    CypherValue::Duration(neg_dur),
                )
            }

            // DateTime - DateTime → Duration
            (CypherValue::DateTime(dt1), CypherValue::DateTime(dt2)) => {
                let diff = dt1.datetime.signed_duration_since(dt2.datetime);
                Ok(CypherValue::Duration(DurationValue::new(
                    0,
                    0,
                    diff.num_seconds(),
                    (diff.num_nanoseconds().unwrap_or(0) % 1_000_000_000) as i32,
                )))
            }

            // LocalDateTime - LocalDateTime → Duration
            (CypherValue::LocalDateTime(dt1), CypherValue::LocalDateTime(dt2)) => {
                let diff = dt1.datetime.signed_duration_since(dt2.datetime);
                Ok(CypherValue::Duration(DurationValue::new(
                    0,
                    0,
                    diff.num_seconds(),
                    (diff.num_nanoseconds().unwrap_or(0) % 1_000_000_000) as i32,
                )))
            }

            // Date - Date → Duration (in days)
            (CypherValue::Date(d1), CypherValue::Date(d2)) => {
                let diff = d1.date.signed_duration_since(d2.date);
                Ok(CypherValue::Duration(DurationValue::new(
                    0,
                    diff.num_days(),
                    0,
                    0,
                )))
            }

            // ExtendedDate - ExtendedDate → Duration (in days)
            (CypherValue::ExtendedDate(d1), CypherValue::ExtendedDate(d2)) => {
                use crate::result::extended_temporal::ExtendedDate;
                let ext1 = ExtendedDate::from_ymd(d1.year, d1.month as u32, d1.day as u32)
                    .ok_or_else(|| ExecutionError::InvalidArgument("Invalid extended date".to_string()))?;
                let ext2 = ExtendedDate::from_ymd(d2.year, d2.month as u32, d2.day as u32)
                    .ok_or_else(|| ExecutionError::InvalidArgument("Invalid extended date".to_string()))?;
                let diff_days = (ext1.to_epoch_days() - ext2.to_epoch_days()) as i64;
                Ok(CypherValue::Duration(DurationValue::new(0, diff_days, 0, 0)))
            }

            // ExtendedDate - Date → Duration (in days)
            (CypherValue::ExtendedDate(d1), CypherValue::Date(d2)) => {
                use crate::result::extended_temporal::ExtendedDate;
                let ext1 = ExtendedDate::from_ymd(d1.year, d1.month as u32, d1.day as u32)
                    .ok_or_else(|| ExecutionError::InvalidArgument("Invalid extended date".to_string()))?;
                let ext2 = ExtendedDate::from_chrono(d2.date);
                let diff_days = (ext1.to_epoch_days() - ext2.to_epoch_days()) as i64;
                Ok(CypherValue::Duration(DurationValue::new(0, diff_days, 0, 0)))
            }

            // Date - ExtendedDate → Duration (in days)
            (CypherValue::Date(d1), CypherValue::ExtendedDate(d2)) => {
                use crate::result::extended_temporal::ExtendedDate;
                let ext1 = ExtendedDate::from_chrono(d1.date);
                let ext2 = ExtendedDate::from_ymd(d2.year, d2.month as u32, d2.day as u32)
                    .ok_or_else(|| ExecutionError::InvalidArgument("Invalid extended date".to_string()))?;
                let diff_days = (ext1.to_epoch_days() - ext2.to_epoch_days()) as i64;
                Ok(CypherValue::Duration(DurationValue::new(0, diff_days, 0, 0)))
            }

            // LocalTime - LocalTime → Duration
            (CypherValue::LocalTime(t1), CypherValue::LocalTime(t2)) => {
                let nanos1 = t1.time.num_seconds_from_midnight() as i64 * 1_000_000_000
                    + t1.time.nanosecond() as i64;
                let nanos2 = t2.time.num_seconds_from_midnight() as i64 * 1_000_000_000
                    + t2.time.nanosecond() as i64;
                let diff_nanos = nanos1 - nanos2;
                Ok(CypherValue::Duration(DurationValue::new(
                    0,
                    0,
                    diff_nanos / 1_000_000_000,
                    (diff_nanos % 1_000_000_000) as i32,
                )))
            }

            // Time - Time → Duration
            (CypherValue::Time(t1), CypherValue::Time(t2)) => {
                let nanos1 = t1.time.num_seconds_from_midnight() as i64 * 1_000_000_000
                    + t1.time.nanosecond() as i64;
                let nanos2 = t2.time.num_seconds_from_midnight() as i64 * 1_000_000_000
                    + t2.time.nanosecond() as i64;
                let diff_nanos = nanos1 - nanos2;
                Ok(CypherValue::Duration(DurationValue::new(
                    0,
                    0,
                    diff_nanos / 1_000_000_000,
                    (diff_nanos % 1_000_000_000) as i32,
                )))
            }

            (a, b) => Err(ExecutionError::Type(format!(
                "Cannot subtract {} from {}",
                b.type_name(),
                a.type_name()
            ))),
        }
    }

    fn multiply(&self, left: CypherValue, right: CypherValue) -> ExecutionResult<CypherValue> {
        match (left, right) {
            (CypherValue::Integer(a), CypherValue::Integer(b)) => Ok(CypherValue::Integer(a * b)),
            (CypherValue::Float(a), CypherValue::Float(b)) => Ok(CypherValue::Float(a * b)),
            (CypherValue::Integer(a), CypherValue::Float(b)) => {
                Ok(CypherValue::Float(a as f64 * b))
            }
            (CypherValue::Float(a), CypherValue::Integer(b)) => {
                Ok(CypherValue::Float(a * b as f64))
            }
            // Duration * number
            (CypherValue::Duration(d), CypherValue::Integer(n)) => {
                // Identity case - preserve exact values
                if n == 1 {
                    return Ok(CypherValue::Duration(d));
                }
                // Multiply all components, let DurationValue::new handle normalization
                let total_nanos = d.seconds * 1_000_000_000 + d.nanos as i64;
                let scaled_nanos = total_nanos * n;
                let seconds = scaled_nanos / 1_000_000_000;
                let nanos = (scaled_nanos % 1_000_000_000) as i32;
                Ok(CypherValue::Duration(DurationValue::new(
                    d.months * n,
                    d.days * n,
                    seconds,
                    nanos,
                )))
            }
            (CypherValue::Duration(d), CypherValue::Float(n)) => {
                // Identity case - preserve exact values
                if n == 1.0 {
                    return Ok(CypherValue::Duration(d));
                }
                // Multiply components by float, cascading fractional parts to smaller units
                // Neo4j uses avg days/month (365.2425/12 = 30.436875) and 86400 secs/day
                const AVG_DAYS_PER_MONTH: f64 = 30.436875;
                const SECS_PER_DAY: f64 = 86400.0;
                const NANOS_PER_SEC: f64 = 1_000_000_000.0;

                let scaled_months = d.months as f64 * n;
                let months = scaled_months.trunc() as i64;
                let frac_months = scaled_months.fract();

                let scaled_days = d.days as f64 * n + frac_months * AVG_DAYS_PER_MONTH;
                let days = scaled_days.trunc() as i64;
                let frac_days = scaled_days.fract();

                let total_secs = d.seconds as f64 + d.nanos as f64 / NANOS_PER_SEC;
                let scaled_secs = total_secs * n + frac_days * SECS_PER_DAY;
                let seconds = scaled_secs.trunc() as i64;
                // Smart rounding for nanoseconds to handle floating-point precision loss
                let nanos = nanos_from_float((scaled_secs.fract()) * NANOS_PER_SEC);

                Ok(CypherValue::Duration(DurationValue::new(months, days, seconds, nanos)))
            }
            // number * Duration (commutative)
            (CypherValue::Integer(n), CypherValue::Duration(d)) => {
                // Identity case - preserve exact values
                if n == 1 {
                    return Ok(CypherValue::Duration(d));
                }
                let total_nanos = d.seconds * 1_000_000_000 + d.nanos as i64;
                let scaled_nanos = total_nanos * n;
                let seconds = scaled_nanos / 1_000_000_000;
                let nanos = (scaled_nanos % 1_000_000_000) as i32;
                Ok(CypherValue::Duration(DurationValue::new(
                    d.months * n,
                    d.days * n,
                    seconds,
                    nanos,
                )))
            }
            (CypherValue::Float(n), CypherValue::Duration(d)) => {
                // Identity case - preserve exact values
                if n == 1.0 {
                    return Ok(CypherValue::Duration(d));
                }
                // Same logic as Duration * Float (commutative)
                const AVG_DAYS_PER_MONTH: f64 = 30.436875;
                const SECS_PER_DAY: f64 = 86400.0;
                const NANOS_PER_SEC: f64 = 1_000_000_000.0;

                let scaled_months = d.months as f64 * n;
                let months = scaled_months.trunc() as i64;
                let frac_months = scaled_months.fract();

                let scaled_days = d.days as f64 * n + frac_months * AVG_DAYS_PER_MONTH;
                let days = scaled_days.trunc() as i64;
                let frac_days = scaled_days.fract();

                let total_secs = d.seconds as f64 + d.nanos as f64 / NANOS_PER_SEC;
                let scaled_secs = total_secs * n + frac_days * SECS_PER_DAY;
                let seconds = scaled_secs.trunc() as i64;
                // Smart rounding for nanoseconds to handle floating-point precision loss
                let nanos = nanos_from_float((scaled_secs.fract()) * NANOS_PER_SEC);

                Ok(CypherValue::Duration(DurationValue::new(months, days, seconds, nanos)))
            }
            (a, b) => Err(ExecutionError::Type(format!(
                "Cannot multiply {} and {}",
                a.type_name(),
                b.type_name()
            ))),
        }
    }

    fn divide(&self, left: CypherValue, right: CypherValue) -> ExecutionResult<CypherValue> {
        match (left, right) {
            // Integer division by zero returns null
            (_, CypherValue::Integer(0)) => Ok(CypherValue::Null),
            // Float division by zero returns NaN or Infinity (IEEE 754 behavior)
            // Let Rust's f64 division handle it naturally
            // Per openCypher spec: Integer / Integer returns Integer (truncated toward zero)
            (CypherValue::Integer(a), CypherValue::Integer(b)) => Ok(CypherValue::Integer(a / b)),
            (CypherValue::Float(a), CypherValue::Float(b)) => Ok(CypherValue::Float(a / b)),
            (CypherValue::Integer(a), CypherValue::Float(b)) => {
                Ok(CypherValue::Float(a as f64 / b))
            }
            (CypherValue::Float(a), CypherValue::Integer(b)) => {
                Ok(CypherValue::Float(a / b as f64))
            }
            // Duration / number - use cascading fractional parts
            (CypherValue::Duration(d), CypherValue::Integer(n)) => {
                if n == 0 {
                    return Ok(CypherValue::Null);
                }
                // Identity case - preserve exact values
                if n == 1 {
                    return Ok(CypherValue::Duration(d));
                }
                const AVG_DAYS_PER_MONTH: f64 = 30.436875;
                const SECS_PER_DAY: f64 = 86400.0;
                const NANOS_PER_SEC: f64 = 1_000_000_000.0;

                let divisor = n as f64;
                let scaled_months = d.months as f64 / divisor;
                let months = scaled_months.trunc() as i64;
                let frac_months = scaled_months.fract();

                let scaled_days = d.days as f64 / divisor + frac_months * AVG_DAYS_PER_MONTH;
                let days = scaled_days.trunc() as i64;
                let frac_days = scaled_days.fract();

                let total_secs = d.seconds as f64 + d.nanos as f64 / NANOS_PER_SEC;
                let scaled_secs = total_secs / divisor + frac_days * SECS_PER_DAY;
                let seconds = scaled_secs.trunc() as i64;
                // Smart rounding for nanoseconds to handle floating-point precision loss
                let nanos = nanos_from_float((scaled_secs.fract()) * NANOS_PER_SEC);

                Ok(CypherValue::Duration(DurationValue::new(months, days, seconds, nanos)))
            }
            (CypherValue::Duration(d), CypherValue::Float(n)) => {
                if n == 0.0 {
                    return Ok(CypherValue::Null);
                }
                // Identity case - preserve exact values
                if n == 1.0 {
                    return Ok(CypherValue::Duration(d));
                }
                const AVG_DAYS_PER_MONTH: f64 = 30.436875;
                const SECS_PER_DAY: f64 = 86400.0;
                const NANOS_PER_SEC: f64 = 1_000_000_000.0;

                let scaled_months = d.months as f64 / n;
                let months = scaled_months.trunc() as i64;
                let frac_months = scaled_months.fract();

                let scaled_days = d.days as f64 / n + frac_months * AVG_DAYS_PER_MONTH;
                let days = scaled_days.trunc() as i64;
                let frac_days = scaled_days.fract();

                let total_secs = d.seconds as f64 + d.nanos as f64 / NANOS_PER_SEC;
                let scaled_secs = total_secs / n + frac_days * SECS_PER_DAY;
                let seconds = scaled_secs.trunc() as i64;
                // Smart rounding for nanoseconds to handle floating-point precision loss
                let nanos = nanos_from_float((scaled_secs.fract()) * NANOS_PER_SEC);

                Ok(CypherValue::Duration(DurationValue::new(months, days, seconds, nanos)))
            }
            (a, b) => Err(ExecutionError::Type(format!(
                "Cannot divide {} by {}",
                a.type_name(),
                b.type_name()
            ))),
        }
    }

    fn modulo(&self, left: CypherValue, right: CypherValue) -> ExecutionResult<CypherValue> {
        match (left, right) {
            // Modulo by zero returns null
            (_, CypherValue::Integer(0)) => Ok(CypherValue::Null),
            (_, CypherValue::Float(0.0)) => Ok(CypherValue::Null),
            (CypherValue::Integer(a), CypherValue::Integer(b)) => Ok(CypherValue::Integer(a % b)),
            (CypherValue::Float(a), CypherValue::Float(b)) => Ok(CypherValue::Float(a % b)),
            (CypherValue::Integer(a), CypherValue::Float(b)) => {
                Ok(CypherValue::Float(a as f64 % b))
            }
            (CypherValue::Float(a), CypherValue::Integer(b)) => {
                Ok(CypherValue::Float(a % b as f64))
            }
            (a, b) => Err(ExecutionError::Type(format!(
                "Cannot compute {} % {}",
                a.type_name(),
                b.type_name()
            ))),
        }
    }

    fn power(&self, left: CypherValue, right: CypherValue) -> ExecutionResult<CypherValue> {
        // Cypher's power operator always returns Float
        match (left, right) {
            (CypherValue::Integer(a), CypherValue::Integer(b)) => {
                Ok(CypherValue::Float((a as f64).powf(b as f64)))
            }
            (CypherValue::Float(a), CypherValue::Float(b)) => Ok(CypherValue::Float(a.powf(b))),
            (CypherValue::Integer(a), CypherValue::Float(b)) => {
                Ok(CypherValue::Float((a as f64).powf(b)))
            }
            (CypherValue::Float(a), CypherValue::Integer(b)) => {
                Ok(CypherValue::Float(a.powf(b as f64)))
            }
            (a, b) => Err(ExecutionError::Type(format!(
                "Cannot compute {} ^ {}",
                a.type_name(),
                b.type_name()
            ))),
        }
    }

    fn regex_match(&self, left: CypherValue, right: CypherValue) -> ExecutionResult<CypherValue> {
        match (left, right) {
            (CypherValue::String(text), CypherValue::String(pattern)) => {
                let re = Regex::new(&pattern)
                    .map_err(|e| ExecutionError::InvalidRegex(e.to_string()))?;
                Ok(CypherValue::Boolean(re.is_match(&text)))
            }
            (a, b) => Err(ExecutionError::Type(format!(
                "Cannot regex match {} with {}",
                a.type_name(),
                b.type_name()
            ))),
        }
    }

    fn evaluate_unary_op(
        &self,
        op: &UnaryOperator,
        expr: &Expression,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        let value = self.evaluate(expr, ctx)?;

        if value.is_null() {
            return Ok(CypherValue::Null);
        }

        match op {
            UnaryOperator::Not => Ok(CypherValue::Boolean(!value.is_truthy())),
            UnaryOperator::Negate => match value {
                CypherValue::Integer(i) => Ok(CypherValue::Integer(-i)),
                CypherValue::Float(f) => Ok(CypherValue::Float(-f)),
                _ => Err(ExecutionError::Type(format!(
                    "Cannot negate {}",
                    value.type_name()
                ))),
            },
            UnaryOperator::Positive => match value {
                CypherValue::Integer(_) | CypherValue::Float(_) => Ok(value),
                _ => Err(ExecutionError::Type(format!(
                    "Cannot apply + to {}",
                    value.type_name()
                ))),
            },
        }
    }

    fn evaluate_function_call(
        &self,
        call: &FunctionCall,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        let args: ExecutionResult<Vec<_>> = call
            .arguments
            .iter()
            .map(|arg| self.evaluate(arg, ctx))
            .collect();
        let args = args?;

        let func_name = call.full_name().to_lowercase();

        // Special handling for startNode/endNode - these need graph access to get full node data
        if func_name == "startnode" {
            return self.evaluate_start_node(&args);
        }
        if func_name == "endnode" {
            return self.evaluate_end_node(&args);
        }

        // Special handling for labels() - check if node still exists
        if func_name == "labels" {
            if args.len() != 1 {
                return Err(ExecutionError::InvalidArgument(
                    "labels() requires exactly 1 argument".to_string(),
                ));
            }
            match &args[0] {
                CypherValue::Node(node) => {
                    // Check if node still exists in the graph
                    if self.graph.get_node(node.id).is_none() {
                        return Err(ExecutionError::NodeNotFound(node.id));
                    }
                    // Node exists, call the regular labels() function
                }
                CypherValue::Null => {} // labels(null) returns null
                _ => {} // Other types will be handled by the function itself
            }
        }

        // Use full_name() to include namespace (e.g., "date.truncate")
        self.functions
            .call(&func_name, args, call.distinct)
    }

    /// Evaluate startNode(relationship) - fetches the full start node from the graph.
    fn evaluate_start_node(&self, args: &[CypherValue]) -> ExecutionResult<CypherValue> {
        if args.len() != 1 {
            return Err(ExecutionError::InvalidArgument(
                "startNode() requires exactly 1 argument".to_string(),
            ));
        }

        match &args[0] {
            CypherValue::Relationship(r) => {
                // Fetch the full node from the graph
                if let Some(node) = self.graph.get_node(r.start_node_id) {
                    Ok(CypherValue::Node(NodeValue::from_graph_node(node)))
                } else {
                    // Node doesn't exist, return null
                    Ok(CypherValue::Null)
                }
            }
            CypherValue::Null => Ok(CypherValue::Null),
            other => Err(ExecutionError::Type(format!(
                "startNode() requires a relationship, got {}",
                other.type_name()
            ))),
        }
    }

    /// Evaluate endNode(relationship) - fetches the full end node from the graph.
    fn evaluate_end_node(&self, args: &[CypherValue]) -> ExecutionResult<CypherValue> {
        if args.len() != 1 {
            return Err(ExecutionError::InvalidArgument(
                "endNode() requires exactly 1 argument".to_string(),
            ));
        }

        match &args[0] {
            CypherValue::Relationship(r) => {
                // Fetch the full node from the graph
                if let Some(node) = self.graph.get_node(r.end_node_id) {
                    Ok(CypherValue::Node(NodeValue::from_graph_node(node)))
                } else {
                    // Node doesn't exist, return null
                    Ok(CypherValue::Null)
                }
            }
            CypherValue::Null => Ok(CypherValue::Null),
            other => Err(ExecutionError::Type(format!(
                "endNode() requires a relationship, got {}",
                other.type_name()
            ))),
        }
    }

    fn evaluate_case(
        &self,
        case: &CaseExpression,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        if let Some(operand) = &case.operand {
            // Simple CASE: CASE x WHEN v1 THEN r1 WHEN v2 THEN r2 ELSE default END
            let test_value = self.evaluate(operand, ctx)?;
            for (when, then) in &case.alternatives {
                let when_value = self.evaluate(when, ctx)?;
                if test_value == when_value {
                    return self.evaluate(then, ctx);
                }
            }
        } else {
            // Searched CASE: CASE WHEN cond1 THEN r1 WHEN cond2 THEN r2 ELSE default END
            for (when, then) in &case.alternatives {
                let cond = self.evaluate(when, ctx)?;
                if cond.is_truthy() {
                    return self.evaluate(then, ctx);
                }
            }
        }

        // No match, return default or null
        if let Some(default) = &case.default {
            self.evaluate(default, ctx)
        } else {
            Ok(CypherValue::Null)
        }
    }

    fn evaluate_list_comprehension(
        &self,
        comp: &ListComprehension,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        let list_value = self.evaluate(&comp.list, ctx)?;
        let list = match list_value {
            CypherValue::List(l) => l,
            CypherValue::Null => return Ok(CypherValue::Null),
            _ => {
                return Err(ExecutionError::Type(format!(
                    "Expected list, got {}",
                    list_value.type_name()
                )))
            }
        };

        let mut result = Vec::new();
        let mut local_ctx = ctx.clone();

        for item in list {
            local_ctx.set(&comp.variable, item);

            // Apply filter if present
            if let Some(filter) = &comp.filter {
                let filter_result = self.evaluate(filter, &local_ctx)?;
                if !filter_result.is_truthy() {
                    continue;
                }
            }

            // Apply projection if present, otherwise use the item
            let value = if let Some(projection) = &comp.projection {
                self.evaluate(projection, &local_ctx)?
            } else {
                local_ctx
                    .get(&comp.variable)
                    .cloned()
                    .unwrap_or(CypherValue::Null)
            };

            result.push(value);
        }

        Ok(CypherValue::List(result))
    }

    fn evaluate_pattern_comprehension(
        &self,
        comp: &PatternComprehension,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {

        // Modify the pattern to include the path variable if specified
        let pattern = if let Some(path_var) = &comp.variable {
            let mut modified_pattern = comp.pattern.clone();
            // Set the path variable on the first (and typically only) pattern part
            if let Some(part) = modified_pattern.parts.get_mut(0) {
                part.variable = Some(path_var.clone());
            }
            modified_pattern
        } else {
            comp.pattern.clone()
        };

        // Use pattern matcher to find all matches for the pattern
        let matcher = PatternMatcher::new(self.graph, self.functions);
        let matches = matcher.match_pattern(&pattern, ctx, false)?;

        let mut result = Vec::new();

        // For each match, evaluate the projection expression
        for record in matches.iter() {
            let mut local_ctx = ctx.clone();

            // Bind all pattern variables from the match to the local context
            for (var, value) in record.iter() {
                local_ctx.set(var, value.clone());
            }

            // Apply filter if present
            if let Some(filter) = &comp.filter {
                let filter_result = self.evaluate(filter, &local_ctx)?;
                if !filter_result.is_truthy() {
                    continue;
                }
            }

            // Evaluate projection
            let value = self.evaluate(&comp.projection, &local_ctx)?;
            result.push(value);
        }

        Ok(CypherValue::List(result))
    }

    fn evaluate_string_predicate(
        &self,
        base: &Expression,
        op: &StringPredicateOp,
        pattern: &Expression,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        let base_val = self.evaluate(base, ctx)?;
        let pattern_val = self.evaluate(pattern, ctx)?;

        match (base_val, pattern_val) {
            (CypherValue::Null, _) | (_, CypherValue::Null) => Ok(CypherValue::Null),
            (CypherValue::String(text), CypherValue::String(pat)) => {
                let result = match op {
                    StringPredicateOp::StartsWith => text.starts_with(&pat),
                    StringPredicateOp::EndsWith => text.ends_with(&pat),
                    StringPredicateOp::Contains => text.contains(&pat),
                };
                Ok(CypherValue::Boolean(result))
            }
            // Type mismatches return NULL in Cypher semantics
            (_, _) => Ok(CypherValue::Null),
        }
    }

    fn evaluate_in_predicate(
        &self,
        value: &Expression,
        list: &Expression,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        let val = self.evaluate(value, ctx)?;
        let list_val = self.evaluate(list, ctx)?;

        match list_val {
            CypherValue::Null => Ok(CypherValue::Null),
            CypherValue::List(items) => {
                if val.is_null() {
                    // null IN [1, 2, 3] = null
                    // null IN [] = false
                    if items.is_empty() {
                        return Ok(CypherValue::Boolean(false));
                    }
                    return Ok(CypherValue::Null);
                }

                // Check for exact match or null in list using three-valued logic
                let mut has_uncertain = false;
                for item in &items {
                    // Use three-valued equality logic
                    match equals_with_null_handling(val.clone(), item.clone())? {
                        CypherValue::Boolean(true) => {
                            // Found exact match
                            return Ok(CypherValue::Boolean(true));
                        }
                        CypherValue::Null => {
                            // Uncertain - equality might be true
                            has_uncertain = true;
                        }
                        _ => {} // false, continue searching
                    }
                }

                // No exact match found
                if has_uncertain {
                    // Some comparisons were uncertain
                    Ok(CypherValue::Null)
                } else {
                    // No match and no uncertainty
                    Ok(CypherValue::Boolean(false))
                }
            }
            CypherValue::String(s) => {
                // IN can also work with strings: 'a' IN 'abc' = true
                if val.is_null() {
                    return Ok(CypherValue::Null);
                }
                match val {
                    CypherValue::String(needle) => {
                        Ok(CypherValue::Boolean(s.contains(needle.as_str())))
                    }
                    _ => Ok(CypherValue::Boolean(false)),
                }
            }
            _ => Err(ExecutionError::Type(format!(
                "IN requires a list or string, got {}",
                list_val.type_name()
            ))),
        }
    }

    fn evaluate_null_predicate(
        &self,
        value: &Expression,
        is_null: bool,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        let val = self.evaluate(value, ctx)?;
        let result = val.is_null() == is_null;
        Ok(CypherValue::Boolean(result))
    }

    fn evaluate_reduce(
        &self,
        reduce: &ReduceExpression,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        let list_value = self.evaluate(&reduce.list, ctx)?;
        let list = match list_value {
            CypherValue::List(l) => l,
            CypherValue::Null => return Ok(CypherValue::Null),
            _ => {
                return Err(ExecutionError::Type(format!(
                    "Expected list, got {}",
                    list_value.type_name()
                )))
            }
        };

        let mut acc = self.evaluate(&reduce.init, ctx)?;
        let mut local_ctx = ctx.clone();

        for item in list {
            local_ctx.set(&reduce.accumulator, acc);
            local_ctx.set(&reduce.variable, item);
            acc = self.evaluate(&reduce.expression, &local_ctx)?;
        }

        Ok(acc)
    }

    fn evaluate_quantifier(
        &self,
        quant: &QuantifierExpression,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<CypherValue> {
        let list_value = self.evaluate(&quant.list, ctx)?;
        let list = match list_value {
            CypherValue::List(l) => l,
            CypherValue::Null => return Ok(CypherValue::Null),
            _ => {
                return Err(ExecutionError::Type(format!(
                    "Expected list, got {}",
                    list_value.type_name()
                )))
            }
        };

        let mut local_ctx = ctx.clone();
        let mut true_count = 0;
        let mut false_count = 0;
        let mut null_count = 0;

        for item in &list {
            local_ctx.set(&quant.variable, item.clone());
            let cond = self.evaluate(&quant.condition, &local_ctx)?;

            match cond {
                CypherValue::Boolean(true) => true_count += 1,
                CypherValue::Boolean(false) => false_count += 1,
                CypherValue::Null => null_count += 1,
                // Non-boolean values are coerced via truthiness
                _ => {
                    if cond.is_truthy() {
                        true_count += 1;
                    } else {
                        false_count += 1;
                    }
                }
            }
        }

        // Apply three-valued logic for quantifiers per Cypher specification
        let result = match quant.quantifier {
            QuantifierType::All => {
                // ALL: true if all are true, false if any is false, null if no false but some null
                if false_count > 0 {
                    CypherValue::Boolean(false)
                } else if null_count > 0 {
                    CypherValue::Null
                } else {
                    CypherValue::Boolean(true)
                }
            }
            QuantifierType::Any => {
                // ANY: true if any is true, false if all are false, null if no true but some null
                if true_count > 0 {
                    CypherValue::Boolean(true)
                } else if null_count > 0 {
                    CypherValue::Null
                } else {
                    CypherValue::Boolean(false)
                }
            }
            QuantifierType::None => {
                // NONE: true if all are false, false if any is true, null if no true but some null
                if true_count > 0 {
                    CypherValue::Boolean(false)
                } else if null_count > 0 {
                    CypherValue::Null
                } else {
                    CypherValue::Boolean(true)
                }
            }
            QuantifierType::Single => {
                // SINGLE: true if exactly one is true
                // If we have more than one true, return false (even if there are nulls)
                // If we have exactly 1 true with no nulls, return true
                // If we have exactly 1 true with nulls, return null (nulls might add more trues)
                // If we have 0 true with nulls, return null (one null might be true)
                // If we have 0 true with no nulls, return false
                if true_count > 1 {
                    CypherValue::Boolean(false)
                } else if true_count == 1 && null_count == 0 {
                    CypherValue::Boolean(true)
                } else if null_count > 0 {
                    CypherValue::Null
                } else {
                    CypherValue::Boolean(false)
                }
            }
        };

        Ok(result)
    }
}

/// Handle equality with proper three-valued logic for lists and maps containing null.
fn equals_with_null_handling(left: CypherValue, right: CypherValue) -> ExecutionResult<CypherValue> {
    match (&left, &right) {
        // Special handling for lists: if lists contain null, apply three-valued logic
        (CypherValue::List(left_items), CypherValue::List(right_items)) => {
            if left_items.len() != right_items.len() {
                return Ok(CypherValue::Boolean(false));
            }

            let mut has_null = false;
            let mut all_match = true;

            for (l, r) in left_items.iter().zip(right_items.iter()) {
                if l.is_null() || r.is_null() {
                    has_null = true;
                    continue;
                }

                match equals_with_null_handling(l.clone(), r.clone())? {
                    CypherValue::Boolean(false) => {
                        all_match = false;
                        break;
                    }
                    CypherValue::Null => {
                        has_null = true;
                    }
                    _ => {}
                }
            }

            if !all_match {
                return Ok(CypherValue::Boolean(false));
            }

            if has_null {
                Ok(CypherValue::Null)
            } else {
                Ok(CypherValue::Boolean(true))
            }
        }
        // Special handling for maps
        (CypherValue::Map(left_map), CypherValue::Map(right_map)) => {
            let left_keys: std::collections::HashSet<_> = left_map.keys().collect();
            let right_keys: std::collections::HashSet<_> = right_map.keys().collect();

            if left_keys != right_keys {
                return Ok(CypherValue::Boolean(false));
            }

            let mut has_null = false;

            for key in left_keys {
                let left_val = left_map.get(key)
                    .ok_or_else(|| ExecutionError::Internal(format!("Key '{}' missing from left map", key)))?;
                let right_val = right_map.get(key)
                    .ok_or_else(|| ExecutionError::Internal(format!("Key '{}' missing from right map", key)))?;

                if left_val.is_null() || right_val.is_null() {
                    has_null = true;
                    continue;
                }

                match equals_with_null_handling(left_val.clone(), right_val.clone())? {
                    CypherValue::Boolean(false) => {
                        return Ok(CypherValue::Boolean(false));
                    }
                    CypherValue::Null => {
                        has_null = true;
                    }
                    _ => {}
                }
            }

            if has_null {
                Ok(CypherValue::Null)
            } else {
                Ok(CypherValue::Boolean(true))
            }
        }
        (CypherValue::Null, _) | (_, CypherValue::Null) => Ok(CypherValue::Null),
        _ => Ok(CypherValue::Boolean(left == right)),
    }
}

/// Helper function to determine the number of days in a given month
fn days_in_month(year: i32, month: u32) -> u32 {
    match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        2 => {
            if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {
                29
            } else {
                28
            }
        }
        _ => 30,
    }
}

/// Convert nanoseconds float to i32, using smart rounding to handle floating-point precision loss.
/// Rounds to nearest integer only if very close (within 0.01ns), otherwise truncates.
/// This handles cases like 1.9999999 -> 2 (floating point error) but 0.5 -> 0 (true fraction).
fn nanos_from_float(nanos_float: f64) -> i32 {
    let truncated = nanos_float.trunc();
    let frac = nanos_float.fract().abs();
    const EPSILON: f64 = 0.01; // 0.01 nanoseconds tolerance for floating point error

    if frac < EPSILON {
        truncated as i32
    } else if frac > 1.0 - EPSILON {
        // Very close to next integer, round up (or down for negative)
        (truncated + if nanos_float >= 0.0 { 1.0 } else { -1.0 }) as i32
    } else {
        // True fractional value, truncate
        truncated as i32
    }
}

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

    fn make_evaluator() -> (PropertyGraph, FunctionRegistry) {
        (PropertyGraph::new(), FunctionRegistry::new())
    }

    #[test]
    fn test_literal_evaluation() {
        let (graph, funcs) = make_evaluator();
        let eval = Evaluator::new(&graph, &funcs);
        let ctx = ExecutionContext::new();

        assert_eq!(
            eval.evaluate(&Expression::integer(42), &ctx).unwrap(),
            CypherValue::Integer(42)
        );
        assert_eq!(
            eval.evaluate(&Expression::string("hello"), &ctx).unwrap(),
            CypherValue::String("hello".to_string())
        );
        assert_eq!(
            eval.evaluate(&Expression::boolean(true), &ctx).unwrap(),
            CypherValue::Boolean(true)
        );
    }

    #[test]
    fn test_arithmetic() {
        let (graph, funcs) = make_evaluator();
        let eval = Evaluator::new(&graph, &funcs);
        let ctx = ExecutionContext::new();

        let expr = Expression::BinaryOp(
            Box::new(Expression::integer(10)),
            BinaryOperator::Add,
            Box::new(Expression::integer(5)),
        );
        assert_eq!(
            eval.evaluate(&expr, &ctx).unwrap(),
            CypherValue::Integer(15)
        );

        let expr = Expression::BinaryOp(
            Box::new(Expression::integer(10)),
            BinaryOperator::Multiply,
            Box::new(Expression::integer(5)),
        );
        assert_eq!(
            eval.evaluate(&expr, &ctx).unwrap(),
            CypherValue::Integer(50)
        );
    }

    #[test]
    fn test_comparison() {
        let (graph, funcs) = make_evaluator();
        let eval = Evaluator::new(&graph, &funcs);
        let ctx = ExecutionContext::new();

        let expr = Expression::BinaryOp(
            Box::new(Expression::integer(10)),
            BinaryOperator::GreaterThan,
            Box::new(Expression::integer(5)),
        );
        assert_eq!(
            eval.evaluate(&expr, &ctx).unwrap(),
            CypherValue::Boolean(true)
        );

        let expr = Expression::BinaryOp(
            Box::new(Expression::integer(5)),
            BinaryOperator::Equal,
            Box::new(Expression::integer(5)),
        );
        assert_eq!(
            eval.evaluate(&expr, &ctx).unwrap(),
            CypherValue::Boolean(true)
        );
    }

    #[test]
    fn test_null_propagation() {
        let (graph, funcs) = make_evaluator();
        let eval = Evaluator::new(&graph, &funcs);
        let ctx = ExecutionContext::new();

        let expr = Expression::BinaryOp(
            Box::new(Expression::null()),
            BinaryOperator::Add,
            Box::new(Expression::integer(5)),
        );
        assert_eq!(eval.evaluate(&expr, &ctx).unwrap(), CypherValue::Null);
    }

    #[test]
    fn test_variable_lookup() {
        let (graph, funcs) = make_evaluator();
        let eval = Evaluator::new(&graph, &funcs);
        let mut ctx = ExecutionContext::new();
        ctx.set("x", CypherValue::Integer(42));

        assert_eq!(
            eval.evaluate(&Expression::var("x"), &ctx).unwrap(),
            CypherValue::Integer(42)
        );
    }

    #[test]
    fn test_list_comprehension() {
        let (graph, funcs) = make_evaluator();
        let eval = Evaluator::new(&graph, &funcs);
        let ctx = ExecutionContext::new();

        // [x IN [1, 2, 3] | x * 2]
        let comp = ListComprehension {
            variable: "x".to_string(),
            list: Box::new(Expression::List(vec![
                Expression::integer(1),
                Expression::integer(2),
                Expression::integer(3),
            ])),
            filter: None,
            projection: Some(Box::new(Expression::BinaryOp(
                Box::new(Expression::var("x")),
                BinaryOperator::Multiply,
                Box::new(Expression::integer(2)),
            ))),
        };

        let result = eval
            .evaluate(&Expression::ListComprehension(comp), &ctx)
            .unwrap();
        assert_eq!(
            result,
            CypherValue::List(vec![
                CypherValue::Integer(2),
                CypherValue::Integer(4),
                CypherValue::Integer(6),
            ])
        );
    }

    #[test]
    fn test_pattern_predicate_no_match() {
        // Test pattern predicate returns false when pattern doesn't match
        let (graph, funcs) = make_evaluator();
        let eval = Evaluator::new(&graph, &funcs);
        let ctx = ExecutionContext::new();

        // Create a pattern: (a)-[:KNOWS]->(b)
        let pattern = Pattern::single(PatternPart::new(PatternElement::Chain(
            PatternChain::with_chain(
                NodePattern::with_variable("a"),
                vec![(
                    RelationshipPattern::with_type(RelationshipDirection::Outgoing, "KNOWS"),
                    NodePattern::with_variable("b"),
                )],
            ),
        )));

        let expr = Expression::PatternPredicate(pattern);
        let result = eval.evaluate(&expr, &ctx).unwrap();
        // No nodes or relationships exist, so pattern shouldn't match
        assert_eq!(result, CypherValue::Boolean(false));
    }

    #[test]
    fn test_pattern_predicate_with_match() {
        use crate::graph::PropertyValue;
        use indexmap::IndexMap;

        // Create a graph with nodes and a relationship
        let mut graph = PropertyGraph::new();
        let funcs = FunctionRegistry::new();

        // Create two nodes
        let mut props = IndexMap::new();
        props.insert(
            "name".to_string(),
            PropertyValue::String("Alice".to_string()),
        );
        graph.create_node(vec!["Person".to_string()], props);

        let mut props = IndexMap::new();
        props.insert("name".to_string(), PropertyValue::String("Bob".to_string()));
        graph.create_node(vec!["Person".to_string()], props);

        // Create a KNOWS relationship between them
        graph
            .create_relationship(1, 2, "KNOWS", IndexMap::new())
            .unwrap();

        let eval = Evaluator::new(&graph, &funcs);
        let ctx = ExecutionContext::new();

        // Create a pattern: (a)-[:KNOWS]->(b)
        let pattern = Pattern::single(PatternPart::new(PatternElement::Chain(
            PatternChain::with_chain(
                NodePattern::with_variable("a"),
                vec![(
                    RelationshipPattern::with_type(RelationshipDirection::Outgoing, "KNOWS"),
                    NodePattern::with_variable("b"),
                )],
            ),
        )));

        let expr = Expression::PatternPredicate(pattern);
        let result = eval.evaluate(&expr, &ctx).unwrap();
        // Pattern should match since we have nodes connected by KNOWS relationship
        assert_eq!(result, CypherValue::Boolean(true));
    }

    #[test]
    fn test_pattern_predicate_with_bound_variable() {
        use crate::graph::PropertyValue;
        use crate::result::NodeValue;
        use indexmap::IndexMap;

        // Create a graph with nodes and relationships
        let mut graph = PropertyGraph::new();
        let funcs = FunctionRegistry::new();

        // Create three nodes
        let mut props = IndexMap::new();
        props.insert(
            "name".to_string(),
            PropertyValue::String("Alice".to_string()),
        );
        graph.create_node(vec!["Person".to_string()], props);

        let mut props = IndexMap::new();
        props.insert("name".to_string(), PropertyValue::String("Bob".to_string()));
        graph.create_node(vec!["Person".to_string()], props);

        let mut props = IndexMap::new();
        props.insert(
            "name".to_string(),
            PropertyValue::String("Charlie".to_string()),
        );
        graph.create_node(vec!["Person".to_string()], props);

        // Alice KNOWS Bob
        graph
            .create_relationship(1, 2, "KNOWS", IndexMap::new())
            .unwrap();

        let eval = Evaluator::new(&graph, &funcs);

        // Test with bound variable 'a' to Alice (node id 1)
        let mut ctx = ExecutionContext::new();
        let alice_node = NodeValue::from_graph_node(graph.get_node(1).unwrap());
        ctx.set("a", CypherValue::Node(alice_node));

        // Create a pattern: (a)-[:KNOWS]->(b) where 'a' is bound
        let pattern = Pattern::single(PatternPart::new(PatternElement::Chain(
            PatternChain::with_chain(
                NodePattern::with_variable("a"),
                vec![(
                    RelationshipPattern::with_type(RelationshipDirection::Outgoing, "KNOWS"),
                    NodePattern::with_variable("b"),
                )],
            ),
        )));

        let expr = Expression::PatternPredicate(pattern.clone());
        let result = eval.evaluate(&expr, &ctx).unwrap();
        // Alice knows Bob, so pattern should match
        assert_eq!(result, CypherValue::Boolean(true));

        // Test with bound variable 'a' to Charlie (node id 3)
        let mut ctx = ExecutionContext::new();
        let charlie_node = NodeValue::from_graph_node(graph.get_node(3).unwrap());
        ctx.set("a", CypherValue::Node(charlie_node));

        let result = eval.evaluate(&expr, &ctx).unwrap();
        // Charlie doesn't know anyone, so pattern should not match
        assert_eq!(result, CypherValue::Boolean(false));
    }

    #[test]
    fn test_temporal_arithmetic() {
        use chrono::{Datelike, FixedOffset, NaiveDate, TimeZone};

        let (graph, funcs) = make_evaluator();
        let eval = Evaluator::new(&graph, &funcs);
        let _ctx = ExecutionContext::new();

        // Test Duration + Duration
        let dur1 = DurationValue::new(0, 5, 0, 0);
        let dur2 = DurationValue::new(0, 3, 0, 0);
        let result = eval
            .add(
                CypherValue::Duration(dur1),
                CypherValue::Duration(dur2),
            )
            .unwrap();
        match result {
            CypherValue::Duration(d) => {
                assert_eq!(d.days, 8);
            }
            _ => panic!("Expected Duration"),
        }

        // Test Date + Duration
        let date = DateValue::new(NaiveDate::from_ymd_opt(1984, 10, 11).unwrap());
        let dur = DurationValue::new(149, 14, 0, 0); // 12 years + 5 months = 149 months, 14 days
        let result = eval
            .add(CypherValue::Date(date), CypherValue::Duration(dur))
            .unwrap();
        match result {
            CypherValue::Date(d) => {
                assert_eq!(d.date.year(), 1997);
                assert_eq!(d.date.month(), 3);
                assert_eq!(d.date.day(), 25);
            }
            _ => panic!("Expected Date, got {:?}", result),
        }

        // Test DateTime - DateTime
        let dt1 = DateTimeValue::new(
            FixedOffset::east_opt(0)
                .unwrap()
                .from_utc_datetime(&chrono::DateTime::from_timestamp(100, 0).unwrap().naive_utc()),
        );
        let dt2 = DateTimeValue::new(
            FixedOffset::east_opt(0)
                .unwrap()
                .from_utc_datetime(&chrono::DateTime::from_timestamp(50, 0).unwrap().naive_utc()),
        );
        let result = eval
            .subtract(CypherValue::DateTime(dt1), CypherValue::DateTime(dt2))
            .unwrap();
        match result {
            CypherValue::Duration(d) => {
                assert_eq!(d.seconds, 50);
            }
            _ => panic!("Expected Duration"),
        }
    }
}
