//! Traversal API
//!
//! Fluent API for graph traversal with lazy iterator results.
//!
//! Ported from src/api/traversal.ts

use crate::types::{ETypeId, Edge, NodeId, PropValue};
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::Arc;

/// Type alias for edge filter predicates
pub type EdgeFilter = Arc<dyn Fn(&EdgeInfo) -> bool + Send + Sync>;

/// Type alias for node filter predicates  
pub type NodeFilter = Arc<dyn Fn(&NodeInfo) -> bool + Send + Sync>;

// ============================================================================
// Traversal Types
// ============================================================================

/// Direction for traversal
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TraversalDirection {
  Out,
  In,
  Both,
}

/// Options for variable-depth traversal
#[derive(Clone)]
pub struct TraverseOptions {
  /// Direction of traversal
  pub direction: TraversalDirection,
  /// Minimum depth (default: 1)
  pub min_depth: usize,
  /// Maximum depth
  pub max_depth: usize,
  /// Whether to only visit unique nodes (default: true)
  pub unique: bool,
  /// Edge filter predicate for variable-depth traversal
  pub where_edge: Option<EdgeFilter>,
  /// Node filter predicate for variable-depth traversal
  pub where_node: Option<NodeFilter>,
}

impl std::fmt::Debug for TraverseOptions {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    f.debug_struct("TraverseOptions")
      .field("direction", &self.direction)
      .field("min_depth", &self.min_depth)
      .field("max_depth", &self.max_depth)
      .field("unique", &self.unique)
      .field("where_edge", &self.where_edge.as_ref().map(|_| "<fn>"))
      .field("where_node", &self.where_node.as_ref().map(|_| "<fn>"))
      .finish()
  }
}

impl Default for TraverseOptions {
  fn default() -> Self {
    Self {
      direction: TraversalDirection::Out,
      min_depth: 1,
      max_depth: 1,
      unique: true,
      where_edge: None,
      where_node: None,
    }
  }
}

impl TraverseOptions {
  pub fn new(direction: TraversalDirection, max_depth: usize) -> Self {
    Self {
      direction,
      min_depth: 1,
      max_depth,
      unique: true,
      where_edge: None,
      where_node: None,
    }
  }

  pub fn with_min_depth(mut self, min_depth: usize) -> Self {
    self.min_depth = min_depth;
    self
  }

  pub fn with_unique(mut self, unique: bool) -> Self {
    self.unique = unique;
    self
  }

  /// Add an edge filter predicate for variable-depth traversal
  pub fn with_edge_filter<F>(mut self, predicate: F) -> Self
  where
    F: Fn(&EdgeInfo) -> bool + Send + Sync + 'static,
  {
    self.where_edge = Some(Arc::new(predicate));
    self
  }

  /// Add a node filter predicate for variable-depth traversal
  pub fn with_node_filter<F>(mut self, predicate: F) -> Self
  where
    F: Fn(&NodeInfo) -> bool + Send + Sync + 'static,
  {
    self.where_node = Some(Arc::new(predicate));
    self
  }
}

/// Raw edge data without any property loading
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RawEdge {
  pub src: NodeId,
  pub dst: NodeId,
  pub etype: ETypeId,
}

impl From<Edge> for RawEdge {
  fn from(edge: Edge) -> Self {
    Self {
      src: edge.src,
      dst: edge.dst,
      etype: edge.etype,
    }
  }
}

/// Edge result with properties
#[derive(Debug, Clone)]
pub struct EdgeResult {
  pub src: NodeId,
  pub dst: NodeId,
  pub etype: ETypeId,
  pub props: Vec<(String, PropValue)>,
}

/// Traversal result with node and edge
#[derive(Debug, Clone)]
pub struct TraversalResult {
  pub node_id: NodeId,
  pub edge: Option<RawEdge>,
  pub depth: usize,
}

/// Edge info for filter predicates
#[derive(Debug, Clone)]
pub struct EdgeInfo {
  pub src: NodeId,
  pub dst: NodeId,
  pub etype: ETypeId,
  pub props: HashMap<String, PropValue>,
}

impl From<RawEdge> for EdgeInfo {
  fn from(edge: RawEdge) -> Self {
    Self {
      src: edge.src,
      dst: edge.dst,
      etype: edge.etype,
      props: HashMap::new(),
    }
  }
}

/// Node info for filter predicates
#[derive(Debug, Clone)]
pub struct NodeInfo {
  pub id: NodeId,
  pub props: HashMap<String, PropValue>,
}

// ============================================================================
// Traversal Step
// ============================================================================

/// A single step in a traversal query
#[derive(Clone)]
pub enum TraversalStep {
  /// Single-hop traversal (out, in, or both)
  SingleHop {
    direction: TraversalDirection,
    etype: Option<ETypeId>,
    /// Edge filter for this step
    edge_filter: Option<EdgeFilter>,
    /// Node filter for this step
    node_filter: Option<NodeFilter>,
  },
  /// Variable-depth traversal
  Traverse {
    etype: Option<ETypeId>,
    options: TraverseOptions,
  },
}

impl std::fmt::Debug for TraversalStep {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
      Self::SingleHop {
        direction,
        etype,
        edge_filter,
        node_filter,
      } => f
        .debug_struct("SingleHop")
        .field("direction", direction)
        .field("etype", etype)
        .field("edge_filter", &edge_filter.as_ref().map(|_| "<fn>"))
        .field("node_filter", &node_filter.as_ref().map(|_| "<fn>"))
        .finish(),
      Self::Traverse { etype, options } => f
        .debug_struct("Traverse")
        .field("etype", etype)
        .field("options", options)
        .finish(),
    }
  }
}

// ============================================================================
// Traversal Builder
// ============================================================================

/// Builder for constructing traversal queries
///
/// # Example
/// ```rust,no_run
/// # use kitedb::api::traversal::{TraversalBuilder, TraversalDirection};
/// # use kitedb::types::{Edge, ETypeId, NodeId};
/// # fn neighbors_fn(
/// #   _: NodeId,
/// #   _: TraversalDirection,
/// #   _: Option<ETypeId>,
/// # ) -> Vec<Edge> {
/// #   Vec::new()
/// # }
/// # fn main() {
/// # let start_node_id: NodeId = 1;
/// # let follows_etype: ETypeId = 1;
/// # let knows_etype: ETypeId = 2;
/// let builder = TraversalBuilder::new(vec![start_node_id])
///     .out(Some(follows_etype))
///     .out(Some(knows_etype))
///     .where_edge(|e| e.etype == 1)
///     .take(10);
///
/// for result in builder.execute(&neighbors_fn) {
///     println!("Found node: {}", result.node_id);
/// }
/// # }
/// ```
#[derive(Clone)]
pub struct TraversalBuilder {
  /// Starting node IDs
  start_nodes: Vec<NodeId>,
  /// Traversal steps to execute
  steps: Vec<TraversalStep>,
  /// Maximum number of results (None = unlimited)
  limit: Option<usize>,
  /// Whether to skip visited nodes across all steps
  unique_nodes: bool,
  /// Global edge filter applied to all results
  edge_filter: Option<EdgeFilter>,
  /// Global node filter applied to all results
  node_filter: Option<NodeFilter>,
  /// Selected properties for node projection (None = load all)
  selected_props: Option<Vec<String>>,
}

impl std::fmt::Debug for TraversalBuilder {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    f.debug_struct("TraversalBuilder")
      .field("start_nodes", &self.start_nodes)
      .field("steps", &self.steps)
      .field("limit", &self.limit)
      .field("unique_nodes", &self.unique_nodes)
      .field("edge_filter", &self.edge_filter.as_ref().map(|_| "<fn>"))
      .field("node_filter", &self.node_filter.as_ref().map(|_| "<fn>"))
      .field("selected_props", &self.selected_props)
      .finish()
  }
}

impl TraversalBuilder {
  /// Create a new traversal builder starting from the given nodes
  pub fn new(start_nodes: Vec<NodeId>) -> Self {
    Self {
      start_nodes,
      steps: Vec::new(),
      limit: None,
      unique_nodes: true,
      edge_filter: None,
      node_filter: None,
      selected_props: None,
    }
  }

  /// Create a new traversal builder starting from a single node
  pub fn from_node(node_id: NodeId) -> Self {
    Self::new(vec![node_id])
  }

  pub(crate) fn push_step(&mut self, step: TraversalStep) {
    self.steps.push(step);
  }

  /// Add an outgoing edge traversal step
  pub fn out(mut self, etype: Option<ETypeId>) -> Self {
    self.steps.push(TraversalStep::SingleHop {
      direction: TraversalDirection::Out,
      etype,
      edge_filter: None,
      node_filter: None,
    });
    self
  }

  /// Add an incoming edge traversal step
  pub fn r#in(mut self, etype: Option<ETypeId>) -> Self {
    self.steps.push(TraversalStep::SingleHop {
      direction: TraversalDirection::In,
      etype,
      edge_filter: None,
      node_filter: None,
    });
    self
  }

  /// Add a bidirectional edge traversal step
  pub fn both(mut self, etype: Option<ETypeId>) -> Self {
    self.steps.push(TraversalStep::SingleHop {
      direction: TraversalDirection::Both,
      etype,
      edge_filter: None,
      node_filter: None,
    });
    self
  }

  /// Add a variable-depth traversal step
  pub fn traverse(mut self, etype: Option<ETypeId>, options: TraverseOptions) -> Self {
    self.steps.push(TraversalStep::Traverse { etype, options });
    self
  }

  /// Limit the number of results
  pub fn take(mut self, limit: usize) -> Self {
    self.limit = Some(limit);
    self
  }

  /// Set whether to only visit unique nodes
  pub fn unique(mut self, unique: bool) -> Self {
    self.unique_nodes = unique;
    self
  }

  /// Add a global edge filter predicate
  ///
  /// This filter is applied to all edges traversed. Only edges where
  /// the predicate returns `true` will be included in results.
  ///
  /// # Example
  /// ```rust,no_run
  /// # use kitedb::api::traversal::TraversalBuilder;
  /// # use kitedb::types::ETypeId;
  /// # fn main() {
  /// # let knows_etype: ETypeId = 1;
  /// let builder = TraversalBuilder::from_node(1)
  ///     .out(Some(knows_etype))
  ///     .where_edge(|edge| edge.etype == 1);
  /// # }
  /// ```
  pub fn where_edge<F>(mut self, predicate: F) -> Self
  where
    F: Fn(&EdgeInfo) -> bool + Send + Sync + 'static,
  {
    self.edge_filter = Some(Arc::new(predicate));
    self
  }

  /// Add a global node filter predicate
  ///
  /// This filter is applied to all nodes traversed. Only nodes where
  /// the predicate returns `true` will be included in results.
  ///
  /// # Example
  /// ```rust,no_run
  /// # use kitedb::api::traversal::TraversalBuilder;
  /// # use kitedb::types::ETypeId;
  /// # fn main() {
  /// # let knows_etype: ETypeId = 1;
  /// let builder = TraversalBuilder::from_node(1)
  ///     .out(Some(knows_etype))
  ///     .where_node(|node| node.id > 5);
  /// # }
  /// ```
  pub fn where_node<F>(mut self, predicate: F) -> Self
  where
    F: Fn(&NodeInfo) -> bool + Send + Sync + 'static,
  {
    self.node_filter = Some(Arc::new(predicate));
    self
  }

  /// Select specific properties to load (optimization)
  ///
  /// Only the specified properties will be loaded when collecting results,
  /// reducing overhead. This is useful when you only need a few properties
  /// from nodes that have many properties.
  ///
  /// # Example
  /// ```rust,no_run
  /// # use kitedb::api::traversal::TraversalBuilder;
  /// # use kitedb::types::ETypeId;
  /// # fn main() {
  /// # let knows_etype: ETypeId = 1;
  /// let builder = TraversalBuilder::from_node(1)
  ///     .out(Some(knows_etype))
  ///     .select(vec!["name".to_string(), "age".to_string()]);
  ///
  /// // Only "name" and "age" properties will be loaded
  /// # }
  /// ```
  pub fn select(mut self, props: Vec<String>) -> Self {
    self.selected_props = Some(props);
    self
  }

  /// Select specific properties to load using string slices
  ///
  /// Convenience method that accepts &str instead of String.
  pub fn select_props(mut self, props: &[&str]) -> Self {
    self.selected_props = Some(props.iter().map(|s| s.to_string()).collect());
    self
  }

  /// Get the selected properties (if any)
  pub fn selected_properties(&self) -> Option<&[String]> {
    self.selected_props.as_deref()
  }

  /// Check if the builder has any filters set
  pub fn has_filters(&self) -> bool {
    if self.edge_filter.is_some() || self.node_filter.is_some() {
      return true;
    }
    for step in &self.steps {
      match step {
        TraversalStep::SingleHop {
          edge_filter,
          node_filter,
          ..
        } => {
          if edge_filter.is_some() || node_filter.is_some() {
            return true;
          }
        }
        TraversalStep::Traverse { options, .. } => {
          if options.where_edge.is_some() || options.where_node.is_some() {
            return true;
          }
        }
      }
    }
    false
  }

  /// Build CollectOptions from the current builder configuration
  ///
  /// This creates CollectOptions with the selected properties for use
  /// when materializing results with properties.
  pub fn collect_options(&self) -> CollectOptions {
    let mut opts = CollectOptions::new();
    if let Some(ref props) = self.selected_props {
      opts = opts.select_node_props(props.clone());
    }
    opts
  }

  /// Execute the traversal and return an iterator of results
  ///
  /// The `neighbors` function should return neighbors for a given node and direction.
  pub fn execute<F>(self, neighbors: F) -> TraversalIterator<F>
  where
    F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
  {
    TraversalIterator::new(self, neighbors)
  }

  /// Execute the traversal and collect all node IDs
  pub fn collect_node_ids<F>(self, neighbors: F) -> Vec<NodeId>
  where
    F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
  {
    self.execute(neighbors).map(|r| r.node_id).collect()
  }

  /// Execute the traversal and count results (optimized path)
  pub fn count<F>(self, neighbors: F) -> usize
  where
    F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
  {
    // For simple traversals without variable-depth, use fast counting
    if self.can_use_fast_count() {
      return self.count_fast(&neighbors);
    }

    // Fall back to full iteration
    self.execute(neighbors).count()
  }

  /// Check if we can use the fast count path
  fn can_use_fast_count(&self) -> bool {
    // Cannot use fast path if any filters are set
    if self.has_filters() {
      return false;
    }

    // Can only use fast path for simple single-hop traversals
    for step in &self.steps {
      if matches!(step, TraversalStep::Traverse { .. }) {
        return false;
      }
    }
    true
  }

  /// Fast count for simple traversals
  fn count_fast<F>(&self, neighbors: &F) -> usize
  where
    F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
  {
    let mut current_nodes: HashSet<NodeId> = self.start_nodes.iter().copied().collect();

    for step in &self.steps {
      let TraversalStep::SingleHop {
        direction, etype, ..
      } = step
      else {
        unreachable!()
      };

      let mut next_nodes = HashSet::new();

      for node_id in current_nodes {
        let edges = neighbors(node_id, *direction, *etype);
        for edge in edges {
          let neighbor = match direction {
            TraversalDirection::Out => edge.dst,
            TraversalDirection::In => edge.src,
            TraversalDirection::Both => {
              if edge.src == node_id {
                edge.dst
              } else {
                edge.src
              }
            }
          };
          next_nodes.insert(neighbor);
        }
      }

      current_nodes = next_nodes;
    }

    // Apply limit if set
    if let Some(limit) = self.limit {
      current_nodes.len().min(limit)
    } else {
      current_nodes.len()
    }
  }

  /// Get raw edges without property loading (fastest traversal mode)
  pub fn raw_edges<F>(self, neighbors: F) -> RawEdgeIterator<F>
  where
    F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
  {
    RawEdgeIterator::new(self, neighbors)
  }
}

// ============================================================================
// Traversal Iterator
// ============================================================================

/// Iterator for traversal results
pub struct TraversalIterator<F> {
  /// The neighbors function
  neighbors: F,
  /// Current step index
  step_index: usize,
  /// Steps to execute
  steps: Vec<TraversalStep>,
  /// Current frontier of node IDs to process
  current_frontier: VecDeque<TraversalResult>,
  /// Visited nodes (for uniqueness)
  visited: HashSet<NodeId>,
  /// Whether to track unique nodes
  unique_nodes: bool,
  /// Maximum results
  limit: Option<usize>,
  /// Results yielded so far
  yielded: usize,
  /// Whether we're done
  done: bool,
  /// Global edge filter
  edge_filter: Option<EdgeFilter>,
  /// Global node filter
  node_filter: Option<NodeFilter>,
}

impl<F> TraversalIterator<F>
where
  F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
  fn new(builder: TraversalBuilder, neighbors: F) -> Self {
    let mut frontier = VecDeque::new();
    let mut visited = HashSet::new();

    // Initialize with start nodes
    for node_id in builder.start_nodes {
      if builder.unique_nodes {
        visited.insert(node_id);
      }
      frontier.push_back(TraversalResult {
        node_id,
        edge: None,
        depth: 0,
      });
    }

    Self {
      neighbors,
      step_index: 0,
      steps: builder.steps,
      current_frontier: frontier,
      visited,
      unique_nodes: builder.unique_nodes,
      limit: builder.limit,
      yielded: 0,
      done: false,
      edge_filter: builder.edge_filter,
      node_filter: builder.node_filter,
    }
  }

  /// Check if a result passes all filters
  fn passes_filters(&self, result: &TraversalResult) -> bool {
    // Check edge filter
    if let Some(ref edge_filter) = self.edge_filter {
      if let Some(ref raw_edge) = result.edge {
        let edge_info = EdgeInfo::from(*raw_edge);
        if !edge_filter(&edge_info) {
          return false;
        }
      }
    }

    // Check node filter
    if let Some(ref node_filter) = self.node_filter {
      let node_info = NodeInfo {
        id: result.node_id,
        props: HashMap::new(), // Note: props would need to be loaded if used
      };
      if !node_filter(&node_info) {
        return false;
      }
    }

    true
  }

  /// Process a single-hop step
  fn process_single_hop(
    &mut self,
    direction: TraversalDirection,
    etype: Option<ETypeId>,
    step_edge_filter: &Option<EdgeFilter>,
    step_node_filter: &Option<NodeFilter>,
  ) -> VecDeque<TraversalResult> {
    let mut next_frontier = VecDeque::new();

    for result in self.current_frontier.drain(..) {
      let edges = (self.neighbors)(result.node_id, direction, etype);

      for edge in edges {
        let neighbor_id = match direction {
          TraversalDirection::Out => edge.dst,
          TraversalDirection::In => edge.src,
          TraversalDirection::Both => {
            if edge.src == result.node_id {
              edge.dst
            } else {
              edge.src
            }
          }
        };

        // Skip if already visited (and uniqueness is enabled)
        if self.unique_nodes && self.visited.contains(&neighbor_id) {
          continue;
        }

        let raw_edge = RawEdge::from(edge);

        // Apply step-level edge filter
        if let Some(ref edge_filter) = step_edge_filter {
          let edge_info = EdgeInfo::from(raw_edge);
          if !edge_filter(&edge_info) {
            continue;
          }
        }

        // Apply step-level node filter
        if let Some(ref node_filter) = step_node_filter {
          let node_info = NodeInfo {
            id: neighbor_id,
            props: HashMap::new(),
          };
          if !node_filter(&node_info) {
            continue;
          }
        }

        if self.unique_nodes {
          self.visited.insert(neighbor_id);
        }

        next_frontier.push_back(TraversalResult {
          node_id: neighbor_id,
          edge: Some(raw_edge),
          depth: result.depth + 1,
        });
      }
    }

    next_frontier
  }

  /// Process a variable-depth traversal step (BFS)
  fn process_traverse(
    &mut self,
    etype: Option<ETypeId>,
    options: &TraverseOptions,
  ) -> VecDeque<TraversalResult> {
    let mut results = VecDeque::new();
    let mut local_visited: HashSet<NodeId> = if options.unique {
      self.current_frontier.iter().map(|r| r.node_id).collect()
    } else {
      HashSet::new()
    };

    // BFS queue: (node_id, depth)
    let mut queue: VecDeque<(NodeId, usize)> = self
      .current_frontier
      .drain(..)
      .map(|r| (r.node_id, 0))
      .collect();

    while let Some((current_id, depth)) = queue.pop_front() {
      if depth >= options.max_depth {
        continue;
      }

      // Get neighbors based on direction
      let directions = match options.direction {
        TraversalDirection::Both => vec![TraversalDirection::Out, TraversalDirection::In],
        dir => vec![dir],
      };

      for dir in directions {
        let edges = (self.neighbors)(current_id, dir, etype);

        for edge in edges {
          let neighbor_id = match dir {
            TraversalDirection::Out => edge.dst,
            TraversalDirection::In => edge.src,
            TraversalDirection::Both => unreachable!(),
          };

          // Check uniqueness
          if options.unique && local_visited.contains(&neighbor_id) {
            continue;
          }

          let raw_edge = RawEdge::from(edge);

          // Apply edge filter from TraverseOptions
          if let Some(ref edge_filter) = options.where_edge {
            let edge_info = EdgeInfo::from(raw_edge);
            if !edge_filter(&edge_info) {
              continue;
            }
          }

          // Apply node filter from TraverseOptions
          if let Some(ref node_filter) = options.where_node {
            let node_info = NodeInfo {
              id: neighbor_id,
              props: HashMap::new(),
            };
            if !node_filter(&node_info) {
              continue;
            }
          }

          if options.unique {
            local_visited.insert(neighbor_id);
          }

          // Also check global visited set
          if self.unique_nodes && self.visited.contains(&neighbor_id) {
            continue;
          }
          if self.unique_nodes {
            self.visited.insert(neighbor_id);
          }

          let new_depth = depth + 1;

          // Yield if at or past min_depth
          if new_depth >= options.min_depth {
            results.push_back(TraversalResult {
              node_id: neighbor_id,
              edge: Some(raw_edge),
              depth: new_depth,
            });
          }

          // Continue BFS if not at max depth
          if new_depth < options.max_depth {
            queue.push_back((neighbor_id, new_depth));
          }
        }
      }
    }

    results
  }
}

impl<F> Iterator for TraversalIterator<F>
where
  F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
  type Item = TraversalResult;

  fn next(&mut self) -> Option<Self::Item> {
    // Check if we're done
    if self.done {
      return None;
    }

    // Check limit
    if let Some(limit) = self.limit {
      if self.yielded >= limit {
        self.done = true;
        return None;
      }
    }

    loop {
      // If we have results in the current frontier, yield one
      if !self.current_frontier.is_empty() {
        // If we've processed all steps, yield from frontier
        if self.step_index >= self.steps.len() {
          let result = self.current_frontier.pop_front()?;

          // Apply global filters
          if !self.passes_filters(&result) {
            continue;
          }

          self.yielded += 1;

          // Check limit
          if let Some(limit) = self.limit {
            if self.yielded >= limit {
              self.done = true;
            }
          }

          return Some(result);
        }
      }

      // Process the next step
      if self.step_index < self.steps.len() {
        let step = self.steps[self.step_index].clone();
        self.step_index += 1;

        let next_frontier = match step {
          TraversalStep::SingleHop {
            direction,
            etype,
            edge_filter,
            node_filter,
          } => self.process_single_hop(direction, etype, &edge_filter, &node_filter),
          TraversalStep::Traverse { etype, options } => self.process_traverse(etype, &options),
        };

        self.current_frontier = next_frontier;
      } else {
        // No more steps and empty frontier
        self.done = true;
        return None;
      }
    }
  }
}

// ============================================================================
// Raw Edge Iterator
// ============================================================================

/// Iterator for raw edges (fastest traversal mode, no property loading)
pub struct RawEdgeIterator<F> {
  neighbors: F,
  steps: Vec<TraversalStep>,
  step_index: usize,
  current_nodes: VecDeque<NodeId>,
  pending_edges: VecDeque<RawEdge>,
}

impl<F> RawEdgeIterator<F>
where
  F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
  fn new(builder: TraversalBuilder, neighbors: F) -> Self {
    // Check that there are no variable-depth steps
    for step in &builder.steps {
      if matches!(step, TraversalStep::Traverse { .. }) {
        panic!("raw_edges() does not support variable-depth traverse()");
      }
    }

    let current_nodes = builder.start_nodes.into_iter().collect();

    Self {
      neighbors,
      steps: builder.steps,
      step_index: 0,
      current_nodes,
      pending_edges: VecDeque::new(),
    }
  }
}

impl<F> Iterator for RawEdgeIterator<F>
where
  F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
{
  type Item = RawEdge;

  fn next(&mut self) -> Option<Self::Item> {
    loop {
      // Return pending edge if available
      if let Some(edge) = self.pending_edges.pop_front() {
        return Some(edge);
      }

      // Process next node
      if let Some(node_id) = self.current_nodes.pop_front() {
        if self.step_index < self.steps.len() {
          let TraversalStep::SingleHop {
            direction, etype, ..
          } = &self.steps[self.step_index]
          else {
            unreachable!()
          };

          let edges = (self.neighbors)(node_id, *direction, *etype);
          for edge in edges {
            self.pending_edges.push_back(RawEdge::from(edge));
          }
        }
      } else {
        // Move to next step
        if self.step_index < self.steps.len() {
          self.step_index += 1;

          // Collect neighbors from pending edges for next step
          if self.step_index < self.steps.len() {
            let TraversalStep::SingleHop { .. } = &self.steps[self.step_index - 1] else {
              unreachable!()
            };

            // The pending edges from previous step become the nodes for next step
            // This isn't quite right - we need to track this differently
            // For now, return None to end iteration
          }
        }

        if self.pending_edges.is_empty() {
          return None;
        }
      }
    }
  }
}

// ============================================================================
// Result Accessors
// ============================================================================

/// Result of a traversal query with accessor methods.
///
/// Provides fluent methods to access traversal results:
/// - `.nodes()` - Get an iterator over node IDs
/// - `.edges()` - Get an iterator over edges (with traversal info)
/// - `.to_vec()` - Collect all results into a Vec
/// - `.first()` - Get the first result
/// - `.count()` - Count all results
///
/// # Example
///
/// ```rust,no_run
/// # use kitedb::api::traversal::{TraversalBuilder, TraversalDirection};
/// # use kitedb::types::{Edge, ETypeId, NodeId};
/// # fn main() {
/// # let knows_etype: ETypeId = 1;
/// # let neighbors = |_: NodeId, _: TraversalDirection, _: Option<ETypeId>| -> Vec<Edge> {
/// #   Vec::new()
/// # };
/// // Get first node
/// let first = TraversalBuilder::from_node(1)
///     .out(Some(knows_etype))
///     .results(&neighbors)
///     .first();
///
/// // Collect all node IDs
/// let node_ids = TraversalBuilder::from_node(1)
///     .out(Some(knows_etype))
///     .results(&neighbors)
///     .nodes()
///     .collect::<Vec<_>>();
///
/// // Collect all edges
/// let edges = TraversalBuilder::from_node(1)
///     .out(Some(knows_etype))
///     .results(&neighbors)
///     .edges()
///     .collect::<Vec<_>>();
/// # }
/// ```
pub struct TraversalResults<I> {
  iter: I,
}

impl<I> TraversalResults<I>
where
  I: Iterator<Item = TraversalResult>,
{
  /// Create a new TraversalResults wrapper
  pub fn new(iter: I) -> Self {
    Self { iter }
  }

  /// Get an iterator over node IDs only
  ///
  /// This is useful when you only need the node IDs and don't care about
  /// the edges or depth information.
  pub fn nodes(self) -> NodeIdIterator<I> {
    NodeIdIterator { inner: self.iter }
  }

  /// Get an iterator over edges only
  ///
  /// Returns edges that were traversed to reach each node.
  /// The first result (start nodes) will have `None` for the edge.
  pub fn edges(self) -> EdgeIterator<I> {
    EdgeIterator { inner: self.iter }
  }

  /// Get an iterator over full traversal results
  ///
  /// Each result contains the node ID, the edge used to reach it,
  /// and the depth in the traversal.
  pub fn full(self) -> I {
    self.iter
  }

  /// Collect all node IDs into a Vec
  pub fn to_vec(self) -> Vec<NodeId> {
    self.iter.map(|r| r.node_id).collect()
  }

  /// Get the first result, or None if empty
  pub fn first(mut self) -> Option<TraversalResult> {
    self.iter.next()
  }

  /// Get the first node ID, or None if empty
  pub fn first_node(mut self) -> Option<NodeId> {
    self.iter.next().map(|r| r.node_id)
  }

  /// Count the number of results
  ///
  /// Note: This consumes the iterator.
  pub fn count(self) -> usize {
    self.iter.count()
  }
}

/// Iterator adapter that yields only node IDs from traversal results
pub struct NodeIdIterator<I> {
  inner: I,
}

impl<I> Iterator for NodeIdIterator<I>
where
  I: Iterator<Item = TraversalResult>,
{
  type Item = NodeId;

  fn next(&mut self) -> Option<Self::Item> {
    self.inner.next().map(|r| r.node_id)
  }

  fn size_hint(&self) -> (usize, Option<usize>) {
    self.inner.size_hint()
  }
}

/// Iterator adapter that yields only edges from traversal results
pub struct EdgeIterator<I> {
  inner: I,
}

impl<I> Iterator for EdgeIterator<I>
where
  I: Iterator<Item = TraversalResult>,
{
  type Item = RawEdge;

  fn next(&mut self) -> Option<Self::Item> {
    loop {
      match self.inner.next() {
        Some(result) => {
          if let Some(edge) = result.edge {
            return Some(edge);
          }
          // Skip results without edges (start nodes)
        }
        None => return None,
      }
    }
  }

  fn size_hint(&self) -> (usize, Option<usize>) {
    let (_, upper) = self.inner.size_hint();
    (0, upper) // Lower bound is 0 because start nodes have no edges
  }
}

// ============================================================================
// TraversalBuilder Result Methods
// ============================================================================

impl TraversalBuilder {
  /// Execute the traversal and return results with accessor methods
  ///
  /// This is the recommended way to execute traversals when you need
  /// flexible access to the results.
  ///
  /// # Example
  ///
  /// ```rust,no_run
  /// # use kitedb::api::traversal::{TraversalBuilder, TraversalDirection};
  /// # use kitedb::types::{Edge, ETypeId, NodeId};
  /// # fn main() {
  /// # let knows_etype: ETypeId = 1;
  /// # let neighbors = |_: NodeId, _: TraversalDirection, _: Option<ETypeId>| -> Vec<Edge> {
  /// #   Vec::new()
  /// # };
  /// // Get first node ID
  /// let first = TraversalBuilder::from_node(1)
  ///     .out(Some(knows_etype))
  ///     .results(&neighbors)
  ///     .first_node();
  ///
  /// // Or collect all nodes
  /// let all_nodes = TraversalBuilder::from_node(1)
  ///     .out(Some(knows_etype))
  ///     .results(&neighbors)
  ///     .to_vec();
  /// # }
  /// ```
  pub fn results<F>(self, neighbors: F) -> TraversalResults<TraversalIterator<F>>
  where
    F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
  {
    TraversalResults::new(self.execute(neighbors))
  }

  /// Execute and get the first result
  ///
  /// Convenience method equivalent to `.results(f).first()`.
  pub fn first<F>(self, neighbors: F) -> Option<TraversalResult>
  where
    F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
  {
    self.execute(neighbors).next()
  }

  /// Execute and get the first node ID
  ///
  /// Convenience method equivalent to `.results(f).first_node()`.
  pub fn first_node<F>(self, neighbors: F) -> Option<NodeId>
  where
    F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
  {
    self.execute(neighbors).next().map(|r| r.node_id)
  }

  /// Execute and collect all node IDs into a Vec
  ///
  /// Convenience method equivalent to `.results(f).to_vec()`.
  pub fn to_vec<F>(self, neighbors: F) -> Vec<NodeId>
  where
    F: Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge>,
  {
    self.collect_node_ids(neighbors)
  }
}

// ============================================================================
// Extended Result Types with Properties
// ============================================================================

/// A node result with loaded properties
#[derive(Debug, Clone)]
pub struct NodeResult {
  /// Node ID
  pub id: NodeId,
  /// Node key (if available)
  pub key: Option<String>,
  /// Node properties
  pub props: HashMap<String, PropValue>,
}

impl NodeResult {
  /// Create a new NodeResult
  pub fn new(id: NodeId) -> Self {
    Self {
      id,
      key: None,
      props: HashMap::new(),
    }
  }

  /// Set the node key
  pub fn with_key(mut self, key: String) -> Self {
    self.key = Some(key);
    self
  }

  /// Set the node properties
  pub fn with_props(mut self, props: HashMap<String, PropValue>) -> Self {
    self.props = props;
    self
  }

  /// Get a property value by name
  pub fn get(&self, name: &str) -> Option<&PropValue> {
    self.props.get(name)
  }

  /// Get a string property
  pub fn string(&self, name: &str) -> Option<&str> {
    match self.props.get(name) {
      Some(PropValue::String(s)) => Some(s),
      _ => None,
    }
  }

  /// Get an integer property
  pub fn int(&self, name: &str) -> Option<i64> {
    match self.props.get(name) {
      Some(PropValue::I64(v)) => Some(*v),
      _ => None,
    }
  }

  /// Get a float property
  pub fn float(&self, name: &str) -> Option<f64> {
    match self.props.get(name) {
      Some(PropValue::F64(v)) => Some(*v),
      _ => None,
    }
  }

  /// Get a boolean property
  pub fn bool(&self, name: &str) -> Option<bool> {
    match self.props.get(name) {
      Some(PropValue::Bool(v)) => Some(*v),
      _ => None,
    }
  }
}

/// An edge result with loaded properties
#[derive(Debug, Clone)]
pub struct FullEdgeResult {
  /// Source node ID
  pub src: NodeId,
  /// Destination node ID
  pub dst: NodeId,
  /// Edge type ID
  pub etype: ETypeId,
  /// Edge properties
  pub props: HashMap<String, PropValue>,
}

impl FullEdgeResult {
  /// Create from a RawEdge
  pub fn from_raw(edge: RawEdge) -> Self {
    Self {
      src: edge.src,
      dst: edge.dst,
      etype: edge.etype,
      props: HashMap::new(),
    }
  }

  /// Set the edge properties
  pub fn with_props(mut self, props: HashMap<String, PropValue>) -> Self {
    self.props = props;
    self
  }

  /// Get a property value by name
  pub fn get(&self, name: &str) -> Option<&PropValue> {
    self.props.get(name)
  }
}

// ============================================================================
// Collecting Results with Properties
// ============================================================================

/// Options for collecting results with properties
#[derive(Debug, Clone, Default)]
pub struct CollectOptions {
  /// Property names to load for nodes (None = load all)
  pub node_props: Option<Vec<String>>,
  /// Property names to load for edges (None = load all)
  pub edge_props: Option<Vec<String>>,
  /// Whether to load node keys
  pub load_keys: bool,
}

impl CollectOptions {
  /// Create new options
  pub fn new() -> Self {
    Self::default()
  }

  /// Specify which node properties to load
  pub fn select_node_props(mut self, props: Vec<String>) -> Self {
    self.node_props = Some(props);
    self
  }

  /// Specify which edge properties to load
  pub fn select_edge_props(mut self, props: Vec<String>) -> Self {
    self.edge_props = Some(props);
    self
  }

  /// Enable loading node keys
  pub fn with_keys(mut self) -> Self {
    self.load_keys = true;
    self
  }
}

// ============================================================================
// Tests
// ============================================================================

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

  fn mock_graph() -> impl Fn(NodeId, TraversalDirection, Option<ETypeId>) -> Vec<Edge> {
    // Create a simple graph:
    // 1 --knows--> 2 --knows--> 3
    // 1 --follows--> 4
    // 2 --follows--> 5
    move |node_id: NodeId, direction: TraversalDirection, etype: Option<ETypeId>| {
      let mut edges = Vec::new();

      match direction {
        TraversalDirection::Out => match node_id {
          1 => {
            if etype.is_none() || etype == Some(1) {
              edges.push(Edge {
                src: 1,
                etype: 1,
                dst: 2,
              });
            }
            if etype.is_none() || etype == Some(2) {
              edges.push(Edge {
                src: 1,
                etype: 2,
                dst: 4,
              });
            }
          }
          2 => {
            if etype.is_none() || etype == Some(1) {
              edges.push(Edge {
                src: 2,
                etype: 1,
                dst: 3,
              });
            }
            if etype.is_none() || etype == Some(2) {
              edges.push(Edge {
                src: 2,
                etype: 2,
                dst: 5,
              });
            }
          }
          _ => {}
        },
        TraversalDirection::In => match node_id {
          2 => {
            if etype.is_none() || etype == Some(1) {
              edges.push(Edge {
                src: 1,
                etype: 1,
                dst: 2,
              });
            }
          }
          3 => {
            if etype.is_none() || etype == Some(1) {
              edges.push(Edge {
                src: 2,
                etype: 1,
                dst: 3,
              });
            }
          }
          4 => {
            if etype.is_none() || etype == Some(2) {
              edges.push(Edge {
                src: 1,
                etype: 2,
                dst: 4,
              });
            }
          }
          5 => {
            if etype.is_none() || etype == Some(2) {
              edges.push(Edge {
                src: 2,
                etype: 2,
                dst: 5,
              });
            }
          }
          _ => {}
        },
        TraversalDirection::Both => {
          // Combine out and in edges
          let out_edges = mock_graph()(node_id, TraversalDirection::Out, etype);
          let in_edges = mock_graph()(node_id, TraversalDirection::In, etype);
          edges.extend(out_edges);
          edges.extend(in_edges);
        }
      }

      edges
    }
  }

  #[test]
  fn test_single_hop_out() {
    let neighbors = mock_graph();

    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(Some(1)) // knows
      .execute(&neighbors)
      .collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 2);
  }

  #[test]
  fn test_single_hop_all_etypes() {
    let neighbors = mock_graph();

    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(None) // all edge types
      .execute(&neighbors)
      .collect();

    assert_eq!(results.len(), 2);
    let node_ids: HashSet<_> = results.iter().map(|r| r.node_id).collect();
    assert!(node_ids.contains(&2));
    assert!(node_ids.contains(&4));
  }

  #[test]
  fn test_two_hops() {
    let neighbors = mock_graph();

    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(Some(1)) // 1 -> 2
      .out(Some(1)) // 2 -> 3
      .execute(&neighbors)
      .collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 3);
  }

  #[test]
  fn test_incoming() {
    let neighbors = mock_graph();

    let results: Vec<_> = TraversalBuilder::from_node(3)
      .r#in(Some(1)) // 3 <- 2
      .execute(&neighbors)
      .collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 2);
  }

  #[test]
  fn test_take_limit() {
    let neighbors = mock_graph();

    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(None)
      .take(1)
      .execute(&neighbors)
      .collect();

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

  #[test]
  fn test_count() {
    let neighbors = mock_graph();

    let count = TraversalBuilder::from_node(1).out(None).count(&neighbors);

    assert_eq!(count, 2);
  }

  #[test]
  fn test_count_with_limit() {
    let neighbors = mock_graph();

    let count = TraversalBuilder::from_node(1)
      .out(None)
      .take(1)
      .count(&neighbors);

    assert_eq!(count, 1);
  }

  #[test]
  fn test_traverse_variable_depth() {
    let neighbors = mock_graph();

    let results: Vec<_> = TraversalBuilder::from_node(1)
      .traverse(Some(1), TraverseOptions::new(TraversalDirection::Out, 2))
      .execute(&neighbors)
      .collect();

    // Should find: 2 (depth 1), 3 (depth 2)
    assert_eq!(results.len(), 2);
    let node_ids: HashSet<_> = results.iter().map(|r| r.node_id).collect();
    assert!(node_ids.contains(&2));
    assert!(node_ids.contains(&3));
  }

  #[test]
  fn test_traverse_min_depth() {
    let neighbors = mock_graph();

    let options = TraverseOptions::new(TraversalDirection::Out, 2).with_min_depth(2);

    let results: Vec<_> = TraversalBuilder::from_node(1)
      .traverse(Some(1), options)
      .execute(&neighbors)
      .collect();

    // Should only find: 3 (depth 2, skipping depth 1)
    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 3);
  }

  #[test]
  fn test_multiple_start_nodes() {
    let neighbors = mock_graph();

    let results: Vec<_> = TraversalBuilder::new(vec![1, 2])
      .out(Some(1))
      .execute(&neighbors)
      .collect();

    // From 1: finds 2
    // From 2: finds 3
    // But 2 is already visited, so only 3 is new
    // Wait - start nodes are marked visited, so 2 from node 1 won't be yielded
    // Actually the implementation marks start nodes as visited
    // Let me check... yes, start nodes are visited, so 2 won't be yielded from 1
    // Result should be: 2 (from node 1), 3 (from node 2)
    // Hmm, but 2 is a start node so it's visited... Let me re-check
    // Actually start nodes 1 and 2 are visited, then:
    // - From 1, we find 2, but 2 is already visited, skip
    // - From 2, we find 3, which is not visited, yield
    // So only 1 result
    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 3);
  }

  #[test]
  fn test_unique_false() {
    let neighbors = mock_graph();

    let results: Vec<_> = TraversalBuilder::new(vec![1, 2])
      .unique(false)
      .out(Some(1))
      .execute(&neighbors)
      .collect();

    // Without uniqueness, we get all results:
    // From 1: 2
    // From 2: 3
    assert_eq!(results.len(), 2);
  }

  #[test]
  fn test_collect_node_ids() {
    let neighbors = mock_graph();

    let node_ids = TraversalBuilder::from_node(1)
      .out(Some(1))
      .out(Some(1))
      .collect_node_ids(&neighbors);

    assert_eq!(node_ids, vec![3]);
  }

  #[test]
  fn test_empty_result() {
    let neighbors = mock_graph();

    let results: Vec<_> = TraversalBuilder::from_node(999)
      .out(None)
      .execute(&neighbors)
      .collect();

    assert!(results.is_empty());
  }

  #[test]
  fn test_no_steps() {
    let neighbors = mock_graph();

    // With no steps, should just yield start nodes
    // But wait, the implementation doesn't yield start nodes unless there are steps
    // Actually looking at the iterator, if step_index >= steps.len() and frontier not empty,
    // it yields from frontier. So start nodes should be yielded.
    let results: Vec<_> = TraversalBuilder::from_node(1).execute(&neighbors).collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 1);
  }

  // ============================================================================
  // Filter Predicate Tests
  // ============================================================================

  #[test]
  fn test_where_edge_filter_by_etype() {
    let neighbors = mock_graph();

    // Filter to only include edges with etype == 1 (knows)
    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(None) // all edge types
      .where_edge(|edge| edge.etype == 1)
      .execute(&neighbors)
      .collect();

    // Should only find node 2 (via knows edge), not node 4 (via follows edge)
    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 2);
  }

  #[test]
  fn test_where_edge_filter_by_dst() {
    let neighbors = mock_graph();

    // Filter to only include edges where dst > 3
    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(None)
      .where_edge(|edge| edge.dst > 3)
      .execute(&neighbors)
      .collect();

    // Should only find node 4 (dst=4)
    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 4);
  }

  #[test]
  fn test_where_node_filter() {
    let neighbors = mock_graph();

    // Filter to only include nodes with id > 3
    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(None)
      .where_node(|node| node.id > 3)
      .execute(&neighbors)
      .collect();

    // Should only find node 4
    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 4);
  }

  #[test]
  fn test_where_node_filter_excludes_all() {
    let neighbors = mock_graph();

    // Filter that excludes all nodes
    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(None)
      .where_node(|node| node.id > 100)
      .execute(&neighbors)
      .collect();

    assert!(results.is_empty());
  }

  #[test]
  fn test_combined_edge_and_node_filters() {
    let neighbors = mock_graph();

    // Combine edge and node filters
    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(None)
      .where_edge(|edge| edge.etype == 2) // follows only
      .where_node(|node| node.id >= 4)
      .execute(&neighbors)
      .collect();

    // Should find node 4 (follows edge with dst >= 4)
    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 4);
  }

  #[test]
  fn test_filter_with_limit() {
    let neighbors = mock_graph();

    // From node 2, we can reach nodes 3 and 5 (knows->3, follows->5)
    // Filter to etype 1 only, but also with limit
    let results: Vec<_> = TraversalBuilder::from_node(2)
      .out(None)
      .where_edge(|edge| edge.etype == 1)
      .take(10)
      .execute(&neighbors)
      .collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 3);
  }

  #[test]
  fn test_has_filters_detects_global_edge_filter() {
    let builder = TraversalBuilder::from_node(1)
      .out(None)
      .where_edge(|_| true);

    assert!(builder.has_filters());
  }

  #[test]
  fn test_has_filters_detects_global_node_filter() {
    let builder = TraversalBuilder::from_node(1)
      .out(None)
      .where_node(|_| true);

    assert!(builder.has_filters());
  }

  #[test]
  fn test_has_filters_false_when_no_filters() {
    let builder = TraversalBuilder::from_node(1).out(None);

    assert!(!builder.has_filters());
  }

  #[test]
  fn test_count_falls_back_to_iteration_with_filters() {
    let neighbors = mock_graph();

    // With filters, count should use iteration (slow path)
    let builder = TraversalBuilder::from_node(1)
      .out(None)
      .where_edge(|edge| edge.etype == 1);

    // Can't use fast count
    assert!(!builder.can_use_fast_count());

    // But count still works
    let count = builder.count(&neighbors);
    assert_eq!(count, 1);
  }

  #[test]
  fn test_traverse_with_edge_filter() {
    let neighbors = mock_graph();

    // Variable-depth traversal with edge filter in options
    let options =
      TraverseOptions::new(TraversalDirection::Out, 2).with_edge_filter(|edge| edge.etype == 1);

    let results: Vec<_> = TraversalBuilder::from_node(1)
      .traverse(Some(1), options)
      .execute(&neighbors)
      .collect();

    // Should find: 2 (depth 1), 3 (depth 2) - all via knows edges
    assert_eq!(results.len(), 2);
    let node_ids: HashSet<_> = results.iter().map(|r| r.node_id).collect();
    assert!(node_ids.contains(&2));
    assert!(node_ids.contains(&3));
  }

  #[test]
  fn test_traverse_with_node_filter() {
    let neighbors = mock_graph();

    // Variable-depth traversal with node filter in options
    // Filter only yields nodes with id >= 2 (which is all reachable nodes)
    let options =
      TraverseOptions::new(TraversalDirection::Out, 2).with_node_filter(|node| node.id >= 2);

    let results: Vec<_> = TraversalBuilder::from_node(1)
      .traverse(Some(1), options)
      .execute(&neighbors)
      .collect();

    // Should find: 2 (depth 1), 3 (depth 2)
    assert_eq!(results.len(), 2);
    let node_ids: HashSet<_> = results.iter().map(|r| r.node_id).collect();
    assert!(node_ids.contains(&2));
    assert!(node_ids.contains(&3));
  }

  #[test]
  fn test_traverse_with_node_filter_excludes_intermediate() {
    let neighbors = mock_graph();

    // Filter for id >= 3 - this will filter out node 2, so we can't reach node 3
    // because the traversal stops at filtered nodes
    let options =
      TraverseOptions::new(TraversalDirection::Out, 3).with_node_filter(|node| node.id >= 3);

    let results: Vec<_> = TraversalBuilder::from_node(1)
      .traverse(Some(1), options)
      .execute(&neighbors)
      .collect();

    // Node 2 is filtered out, so we can't traverse through it to reach node 3
    // This demonstrates that node filters affect traversal continuation
    assert!(results.is_empty());
  }

  #[test]
  fn test_traverse_options_with_combined_filters() {
    let neighbors = mock_graph();

    // Variable-depth traversal with both filters
    let options = TraverseOptions::new(TraversalDirection::Out, 3)
      .with_edge_filter(|edge| edge.etype == 1)
      .with_node_filter(|node| node.id >= 2);

    let results: Vec<_> = TraversalBuilder::from_node(1)
      .traverse(None, options)
      .execute(&neighbors)
      .collect();

    // Should find nodes 2 and 3 via knows edges (etype=1)
    let node_ids: HashSet<_> = results.iter().map(|r| r.node_id).collect();
    assert!(node_ids.contains(&2));
    assert!(node_ids.contains(&3));
  }

  #[test]
  fn test_edge_info_from_raw_edge() {
    let raw_edge = RawEdge {
      src: 1,
      dst: 2,
      etype: 3,
    };

    let edge_info = EdgeInfo::from(raw_edge);

    assert_eq!(edge_info.src, 1);
    assert_eq!(edge_info.dst, 2);
    assert_eq!(edge_info.etype, 3);
    assert!(edge_info.props.is_empty());
  }

  // ============================================================================
  // Result Accessor Tests
  // ============================================================================

  #[test]
  fn test_results_to_vec() {
    let neighbors = mock_graph();

    let nodes = TraversalBuilder::from_node(1)
      .out(Some(1))
      .results(&neighbors)
      .to_vec();

    assert_eq!(nodes, vec![2]);
  }

  #[test]
  fn test_results_first() {
    let neighbors = mock_graph();

    let first = TraversalBuilder::from_node(1)
      .out(Some(1))
      .results(&neighbors)
      .first();

    assert!(first.is_some());
    assert_eq!(first.expect("expected value").node_id, 2);
  }

  #[test]
  fn test_results_first_node() {
    let neighbors = mock_graph();

    let first = TraversalBuilder::from_node(1)
      .out(Some(1))
      .results(&neighbors)
      .first_node();

    assert_eq!(first, Some(2));
  }

  #[test]
  fn test_results_first_empty() {
    let neighbors = mock_graph();

    let first = TraversalBuilder::from_node(999)
      .out(None)
      .results(&neighbors)
      .first();

    assert!(first.is_none());
  }

  #[test]
  fn test_results_count() {
    let neighbors = mock_graph();

    let count = TraversalBuilder::from_node(1)
      .out(None)
      .results(&neighbors)
      .count();

    assert_eq!(count, 2);
  }

  #[test]
  fn test_results_nodes_iterator() {
    let neighbors = mock_graph();

    let nodes: Vec<_> = TraversalBuilder::from_node(1)
      .out(None)
      .results(&neighbors)
      .nodes()
      .collect();

    assert_eq!(nodes.len(), 2);
    assert!(nodes.contains(&2));
    assert!(nodes.contains(&4));
  }

  #[test]
  fn test_results_edges_iterator() {
    let neighbors = mock_graph();

    let edges: Vec<_> = TraversalBuilder::from_node(1)
      .out(None)
      .results(&neighbors)
      .edges()
      .collect();

    assert_eq!(edges.len(), 2);
    // All edges should have src=1
    for edge in &edges {
      assert_eq!(edge.src, 1);
    }
  }

  #[test]
  fn test_results_full_iterator() {
    let neighbors = mock_graph();

    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(Some(1))
      .results(&neighbors)
      .full()
      .collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 2);
    assert!(results[0].edge.is_some());
    assert_eq!(results[0].depth, 1);
  }

  #[test]
  fn test_builder_first_method() {
    let neighbors = mock_graph();

    let first = TraversalBuilder::from_node(1)
      .out(Some(1))
      .first(&neighbors);

    assert!(first.is_some());
    assert_eq!(first.expect("expected value").node_id, 2);
  }

  #[test]
  fn test_builder_first_node_method() {
    let neighbors = mock_graph();

    let first = TraversalBuilder::from_node(1)
      .out(Some(1))
      .first_node(&neighbors);

    assert_eq!(first, Some(2));
  }

  #[test]
  fn test_builder_to_vec_method() {
    let neighbors = mock_graph();

    let nodes = TraversalBuilder::from_node(1).out(None).to_vec(&neighbors);

    assert_eq!(nodes.len(), 2);
    assert!(nodes.contains(&2));
    assert!(nodes.contains(&4));
  }

  #[test]
  fn test_node_result_accessors() {
    let mut props = HashMap::new();
    props.insert("name".to_string(), PropValue::String("Alice".to_string()));
    props.insert("age".to_string(), PropValue::I64(30));
    props.insert("score".to_string(), PropValue::F64(95.5));
    props.insert("active".to_string(), PropValue::Bool(true));

    let result = NodeResult::new(1)
      .with_key("user:alice".to_string())
      .with_props(props);

    assert_eq!(result.id, 1);
    assert_eq!(result.key, Some("user:alice".to_string()));
    assert_eq!(result.string("name"), Some("Alice"));
    assert_eq!(result.int("age"), Some(30));
    assert_eq!(result.float("score"), Some(95.5));
    assert_eq!(result.bool("active"), Some(true));
    assert_eq!(result.string("missing"), None);
  }

  #[test]
  fn test_full_edge_result() {
    let raw = RawEdge {
      src: 1,
      dst: 2,
      etype: 3,
    };

    let mut props = HashMap::new();
    props.insert("weight".to_string(), PropValue::F64(0.5));

    let edge = FullEdgeResult::from_raw(raw).with_props(props);

    assert_eq!(edge.src, 1);
    assert_eq!(edge.dst, 2);
    assert_eq!(edge.etype, 3);
    assert!(edge.get("weight").is_some());
  }

  #[test]
  fn test_two_hop_results() {
    let neighbors = mock_graph();

    // 1 -> 2 -> 3
    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(Some(1))
      .out(Some(1))
      .results(&neighbors)
      .full()
      .collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 3);
    assert_eq!(results[0].depth, 2);
  }

  #[test]
  fn test_collect_options() {
    let opts = CollectOptions::new()
      .select_node_props(vec!["name".to_string(), "age".to_string()])
      .select_edge_props(vec!["weight".to_string()])
      .with_keys();

    assert!(opts.node_props.is_some());
    assert_eq!(opts.node_props.as_ref().expect("expected value").len(), 2);
    assert!(opts.edge_props.is_some());
    assert!(opts.load_keys);
  }

  // ============================================================================
  // Select Property Tests
  // ============================================================================

  #[test]
  fn test_select_stores_properties() {
    let builder = TraversalBuilder::from_node(1)
      .out(Some(1))
      .select(vec!["name".to_string(), "age".to_string()]);

    let selected = builder.selected_properties();
    assert!(selected.is_some());
    let props = selected.expect("expected value");
    assert_eq!(props.len(), 2);
    assert!(props.contains(&"name".to_string()));
    assert!(props.contains(&"age".to_string()));
  }

  #[test]
  fn test_select_props_with_str_slices() {
    let builder = TraversalBuilder::from_node(1)
      .out(Some(1))
      .select_props(&["name", "email"]);

    let selected = builder.selected_properties();
    assert!(selected.is_some());
    let props = selected.expect("expected value");
    assert_eq!(props.len(), 2);
    assert!(props.contains(&"name".to_string()));
    assert!(props.contains(&"email".to_string()));
  }

  #[test]
  fn test_select_no_properties_by_default() {
    let builder = TraversalBuilder::from_node(1).out(Some(1));

    assert!(builder.selected_properties().is_none());
  }

  #[test]
  fn test_collect_options_from_builder() {
    let builder = TraversalBuilder::from_node(1)
      .out(Some(1))
      .select_props(&["name", "age"]);

    let opts = builder.collect_options();

    assert!(opts.node_props.is_some());
    let props = opts.node_props.expect("expected value");
    assert_eq!(props.len(), 2);
    assert!(props.contains(&"name".to_string()));
    assert!(props.contains(&"age".to_string()));
  }

  #[test]
  fn test_collect_options_empty_without_select() {
    let builder = TraversalBuilder::from_node(1).out(Some(1));

    let opts = builder.collect_options();

    // No props selected means load all (None)
    assert!(opts.node_props.is_none());
  }

  #[test]
  fn test_select_does_not_affect_execution() {
    let neighbors = mock_graph();

    // Select should not affect traversal execution, only property loading
    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(Some(1))
      .select_props(&["name", "age"])
      .execute(&neighbors)
      .collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 2);
  }

  #[test]
  fn test_select_with_take() {
    let neighbors = mock_graph();

    // Select combined with take should work
    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(None)
      .select_props(&["name"])
      .take(1)
      .execute(&neighbors)
      .collect();

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

  #[test]
  fn test_select_with_filters() {
    let neighbors = mock_graph();

    // Select combined with filters
    let results: Vec<_> = TraversalBuilder::from_node(1)
      .out(None)
      .select_props(&["name"])
      .where_edge(|e| e.etype == 1)
      .execute(&neighbors)
      .collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].node_id, 2);
  }
}
