//! Pattern matching engine for MATCH clauses.

use std::collections::HashSet;

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

use super::context::{ExecutionContext, ResultSet};
use super::evaluator::Evaluator;
use super::functions::FunctionRegistry;

/// Parameters for variable-length path search.
struct VarLenPathParams<'p> {
    rel_pattern: &'p RelationshipPattern,
    end_pattern: &'p NodePattern,
    min: usize,
    max: usize,
    excluded_rel_ids: &'p HashSet<u64>,
}

/// Pattern matcher for finding graph patterns.
pub struct PatternMatcher<'a, G: GraphBackend> {
    graph: &'a G,
    functions: &'a FunctionRegistry,
}

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

    /// Match a pattern and return all matching bindings.
    pub fn match_pattern(
        &self,
        pattern: &Pattern,
        ctx: &ExecutionContext,
        optional: bool,
    ) -> ExecutionResult<ResultSet> {
        let mut result = ResultSet::single_empty();
        let mut had_empty_part = false;

        for part in &pattern.parts {
            let part_result = self.match_pattern_part(part, ctx)?;

            if part_result.is_empty() {
                if optional {
                    // For OPTIONAL MATCH, mark that we had an empty part
                    had_empty_part = true;
                    continue;
                } else {
                    // For regular MATCH, if any part has no matches, the whole pattern has no matches
                    return Ok(ResultSet::new());
                }
            }

            result = result.cross_join(part_result);
        }

        // If optional and we had no matches at all, or had an empty part,
        // return a single row (to preserve the incoming row count)
        if optional && (result.is_empty() || (result.len() == 1 && result.records[0].is_empty() && had_empty_part)) {
            // The result will have nulls added by execute_match for pattern variables
            return Ok(ResultSet::single_empty());
        }

        Ok(result)
    }

    /// Match a single pattern part.
    fn match_pattern_part(
        &self,
        part: &PatternPart,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<ResultSet> {
        match &part.element {
            PatternElement::Chain(chain) => self.match_chain(chain, ctx, &part.variable),
            PatternElement::ShortestPath(inner) => {
                self.match_shortest_path(inner, ctx, &part.variable, false)
            }
            PatternElement::AllShortestPaths(inner) => {
                self.match_shortest_path(inner, ctx, &part.variable, true)
            }
        }
    }

    /// Match a pattern chain.
    fn match_chain(
        &self,
        chain: &PatternChain,
        ctx: &ExecutionContext,
        path_var: &Option<String>,
    ) -> ExecutionResult<ResultSet> {
        // Start with matching the first node
        let start_matches = self.match_node(&chain.start, ctx)?;

        if chain.chain.is_empty() {
            // No relationships - this is a zero-length path (just a node)
            // If path variable is specified, add path values for zero-length paths
            if let Some(var) = path_var {
                let mut result = start_matches;
                for record in result.iter_mut() {
                    // Zero-length path: just the node, no relationships
                    let mut nodes = Vec::new();
                    if let Some(CypherValue::Node(node)) = record.get("__last_node__") {
                        nodes.push(node.clone());
                    } else if let Some(node_var) = &chain.start.variable {
                        if let Some(CypherValue::Node(node)) = record.get(node_var) {
                            nodes.push(node.clone());
                        }
                    }
                    let path = CypherValue::Path(PathValue {
                        nodes,
                        relationships: Vec::new(),
                    });
                    record.add(var.clone(), path);
                }
                return Ok(result);
            }
            // No path variable, just return node matches
            return Ok(start_matches);
        }

        // Collect bound relationship IDs that appear later in the chain.
        // These should be excluded from variable-length patterns to enforce uniqueness.
        // For example, in pattern (n)-[*0..1]-()-[r]-()-[*0..1]-(m) where r is bound,
        // the [*0..1] parts shouldn't use r because it will be used by -[r]-.
        let mut future_bound_rel_ids: HashSet<u64> = HashSet::new();
        for (rel, _node) in &chain.chain {
            if let Some(var) = &rel.variable {
                if let Some(CypherValue::Relationship(bound_rel)) = ctx.get(var) {
                    future_bound_rel_ids.insert(bound_rel.id);
                }
            }
        }

        // Extend through the chain
        let mut current = start_matches;
        for (i, (rel, node)) in chain.chain.iter().enumerate() {
            // For variable-length patterns, compute which relationships should be excluded:
            // 1. Relationships already used in the path (__path_relationships__)
            // 2. Bound relationships that appear LATER in this chain (not including current)
            //
            // We need to recalculate future_bound_rel_ids for each step to exclude
            // only relationships that appear AFTER the current position.
            let remaining_bound_rel_ids: HashSet<u64> = chain.chain[i+1..].iter()
                .filter_map(|(r, _)| {
                    if let Some(var) = &r.variable {
                        if let Some(CypherValue::Relationship(bound_rel)) = ctx.get(var) {
                            return Some(bound_rel.id);
                        }
                    }
                    None
                })
                .collect();

            current = self.extend_matches_with_exclusions(current, rel, node, ctx, &remaining_bound_rel_ids)?;
        }

        // Note: For undirected patterns like ()-[r]-(), Cypher semantics return each
        // relationship binding twice (once for each direction of traversal). This is
        // intentional - the direction matters for how the relationship is used in
        // subsequent pattern matching.

        // If path variable is specified, add path values
        if let Some(var) = path_var {
            // For now, we don't track full paths - would need to collect them during traversal
            // This is a simplification
            for record in current.iter_mut() {
                // Build path from collected nodes and relationships
                // This is a placeholder - full implementation would track the path
                let path = self.build_path_from_record(record, chain);
                record.add(var.clone(), path);
            }
        }

        Ok(current)
    }

    /// Match nodes for a node pattern.
    fn match_node(
        &self,
        pattern: &NodePattern,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<ResultSet> {
        let mut results = ResultSet::new();

        // Check if variable is already bound
        if let Some(var) = &pattern.variable {
            if let Some(value) = ctx.get(var) {
                if let CypherValue::Node(node) = value {
                    // Check if bound node matches pattern
                    if self.node_matches_pattern(node, pattern, ctx)? {
                        let mut record = Record::new();
                        record.add(var.clone(), CypherValue::Node(node.clone()));
                        results.add(record);
                    }
                }
                return Ok(results);
            }
        }

        // Find all matching nodes
        let candidates: Vec<_> = if !pattern.labels.is_empty() {
            // Use label index
            let mut nodes = HashSet::new();
            for label in &pattern.labels {
                for node in self.graph.nodes_with_label(label) {
                    nodes.insert(node.id());
                }
            }
            nodes.into_iter().filter_map(|id| self.graph.get_node(id)).collect()
        } else {
            self.graph.all_nodes().collect()
        };

        for node in candidates {
            let node_value = NodeValue::from_graph_node(node);
            if self.node_matches_pattern(&node_value, pattern, ctx)? {
                let mut record = Record::new();
                if let Some(var) = &pattern.variable {
                    record.add(var.clone(), CypherValue::Node(node_value.clone()));
                }
                // Always track the last node for relationship extension
                record.add("__last_node__".to_string(), CypherValue::Node(node_value.clone()));
                // Track start node for path construction
                record.add("__path_start_node__".to_string(), CypherValue::Node(node_value));
                results.add(record);
            }
        }

        Ok(results)
    }

    /// Check if a node matches a pattern.
    fn node_matches_pattern(
        &self,
        node: &NodeValue,
        pattern: &NodePattern,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<bool> {
        // Check labels
        for label in &pattern.labels {
            if !node.labels.contains(label) {
                return Ok(false);
            }
        }

        // Check properties
        if let Some(props) = &pattern.properties {
            match props {
                Properties::Map(map) => {
                    let evaluator = Evaluator::new(self.graph, self.functions);
                    for (key, expr) in map {
                        let expected = evaluator.evaluate(expr, ctx)?;
                        let actual = node.properties.get(key).cloned().unwrap_or(CypherValue::Null);
                        if expected != actual {
                            return Ok(false);
                        }
                    }
                }
                Properties::Parameter(name) => {
                    if let Some(CypherValue::Map(map)) = ctx.get_parameter(name) {
                        for (key, expected) in map {
                            let actual = node.properties.get(key).cloned().unwrap_or(CypherValue::Null);
                            if *expected != actual {
                                return Ok(false);
                            }
                        }
                    }
                }
            }
        }

        Ok(true)
    }

    /// Extend matches through a relationship pattern.
    /// `future_excluded_rel_ids` contains relationship IDs that should be excluded from
    /// variable-length patterns because they will be used later in the chain.
    fn extend_matches_with_exclusions(
        &self,
        current: ResultSet,
        rel_pattern: &RelationshipPattern,
        node_pattern: &NodePattern,
        ctx: &ExecutionContext,
        future_excluded_rel_ids: &HashSet<u64>,
    ) -> ExecutionResult<ResultSet> {
        let mut results = ResultSet::new();

        for record in current.iter() {
            // Get the last node from the current record
            let Some(last_node) = self.get_last_node_from_record(record) else {
                continue;
            };

            // Find edges from/to this node based on direction
            let edges = match rel_pattern.direction {
                RelationshipDirection::Outgoing => self.graph.outgoing_edges(last_node.id),
                RelationshipDirection::Incoming => self.graph.incoming_edges(last_node.id),
                RelationshipDirection::Both => {
                    let mut all = self.graph.outgoing_edges(last_node.id);
                    let incoming = self.graph.incoming_edges(last_node.id);

                    // Deduplicate to avoid counting self-loops twice
                    for edge in incoming {
                        if !all.iter().any(|e| e.1.id() == edge.1.id()) {
                            all.push(edge);
                        }
                    }
                    all
                }
            };

            // Handle variable-length relationships
            if let Some(length) = &rel_pattern.length {
                // Check if the relationship variable is already bound to a list of relationships.
                // In this case, we should use those exact relationships instead of finding new paths.
                // This handles patterns like: WITH [r1, r2] AS rs MATCH (a)-[rs*]->(b)
                if let Some(var) = &rel_pattern.variable {
                    let bound_rel_list = record.get(var)
                        .or_else(|| ctx.get(var));

                    if let Some(CypherValue::List(bound_rels)) = bound_rel_list {
                        // Variable is bound to a list - use these exact relationships
                        let rels: Vec<RelationshipValue> = bound_rels.iter()
                            .filter_map(|v| {
                                if let CypherValue::Relationship(r) = v {
                                    Some(r.clone())
                                } else {
                                    None
                                }
                            })
                            .collect();

                        if !rels.is_empty() {
                            // Verify the path: start from last_node, follow rels, end at a node matching node_pattern
                            let mut current_node_id = last_node.id;
                            let mut valid_path = true;

                            for rel in &rels {
                                // Determine direction and next node
                                let next_node_id = match rel_pattern.direction {
                                    RelationshipDirection::Outgoing => {
                                        if rel.start_node_id == current_node_id {
                                            rel.end_node_id
                                        } else {
                                            valid_path = false;
                                            break;
                                        }
                                    }
                                    RelationshipDirection::Incoming => {
                                        if rel.end_node_id == current_node_id {
                                            rel.start_node_id
                                        } else {
                                            valid_path = false;
                                            break;
                                        }
                                    }
                                    RelationshipDirection::Both => {
                                        if rel.start_node_id == current_node_id {
                                            rel.end_node_id
                                        } else if rel.end_node_id == current_node_id {
                                            rel.start_node_id
                                        } else {
                                            valid_path = false;
                                            break;
                                        }
                                    }
                                };
                                current_node_id = next_node_id;
                            }

                            if valid_path {
                                // Check if end node matches the pattern
                                if let Some(end_node) = self.graph.get_node(current_node_id) {
                                    let end_node_value = NodeValue::from_graph_node(end_node);
                                    if self.node_matches_pattern(&end_node_value, node_pattern, ctx)? {
                                        // Valid path using the bound relationships
                                        let mut new_record = record.clone();

                                        let rels_as_values: Vec<CypherValue> = rels.iter()
                                            .map(|r| CypherValue::Relationship(r.clone()))
                                            .collect();
                                        new_record.add("__path_relationships__".to_string(), CypherValue::List(rels_as_values.clone()));
                                        new_record.add(var.clone(), CypherValue::List(rels_as_values));

                                        if let Some(node_var) = &node_pattern.variable {
                                            new_record.add(node_var.clone(), CypherValue::Node(end_node_value.clone()));
                                        }
                                        new_record.add("__last_node__".to_string(), CypherValue::Node(end_node_value));

                                        results.add(new_record);
                                    }
                                }
                            }
                            continue; // Skip the normal variable-length path finding
                        }
                    }
                }

                // If min is explicitly specified (even as 0), use it. Otherwise default to 1.
                let min = if length.min_explicit {
                    length.min.unwrap_or(0) as usize
                } else {
                    length.min.unwrap_or(1) as usize
                };
                let max = length.max.unwrap_or(usize::MAX as u32) as usize;

                // Get excluded relationship IDs:
                // 1. From __path_relationships__ (relationships already used in the path)
                // 2. From future_excluded_rel_ids (bound relationships appearing later in chain)
                // This prevents variable-length patterns from reusing these relationships.
                let mut excluded_rel_ids: HashSet<u64> = if let Some(CypherValue::List(existing)) = record.get("__path_relationships__") {
                    existing.iter().filter_map(|v| {
                        if let CypherValue::Relationship(r) = v {
                            Some(r.id)
                        } else {
                            None
                        }
                    }).collect()
                } else {
                    HashSet::new()
                };
                excluded_rel_ids.extend(future_excluded_rel_ids);

                let paths = self.find_variable_length_paths(
                    last_node.id,
                    VarLenPathParams {
                        rel_pattern,
                        end_pattern: node_pattern,
                        min,
                        max,
                        excluded_rel_ids: &excluded_rel_ids,
                    },
                    ctx,
                )?;

                for (end_node, rels) in paths {
                    // Check if the node variable is already bound and must match
                    // Check BOTH the record AND the context (context has bindings from parent MATCH)
                    if let Some(var) = &node_pattern.variable {
                        let mut bound_node_id = None;

                        // First check the record (from current pattern matching)
                        if let Some(CypherValue::Node(bound_node)) = record.get(var) {
                            bound_node_id = Some(bound_node.id);
                        }
                        // Then check the context (from parent MATCH clauses)
                        else if let Some(CypherValue::Node(bound_node)) = ctx.get(var) {
                            bound_node_id = Some(bound_node.id);
                        }

                        // If variable is bound, end node must match
                        if let Some(id) = bound_node_id {
                            if id != end_node.id {
                                continue;
                            }
                        }
                    }

                    let mut new_record = record.clone();

                    // Always store relationships for path construction
                    let rels_as_values: Vec<CypherValue> = rels.into_iter().map(CypherValue::Relationship).collect();
                    new_record.add("__path_relationships__".to_string(), CypherValue::List(rels_as_values.clone()));

                    if let Some(var) = &rel_pattern.variable {
                        new_record.add(var.clone(), CypherValue::List(rels_as_values));
                    }

                    if let Some(var) = &node_pattern.variable {
                        new_record.add(var.clone(), CypherValue::Node(end_node.clone()));
                    }
                    // Always update the last node for chained pattern matching
                    new_record.add("__last_node__".to_string(), CypherValue::Node(end_node.clone()));

                    results.add(new_record);
                }
            } else {
                // Single-hop relationship
                // Get list of already used relationship IDs to enforce uniqueness
                let used_rels: HashSet<u64> = if let Some(CypherValue::List(existing)) = record.get("__path_relationships__") {
                    existing.iter().filter_map(|v| {
                        if let CypherValue::Relationship(r) = v {
                            Some(r.id)
                        } else {
                            None
                        }
                    }).collect()
                } else {
                    HashSet::new()
                };

                for (other_id, edge) in edges {
                    // Check if this relationship has already been used in the path
                    if used_rels.contains(&edge.id()) {
                        continue;
                    }

                    // Check relationship type
                    if !rel_pattern.types.is_empty() && !rel_pattern.types.iter().any(|t| t.as_str() == edge.rel_type()) {
                        continue;
                    }

                    // Check if the relationship variable is already bound and must match
                    // This handles cases like: MATCH ()-[r1]->(:X) WITH r1 AS r2 MATCH ()-[r2]->()
                    if let Some(var) = &rel_pattern.variable {
                        let mut bound_rel_id = None;

                        // First check the record (from current pattern matching)
                        if let Some(CypherValue::Relationship(bound_rel)) = record.get(var) {
                            bound_rel_id = Some(bound_rel.id);
                        }
                        // Then check the context (from parent MATCH clauses or WITH)
                        else if let Some(CypherValue::Relationship(bound_rel)) = ctx.get(var) {
                            bound_rel_id = Some(bound_rel.id);
                        }

                        // If variable is bound, edge must match
                        if let Some(id) = bound_rel_id {
                            if id != edge.id() {
                                continue;
                            }
                        }
                    }

                    // Get the target node
                    let Some(target_node) = self.graph.get_node(other_id) else {
                        continue;
                    };
                    let target_value = NodeValue::from_graph_node(target_node);

                    // Check if target matches pattern
                    if !self.node_matches_pattern(&target_value, node_pattern, ctx)? {
                        continue;
                    }

                    // Check if the node variable is already bound and must match
                    // Check BOTH the record AND the context (context has bindings from parent MATCH)
                    if let Some(var) = &node_pattern.variable {
                        let mut bound_node_id = None;

                        // First check the record (from current pattern matching)
                        if let Some(CypherValue::Node(bound_node)) = record.get(var) {
                            bound_node_id = Some(bound_node.id);
                        }
                        // Then check the context (from parent MATCH clauses)
                        else if let Some(CypherValue::Node(bound_node)) = ctx.get(var) {
                            bound_node_id = Some(bound_node.id);
                        }

                        // If variable is bound, target must match
                        if let Some(id) = bound_node_id {
                            if id != target_value.id {
                                continue;
                            }
                        }
                    }

                    // Get edge info before consuming edge
                    let edge_id = edge.id();
                    let (start_id, end_id) = self.graph.get_edge_endpoints(edge_id).unwrap_or((0, 0));

                    // Check relationship properties
                    if !self.relationship_matches_pattern(edge, rel_pattern, ctx)? {
                        continue;
                    }

                    // Create relationship value
                    let edge = self.graph.get_edge(edge_id)
                        .ok_or(ExecutionError::RelationshipNotFound(edge_id))?;
                    let rel_value = RelationshipValue::from_graph_edge(edge, start_id, end_id);

                    // Add to results
                    let mut new_record = record.clone();

                    // Always track relationships for path construction (even anonymous ones)
                    // Append to existing path relationships if any
                    let mut path_rels = if let Some(CypherValue::List(existing)) = record.get("__path_relationships__") {
                        existing.clone()
                    } else {
                        Vec::new()
                    };
                    path_rels.push(CypherValue::Relationship(rel_value.clone()));
                    new_record.add("__path_relationships__".to_string(), CypherValue::List(path_rels));

                    if let Some(var) = &rel_pattern.variable {
                        new_record.add(var.clone(), CypherValue::Relationship(rel_value));
                    }
                    if let Some(var) = &node_pattern.variable {
                        new_record.add(var.clone(), CypherValue::Node(target_value.clone()));
                    }
                    // Always update the last node for chained pattern matching
                    new_record.add("__last_node__".to_string(), CypherValue::Node(target_value.clone()));

                    results.add(new_record);
                }
            }
        }

        Ok(results)
    }

    /// Check if a relationship matches a pattern.
    fn relationship_matches_pattern(
        &self,
        edge: impl EdgeLike,
        pattern: &RelationshipPattern,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<bool> {
        // Check properties
        if let Some(props) = &pattern.properties {
            match props {
                Properties::Map(map) => {
                    let evaluator = Evaluator::new(self.graph, self.functions);
                    for (key, expr) in map {
                        let expected = evaluator.evaluate(expr, ctx)?;
                        let actual = edge.properties().get(key)
                            .map(|v| CypherValue::from(v.clone()))
                            .unwrap_or(CypherValue::Null);
                        if expected != actual {
                            return Ok(false);
                        }
                    }
                }
                Properties::Parameter(name) => {
                    if let Some(CypherValue::Map(map)) = ctx.get_parameter(name) {
                        for (key, expected) in map {
                            let actual = edge.properties().get(key)
                                .map(|v| CypherValue::from(v.clone()))
                                .unwrap_or(CypherValue::Null);
                            if *expected != actual {
                                return Ok(false);
                            }
                        }
                    }
                }
            }
        }

        Ok(true)
    }

    /// Find variable-length paths.
    /// `excluded_rel_ids` contains relationship IDs that should not be traversed
    /// (e.g., relationships already used earlier in the pattern or bound from context).
    fn find_variable_length_paths(
        &self,
        start_id: u64,
        params: VarLenPathParams<'_>,
        ctx: &ExecutionContext,
    ) -> ExecutionResult<Vec<(NodeValue, Vec<RelationshipValue>)>> {
        let VarLenPathParams { rel_pattern, end_pattern, min, max, excluded_rel_ids } = params;
        let mut results = Vec::new();

        // If min=0, include zero-length path (start node with empty relationship list)
        if min == 0 {
            if let Some(node) = self.graph.get_node(start_id) {
                let node_value = NodeValue::from_graph_node(node);
                if self.node_matches_pattern(&node_value, end_pattern, ctx)? {
                    results.push((node_value, Vec::new()));
                }
            }
        }

        // Initialize visited set with excluded relationship IDs to prevent using them
        let initial_visited: HashSet<u64> = excluded_rel_ids.clone();
        let mut queue = vec![(start_id, Vec::new(), initial_visited)];

        while let Some((current_id, path, visited)) = queue.pop() {
            // Check if we've reached minimum length and end pattern matches
            // Skip if this is the initial empty path and min=0 (already added above)
            if path.len() >= min && !(path.is_empty() && min == 0) {
                if let Some(node) = self.graph.get_node(current_id) {
                    let node_value = NodeValue::from_graph_node(node);
                    if self.node_matches_pattern(&node_value, end_pattern, ctx)? {
                        results.push((node_value, path.clone()));
                    }
                }
            }

            // Continue if we haven't reached max length
            if path.len() < max {
                let edges = match rel_pattern.direction {
                    RelationshipDirection::Outgoing => self.graph.outgoing_edges(current_id),
                    RelationshipDirection::Incoming => self.graph.incoming_edges(current_id),
                    RelationshipDirection::Both => {
                        let mut all = self.graph.outgoing_edges(current_id);
                        let incoming = self.graph.incoming_edges(current_id);

                        // Deduplicate to avoid counting self-loops twice
                        for edge in incoming {
                            if !all.iter().any(|e| e.1.id() == edge.1.id()) {
                                all.push(edge);
                            }
                        }
                        all
                    }
                };

                for (next_id, edge) in edges {
                    // Avoid cycles
                    if visited.contains(&edge.id()) {
                        continue;
                    }

                    // Check relationship type
                    if !rel_pattern.types.is_empty() && !rel_pattern.types.iter().any(|t| t.as_str() == edge.rel_type()) {
                        continue;
                    }

                    let edge_id = edge.id();
                    let (start, end) = self.graph.get_edge_endpoints(edge_id).unwrap_or((0, 0));
                    let rel_value = RelationshipValue::from_graph_edge(edge, start, end);

                    // Check relationship properties (for variable-length path property predicates)
                    if let Some(props) = &rel_pattern.properties {
                        match props {
                            Properties::Map(map) => {
                                let evaluator = Evaluator::new(self.graph, self.functions);
                                let mut matches = true;
                                for (key, expr) in map {
                                    let expected = evaluator.evaluate(expr, ctx)?;
                                    let actual = rel_value.properties.get(key).cloned().unwrap_or(CypherValue::Null);
                                    if expected != actual {
                                        matches = false;
                                        break;
                                    }
                                }
                                if !matches {
                                    continue;
                                }
                            }
                            Properties::Parameter(_) => {
                                // Skip - parameter-based property matching would need evaluation
                                // against the relationship's full property map
                            }
                        }
                    }

                    let mut new_path = path.clone();
                    new_path.push(rel_value);

                    let mut new_visited = visited.clone();
                    new_visited.insert(edge_id);

                    queue.push((next_id, new_path, new_visited));
                }
            }
        }

        // For undirected patterns, deduplicate truly equivalent paths.
        // A path A-B-C is the same as C-B-A (reverse direction) but NOT the same
        // as a different sequence using the same edges.
        // Example: (4, 5, 2) and (5, 4, 2) are DIFFERENT paths.
        // But (4, 5, 2) from start to end equals (2, 5, 4) from end to start.
        if rel_pattern.direction == RelationshipDirection::Both {
            let mut seen = std::collections::HashSet::new();
            results.retain(|(end_node, path)| {
                // Get relationship IDs in traversal order
                let rel_ids: Vec<u64> = path.iter().map(|r| r.id).collect();

                // Create canonical form by choosing the lexicographically smaller
                // between forward and reverse sequence. This ensures that a path
                // and its reverse are treated as equivalent.
                let reverse_ids: Vec<u64> = rel_ids.iter().rev().cloned().collect();
                let canonical_ids = if rel_ids <= reverse_ids { rel_ids } else { reverse_ids };

                // Include start and end node IDs in sorted order to distinguish
                // paths with same relationships but different endpoints
                let (min_node, max_node) = if start_id <= end_node.id {
                    (start_id, end_node.id)
                } else {
                    (end_node.id, start_id)
                };
                let path_sig = format!("{}_{}_{:?}", min_node, max_node, canonical_ids);
                seen.insert(path_sig)
            });
        }

        Ok(results)
    }

    /// Match shortest path patterns.
    fn match_shortest_path(
        &self,
        element: &PatternElement,
        ctx: &ExecutionContext,
        path_var: &Option<String>,
        all_paths: bool,
    ) -> ExecutionResult<ResultSet> {
        let chain = match element {
            PatternElement::Chain(c) => c,
            _ => return Err(ExecutionError::Internal("Invalid shortest path element".to_string())),
        };

        if chain.chain.is_empty() {
            return Err(ExecutionError::PathError(
                "shortestPath requires at least one relationship".to_string(),
            ));
        }

        let mut results = ResultSet::new();

        // Match start and end nodes
        let start_matches = self.match_node(&chain.start, ctx)?;
        let end_node = &chain.chain.last()
            .ok_or_else(|| ExecutionError::PatternMatchFailed("Empty relationship chain in pattern".to_string()))?
            .1;
        let end_matches = self.match_node(end_node, ctx)?;

        for start_rec in start_matches.iter() {
            for end_rec in end_matches.iter() {
                let start_node = self.get_last_node_from_record(start_rec);
                let end_node = self.get_last_node_from_record(end_rec);

                if let (Some(s), Some(e)) = (start_node, end_node) {
                    let paths = if all_paths {
                        self.graph.all_shortest_paths(s.id, e.id)
                    } else {
                        self.graph.shortest_path(s.id, e.id).into_iter().collect()
                    };

                    for path in paths {
                        let path_value = PathValue::from_path(&path, |id| {
                            self.graph.get_edge_endpoints(id).unwrap_or((0, 0))
                        });

                        let mut record = Record::new();
                        record.merge(start_rec.clone());
                        record.merge(end_rec.clone());

                        if let Some(var) = path_var {
                            record.add(var.clone(), CypherValue::Path(path_value));
                        }

                        results.add(record);
                    }
                }
            }
        }

        Ok(results)
    }

    /// Helper to get the last node from a record.
    fn get_last_node_from_record(&self, record: &Record) -> Option<NodeValue> {
        // First try the explicit __last_node__ key
        if let Some(CypherValue::Node(n)) = record.get("__last_node__") {
            return Some(n.clone());
        }
        // Fallback: find any node in the record
        for (_, value) in record.iter() {
            if let CypherValue::Node(n) = value {
                return Some(n.clone());
            }
        }
        None
    }

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

        // Extract nodes and relationships from the record
        // The pattern variables should be in the record

        // Get the start node - try variable first, then internal key
        let start_node = if let Some(var) = &chain.start.variable {
            record.get(var).and_then(|v| if let CypherValue::Node(n) = v { Some(n.clone()) } else { None })
        } else {
            record.get("__path_start_node__").and_then(|v| if let CypherValue::Node(n) = v { Some(n.clone()) } else { None })
        };

        if let Some(node) = start_node.clone() {
            nodes.push(node);
        }

        // Get relationship and end nodes from the chain
        for (rel_pattern, node_pattern) in &chain.chain {
            // Collect relationships
            let mut chain_rels: Vec<RelationshipValue> = Vec::new();

            if let Some(var) = &rel_pattern.variable {
                if let Some(CypherValue::Relationship(rel)) = record.get(var) {
                    chain_rels.push(rel.clone());
                } else if let Some(CypherValue::List(rels)) = record.get(var) {
                    // Variable-length relationship
                    for rel_val in rels {
                        if let CypherValue::Relationship(rel) = rel_val {
                            chain_rels.push(rel.clone());
                        }
                    }
                }
            } else if let Some(CypherValue::List(rels)) = record.get("__path_relationships__") {
                // Use internal relationships for anonymous variable-length paths
                for rel_val in rels {
                    if let CypherValue::Relationship(rel) = rel_val {
                        chain_rels.push(rel.clone());
                    }
                }
            }

            // For variable-length paths, we need to add intermediate nodes
            // by following the relationship chain from the start node
            if chain_rels.len() > 1 || (chain_rels.len() == 1 && rel_pattern.length.is_some()) {
                // This is a variable-length path - reconstruct intermediate nodes
                let mut current_node_id = start_node.as_ref().map(|n| n.id).unwrap_or(0);

                for rel in &chain_rels {
                    // Determine which end of the relationship we came from and which we're going to
                    let next_node_id = if rel.start_node_id == current_node_id {
                        rel.end_node_id
                    } else {
                        rel.start_node_id
                    };

                    // Add the next node in the path (fetch from graph)
                    if let Some(node) = self.graph.get_node(next_node_id) {
                        nodes.push(NodeValue::from_graph_node(node));
                    }

                    current_node_id = next_node_id;
                }

                relationships.extend(chain_rels);
            } else {
                // Single-hop relationship or no relationships
                relationships.extend(chain_rels);

                if let Some(var) = &node_pattern.variable {
                    if let Some(CypherValue::Node(node)) = record.get(var) {
                        // For zero-length paths, don't add the end node if it's the same as the start
                        let should_add = if relationships.is_empty() {
                            nodes.last().is_none_or(|last| last.id != node.id)
                        } else {
                            true
                        };
                        if should_add {
                            nodes.push(node.clone());
                        }
                    }
                }
            }
        }

        // Add the end node if we have relationships but no named end node
        if !relationships.is_empty() && (nodes.len() < 2 || chain.chain.last().is_some_and(|(_, n)| n.variable.is_none())) {
            if let Some(CypherValue::Node(node)) = record.get("__last_node__") {
                if nodes.is_empty() || nodes.last().is_none_or(|n| n.id != node.id) {
                    nodes.push(node.clone());
                }
            }
        }

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

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

    fn create_test_graph() -> PropertyGraph {
        let mut graph = PropertyGraph::new();

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

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

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

        // Create relationships
        graph.create_relationship(1, 2, "KNOWS", IndexMap::new()).unwrap();
        graph.create_relationship(2, 3, "KNOWS", IndexMap::new()).unwrap();

        graph
    }

    #[test]
    fn test_match_all_nodes() {
        let graph = create_test_graph();
        let funcs = FunctionRegistry::new();
        let matcher = PatternMatcher::new(&graph, &funcs);
        let ctx = ExecutionContext::new();

        let pattern = NodePattern::with_variable("n");
        let results = matcher.match_node(&pattern, &ctx).unwrap();

        assert_eq!(results.len(), 3);
    }

    #[test]
    fn test_match_labeled_nodes() {
        let graph = create_test_graph();
        let funcs = FunctionRegistry::new();
        let matcher = PatternMatcher::new(&graph, &funcs);
        let ctx = ExecutionContext::new();

        let pattern = NodePattern::with_label(Some("n".to_string()), "Person");
        let results = matcher.match_node(&pattern, &ctx).unwrap();

        assert_eq!(results.len(), 3);
    }
}
