//! Query executor module.
//!
//! This module provides the query execution engine that evaluates
//! parsed Cypher queries against a property graph.

mod context;
mod evaluator;
pub mod functions;
mod pattern_matcher;
mod subquery;
pub mod undo;

pub use context::{ExecutionContext, ResultSet};
pub use evaluator::Evaluator;
pub use functions::{clear_temporal_cache, FunctionRegistry};
pub use pattern_matcher::PatternMatcher;
pub use subquery::SubqueryExecutor;

use std::cmp::Ordering;
use std::collections::HashMap;

use indexmap::IndexMap;

use crate::ast::*;
use crate::error::{ExecutionError, ExecutionResult};
use crate::graph::{EdgeLike, GraphBackend, NodeLike, PropertyValue};
use crate::procedures::ProcedureRegistry;
use crate::result::{CypherValue, NodeValue, PathValue, QueryResult, QueryStats, Record, RelationshipValue};

/// Query executor that runs Cypher queries against a property graph.
///
/// This executor is generic over any graph backend that implements the `GraphBackend` trait,
/// allowing for pluggable storage implementations (e.g., petgraph, NetworKit).
pub struct QueryExecutor<'a, G: GraphBackend> {
    pub(crate) graph: &'a mut G,
    functions: FunctionRegistry,
    procedures: ProcedureRegistry<G>,
    parameters: HashMap<String, CypherValue>,
}

impl<'a, G: GraphBackend> QueryExecutor<'a, G> {
    /// Create a new query executor.
    pub fn new(graph: &'a mut G) -> Self {
        let mut procedures = ProcedureRegistry::new();
        Self::register_standard_procedures(&mut procedures);

        Self {
            graph,
            functions: FunctionRegistry::new(),
            procedures,
            parameters: HashMap::new(),
        }
    }

    /// Register standard procedures (db.*, dbms.*).
    fn register_standard_procedures(registry: &mut ProcedureRegistry<G>) {
        use crate::procedures::{DbLabels, DbPropertyKeys, DbRelationshipTypes, DbmsProcedures};

        // Register db.* procedures
        registry.register(Box::new(DbLabels));
        registry.register(Box::new(DbRelationshipTypes));
        registry.register(Box::new(DbPropertyKeys));

        // Register dbms.procedures() - it will include all procedures including itself
        // We pass an empty list here but dbms.procedures() should really query the registry at call time
        // For now, we'll update it to get the full list after registration
        registry.register(Box::new(DbmsProcedures::new(vec![])));

        // Get the updated list and replace dbms.procedures() with the full list
        let full_list = registry.list_procedures();
        registry.register(Box::new(DbmsProcedures::new(full_list)));
    }

    /// Set a query parameter.
    pub fn set_parameter(&mut self, name: impl Into<String>, value: impl Into<CypherValue>) {
        self.parameters.insert(name.into(), value.into());
    }

    /// Register a custom procedure for testing or extension.
    pub fn register_procedure(&mut self, procedure: Box<dyn crate::procedures::Procedure<G>>) {
        self.procedures.register(procedure);
    }

    /// Execute a statement.
    pub fn execute_statement(&mut self, stmt: &Statement) -> ExecutionResult<QueryResult> {
        match stmt {
            Statement::Query(query) => self.execute_query(query),
            Statement::SchemaCommand(cmd) => {
                use crate::ast::{ConstraintType, SchemaCommand};
                match cmd {
                    SchemaCommand::CreateIndex(idx) => {
                        self.graph.create_index(&idx.label, &idx.property);
                    }
                    SchemaCommand::DropIndex(idx) => {
                        self.graph.drop_index(&idx.label, &idx.property);
                    }
                    SchemaCommand::CreateConstraint(c) if c.constraint_type == ConstraintType::Unique => {
                        self.graph.create_unique_constraint(&c.label, &c.property);
                    }
                    SchemaCommand::DropConstraint(c) if c.constraint_type == ConstraintType::Unique => {
                        self.graph.drop_unique_constraint(&c.label, &c.property);
                    }
                    _ => {}
                }
                Ok(QueryResult::new())
            }
        }
    }

    /// Execute a query.
    pub fn execute_query(&mut self, query: &Query) -> ExecutionResult<QueryResult> {
        // Clear the temporal cache at the start of each query to ensure
        // temporal functions like datetime() return consistent values within the query
        clear_temporal_cache();

        if query.parts.len() == 1 {
            return self.execute_single_query(&query.parts[0]);
        }

        // Handle UNION
        let mut combined = QueryResult::new();
        for (i, part) in query.parts.iter().enumerate() {
            let result = self.execute_single_query(part)?;

            if i == 0 {
                combined.columns = result.columns.clone();
            }

            for row in result.rows {
                combined.rows.push(row);
            }

            combined.stats.merge(&result.stats);
        }

        if !query.union_all {
            // Remove duplicates for UNION (not UNION ALL)
            let mut seen = std::collections::HashSet::new();
            combined.rows.retain(|row| {
                let key = format!("{:?}", row);
                seen.insert(key)
            });
        }

        Ok(combined)
    }

    /// Execute a single query (sequence of clauses).
    fn execute_single_query(&mut self, query: &SingleQuery) -> ExecutionResult<QueryResult> {

        let mut ctx = ExecutionContext::with_parameters(self.parameters.clone());
        let mut result_set = ResultSet::single_empty();
        let mut stats = QueryStats::default();
        let mut return_clause = None;

        for clause in query.clauses.iter() {
            match clause {
                Clause::Match(m) => {
                    result_set = self.execute_match(m, &ctx, false, result_set)?;
                }
                Clause::OptionalMatch(m) => {
                    result_set = self.execute_match(m, &ctx, true, result_set)?;
                }
                Clause::Create(c) => {
                    let (new_set, new_stats) = self.execute_create(c, &ctx, result_set)?;
                    result_set = new_set;
                    stats.merge(&new_stats);
                }
                Clause::Merge(m) => {
                    let (new_set, new_stats) = self.execute_merge(m, &ctx, result_set)?;
                    result_set = new_set;
                    stats.merge(&new_stats);
                }
                Clause::Delete(d) => {
                    result_set = self.execute_delete(d, &ctx, result_set, &mut stats)?;
                }
                Clause::Set(s) => {
                    let (new_set, new_stats) = self.execute_set(s, &ctx, result_set)?;
                    result_set = new_set;
                    stats.merge(&new_stats);
                }
                Clause::Remove(r) => {
                    let (new_set, new_stats) = self.execute_remove(r, &ctx, result_set)?;
                    result_set = new_set;
                    stats.merge(&new_stats);
                }
                Clause::With(w) => {
                    result_set = self.execute_with(w, &ctx, result_set)?;
                }
                Clause::Return(r) => {
                    return_clause = Some(r.clone());
                }
                Clause::Unwind(u) => {
                    result_set = self.execute_unwind(u, &ctx, result_set)?;
                }
                Clause::Foreach(f) => {
                    let (new_set, new_stats) = self.execute_foreach(f, &ctx, result_set)?;
                    result_set = new_set;
                    stats.merge(&new_stats);
                }
                Clause::Call(c) => {
                    result_set = self.execute_call(c, &ctx, result_set)?;
                }
            }

            // Update context with current bindings for next clause
            // Replace bindings to respect WITH clause scoping (remove out-of-scope variables)
            if let Some(record) = result_set.records.first() {
                ctx.replace_bindings_from_record(record);
            }
        }

        // Finalize stats once for the entire query (after all clauses merged)
        stats.finalize();

        // Execute RETURN if present
        if let Some(ret) = return_clause {
            let result = self.execute_return(&ret.projection, result_set)?;
            Ok(QueryResult {
                columns: result.0,
                rows: result.1,
                stats,
            })
        } else {
            // No explicit RETURN clause
            // For standalone CALL (e.g., "CALL db.labels()"), return procedure results
            // For other queries without RETURN, return empty result
            if !result_set.is_empty() && query.clauses.len() == 1 {
                if let Some(Clause::Call(_)) = query.clauses.first() {
                    // Standalone CALL - return procedure results
                    let columns: Vec<String> = result_set.records()
                        .first()
                        .map(|r| r.columns().to_vec())
                        .unwrap_or_default();

                    Ok(QueryResult {
                        columns,
                        rows: result_set.records().to_vec(),
                        stats,
                    })
                } else {
                    // Other query without RETURN - return empty
                    Ok(QueryResult {
                        columns: vec![],
                        rows: vec![],
                        stats,
                    })
                }
            } else {
                // Multiple clauses or empty result - return empty
                Ok(QueryResult {
                    columns: vec![],
                    rows: vec![],
                    stats,
                })
            }
        }
    }

    /// Execute a MATCH clause.
    fn execute_match(
        &self,
        clause: &MatchClause,
        ctx: &ExecutionContext,
        optional: bool,
        current: ResultSet,
    ) -> ExecutionResult<ResultSet> {
        let matcher = PatternMatcher::new(self.graph, &self.functions);
        let evaluator = Evaluator::new(self.graph, &self.functions);

        let mut results = ResultSet::new();

        for record in current.iter() {
            let mut local_ctx = ctx.clone();
            local_ctx.load_record(record);

            let pattern_results = matcher.match_pattern(&clause.pattern, &local_ctx, optional)?;

            // Handle case where pattern has no matches for OPTIONAL MATCH
            if pattern_results.is_empty() || pattern_results.records.iter().all(|r| r.is_empty()) {
                if optional {
                    // For OPTIONAL MATCH with no matches, bind all NEW pattern variables to NULL
                    // (don't overwrite already-bound variables)
                    let mut null_record = record.clone();
                    for var_name in clause.pattern.variable_names() {
                        if null_record.get(&var_name).is_none() {
                            null_record.add(var_name, CypherValue::Null);
                        }
                    }
                    results.add(null_record);
                }
                continue;
            }

            // Track if we added any matches for this record (for OPTIONAL MATCH)
            let mut added_match_for_record = false;

            for pattern_record in pattern_results.iter() {
                let mut merged = record.clone();
                merged.merge(pattern_record.clone());

                // Apply WHERE clause
                if let Some(where_expr) = &clause.where_clause {
                    local_ctx.load_record(&merged);
                    let result = evaluator.evaluate(where_expr, &local_ctx)?;
                    if !result.is_truthy() {
                        // WHERE clause failed, skip this match
                        continue;
                    }
                }

                results.add(merged);
                added_match_for_record = true;
            }

            // If OPTIONAL MATCH and no matches were added (all WHERE clauses failed),
            // add a null record for NEW variables only
            if optional && !added_match_for_record {
                let mut null_record = record.clone();
                for var_name in clause.pattern.variable_names() {
                    if null_record.get(&var_name).is_none() {
                        null_record.add(var_name, CypherValue::Null);
                    }
                }
                results.add(null_record);
            }
        }

        Ok(results)
    }

    /// Execute a CREATE clause.
    fn execute_create(
        &mut self,
        clause: &CreateClause,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<(ResultSet, QueryStats)> {
        let mut stats = QueryStats::default();
        let mut results = ResultSet::new();

        // We need to collect all the data to create first, then create the nodes/rels
        // This avoids borrow issues with the evaluator

        for record in current.iter() {
            let mut local_ctx = ctx.clone();
            local_ctx.load_record(record);
            let mut new_record = record.clone();

            for part in &clause.pattern.parts {
                if let PatternElement::Chain(chain) = &part.element {
                    // Check if start node variable is already bound
                    let start_node_id = if let Some(var) = &chain.start.variable {
                        if let Some(CypherValue::Node(node)) = local_ctx.get(var) {
                            // Variable already bound - use existing node
                            node.id
                        } else {
                            // Variable not bound - create new node
                            let start_props = self.evaluate_properties(&chain.start.properties, &local_ctx)?;
                            // Check UNIQUE constraints before creating
                            self.graph.check_node_constraints(&chain.start.labels, &start_props)?;
                            // Count only non-null properties (Cypher doesn't count null as "setting" a property)
                            // Check which labels are new to the graph BEFORE creating the node
                            let new_label_types: Vec<String> = chain.start.labels.iter()
                                .filter(|l| self.graph.nodes_with_label(l).is_empty())
                                .cloned()
                                .collect();
                            let start_node_id = self.graph.create_node(chain.start.labels.clone(), start_props.clone());
                            stats.nodes_created += 1;
                            stats.nodes_created_ids.insert(start_node_id);
                            // Track unique label types (labels_added will be set in finalize())
                            // Only count labels that were not already present in the graph
                            for label in new_label_types {
                                stats.unique_labels.insert(label);
                            }
                            // Track unique properties set (only non-null values count)
                            for (prop_name, prop_val) in &start_props {
                                if !matches!(prop_val, PropertyValue::Null) {
                                    stats.unique_node_properties.insert((start_node_id, prop_name.clone()));
                                }
                            }

                            let node_value = NodeValue::from_graph_node(self.graph.get_node(start_node_id).unwrap());
                            new_record.add(var.clone(), CypherValue::Node(node_value.clone()));
                            local_ctx.set(var.clone(), CypherValue::Node(node_value));
                            start_node_id
                        }
                    } else {
                        // No variable - create new node
                        let start_props = self.evaluate_properties(&chain.start.properties, &local_ctx)?;
                        // Check UNIQUE constraints before creating
                        self.graph.check_node_constraints(&chain.start.labels, &start_props)?;
                        // Check which labels are new to the graph BEFORE creating the node
                        let new_label_types: Vec<String> = chain.start.labels.iter()
                            .filter(|l| self.graph.nodes_with_label(l).is_empty())
                            .cloned()
                            .collect();
                        let start_node_id = self.graph.create_node(chain.start.labels.clone(), start_props.clone());
                        stats.nodes_created += 1;
                        stats.nodes_created_ids.insert(start_node_id);
                        // Track unique label types (labels_added will be set in finalize())
                        // Only count labels that were not already present in the graph
                        for label in new_label_types {
                            stats.unique_labels.insert(label);
                        }
                        // Track unique properties set (only non-null values count)
                        for (prop_name, prop_val) in &start_props {
                            if !matches!(prop_val, PropertyValue::Null) {
                                stats.unique_node_properties.insert((start_node_id, prop_name.clone()));
                            }
                        }
                        start_node_id
                    };

                    let mut prev_node_id = start_node_id;

                    // Create remaining nodes and relationships
                    for (rel, node) in &chain.chain {
                        let rel_props = self.evaluate_properties(&rel.properties, &local_ctx)?;

                        // Check if node variable is already bound
                        let new_node_id = if let Some(var) = &node.variable {
                            if let Some(CypherValue::Node(existing_node)) = local_ctx.get(var) {
                                // Variable already bound - use existing node
                                existing_node.id
                            } else {
                                // Variable not bound - create new node
                                let node_props = self.evaluate_properties(&node.properties, &local_ctx)?;
                                // Check UNIQUE constraints before creating
                                self.graph.check_node_constraints(&node.labels, &node_props)?;
                                // Check which labels are new to the graph BEFORE creating the node
                                let new_label_types: Vec<String> = node.labels.iter()
                                    .filter(|l| self.graph.nodes_with_label(l).is_empty())
                                    .cloned()
                                    .collect();
                                let new_node_id = self.graph.create_node(node.labels.clone(), node_props.clone());
                                stats.nodes_created += 1;
                                stats.nodes_created_ids.insert(new_node_id);
                                // Track unique label types (labels_added will be set in finalize())
                                // Only count labels that were not already present in the graph
                                for label in new_label_types {
                                    stats.unique_labels.insert(label);
                                }
                                // Track unique properties set
                                for (prop_name, prop_val) in &node_props {
                                    if !matches!(prop_val, PropertyValue::Null) {
                                        stats.unique_node_properties.insert((new_node_id, prop_name.clone()));
                                    }
                                }

                                let node_value = NodeValue::from_graph_node(self.graph.get_node(new_node_id).unwrap());
                                new_record.add(var.clone(), CypherValue::Node(node_value.clone()));
                                local_ctx.set(var.clone(), CypherValue::Node(node_value));
                                new_node_id
                            }
                        } else {
                            // No variable - create new node
                            let node_props = self.evaluate_properties(&node.properties, &local_ctx)?;
                            // Check UNIQUE constraints before creating
                            self.graph.check_node_constraints(&node.labels, &node_props)?;
                            // Check which labels are new to the graph BEFORE creating the node
                            let new_label_types: Vec<String> = node.labels.iter()
                                .filter(|l| self.graph.nodes_with_label(l).is_empty())
                                .cloned()
                                .collect();
                            let new_node_id = self.graph.create_node(node.labels.clone(), node_props.clone());
                            stats.nodes_created += 1;
                            stats.nodes_created_ids.insert(new_node_id);
                            // Track unique label types (labels_added will be set in finalize())
                            // Only count labels that were not already present in the graph
                            for label in new_label_types {
                                stats.unique_labels.insert(label);
                            }
                            // Track unique properties set
                            for (prop_name, prop_val) in &node_props {
                                if !matches!(prop_val, PropertyValue::Null) {
                                    stats.unique_node_properties.insert((new_node_id, prop_name.clone()));
                                }
                            }
                            new_node_id
                        };

                        // Create relationship
                        let (start, end) = match rel.direction {
                            RelationshipDirection::Outgoing => (prev_node_id, new_node_id),
                            RelationshipDirection::Incoming => (new_node_id, prev_node_id),
                            RelationshipDirection::Both => (prev_node_id, new_node_id),
                        };

                        let rel_type = rel.types.first().cloned().unwrap_or_else(|| "RELATED".to_string());

                        let edge_id = self.graph.create_relationship(start, end, rel_type, rel_props.clone())?;
                        stats.relationships_created += 1;
                        stats.relationships_created_ids.insert(edge_id);
                        // Track unique relationship properties set
                        for (prop_name, prop_val) in &rel_props {
                            if !matches!(prop_val, PropertyValue::Null) {
                                stats.unique_rel_properties.insert((edge_id, prop_name.clone()));
                            }
                        }

                        if let Some(var) = &rel.variable {
                            let edge = self.graph.get_edge(edge_id).unwrap();
                            let rel_value = RelationshipValue::from_graph_edge(edge, start, end);
                            new_record.add(var.clone(), CypherValue::Relationship(rel_value));
                        }

                        prev_node_id = new_node_id;
                    }
                }
            }

            results.add(new_record);
        }

        stats.finalize();
        Ok((results, stats))
    }

    /// Evaluate properties without holding a reference to the graph.
    fn evaluate_properties(
        &self,
        props: &Option<Properties>,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<IndexMap<String, PropertyValue>> {
        let evaluator = Evaluator::new(self.graph, &self.functions);
        let mut result = IndexMap::new();

        if let Some(props) = props {
            match props {
                Properties::Map(map) => {
                    for (key, expr) in map {
                        let value = evaluator.evaluate(expr, ctx)?;
                        result.insert(key.clone(), value.into());
                    }
                }
                Properties::Parameter(name) => {
                    if let Some(CypherValue::Map(map)) = ctx.get_parameter(name) {
                        for (key, value) in map {
                            result.insert(key.clone(), value.clone().into());
                        }
                    }
                }
            }
        }

        Ok(result)
    }

    /// Execute a MERGE clause.
    fn execute_merge(
        &mut self,
        clause: &MergeClause,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<(ResultSet, QueryStats)> {
        let mut stats = QueryStats::default();
        let mut results = ResultSet::new();

        // Process each record
        for record in current.iter() {
            let mut local_ctx = ctx.clone();
            local_ctx.load_record(record);

            // Validate: MERGE cannot use null property values (can't match on null)
            let evaluator = Evaluator::new(self.graph, &self.functions);
            if let PatternElement::Chain(chain) = &clause.pattern.element {
                // Check start node properties
                if let Some(Properties::Map(map)) = &chain.start.properties {
                    for (key, expr) in map {
                        let value = evaluator.evaluate(expr, &local_ctx)?;
                        if matches!(value, CypherValue::Null) {
                            return Err(ExecutionError::ConstraintViolation(format!(
                                "Semantic error: Cannot merge node with null property value for key '{}'", key
                            )));
                        }
                    }
                }
                // Check chain node and relationship properties
                for (rel, node) in &chain.chain {
                    if let Some(Properties::Map(map)) = &rel.properties {
                        for (key, expr) in map {
                            let value = evaluator.evaluate(expr, &local_ctx)?;
                            if matches!(value, CypherValue::Null) {
                                return Err(ExecutionError::ConstraintViolation(format!(
                                    "Semantic error: Cannot merge relationship with null property value for key '{}'", key
                                )));
                            }
                        }
                    }
                    if let Some(Properties::Map(map)) = &node.properties {
                        for (key, expr) in map {
                            let value = evaluator.evaluate(expr, &local_ctx)?;
                            if matches!(value, CypherValue::Null) {
                                return Err(ExecutionError::ConstraintViolation(format!(
                                    "Cannot merge node with null property value for key '{}'", key
                                )));
                            }
                        }
                    }
                }
            }

            // Try to match the pattern - in a block so matcher is dropped before mutations
            let pattern = Pattern::single(clause.pattern.clone());
            let matches = {
                let matcher = PatternMatcher::new(self.graph, &self.functions);
                matcher.match_pattern(&pattern, &local_ctx, false)?
            };

            if matches.is_empty() {
                // Create the pattern
                let create_clause = CreateClause::new(pattern.clone());
                let (mut new_set, create_stats) = self.execute_create(&create_clause, &local_ctx, ResultSet::from_records(vec![record.clone()]))?;
                stats.merge(&create_stats);

                // Bind path variable if specified
                if let Some(path_var) = &clause.pattern.variable {
                    for r in new_set.iter_mut() {
                        // Build path from the created nodes/relationships in the record
                        if let Some(PatternElement::Chain(chain)) = pattern.parts.first().map(|p| &p.element) {
                            let path = self.build_path_from_record_simple(r, chain);
                            r.add(path_var.clone(), path);
                        }
                    }
                }

                // Execute ON CREATE if present
                if let Some(set_clause) = &clause.on_create {
                    let (final_set, set_stats) = self.execute_set(set_clause, &local_ctx, new_set)?;
                    stats.merge(&set_stats);
                    for r in final_set.iter() {
                        results.add(r.clone());
                    }
                } else {
                    for r in new_set.iter() {
                        results.add(r.clone());
                    }
                }
            } else {
                // Pattern matched - execute ON MATCH if present
                if let Some(set_clause) = &clause.on_match {
                    for match_record in matches.iter() {
                        let mut merged = record.clone();
                        merged.merge(match_record.clone());

                        // Bind path variable if specified
                        if let Some(path_var) = &clause.pattern.variable {
                            if let Some(PatternElement::Chain(chain)) = pattern.parts.first().map(|p| &p.element) {
                                let path = self.build_path_from_record_simple(&merged, chain);
                                merged.add(path_var.clone(), path);
                            }
                        }

                        let (final_set, set_stats) = self.execute_set(set_clause, &local_ctx, ResultSet::from_records(vec![merged]))?;
                        stats.merge(&set_stats);
                        for r in final_set.iter() {
                            results.add(r.clone());
                        }
                    }
                } else {
                    for match_record in matches.iter() {
                        let mut merged = record.clone();
                        merged.merge(match_record.clone());

                        // Bind path variable if specified
                        if let Some(path_var) = &clause.pattern.variable {
                            if let Some(PatternElement::Chain(chain)) = pattern.parts.first().map(|p| &p.element) {
                                let path = self.build_path_from_record_simple(&merged, chain);
                                merged.add(path_var.clone(), path);
                            }
                        }

                        results.add(merged);
                    }
                }
            }
        }

        stats.finalize();
        Ok((results, stats))
    }

    /// Execute a DELETE clause.
    /// `accumulated_stats` is passed in to allow netting of create/delete within the same query.
    fn execute_delete(
        &mut self,
        clause: &DeleteClause,
        ctx: &ExecutionContext,
        current: ResultSet,
        accumulated_stats: &mut QueryStats,
    ) -> ExecutionResult<ResultSet> {
        let evaluator = Evaluator::new(self.graph, &self.functions);
        // Use HashSet to automatically deduplicate (e.g., bidirectional patterns)
        let mut to_delete_nodes = std::collections::HashSet::new();
        let mut to_delete_rels = std::collections::HashSet::new();

        for record in current.iter() {
            let mut local_ctx = ctx.clone();
            local_ctx.load_record(record);

            for expr in &clause.expressions {
                let value = evaluator.evaluate(expr, &local_ctx)?;
                match value {
                    CypherValue::Node(n) => { to_delete_nodes.insert((n.id, clause.detach)); },
                    CypherValue::Relationship(r) => { to_delete_rels.insert(r.id); },
                    CypherValue::Path(p) => {
                        // Delete all nodes and relationships in the path
                        for rel in &p.relationships {
                            to_delete_rels.insert(rel.id);
                        }
                        for node in &p.nodes {
                            to_delete_nodes.insert((node.id, clause.detach));
                        }
                    }
                    CypherValue::Null => {}
                    _ => {
                        return Err(ExecutionError::DeleteError(format!(
                            "Cannot delete {}",
                            value.type_name()
                        )))
                    }
                }
            }
        }

        // Delete relationships first
        for rel_id in to_delete_rels {
            self.graph.delete_relationship(rel_id)?;
            // Net out: if this rel was created in the same query, decrement created count
            if accumulated_stats.relationships_created_ids.remove(&rel_id) {
                // This rel was created and deleted in the same query - net by decrementing create
                if accumulated_stats.relationships_created > 0 {
                    accumulated_stats.relationships_created -= 1;
                }
            } else {
                accumulated_stats.relationships_deleted += 1;
                accumulated_stats.relationships_deleted_ids.insert(rel_id);
            }
        }

        // Delete nodes
        for (node_id, detach) in to_delete_nodes {
            // Track unique labels before deleting
            if let Some(node) = self.graph.get_node(node_id) {
                // Only track labels removed if this node wasn't created in the same query
                if !accumulated_stats.nodes_created_ids.contains(&node_id) {
                    for label in node.labels() {
                        accumulated_stats.unique_labels_removed.insert(label.clone());
                    }
                }
            }

            if detach {
                // Collect relationship IDs before detach-delete for WAL tracking
                let rel_ids: Vec<u64> = self.graph.all_edges_for_node(node_id)
                    .into_iter()
                    .map(|(_, _, e)| e.id())
                    .filter(|&rid| !accumulated_stats.relationships_created_ids.contains(&rid))
                    .collect();
                let rel_count = self.graph.count_node_relationships(node_id);
                self.graph.detach_delete_node(node_id)?;
                accumulated_stats.relationships_deleted += rel_count;
                for rid in rel_ids {
                    accumulated_stats.relationships_deleted_ids.insert(rid);
                }
            } else {
                self.graph.delete_node(node_id)?;
            }

            // Net out: if this node was created in the same query, decrement created count
            if accumulated_stats.nodes_created_ids.remove(&node_id) {
                // This node was created and deleted in the same query - net by decrementing create
                if accumulated_stats.nodes_created > 0 {
                    accumulated_stats.nodes_created -= 1;
                }
                // Also net out the labels
                // (the labels were added when the node was created, so we need to remove them)
            } else {
                accumulated_stats.nodes_deleted += 1;
                accumulated_stats.nodes_deleted_ids.insert(node_id);
            }
        }

        Ok(current)
    }

    /// Execute a SET clause.
    fn execute_set(
        &mut self,
        clause: &SetClause,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<(ResultSet, QueryStats)> {
        // Validate that a CypherValue can be stored as a property
        fn validate_property_value(value: &CypherValue) -> ExecutionResult<()> {
            match value {
                CypherValue::Null | CypherValue::Boolean(_) | CypherValue::Integer(_)
                | CypherValue::Float(_) | CypherValue::String(_)
                | CypherValue::Date(_) | CypherValue::Time(_) | CypherValue::DateTime(_)
                | CypherValue::LocalTime(_) | CypherValue::LocalDateTime(_)
                | CypherValue::Duration(_) | CypherValue::ExtendedDate(_)
                | CypherValue::ExtendedLocalDateTime(_) => Ok(()),
                CypherValue::List(items) => {
                    // Lists are allowed, but must contain valid property values
                    for item in items {
                        validate_property_value(item)?;
                    }
                    Ok(())
                }
                CypherValue::Map(_) => {
                    Err(ExecutionError::Type("Cannot store map as property value".to_string()))
                }
                CypherValue::Node(_) | CypherValue::Relationship(_) | CypherValue::Path(_) => {
                    Err(ExecutionError::Type(format!(
                        "Cannot store {} as property value",
                        value.type_name()
                    )))
                }
            }
        }

        let mut stats = QueryStats::default();

        // Collect all set operations first
        enum SetOp {
            NodeProperty(u64, String, PropertyValue),
            EdgeProperty(u64, String, PropertyValue),
            NodeAllProperties(u64, IndexMap<String, PropertyValue>),
            NodeMergeProperties(u64, IndexMap<String, PropertyValue>),
            EdgeAllProperties(u64, IndexMap<String, PropertyValue>),
            EdgeMergeProperties(u64, IndexMap<String, PropertyValue>),
            NodeLabel(u64, String),
        }

        let mut operations: Vec<SetOp> = Vec::new();

        // Evaluate all operations in a block so evaluator is dropped before mutations
        {
            let evaluator = Evaluator::new(self.graph, &self.functions);

            for record in current.iter() {
                let mut local_ctx = ctx.clone();
                local_ctx.load_record(record);

                for item in &clause.items {
                    match item {
                        SetItem::Property(base_expr, prop, value_expr) => {
                            let base = evaluator.evaluate(base_expr, &local_ctx)?;
                            let value = evaluator.evaluate(value_expr, &local_ctx)?;

                            // Validate property value
                            validate_property_value(&value)?;

                            match base {
                                CypherValue::Node(n) => {
                                    operations.push(SetOp::NodeProperty(n.id, prop.clone(), value.into()));
                                }
                                CypherValue::Relationship(r) => {
                                    operations.push(SetOp::EdgeProperty(r.id, prop.clone(), value.into()));
                                }
                                _ => {}
                            }
                        }
                        SetItem::AllProperties(var, value_expr) => {
                            let value = evaluator.evaluate(value_expr, &local_ctx)?;
                            // Extract properties from value (can be Map, Node, or Relationship)
                            let props: Option<IndexMap<String, PropertyValue>> = match value {
                                CypherValue::Map(map) => Some(map.into_iter()
                                    .map(|(k, v)| (k, v.into()))
                                    .collect()),
                                CypherValue::Node(n) => Some(n.properties.into_iter()
                                    .map(|(k, v)| (k, v.into()))
                                    .collect()),
                                CypherValue::Relationship(r) => Some(r.properties.into_iter()
                                    .map(|(k, v)| (k, v.into()))
                                    .collect()),
                                _ => None,
                            };
                            if let Some(props) = props {
                                match local_ctx.get(var) {
                                    Some(CypherValue::Node(n)) => {
                                        operations.push(SetOp::NodeAllProperties(n.id, props));
                                    }
                                    Some(CypherValue::Relationship(r)) => {
                                        operations.push(SetOp::EdgeAllProperties(r.id, props));
                                    }
                                    _ => {}
                                }
                            }
                        }
                        SetItem::MergeProperties(var, value_expr) => {
                            let value = evaluator.evaluate(value_expr, &local_ctx)?;
                            // Extract properties from value (can be Map, Node, or Relationship)
                            let props: Option<IndexMap<String, PropertyValue>> = match value {
                                CypherValue::Map(map) => Some(map.into_iter()
                                    .map(|(k, v)| (k, v.into()))
                                    .collect()),
                                CypherValue::Node(n) => Some(n.properties.into_iter()
                                    .map(|(k, v)| (k, v.into()))
                                    .collect()),
                                CypherValue::Relationship(r) => Some(r.properties.into_iter()
                                    .map(|(k, v)| (k, v.into()))
                                    .collect()),
                                _ => None,
                            };
                            if let Some(props) = props {
                                match local_ctx.get(var) {
                                    Some(CypherValue::Node(n)) => {
                                        operations.push(SetOp::NodeMergeProperties(n.id, props));
                                    }
                                    Some(CypherValue::Relationship(r)) => {
                                        operations.push(SetOp::EdgeMergeProperties(r.id, props));
                                    }
                                    _ => {}
                                }
                            }
                        }
                        SetItem::Labels(var, labels) => {
                            if let Some(CypherValue::Node(n)) = local_ctx.get(var) {
                                for label in labels {
                                    operations.push(SetOp::NodeLabel(n.id, label.clone()));
                                }
                            }
                        }
                    }
                }
            }
        }

        // Now apply all operations
        for op in operations {
            match op {
                SetOp::NodeProperty(id, prop, value) => {
                    self.graph.set_node_property(id, prop.clone(), value)?;
                    stats.unique_node_properties.insert((id, prop));
                }
                SetOp::EdgeProperty(id, prop, value) => {
                    self.graph.set_edge_property(id, prop.clone(), value)?;
                    stats.unique_rel_properties.insert((id, prop));
                }
                SetOp::NodeAllProperties(id, props) => {
                    if let Some(node) = self.graph.get_node_mut(id) {
                        node.properties.clear();
                        for (k, v) in props {
                            node.properties.insert(k.clone(), v);
                            stats.unique_node_properties.insert((id, k));
                        }
                    }
                }
                SetOp::NodeMergeProperties(id, props) => {
                    for (k, v) in props {
                        self.graph.set_node_property(id, k.clone(), v)?;
                        stats.unique_node_properties.insert((id, k));
                    }
                }
                SetOp::EdgeAllProperties(id, props) => {
                    if let Some(edge) = self.graph.get_edge_mut(id) {
                        edge.properties.clear();
                        for (k, v) in props {
                            edge.properties.insert(k.clone(), v);
                            stats.unique_rel_properties.insert((id, k));
                        }
                    }
                }
                SetOp::EdgeMergeProperties(id, props) => {
                    for (k, v) in props {
                        self.graph.set_edge_property(id, k.clone(), v)?;
                        stats.unique_rel_properties.insert((id, k));
                    }
                }
                SetOp::NodeLabel(id, label) => {
                    // Track unique label type (labels_added will be set in finalize())
                    stats.unique_labels.insert(label.clone());
                    stats.labels_added_ids.insert((id, label.clone()));
                    self.graph.add_label(id, label)?;
                }
            }
        }

        // FIX: After SET operations, refresh node/relationship values in records
        // so that subsequent clauses see the updated values
        let mut updated_results = ResultSet::new();
        for record in current.iter() {
            let mut new_record = record.clone();

            // Refresh all Node values in the record from the updated graph
            for (key, value) in record.iter() {
                match value {
                    CypherValue::Node(node_val) => {
                        // Fetch fresh node data from graph
                        if let Some(updated_node) = self.graph.get_node(node_val.id) {
                            let fresh_node_val = NodeValue::from_graph_node(updated_node);
                            new_record.add(key.clone(), CypherValue::Node(fresh_node_val));
                        }
                    }
                    CypherValue::Relationship(rel_val) => {
                        // Fetch fresh relationship data from graph
                        if let Some(updated_edge) = self.graph.get_edge(rel_val.id) {
                            let (start, end) = self.graph.get_edge_endpoints(rel_val.id).unwrap_or((0, 0));
                            let fresh_rel_val = RelationshipValue::from_graph_edge(updated_edge, start, end);
                            new_record.add(key.clone(), CypherValue::Relationship(fresh_rel_val));
                        }
                    }
                    _ => {
                        // Other types don't need refreshing
                    }
                }
            }

            updated_results.add(new_record);
        }

        stats.finalize();
        Ok((updated_results, stats))
    }

    /// Execute a REMOVE clause.
    fn execute_remove(
        &mut self,
        clause: &RemoveClause,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<(ResultSet, QueryStats)> {
        let mut stats = QueryStats::default();

        // Collect all removals first, then apply them
        // Use HashSet to automatically deduplicate (same node might appear in multiple records)
        let mut node_props_to_remove: std::collections::HashSet<(u64, String)> = std::collections::HashSet::new();
        let mut edge_props_to_remove: std::collections::HashSet<(u64, String)> = std::collections::HashSet::new();
        let mut labels_to_remove: std::collections::HashSet<(u64, String)> = std::collections::HashSet::new();

        {
            let evaluator = Evaluator::new(self.graph, &self.functions);

            for record in current.iter() {
                let mut local_ctx = ctx.clone();
                local_ctx.load_record(record);

                for item in &clause.items {
                    match item {
                        RemoveItem::Property(base_expr, prop) => {
                            let base = evaluator.evaluate(base_expr, &local_ctx)?;
                            match base {
                                CypherValue::Node(n) => {
                                    node_props_to_remove.insert((n.id, prop.clone()));
                                }
                                CypherValue::Relationship(r) => {
                                    edge_props_to_remove.insert((r.id, prop.clone()));
                                }
                                _ => {}
                            }
                        }
                        RemoveItem::Labels(var, labels) => {
                            if let Some(CypherValue::Node(n)) = local_ctx.get(var) {
                                for label in labels {
                                    labels_to_remove.insert((n.id, label.clone()));
                                }
                            }
                        }
                    }
                }
            }
        }

        // Now apply the removals
        for (node_id, prop) in &node_props_to_remove {
            self.graph.remove_node_property(*node_id, prop)?;
        }

        for (edge_id, prop) in &edge_props_to_remove {
            self.graph.remove_edge_property(*edge_id, prop)?;
        }

        for (node_id, label) in &labels_to_remove {
            if self.graph.remove_label(*node_id, label)? {
                stats.unique_labels_removed.insert(label.clone());
                stats.labels_removed_ids.insert((*node_id, label.clone()));
            }
        }

        // After REMOVE operations, refresh node/relationship values in records
        // so that subsequent clauses see the updated values (properties removed, labels removed)
        let mut updated_results = ResultSet::new();
        for record in current.iter() {
            let mut new_record = record.clone();

            // Refresh all Node values in the record from the updated graph
            for (key, value) in record.iter() {
                match value {
                    CypherValue::Node(node_val) => {
                        // Fetch fresh node data from graph
                        if let Some(updated_node) = self.graph.get_node(node_val.id) {
                            let fresh_node_val = NodeValue::from_graph_node(updated_node);
                            new_record.add(key.clone(), CypherValue::Node(fresh_node_val));
                        }
                    }
                    CypherValue::Relationship(rel_val) => {
                        // Fetch fresh relationship data from graph
                        if let Some(updated_edge) = self.graph.get_edge(rel_val.id) {
                            let (start, end) = self.graph.get_edge_endpoints(rel_val.id).unwrap_or((0, 0));
                            let fresh_rel_val = RelationshipValue::from_graph_edge(updated_edge, start, end);
                            new_record.add(key.clone(), CypherValue::Relationship(fresh_rel_val));
                        }
                    }
                    _ => {
                        // Other types don't need refreshing
                    }
                }
            }

            updated_results.add(new_record);
        }

        Ok((updated_results, stats))
    }

    /// Execute a WITH clause.
    fn execute_with(
        &self,
        clause: &WithClause,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<ResultSet> {
        let evaluator = Evaluator::new(self.graph, &self.functions);

        // Check if projection has aggregation
        let has_aggregation = self.has_aggregation(&clause.projection);

        if has_aggregation {
            // WITH aggregation: Project FIRST (to compute aggregates), then apply WHERE
            // This allows WHERE to reference aggregated variables like count(*)
            let projected = self.project_results(&clause.projection, &evaluator, ctx, current)?;

            // Apply WHERE to projected results
            if let Some(where_expr) = &clause.where_clause {
                let mut filtered = ResultSet::new();
                for record in projected.iter() {
                    let mut local_ctx = ctx.clone();
                    local_ctx.load_record(record);  // ← Now has projected vars including aggregates!
                    let result = evaluator.evaluate(where_expr, &local_ctx)?;
                    if result.is_truthy() {
                        filtered.add(record.clone());
                    }
                }
                Ok(filtered)
            } else {
                Ok(projected)
            }
        } else {
            // No aggregation: Project first, then apply WHERE
            // In Cypher, WITH ... WHERE can reference variables defined in the WITH projection
            // (e.g., WITH x + 1 AS y WHERE y > 0)

            // Apply WHERE to projected results
            // Merge with current context so WHERE can access both projected AND original variables
            if let Some(where_expr) = &clause.where_clause {
                // When DISTINCT is used, we need to:
                // 1. Project WITHOUT DISTINCT to maintain 1:1 correspondence with original records
                // 2. Apply WHERE using both projected and original vars
                // 3. Apply DISTINCT to the filtered results
                let mut projection_no_distinct = clause.projection.clone();
                let has_distinct = projection_no_distinct.distinct;
                projection_no_distinct.distinct = false;

                let projected = self.project_results(&projection_no_distinct, &evaluator, ctx, current.clone())?;

                let mut filtered = ResultSet::new();
                for (proj_record, orig_record) in projected.iter().zip(current.iter()) {
                    let mut local_ctx = ctx.clone();
                    // Load original record first, then projected (projected takes precedence)
                    local_ctx.load_record(orig_record);
                    local_ctx.load_record(proj_record);
                    let result = evaluator.evaluate(where_expr, &local_ctx)?;
                    if result.is_truthy() {
                        // FIX: Merge projected vars with WHERE-referenced vars
                        // This preserves OPTIONAL MATCH variables (like 'r' in triadic selection)
                        // that are needed for WHERE filtering but not explicitly projected
                        let mut merged_record = proj_record.clone();
                        let where_vars = Self::extract_expression_variables(where_expr);
                        for var in where_vars {
                            // Only add if not already in projected record
                            if merged_record.get(&var).is_none() {
                                if let Some(value) = orig_record.get(&var) {
                                    merged_record.add(var, value.clone());
                                }
                            }
                        }
                        filtered.add(merged_record);
                    }
                }

                // Now apply DISTINCT if it was requested
                if has_distinct {
                    filtered = filtered.distinct();
                }

                Ok(filtered)
            } else {
                let projected = self.project_results(&clause.projection, &evaluator, ctx, current.clone())?;
                Ok(projected)
            }
        }
    }

    /// Execute RETURN and return (columns, rows).
    fn execute_return(
        &self,
        projection: &Projection,
        current: ResultSet,
    ) -> ExecutionResult<(Vec<String>, Vec<Record>)> {
        let ctx = ExecutionContext::with_parameters(self.parameters.clone());
        let evaluator = Evaluator::new(self.graph, &self.functions);

        let projected = self.project_results(projection, &evaluator, &ctx, current)?;

        // Get column names
        let columns = match &projection.items {
            ProjectionItems::All => {
                if let Some(first) = projected.records.first() {
                    first.columns().to_vec()
                } else {
                    vec![]
                }
            }
            ProjectionItems::Explicit(items) => {
                items
                    .iter()
                    .map(|item| {
                        // Use alias if set, otherwise original_text, otherwise generate from expression
                        item.alias.clone()
                            .or_else(|| item.original_text.clone())
                            .unwrap_or_else(|| Self::expression_to_column_name(&item.expression))
                    })
                    .collect()
            }
        };

        let rows: Vec<Record> = projected.records;
        Ok((columns, rows))
    }

    /// Project results according to a projection.
    fn project_results(
        &self,
        projection: &Projection,
        evaluator: &Evaluator<G>,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<ResultSet> {
        let mut results = Vec::new();

        // Check for aggregations
        let has_aggregation = self.has_aggregation(projection);

        if has_aggregation {
            // Group by non-aggregated expressions and compute aggregates
            results = self.execute_aggregation(projection, evaluator, ctx, current)?;

            // For aggregation results, compute ORDER BY sort keys
            // ORDER BY aggregate expressions should match projected columns
            if let Some(order_items) = &projection.order_by {
                // Build a map from expression string representation to column name
                let expr_to_column = self.build_projection_expr_map(projection);

                for record in &mut results {
                    for (i, item) in order_items.iter().enumerate() {
                        let sort_key = self.resolve_order_by_for_aggregation(
                            &item.expression,
                            record,
                            &expr_to_column,
                            evaluator,
                            ctx,
                        );
                        record.add(format!("__sort_{}__", i), sort_key);
                    }
                }
            }
        } else {
            for record in current.iter() {
                let mut local_ctx = ctx.clone();
                local_ctx.load_record(record);

                let mut new_record = self.project_record(projection, evaluator, &local_ctx, record)?;

                // Pre-compute ORDER BY sort keys in a context that has BOTH:
                // - Original incoming variables (for non-alias references like "sum" from previous WITH)
                // - Newly projected aliases (for aliases defined in this projection)
                if let Some(order_items) = &projection.order_by {
                    // Create a merged context: original record + projected values
                    let mut merged_ctx = ctx.clone();
                    merged_ctx.load_record(record); // Original incoming variables
                    merged_ctx.load_record(&new_record); // Overwrite/add projected aliases

                    for (i, item) in order_items.iter().enumerate() {
                        let sort_key = evaluator.evaluate(&item.expression, &merged_ctx).unwrap_or(CypherValue::Null);
                        new_record.add(format!("__sort_{}__", i), sort_key);
                    }
                }

                results.push(new_record);
            }
        }

        // Apply DISTINCT
        let mut result_set = ResultSet::from_records(results);
        if projection.distinct {
            result_set = result_set.distinct();
        }

        // Apply ORDER BY using pre-computed sort keys
        if let Some(order_items) = &projection.order_by {
            result_set.records.sort_by(|a, b| {
                for (i, item) in order_items.iter().enumerate() {
                    let key_name = format!("__sort_{}__", i);
                    let val_a = a.get(&key_name).cloned().unwrap_or(CypherValue::Null);
                    let val_b = b.get(&key_name).cloned().unwrap_or(CypherValue::Null);

                    // Use order_compare for total ordering (handles NaN, null, and different types)
                    let cmp = val_a.order_compare(&val_b);
                    let cmp = match item.direction {
                        OrderDirection::Descending => cmp.reverse(),
                        OrderDirection::Ascending => cmp,
                    };

                    if cmp != Ordering::Equal {
                        return cmp;
                    }
                }
                Ordering::Equal
            });

            // Remove the internal sort key fields
            for record in &mut result_set.records {
                for i in 0..order_items.len() {
                    record.remove(&format!("__sort_{}__", i));
                }
            }
        }

        // Apply SKIP
        if let Some(skip_expr) = &projection.skip {
            let skip_val = evaluator.evaluate(skip_expr, ctx)?;
            match skip_val {
                CypherValue::Integer(n) => {
                    if n < 0 {
                        return Err(ExecutionError::InvalidArgument(
                            format!("SKIP: Cannot skip a negative number of rows: {}", n)
                        ));
                    }
                    result_set.records = result_set.records.into_iter().skip(n as usize).collect();
                }
                CypherValue::Float(_) => {
                    return Err(ExecutionError::InvalidArgument(
                        "Syntax error: SKIP requires an integer value, got float".to_string()
                    ));
                }
                CypherValue::Null => {
                    // SKIP null is allowed and means skip 0
                }
                _ => {
                    return Err(ExecutionError::InvalidArgument(
                        format!("SKIP: Expected integer, got {}", skip_val.type_name())
                    ));
                }
            }
        }

        // Apply LIMIT
        if let Some(limit_expr) = &projection.limit {
            let limit_val = evaluator.evaluate(limit_expr, ctx)?;
            match limit_val {
                CypherValue::Integer(n) => {
                    if n < 0 {
                        return Err(ExecutionError::InvalidArgument(
                            format!("LIMIT: Cannot limit to a negative number of rows: {}", n)
                        ));
                    }
                    result_set.records = result_set.records.into_iter().take(n as usize).collect();
                }
                CypherValue::Float(_) => {
                    return Err(ExecutionError::InvalidArgument(
                        "Syntax error: LIMIT requires an integer value, got float".to_string()
                    ));
                }
                CypherValue::Null => {
                    // LIMIT null is allowed and means no limit
                }
                _ => {
                    return Err(ExecutionError::InvalidArgument(
                        format!("LIMIT: Expected integer, got {}", limit_val.type_name())
                    ));
                }
            }
        }

        Ok(result_set)
    }

    /// Project a single record.
    fn project_record(
        &self,
        projection: &Projection,
        evaluator: &Evaluator<G>,
        ctx: &ExecutionContext,
        record: &Record,
    ) -> ExecutionResult<Record> {
        let mut new_record = Record::new();

        match &projection.items {
            ProjectionItems::All => {
                // Clone record but filter out internal variables (those starting with __)
                for (key, value) in record.iter() {
                    if !key.starts_with("__") {
                        new_record.add(key.clone(), value.clone());
                    }
                }
            }
            ProjectionItems::Explicit(items) => {
                // FIX: WITH * is sometimes parsed as Explicit with 0 items
                // When items is empty, treat it as "WITH *" and clone the record, filtering internal variables
                if items.is_empty() {
                    for (key, value) in record.iter() {
                        if !key.starts_with("__") {
                            new_record.add(key.clone(), value.clone());
                        }
                    }
                } else {
                    for item in items {
                        let value = evaluator.evaluate(&item.expression, ctx)?;
                        let name = item.alias.clone()
                            .or_else(|| item.original_text.clone())
                            .unwrap_or_else(|| Self::expression_to_column_name(&item.expression));
                        new_record.add(name, value);
                    }
                }
            }
        }

        Ok(new_record)
    }

    /// Check if projection has aggregation functions.
    fn has_aggregation(&self, projection: &Projection) -> bool {
        match &projection.items {
            ProjectionItems::All => false,
            ProjectionItems::Explicit(items) => {
                items.iter().any(|item| self.expr_has_aggregation(&item.expression))
            }
        }
    }

    /// Check if an expression contains aggregation.
    fn expr_has_aggregation(&self, expr: &Expression) -> bool {
        match expr {
            Expression::FunctionCall(call) => {
                // Check if function itself is an aggregate OR any of its arguments contain aggregates
                self.functions.is_aggregate(&call.full_name()) ||
                call.arguments.iter().any(|arg| self.expr_has_aggregation(arg))
            }
            Expression::CountAll => true,
            Expression::BinaryOp(left, _, right) => {
                self.expr_has_aggregation(left) || self.expr_has_aggregation(right)
            }
            Expression::UnaryOp(_, inner) => self.expr_has_aggregation(inner),
            Expression::PropertyAccess(base, _) => self.expr_has_aggregation(base),
            Expression::IndexAccess(base, index) => {
                self.expr_has_aggregation(base) || self.expr_has_aggregation(index)
            }
            Expression::ListSlice(base, start, end) => {
                self.expr_has_aggregation(base) ||
                start.as_ref().map(|s| self.expr_has_aggregation(s)).unwrap_or(false) ||
                end.as_ref().map(|e| self.expr_has_aggregation(e)).unwrap_or(false)
            }
            Expression::List(items) => {
                items.iter().any(|item| self.expr_has_aggregation(item))
            }
            Expression::Map(entries) => {
                entries.values().any(|val| self.expr_has_aggregation(val))
            }
            Expression::Case(case_expr) => {
                case_expr.operand.as_ref().map(|op| self.expr_has_aggregation(op)).unwrap_or(false) ||
                case_expr.alternatives.iter().any(|(when, then)| {
                    self.expr_has_aggregation(when) || self.expr_has_aggregation(then)
                }) ||
                case_expr.default.as_ref().map(|def| self.expr_has_aggregation(def)).unwrap_or(false)
            }
            Expression::ListComprehension(comp) => {
                self.expr_has_aggregation(&comp.list) ||
                comp.filter.as_ref().map(|f| self.expr_has_aggregation(f)).unwrap_or(false) ||
                comp.projection.as_ref().map(|p| self.expr_has_aggregation(p)).unwrap_or(false)
            }
            Expression::Quantifier(quant) => {
                self.expr_has_aggregation(&quant.list) ||
                self.expr_has_aggregation(&quant.condition)
            }
            Expression::Reduce(reduce) => {
                self.expr_has_aggregation(&reduce.init) ||
                self.expr_has_aggregation(&reduce.list) ||
                self.expr_has_aggregation(&reduce.expression)
            }
            Expression::StringPredicate(base, _, pattern) => {
                self.expr_has_aggregation(base) || self.expr_has_aggregation(pattern)
            }
            Expression::InPredicate(value, list) => {
                self.expr_has_aggregation(value) || self.expr_has_aggregation(list)
            }
            Expression::NullPredicate(value, _) => {
                self.expr_has_aggregation(value)
            }
            _ => false,
        }
    }

    /// Extract all variable references from an expression.
    /// Used to preserve OPTIONAL MATCH variables in WITH clause WHERE filtering.
    fn extract_expression_variables(expr: &Expression) -> Vec<String> {
        let mut vars = Vec::new();
        match expr {
            Expression::Variable(v) => {
                vars.push(v.clone());
            }
            Expression::PropertyAccess(base, _) => {
                vars.extend(Self::extract_expression_variables(base));
            }
            Expression::IndexAccess(base, index) => {
                vars.extend(Self::extract_expression_variables(base));
                vars.extend(Self::extract_expression_variables(index));
            }
            Expression::ListSlice(base, start, end) => {
                vars.extend(Self::extract_expression_variables(base));
                if let Some(s) = start {
                    vars.extend(Self::extract_expression_variables(s));
                }
                if let Some(e) = end {
                    vars.extend(Self::extract_expression_variables(e));
                }
            }
            Expression::LabelCheck(base, _) => {
                vars.extend(Self::extract_expression_variables(base));
            }
            Expression::BinaryOp(left, _, right) => {
                vars.extend(Self::extract_expression_variables(left));
                vars.extend(Self::extract_expression_variables(right));
            }
            Expression::UnaryOp(_, operand) => {
                vars.extend(Self::extract_expression_variables(operand));
            }
            Expression::FunctionCall(call) => {
                for arg in &call.arguments {
                    vars.extend(Self::extract_expression_variables(arg));
                }
            }
            Expression::List(items) => {
                for item in items {
                    vars.extend(Self::extract_expression_variables(item));
                }
            }
            Expression::Map(entries) => {
                for val in entries.values() {
                    vars.extend(Self::extract_expression_variables(val));
                }
            }
            Expression::Case(case_expr) => {
                if let Some(operand) = &case_expr.operand {
                    vars.extend(Self::extract_expression_variables(operand));
                }
                for (when, then) in &case_expr.alternatives {
                    vars.extend(Self::extract_expression_variables(when));
                    vars.extend(Self::extract_expression_variables(then));
                }
                if let Some(default) = &case_expr.default {
                    vars.extend(Self::extract_expression_variables(default));
                }
            }
            Expression::ListComprehension(comp) => {
                vars.extend(Self::extract_expression_variables(&comp.list));
                if let Some(filter) = &comp.filter {
                    vars.extend(Self::extract_expression_variables(filter));
                }
                if let Some(proj) = &comp.projection {
                    vars.extend(Self::extract_expression_variables(proj));
                }
            }
            Expression::Quantifier(quant) => {
                vars.extend(Self::extract_expression_variables(&quant.list));
                vars.extend(Self::extract_expression_variables(&quant.condition));
            }
            Expression::Reduce(reduce) => {
                vars.extend(Self::extract_expression_variables(&reduce.init));
                vars.extend(Self::extract_expression_variables(&reduce.list));
                vars.extend(Self::extract_expression_variables(&reduce.expression));
            }
            Expression::StringPredicate(base, _, pattern) => {
                vars.extend(Self::extract_expression_variables(base));
                vars.extend(Self::extract_expression_variables(pattern));
            }
            Expression::InPredicate(value, list) => {
                vars.extend(Self::extract_expression_variables(value));
                vars.extend(Self::extract_expression_variables(list));
            }
            Expression::NullPredicate(value, _) => {
                vars.extend(Self::extract_expression_variables(value));
            }
            Expression::Parenthesized(inner) => {
                vars.extend(Self::extract_expression_variables(inner));
            }
            _ => {}
        }
        vars
    }

    /// Build a map from expression representation to column name for projection items.
    fn build_projection_expr_map(&self, projection: &Projection) -> HashMap<String, String> {
        let mut map = HashMap::new();
        if let ProjectionItems::Explicit(items) = &projection.items {
            for item in items {
                let name = item.alias.clone()
                    .or_else(|| item.original_text.clone())
                    .unwrap_or_else(|| Self::expression_to_column_name(&item.expression));
                let expr_repr = format!("{:?}", item.expression);
                map.insert(expr_repr, name);
            }
        }
        map
    }

    /// Resolve an ORDER BY expression for aggregation results.
    /// Returns the sort key value by matching the expression to a projected column.
    /// Handles composite expressions by substituting sub-expressions with their projected values.
    fn resolve_order_by_for_aggregation(
        &self,
        expr: &Expression,
        record: &Record,
        expr_to_column: &HashMap<String, String>,
        evaluator: &Evaluator<G>,
        ctx: &ExecutionContext,
    ) -> CypherValue {
        // First, try to match the exact expression to a projected column
        let expr_repr = format!("{:?}", expr);
        if let Some(col_name) = expr_to_column.get(&expr_repr) {
            if let Some(value) = record.get(col_name) {
                return value.clone();
            }
        }

        // If it's a simple variable reference, look it up in the record
        if let Expression::Variable(var_name) = expr {
            if let Some(value) = record.get(var_name) {
                return value.clone();
            }
        }

        // For composite expressions (BinaryOp, UnaryOp, etc.), substitute sub-expressions
        // that match projected columns with their values, then evaluate
        if let Some(substituted) = self.substitute_projected_exprs(expr, record, expr_to_column) {
            let temp_ctx = ExecutionContext::new();
            if let Ok(value) = evaluator.evaluate(&substituted, &temp_ctx) {
                return value;
            }
        }

        // Try to evaluate against record context (for non-aggregate expressions)
        let mut local_ctx = ctx.clone();
        local_ctx.load_record(record);
        evaluator.evaluate(expr, &local_ctx).unwrap_or(CypherValue::Null)
    }

    /// Recursively substitute sub-expressions that match projected columns with literal values.
    fn substitute_projected_exprs(
        &self,
        expr: &Expression,
        record: &Record,
        expr_to_column: &HashMap<String, String>,
    ) -> Option<Expression> {
        // Check if this entire expression matches a projected column
        let expr_repr = format!("{:?}", expr);
        if let Some(col_name) = expr_to_column.get(&expr_repr) {
            if let Some(value) = record.get(col_name) {
                return Some(Expression::Literal(self.value_to_literal(value)));
            }
        }

        // Check if variable exists in record directly
        if let Expression::Variable(var_name) = expr {
            if let Some(value) = record.get(var_name) {
                return Some(Expression::Literal(self.value_to_literal(value)));
            }
        }

        // Recursively substitute in composite expressions
        match expr {
            Expression::BinaryOp(left, op, right) => {
                let new_left = self.substitute_projected_exprs(left, record, expr_to_column)?;
                let new_right = self.substitute_projected_exprs(right, record, expr_to_column)?;
                Some(Expression::BinaryOp(Box::new(new_left), *op, Box::new(new_right)))
            }
            Expression::UnaryOp(op, inner) => {
                let new_inner = self.substitute_projected_exprs(inner, record, expr_to_column)?;
                Some(Expression::UnaryOp(*op, Box::new(new_inner)))
            }
            Expression::FunctionCall(call) => {
                let new_args: Option<Vec<_>> = call.arguments.iter()
                    .map(|arg| self.substitute_projected_exprs(arg, record, expr_to_column))
                    .collect();
                let new_args = new_args?;
                Some(Expression::FunctionCall(crate::ast::FunctionCall {
                    namespace: call.namespace.clone(),
                    name: call.name.clone(),
                    arguments: new_args,
                    distinct: call.distinct,
                }))
            }
            Expression::PropertyAccess(_inner, _prop) => {
                // Property access - if the whole expression doesn't match, we can't substitute
                // because we can't construct a meaningful substitution for just part of it
                None
            }
            Expression::Literal(_) => Some(expr.clone()),
            Expression::List(items) => {
                let new_items: Option<Vec<_>> = items.iter()
                    .map(|item| self.substitute_projected_exprs(item, record, expr_to_column))
                    .collect();
                Some(Expression::List(new_items?))
            }
            _ => None, // For unsupported expression types, return None to fall back
        }
    }

    /// Execute aggregation.
    fn execute_aggregation(
        &self,
        projection: &Projection,
        evaluator: &Evaluator<G>,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<Vec<Record>> {
        

        // Identify grouping keys and aggregates
        let items = match &projection.items {
            ProjectionItems::Explicit(items) => items,
            ProjectionItems::All => return Ok(current.records),
        };

        let mut grouping_exprs: Vec<(&Expression, String)> = Vec::new();
        let mut aggregate_exprs: Vec<(&Expression, String)> = Vec::new();

        for item in items {
            let name = item.alias.clone()
                .or_else(|| item.original_text.clone())
                .unwrap_or_else(|| Self::expression_to_column_name(&item.expression));
            if self.expr_has_aggregation(&item.expression) {
                aggregate_exprs.push((&item.expression, name));
            } else {
                grouping_exprs.push((&item.expression, name));
            }
        }

        // Group records by grouping keys
        let mut groups: HashMap<String, Vec<Record>> = HashMap::new();

        for record in current.iter() {
            let mut local_ctx = ctx.clone();
            local_ctx.load_record(record);

            // Build group key
            let mut key_parts = Vec::new();
            for (expr, _) in &grouping_exprs {
                let value = evaluator.evaluate(expr, &local_ctx)?;
                key_parts.push(format!("{:?}", value));
            }
            let key = key_parts.join("|");

            groups.entry(key).or_default().push(record.clone());
        }

        // If no grouping, treat all records as one group
        if grouping_exprs.is_empty() && groups.is_empty() {
            groups.insert(String::new(), current.records);
        }

        // Compute aggregates for each group
        let mut results = Vec::new();

        for (_key, group_records) in groups {
            let mut result_record = Record::new();

            // Add grouping values
            if let Some(first) = group_records.first() {
                let mut local_ctx = ctx.clone();
                local_ctx.load_record(first);

                for (expr, name) in &grouping_exprs {
                    let value = evaluator.evaluate(expr, &local_ctx)?;
                    result_record.add(name.clone(), value);
                }
            }

            // Compute aggregates
            for (expr, name) in &aggregate_exprs {
                let value = self.compute_aggregate(expr, evaluator, ctx, &group_records)?;
                result_record.add(name.clone(), value);
            }

            results.push(result_record);
        }

        Ok(results)
    }

    /// Compute an aggregate expression over a set of records.
    fn compute_aggregate(
        &self,
        expr: &Expression,
        evaluator: &Evaluator<G>,
        ctx: &ExecutionContext,
        records: &[Record],
    ) -> ExecutionResult<CypherValue> {
        use functions::aggregate::create_accumulator;

        match expr {
            Expression::CountAll => {
                Ok(CypherValue::Integer(records.len() as i64))
            }
            Expression::FunctionCall(call) if self.functions.is_aggregate(&call.name) => {
                // For percentile functions, we need to evaluate the second argument (percentile)
                // before creating the accumulator
                let extra_args: Vec<CypherValue> = if call.arguments.len() > 1 {
                    // Evaluate extra arguments (like percentile parameter) using the outer context
                    // Parameters should be resolved from the execution context, not from records
                    call.arguments[1..]
                        .iter()
                        .map(|arg| evaluator.evaluate(arg, ctx))
                        .collect::<ExecutionResult<Vec<_>>>()?
                } else {
                    vec![]
                };

                let mut acc = create_accumulator(&call.full_name(), call.distinct, &extra_args)?;

                for record in records {
                    let mut local_ctx = ctx.clone();
                    local_ctx.load_record(record);

                    let value = if call.arguments.is_empty() {
                        CypherValue::Integer(1) // For count without arguments
                    } else {
                        evaluator.evaluate(&call.arguments[0], &local_ctx)?
                    };

                    acc.accumulate(value)?;
                }

                Ok(acc.finalize())
            }
            Expression::FunctionCall(call) => {
                // Non-aggregate function, but may contain aggregates in arguments
                // Check if any arguments contain aggregates
                let has_agg_args = call.arguments.iter().any(|arg| self.expr_has_aggregation(arg));

                if has_agg_args {
                    // Recursively compute aggregated arguments
                    let agg_args: ExecutionResult<Vec<_>> = call.arguments
                        .iter()
                        .map(|arg| self.compute_aggregate(arg, evaluator, ctx, records))
                        .collect();
                    let agg_args = agg_args?;

                    // Now call the function with the aggregated results
                    self.functions.call(&call.name.to_lowercase(), agg_args, call.distinct)
                } else {
                    // No aggregates in arguments, evaluate normally
                    if let Some(record) = records.first() {
                        let mut local_ctx = ctx.clone();
                        local_ctx.load_record(record);
                        evaluator.evaluate(expr, &local_ctx)
                    } else {
                        Ok(CypherValue::Null)
                    }
                }
            }
            Expression::BinaryOp(left, op, right) => {
                let left_val = self.compute_aggregate(left, evaluator, ctx, records)?;
                let right_val = self.compute_aggregate(right, evaluator, ctx, records)?;

                // For complex types (List, Map, Node) that can't be converted to Literal,
                // handle the operation directly rather than through evaluate()
                match (op, &left_val, &right_val) {
                    // List concatenation
                    (BinaryOperator::Add, CypherValue::List(l1), CypherValue::List(l2)) => {
                        let mut result = l1.clone();
                        result.extend(l2.clone());
                        Ok(CypherValue::List(result))
                    }
                    // List + element (or element + List) - prepend/append
                    (BinaryOperator::Add, CypherValue::List(l), other) => {
                        let mut result = l.clone();
                        result.push(other.clone());
                        Ok(CypherValue::List(result))
                    }
                    (BinaryOperator::Add, other, CypherValue::List(l)) => {
                        let mut result = vec![other.clone()];
                        result.extend(l.clone());
                        Ok(CypherValue::List(result))
                    }
                    // For simple types, use the literal conversion
                    _ => {
                        let temp_ctx = ExecutionContext::new();
                        let left_expr = Expression::Literal(self.value_to_literal(&left_val));
                        let right_expr = Expression::Literal(self.value_to_literal(&right_val));
                        let combined = Expression::BinaryOp(Box::new(left_expr), *op, Box::new(right_expr));
                        evaluator.evaluate(&combined, &temp_ctx)
                    }
                }
            }
            Expression::UnaryOp(op, inner) => {
                let inner_val = self.compute_aggregate(inner, evaluator, ctx, records)?;
                let temp_ctx = ExecutionContext::new();
                let inner_expr = Expression::Literal(self.value_to_literal(&inner_val));
                let combined = Expression::UnaryOp(*op, Box::new(inner_expr));
                evaluator.evaluate(&combined, &temp_ctx)
            }
            Expression::List(items) => {
                // Check if any items contain aggregates
                if items.iter().any(|item| self.expr_has_aggregation(item)) {
                    // Recursively compute aggregated items
                    let agg_items: ExecutionResult<Vec<_>> = items
                        .iter()
                        .map(|item| self.compute_aggregate(item, evaluator, ctx, records))
                        .collect();
                    Ok(CypherValue::List(agg_items?))
                } else {
                    // No aggregates, evaluate normally
                    if let Some(record) = records.first() {
                        let mut local_ctx = ctx.clone();
                        local_ctx.load_record(record);
                        evaluator.evaluate(expr, &local_ctx)
                    } else {
                        Ok(CypherValue::Null)
                    }
                }
            }
            Expression::ListComprehension(comp) => {
                // List comprehension with aggregates: first compute the aggregated list,
                // then filter/project over it
                let list_val = self.compute_aggregate(&comp.list, evaluator, ctx, records)?;
                let list = match list_val {
                    CypherValue::List(l) => l,
                    CypherValue::Null => return Ok(CypherValue::Null),
                    _ => return Err(ExecutionError::Type(format!(
                        "Expected list in comprehension, got {}", list_val.type_name()
                    ))),
                };

                // Now evaluate the comprehension over the aggregated list
                let mut result = Vec::new();
                for item in list {
                    let mut local_ctx = ctx.clone();
                    local_ctx.set(&comp.variable, item.clone());

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

                    // Apply projection or use the variable
                    let value = if let Some(ref proj) = comp.projection {
                        evaluator.evaluate(proj, &local_ctx)?
                    } else {
                        item
                    };
                    result.push(value);
                }
                Ok(CypherValue::List(result))
            }
            Expression::Map(entries) => {
                // Check if any values contain aggregates
                if entries.values().any(|val| self.expr_has_aggregation(val)) {
                    // Recursively compute aggregated values
                    let mut agg_map = IndexMap::new();
                    for (key, val) in entries {
                        let agg_val = self.compute_aggregate(val, evaluator, ctx, records)?;
                        agg_map.insert(key.clone(), agg_val);
                    }
                    Ok(CypherValue::Map(agg_map))
                } else {
                    // No aggregates, evaluate normally
                    if let Some(record) = records.first() {
                        let mut local_ctx = ctx.clone();
                        local_ctx.load_record(record);
                        evaluator.evaluate(expr, &local_ctx)
                    } else {
                        Ok(CypherValue::Null)
                    }
                }
            }
            Expression::Quantifier(quant) => {
                // Quantifier with aggregates: first compute the aggregated list,
                // then evaluate the quantifier over it
                let list_val = self.compute_aggregate(&quant.list, evaluator, ctx, records)?;
                let list = match list_val {
                    CypherValue::List(l) => l,
                    CypherValue::Null => return Ok(CypherValue::Null),
                    _ => return Err(ExecutionError::Type(format!(
                        "Expected list in quantifier, got {}", list_val.type_name()
                    ))),
                };

                // Now evaluate the quantifier over the aggregated list
                match quant.quantifier {
                    crate::ast::QuantifierType::All => {
                        for item in list {
                            let mut local_ctx = ctx.clone();
                            local_ctx.set(&quant.variable, item);
                            let cond = evaluator.evaluate(&quant.condition, &local_ctx)?;
                            if !cond.is_truthy() {
                                return Ok(CypherValue::Boolean(false));
                            }
                        }
                        Ok(CypherValue::Boolean(true))
                    }
                    crate::ast::QuantifierType::Any => {
                        for item in list {
                            let mut local_ctx = ctx.clone();
                            local_ctx.set(&quant.variable, item);
                            let cond = evaluator.evaluate(&quant.condition, &local_ctx)?;
                            if cond.is_truthy() {
                                return Ok(CypherValue::Boolean(true));
                            }
                        }
                        Ok(CypherValue::Boolean(false))
                    }
                    crate::ast::QuantifierType::None => {
                        for item in list {
                            let mut local_ctx = ctx.clone();
                            local_ctx.set(&quant.variable, item);
                            let cond = evaluator.evaluate(&quant.condition, &local_ctx)?;
                            if cond.is_truthy() {
                                return Ok(CypherValue::Boolean(false));
                            }
                        }
                        Ok(CypherValue::Boolean(true))
                    }
                    crate::ast::QuantifierType::Single => {
                        let mut count = 0;
                        for item in list {
                            let mut local_ctx = ctx.clone();
                            local_ctx.set(&quant.variable, item);
                            let cond = evaluator.evaluate(&quant.condition, &local_ctx)?;
                            if cond.is_truthy() {
                                count += 1;
                                if count > 1 {
                                    return Ok(CypherValue::Boolean(false));
                                }
                            }
                        }
                        Ok(CypherValue::Boolean(count == 1))
                    }
                }
            }
            Expression::Literal(_) | Expression::Parameter(_) => {
                // Literals and parameters don't depend on records - evaluate directly
                evaluator.evaluate(expr, ctx)
            }
            _ => {
                // Not an aggregate - evaluate once using first record context if available
                if let Some(record) = records.first() {
                    let mut local_ctx = ctx.clone();
                    local_ctx.load_record(record);
                    evaluator.evaluate(expr, &local_ctx)
                } else {
                    // For expressions that need record context but none exist,
                    // try evaluating without - will fail for variables but work for constants
                    evaluator.evaluate(expr, ctx).or(Ok(CypherValue::Null))
                }
            }
        }
    }

    /// Convert a CypherValue to a Literal (helper for aggregate computation).
    fn value_to_literal(&self, value: &CypherValue) -> Literal {
        match value {
            CypherValue::Null => Literal::Null,
            CypherValue::Boolean(b) => Literal::Boolean(*b),
            CypherValue::Integer(i) => Literal::Integer(*i),
            CypherValue::Float(f) => Literal::Float(*f),
            CypherValue::String(s) => Literal::String(s.clone()),
            _ => Literal::Null,
        }
    }

    /// Build a path from a record and chain (simple version for MERGE).
    fn build_path_from_record_simple(&self, record: &Record, chain: &PatternChain) -> CypherValue {
        let mut nodes = Vec::new();
        let mut relationships = Vec::new();

        // Get the start node
        let start_node_id = if let Some(var) = &chain.start.variable {
            if let Some(CypherValue::Node(node)) = record.get(var) {
                nodes.push(node.clone());
                Some(node.id)
            } else {
                None
            }
        } else {
            None
        };

        let mut prev_node_id = start_node_id;

        // Get relationship and end nodes from the chain
        for (rel_pattern, node_pattern) in &chain.chain {
            // Get the end node for this segment
            let end_node = if let Some(var) = &node_pattern.variable {
                record.get(var).and_then(|v| {
                    if let CypherValue::Node(node) = v {
                        Some(node.clone())
                    } else {
                        None
                    }
                })
            } else {
                None
            };

            // Get the relationship - either from record or from graph
            if let Some(var) = &rel_pattern.variable {
                if let Some(CypherValue::Relationship(rel)) = record.get(var) {
                    relationships.push(rel.clone());
                } else if let Some(CypherValue::List(rels)) = record.get(var) {
                    for rel_val in rels {
                        if let CypherValue::Relationship(rel) = rel_val {
                            relationships.push(rel.clone());
                        }
                    }
                }
            } else if let (Some(prev_id), Some(end)) = (prev_node_id, &end_node) {
                // Anonymous relationship - look it up from the graph
                // Find relationship between prev_node_id and end_node.id
                let end_id = end.id;
                let rel_types = &rel_pattern.types;

                // Find the relationship that connects these nodes with matching type
                if let Some(rel) = self.find_relationship_between(prev_id, end_id, rel_types, &rel_pattern.direction) {
                    relationships.push(rel);
                }
            }

            // Add the end node
            if let Some(node) = end_node {
                prev_node_id = Some(node.id);
                nodes.push(node);
            }
        }

        CypherValue::Path(PathValue {
            nodes,
            relationships,
        })
    }

    /// Find a relationship between two nodes with the given type and direction.
    fn find_relationship_between(
        &self,
        from_id: u64,
        to_id: u64,
        rel_types: &[String],
        direction: &RelationshipDirection,
    ) -> Option<RelationshipValue> {
        // Get all edges from the start node
        let edges = self.graph.all_edges_for_node(from_id);

        for (start_id, end_id, edge) in edges {
            // Check type match (empty rel_types means any type)
            let type_matches = rel_types.is_empty() || rel_types.iter().any(|t| t == edge.rel_type());
            if !type_matches {
                continue;
            }

            // Check direction and endpoint
            match direction {
                RelationshipDirection::Outgoing => {
                    if start_id == from_id && end_id == to_id {
                        return Some(RelationshipValue::from_graph_edge(edge, start_id, end_id));
                    }
                }
                RelationshipDirection::Incoming => {
                    if end_id == from_id && start_id == to_id {
                        return Some(RelationshipValue::from_graph_edge(edge, start_id, end_id));
                    }
                }
                RelationshipDirection::Both => {
                    if (start_id == from_id && end_id == to_id) ||
                       (end_id == from_id && start_id == to_id) {
                        return Some(RelationshipValue::from_graph_edge(edge, start_id, end_id));
                    }
                }
            }
        }
        None
    }

    /// Convert an expression to a column name.
    fn expression_to_column_name(expr: &Expression) -> String {
        match expr {
            Expression::Variable(name) => name.clone(),
            Expression::PropertyAccess(base, prop) => {
                // Wrap complex bases (like index access) in parentheses: (list[1]).prop
                let base_name = Self::expression_to_column_name(base);
                let needs_parens = matches!(**base, Expression::IndexAccess(_, _) | Expression::ListSlice(_, _, _));
                if needs_parens {
                    format!("({}).{}", base_name, prop)
                } else {
                    format!("{}.{}", base_name, prop)
                }
            }
            Expression::FunctionCall(call) => {
                let args: Vec<String> = call.arguments.iter()
                    .map(|arg| Self::expression_to_column_name(arg))
                    .collect();
                if call.distinct {
                    format!("{}(DISTINCT {})", call.name, args.join(", "))
                } else {
                    format!("{}({})", call.name, args.join(", "))
                }
            }
            Expression::Literal(lit) => match lit {
                Literal::Null => "null".to_string(),
                Literal::Boolean(b) => b.to_string(),
                Literal::Integer(i) => i.to_string(),
                Literal::Float(f) => f.to_string(),
                Literal::String(s) => format!("'{}'", s),
            },
            Expression::List(items) => {
                let items_str: Vec<String> = items.iter()
                    .map(|item| Self::expression_to_column_name(item))
                    .collect();
                format!("[{}]", items_str.join(", "))
            }
            Expression::Map(pairs) => {
                let pairs_str: Vec<String> = pairs.iter()
                    .map(|(k, v)| format!("{}: {}", k, Self::expression_to_column_name(v)))
                    .collect();
                format!("{{{}}}", pairs_str.join(", "))
            }
            Expression::Parameter(name) => format!("${}", name),
            Expression::BinaryOp(left, op, right) => {
                let op_str = match op {
                    BinaryOperator::Add => " + ",
                    BinaryOperator::Subtract => " - ",
                    BinaryOperator::Multiply => " * ",
                    BinaryOperator::Divide => " / ",
                    BinaryOperator::Modulo => " % ",
                    BinaryOperator::Power => " ^ ",
                    BinaryOperator::Equal => " = ",
                    BinaryOperator::NotEqual => " <> ",
                    BinaryOperator::LessThan => " < ",
                    BinaryOperator::LessThanOrEqual => " <= ",
                    BinaryOperator::GreaterThan => " > ",
                    BinaryOperator::GreaterThanOrEqual => " >= ",
                    BinaryOperator::And => " AND ",
                    BinaryOperator::Or => " OR ",
                    BinaryOperator::Xor => " XOR ",
                    BinaryOperator::RegexMatch => " =~ ",
                };

                // Get precedence of current operator (higher = binds tighter)
                let outer_prec = Self::operator_precedence(op);

                // Check if left operand needs parentheses
                let left_str = if let Expression::BinaryOp(_, left_op, _) = left.as_ref() {
                    let left_prec = Self::operator_precedence(left_op);
                    let left_name = Self::expression_to_column_name(left);
                    if left_prec < outer_prec {
                        format!("({})", left_name)
                    } else {
                        left_name
                    }
                } else {
                    Self::expression_to_column_name(left)
                };

                // Check if right operand needs parentheses
                let right_str = if let Expression::BinaryOp(_, right_op, _) = right.as_ref() {
                    let right_prec = Self::operator_precedence(right_op);
                    let right_name = Self::expression_to_column_name(right);
                    // Right side needs parens if lower precedence OR same precedence for left-associative ops
                    if right_prec < outer_prec {
                        format!("({})", right_name)
                    } else {
                        right_name
                    }
                } else {
                    Self::expression_to_column_name(right)
                };

                format!("{}{}{}", left_str, op_str, right_str)
            }
            Expression::UnaryOp(op, expr) => {
                let op_str = match op {
                    UnaryOperator::Not => "NOT ",
                    UnaryOperator::Negate => "-",
                    UnaryOperator::Positive => "+",
                };
                format!("{}{}", op_str, Self::expression_to_column_name(expr))
            }
            Expression::IndexAccess(base, index) => {
                format!("{}[{}]", Self::expression_to_column_name(base), Self::expression_to_column_name(index))
            }
            Expression::ListSlice(base, start, end) => {
                let start_str = start.as_ref().map(|e| Self::expression_to_column_name(e)).unwrap_or_default();
                let end_str = end.as_ref().map(|e| Self::expression_to_column_name(e)).unwrap_or_default();
                format!("{}[{}..{}]", Self::expression_to_column_name(base), start_str, end_str)
            }
            Expression::NullPredicate(expr, is_null) => {
                if *is_null {
                    format!("{} IS NULL", Self::expression_to_column_name(expr))
                } else {
                    format!("{} IS NOT NULL", Self::expression_to_column_name(expr))
                }
            }
            Expression::InPredicate(elem, list) => {
                format!("{} IN {}", Self::expression_to_column_name(elem), Self::expression_to_column_name(list))
            }
            Expression::StringPredicate(base, op, pattern) => {
                let op_str = match op {
                    crate::ast::StringPredicateOp::StartsWith => " STARTS WITH ",
                    crate::ast::StringPredicateOp::EndsWith => " ENDS WITH ",
                    crate::ast::StringPredicateOp::Contains => " CONTAINS ",
                };
                format!("{}{}{}", Self::expression_to_column_name(base), op_str, Self::expression_to_column_name(pattern))
            }
            Expression::LabelCheck(expr, labels) => {
                // Column name format: n:Foo without parentheses (TCK expects m:TYPE not (m:TYPE))
                format!("{}:{}", Self::expression_to_column_name(expr), labels.join(":"))
            }
            Expression::CountAll => "count(*)".to_string(),
            Expression::Case(_) => {
                // Simplified representation
                "CASE".to_string()
            }
            Expression::ListComprehension(_) => "[...]".to_string(),
            Expression::PatternComprehension(_) => "[...]".to_string(),
            Expression::Reduce(_) => "reduce(...)".to_string(),
            Expression::Quantifier(_) => "quantifier(...)".to_string(),
            Expression::ExistsSubquery(_) => "EXISTS {...}".to_string(),
            Expression::PatternPredicate(_) => "(pattern)".to_string(),
            Expression::Parenthesized(inner) => {
                format!("({})", Self::expression_to_column_name(inner))
            }
        }
    }

    /// Get the precedence of a binary operator (higher = binds tighter).
    fn operator_precedence(op: &BinaryOperator) -> u8 {
        match op {
            BinaryOperator::Or => 1,
            BinaryOperator::Xor => 2,
            BinaryOperator::And => 3,
            BinaryOperator::Equal
            | BinaryOperator::NotEqual
            | BinaryOperator::LessThan
            | BinaryOperator::LessThanOrEqual
            | BinaryOperator::GreaterThan
            | BinaryOperator::GreaterThanOrEqual
            | BinaryOperator::RegexMatch => 4,
            BinaryOperator::Add | BinaryOperator::Subtract => 5,
            BinaryOperator::Multiply | BinaryOperator::Divide | BinaryOperator::Modulo => 6,
            BinaryOperator::Power => 7,
        }
    }

    /// Execute an UNWIND clause.
    fn execute_unwind(
        &self,
        clause: &UnwindClause,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<ResultSet> {
        let evaluator = Evaluator::new(self.graph, &self.functions);
        let mut results = ResultSet::new();

        for record in current.iter() {
            let mut local_ctx = ctx.clone();
            local_ctx.load_record(record);

            let list_value = evaluator.evaluate(&clause.expression, &local_ctx)?;

            match list_value {
                CypherValue::List(items) => {
                    for item in items {
                        let mut new_record = record.clone();
                        new_record.add(clause.variable.clone(), item);
                        results.add(new_record);
                    }
                }
                CypherValue::Null => {
                    // UNWIND null produces no rows
                }
                _ => {
                    return Err(ExecutionError::Type(format!(
                        "UNWIND requires a list, got {}",
                        list_value.type_name()
                    )));
                }
            }
        }

        Ok(results)
    }

    /// Execute a FOREACH clause.
    fn execute_foreach(
        &mut self,
        clause: &ForeachClause,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<(ResultSet, QueryStats)> {
        let mut total_stats = QueryStats::default();

        // Evaluate list expressions first
        let mut items_per_record: Vec<(Record, Vec<CypherValue>)> = Vec::new();
        {
            let evaluator = Evaluator::new(self.graph, &self.functions);

            for record in current.iter() {
                let mut local_ctx = ctx.clone();
                local_ctx.load_record(record);

                let list_value = evaluator.evaluate(&clause.expression, &local_ctx)?;

                if let CypherValue::List(items) = list_value {
                    items_per_record.push((record.clone(), items));
                }
            }
        }

        // Now execute updates
        for (record, items) in items_per_record {
            let mut local_ctx = ctx.clone();
            local_ctx.load_record(&record);

            for item in items {
                local_ctx.set(clause.variable.clone(), item);

                // Execute update clauses
                for update in &clause.clauses {
                    let stats = match update {
                        UpdateClause::Create(c) => {
                            let (_, s) = self.execute_create(c, &local_ctx, ResultSet::single_empty())?;
                            s
                        }
                        UpdateClause::Set(s) => {
                            let (_, stats) = self.execute_set(s, &local_ctx, ResultSet::single_empty())?;
                            stats
                        }
                        UpdateClause::Delete(d) => {
                            // DELETE modifies total_stats directly for create/delete netting
                            self.execute_delete(d, &local_ctx, ResultSet::single_empty(), &mut total_stats)?;
                            QueryStats::default() // Return empty since stats already updated
                        }
                        UpdateClause::Remove(r) => {
                            let (_, stats) = self.execute_remove(r, &local_ctx, ResultSet::single_empty())?;
                            stats
                        }
                        UpdateClause::Merge(m) => {
                            let (_, stats) = self.execute_merge(m, &local_ctx, ResultSet::single_empty())?;
                            stats
                        }
                        UpdateClause::Foreach(f) => {
                            let (_, stats) = self.execute_foreach(f, &local_ctx, ResultSet::single_empty())?;
                            stats
                        }
                    };
                    total_stats.merge(&stats);
                }
            }
        }

        Ok((current, total_stats))
    }

    /// Execute a CALL clause.
    fn execute_call(
        &self,
        clause: &CallClause,
        ctx: &ExecutionContext,
        current: ResultSet,
    ) -> ExecutionResult<ResultSet> {
        let proc_name = clause.procedure.full_name();

        // Handle two cases: standalone CALL vs in-query CALL
        // Standalone CALL: either empty OR has single empty record (no columns)
        let is_standalone = current.is_empty() ||
            (current.len() == 1 && current.records().first().map(|r| r.columns().is_empty()).unwrap_or(false));

        if is_standalone {
            // Standalone CALL - execute procedure once and return results
            let evaluator = Evaluator::new(self.graph, &self.functions);

            // Evaluate explicit arguments
            let mut args = Vec::new();
            for arg in &clause.procedure.arguments {
                args.push(evaluator.evaluate(arg, ctx)?);
            }

            // Handle implicit arguments from Cypher parameters
            // If no explicit arguments but procedure has parameters, try implicit resolution
            let args = if args.is_empty() {
                // Get procedure parameter info from local registry first, then global test registry
                let proc_params: Option<Vec<String>> = self.procedures.get_info(&proc_name)
                    .map(|info| info.parameters.iter().map(|p| p.name.clone()).collect())
                    .or_else(|| {
                        crate::test_procedures::get_test_procedure(&proc_name)
                            .map(|tp| tp.parameters.iter().map(|p| p.name.clone()).collect())
                    });

                if let Some(param_names) = proc_params {
                    if !param_names.is_empty() {
                        // Try to resolve implicit arguments from Cypher parameters
                        let mut implicit_args = Vec::new();
                        let mut all_found = true;

                        for param_name in &param_names {
                            // Check Cypher parameters (not variables)
                            if let Some(value) = ctx.get_parameter(param_name) {
                                implicit_args.push(value.clone());
                            } else {
                                all_found = false;
                                break;
                            }
                        }

                        if all_found && !implicit_args.is_empty() {
                            implicit_args
                        } else if !all_found {
                            // Missing implicit argument - this is an error
                            return Err(ExecutionError::InvalidArgument("Missing parameter for implicit argument resolution".to_string()));
                        } else {
                            args
                        }
                    } else {
                        args
                    }
                } else {
                    args
                }
            } else {
                args
            };


            // Try local registry first, then fallback to global test registry
            let proc_results = match self.procedures.call(&proc_name, args.clone(), self.graph) {
                Ok(results) => {
                    results
                }
                Err(ExecutionError::ProcedureNotFound(_)) => {
                    if let Some(test_proc) = crate::test_procedures::get_test_procedure(&proc_name) {
                        
                        test_proc.call(args)?
                    } else {
                        return Err(ExecutionError::ProcedureNotFound(proc_name));
                    }
                }
                Err(e) => return Err(e),
            };

            // Apply YIELD filtering if specified
            let yielded = if let Some(yield_clause) = &clause.yield_items {
                
                self.filter_yield(proc_results, yield_clause, ctx)?
            } else {
                proc_results
            };

            let mut results = ResultSet::new();
            for proc_record in yielded {
                results.add(proc_record);
            }
            Ok(results)
        } else {
            // In-query CALL - cross product with input rows
            let mut results = ResultSet::new();

            for record in current.records() {
                // Create context with current record
                let mut local_ctx = ctx.clone();
                for (k, v) in record.iter() {
                    local_ctx.set(k.clone(), v.clone());
                }

                // Evaluate arguments in record context
                let evaluator = Evaluator::new(self.graph, &self.functions);
                let mut args = Vec::new();
                for arg in &clause.procedure.arguments {
                    args.push(evaluator.evaluate(arg, &local_ctx)?);
                }

                // Call procedure
                let proc_results = match self.procedures.call(&proc_name, args.clone(), self.graph) {
                    Ok(results) => results,
                    Err(ExecutionError::ProcedureNotFound(_)) => {
                        if let Some(test_proc) = crate::test_procedures::get_test_procedure(&proc_name) {
                            test_proc.call(args)?
                        } else {
                            return Err(ExecutionError::ProcedureNotFound(proc_name.clone()));
                        }
                    }
                    Err(e) => return Err(e),
                };

                // Apply YIELD filtering
                let yielded = if let Some(yield_clause) = &clause.yield_items {
                    self.filter_yield(proc_results, yield_clause, &local_ctx)?
                } else {
                    proc_results
                };

                if yielded.is_empty() {
                    // Void procedure - keep existing row
                    results.add(record.clone());
                } else {
                    // Cross product with procedure results
                    for proc_record in &yielded {
                        let mut combined = record.clone();
                        combined.merge(proc_record.clone());
                        results.add(combined);
                    }
                }
            }
            Ok(results)
        }
    }

    /// Filter procedure results based on YIELD clause.
    fn filter_yield(
        &self,
        proc_results: Vec<Record>,
        yield_clause: &YieldClause,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<Vec<Record>> {
        let mut filtered = Vec::new();

        for record in proc_results {
            // Project to requested columns
            let projected = match &yield_clause.items {
                YieldItems::All => record.clone(),
                YieldItems::Explicit(items) => {
                    let mut new_record = Record::new();
                    for item in items {
                        let value = record.get(&item.name)
                            .ok_or_else(|| ExecutionError::VariableNotFound(item.name.clone()))?;

                        let output_name = item.alias.as_ref().unwrap_or(&item.name);
                        new_record.add(output_name.clone(), value.clone());
                    }
                    new_record
                }
            };

            // Apply WHERE filter if specified
            if let Some(where_expr) = &yield_clause.where_clause {
                let mut filter_ctx = ctx.clone();
                for (k, v) in projected.iter() {
                    filter_ctx.set(k.clone(), v.clone());
                }

                let evaluator = Evaluator::new(self.graph, &self.functions);
                let condition = evaluator.evaluate(where_expr, &filter_ctx)?;

                if condition.as_bool().unwrap_or(false) {
                    filtered.push(projected);
                }
            } else {
                filtered.push(projected);
            }
        }

        Ok(filtered)
    }
}

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

    fn execute_query<G: GraphBackend>(graph: &mut G, query: &str) -> ExecutionResult<QueryResult> {
        let pairs = parse(query).map_err(|e| ExecutionError::Parse(e.into()))?;
        let statements = AstBuilder::build_script(pairs).map_err(|e| ExecutionError::Parse(e))?;

        let mut executor = QueryExecutor::new(graph);

        let mut final_result = QueryResult::new();
        for stmt in &statements {
            final_result = executor.execute_statement(stmt)?;
        }
        Ok(final_result)
    }

    #[test]
    fn test_create_and_match() {
        let mut graph = PropertyGraph::new();

        let result = execute_query(&mut graph, "CREATE (n:Person {name: 'Alice'}) RETURN n").unwrap();
        assert_eq!(result.stats.nodes_created, 1);
        assert_eq!(result.row_count(), 1);

        let result = execute_query(&mut graph, "MATCH (n:Person) RETURN n.name").unwrap();
        assert_eq!(result.row_count(), 1);
    }

    #[test]
    fn test_where_clause() {
        let mut graph = PropertyGraph::new();

        execute_query(&mut graph, "CREATE (n:Person {name: 'Alice', age: 30})").unwrap();
        execute_query(&mut graph, "CREATE (n:Person {name: 'Bob', age: 25})").unwrap();

        let result = execute_query(&mut graph, "MATCH (n:Person) WHERE n.age > 27 RETURN n.name").unwrap();
        assert_eq!(result.row_count(), 1);
    }

    #[test]
    fn test_aggregation() {
        let mut graph = PropertyGraph::new();

        execute_query(&mut graph, "CREATE (:Person), (:Person), (:Dog)").unwrap();

        let result = execute_query(&mut graph, "MATCH (n:Person) RETURN count(n)").unwrap();
        assert_eq!(result.row_count(), 1);

        if let Some(value) = result.single_value() {
            assert_eq!(*value, CypherValue::Integer(2));
        }
    }

    #[test]
    fn test_optional_match_with_where_is_null() {
        let mut graph = PropertyGraph::new();

        // Create test data: a->b->c (no direct edge from a to c)
        execute_query(&mut graph, "
            CREATE (a:A), (b:B), (c:C)
            CREATE (a)-[:KNOWS]->(b)-[:KNOWS]->(c)
        ").unwrap();

        // Test: OPTIONAL MATCH + WITH + WHERE IS NULL
        // This is the triadic selection pattern
        let result = execute_query(&mut graph, "
            MATCH (a:A)-[:KNOWS]->(b)-[:KNOWS]->(c)
            OPTIONAL MATCH (a)-[r:KNOWS]->(c)
            WITH c WHERE r IS NULL
            RETURN c
        ").unwrap();

        // Should return 1 row (c) because no direct edge from a to c exists
        assert_eq!(result.row_count(), 1,
            "Expected 1 row for OPTIONAL+WITH+WHERE IS NULL (no direct edge)");
    }

    #[test]
    fn test_optional_match_with_where_is_not_null() {
        let mut graph = PropertyGraph::new();

        // Create test data: a->b->c AND a->c (direct edge exists)
        execute_query(&mut graph, "
            CREATE (a:A), (b:B), (c:C)
            CREATE (a)-[:KNOWS]->(b)-[:KNOWS]->(c)
            CREATE (a)-[:KNOWS]->(c)
        ").unwrap();

        // Test: OPTIONAL MATCH + WITH + WHERE IS NOT NULL
        let result = execute_query(&mut graph, "
            MATCH (a:A)-[:KNOWS]->(b)-[:KNOWS]->(c)
            OPTIONAL MATCH (a)-[r:KNOWS]->(c)
            WITH c WHERE r IS NOT NULL
            RETURN c
        ").unwrap();

        // Should return 1 row (c) because direct edge from a to c exists
        assert_eq!(result.row_count(), 1,
            "Expected 1 row for OPTIONAL+WITH+WHERE IS NOT NULL (direct edge exists)");
    }

    #[test]
    fn test_optional_match_preserves_null_binding() {
        let mut graph = PropertyGraph::new();

        // Create simple data
        execute_query(&mut graph, "
            CREATE (a:A {name: 'a'}), (b:B {name: 'b'})
        ").unwrap();

        // OPTIONAL MATCH with no match should preserve NULL binding through WITH
        let result = execute_query(&mut graph, "
            MATCH (a:A)
            OPTIONAL MATCH (a)-[r:MISSING]->(x)
            WITH a, r WHERE r IS NULL
            RETURN a.name
        ").unwrap();

        // Should return 1 row because r is NULL (no match)
        assert_eq!(result.row_count(), 1,
            "Expected 1 row when OPTIONAL MATCH finds no match and WITH filters on NULL");
    }

    #[test]
    fn test_zero_length_path_no_duplicates() {
        let mut graph = PropertyGraph::new();

        // Create simple graph: just one node
        execute_query(&mut graph, "CREATE (a:A {name: 'a'})").unwrap();

        // Test: Zero-length pattern should return exactly 1 row
        let result = execute_query(&mut graph, "
            MATCH (a:A)
            MATCH (a)-[:REL*0]->(c)
            RETURN c.name
        ").unwrap();

        // Should return exactly 1 row (a itself, zero hops)
        assert_eq!(result.row_count(), 1,
            "Zero-length path should return exactly 1 row, got {}", result.row_count());
    }

    #[test]
    fn test_zero_length_path_with_bounds() {
        let mut graph = PropertyGraph::new();

        // Create simple graph
        execute_query(&mut graph, "CREATE (a:A {name: 'a'})").unwrap();

        // Test: Zero-length pattern with explicit bounds *0..0
        let result = execute_query(&mut graph, "
            MATCH (a:A)
            MATCH (a)-[:REL*0..0]->(c)
            RETURN c.name
        ").unwrap();

        assert_eq!(result.row_count(), 1,
            "Zero-length path *0..0 should return exactly 1 row, got {}", result.row_count());
    }

    #[test]
    fn test_zero_length_path_in_chain() {
        let mut graph = PropertyGraph::new();

        // Create graph: a->b, a->c
        execute_query(&mut graph, "
            CREATE (a:A {name: 'a'})-[:REL]->(b:B {name: 'b'}),
                   (a)-[:REL]->(c:B {name: 'c'})
        ").unwrap();

        // Test: zero-length + standard relationship
        let result = execute_query(&mut graph, "
            MATCH (a:A)
            MATCH (a)-[:REL*0]->()-[:REL]->(c)
            RETURN c.name ORDER BY c.name
        ").unwrap();

        // Should return 2 rows (b and c), not 4
        assert_eq!(result.row_count(), 2,
            "Zero-length in chain should return 2 rows, got {}", result.row_count());
    }

    #[test]
    fn test_delete_side_effects() {
        let mut graph = PropertyGraph::new();

        // Create and delete node
        execute_query(&mut graph, "CREATE (n:Person {name: 'Alice'})").unwrap();
        let result = execute_query(&mut graph, "MATCH (n:Person) DELETE n").unwrap();

        // Should report 1 node deleted
        assert_eq!(result.stats.nodes_deleted, 1,
            "Expected 1 node deleted, got {}", result.stats.nodes_deleted);
    }

    #[test]
    fn test_detach_delete_side_effects() {
        let mut graph = PropertyGraph::new();

        // Create nodes and relationship
        execute_query(&mut graph, "
            CREATE (a:Person)-[:KNOWS]->(b:Person)
        ").unwrap();

        let result = execute_query(&mut graph, "
            MATCH (a:Person)-[r:KNOWS]->(b)
            DETACH DELETE a
        ").unwrap();

        // Should report 1 node deleted + 1 relationship deleted
        assert_eq!(result.stats.nodes_deleted, 1,
            "Expected 1 node deleted, got {}", result.stats.nodes_deleted);
        assert_eq!(result.stats.relationships_deleted, 1,
            "Expected 1 relationship deleted, got {}", result.stats.relationships_deleted);
    }

    #[test]
    fn test_delete_with_labels() {
        let mut graph = PropertyGraph::new();

        // Create node with multiple labels
        execute_query(&mut graph, "CREATE (n:Person:Employee {name: 'Bob'})").unwrap();
        let result = execute_query(&mut graph, "MATCH (n:Person) DELETE n").unwrap();

        // Should report node deleted
        assert_eq!(result.stats.nodes_deleted, 1,
            "Expected 1 node deleted");
    }

    #[test]
    fn test_merge_on_create_side_effects() {
        let mut graph = PropertyGraph::new();

        // MERGE creates a new node
        let result = execute_query(&mut graph, "
            MERGE (n:Person {id: 1})
            ON CREATE SET n.created = true
            RETURN n
        ").unwrap();

        // Should report 1 node created + properties set
        assert_eq!(result.stats.nodes_created, 1,
            "Expected 1 node created");
        assert!(result.stats.properties_set > 0,
            "Expected properties to be set");
    }

    #[test]
    fn test_remove_label_side_effects() {
        let mut graph = PropertyGraph::new();

        // Create node with label
        execute_query(&mut graph, "CREATE (n:Person {name: 'Alice'})").unwrap();

        // Remove existing label
        let result = execute_query(&mut graph, "
            MATCH (n:Person)
            REMOVE n:Person
        ").unwrap();

        // Should report labels removed
        assert_eq!(result.stats.labels_removed, 1,
            "Expected 1 label removed");
    }

    #[test]
    fn test_remove_nonexistent_label() {
        let mut graph = PropertyGraph::new();

        // Create node without label
        execute_query(&mut graph, "CREATE (n {name: 'Alice'})").unwrap();

        // Try to remove label that doesn't exist
        let result = execute_query(&mut graph, "
            MATCH (n)
            REMOVE n:NonExistent
        ").unwrap();

        // Should NOT report labels removed
        assert_eq!(result.stats.labels_removed, 0,
            "Expected 0 labels removed when removing non-existent label");
    }
}
