//! Distance and shortest path algorithms ported from NetworKit.
//!
//! This module implements NetworKit's distance algorithms:
//! - BFS: Breadth-First Search for unweighted graphs
//! - Dijkstra: Single-source shortest paths for weighted graphs
//! - Bidirectional Dijkstra: Faster when target is known
//! - APSP: All-Pairs Shortest Paths
//!
//! Reference: <https://networkit.github.io/dev-docs/notebooks/Distance.html>

use std::collections::{VecDeque, BinaryHeap};
use std::cmp::Ordering;

use super::super::graph::{Graph, NodeId};

/// Result of a shortest path computation.
#[derive(Debug, Clone)]
pub struct ShortestPathResult {
    /// Source node.
    pub source: NodeId,
    /// Target node (if single-target query).
    pub target: Option<NodeId>,
    /// Distances from source to each node (None = unreachable).
    pub distances: Vec<Option<f64>>,
    /// Predecessors for path reconstruction (None = no predecessor).
    pub predecessors: Vec<Option<NodeId>>,
    /// Whether paths were found.
    pub found: bool,
}

impl ShortestPathResult {
    /// Get the distance to a specific node.
    pub fn distance(&self, node: NodeId) -> Option<f64> {
        self.distances.get(node as usize).and_then(|d| *d)
    }

    /// Reconstruct the path to a target node.
    pub fn path(&self, target: NodeId) -> Option<Vec<NodeId>> {
        if !self.found || self.distance(target).is_none() {
            return None;
        }

        let mut path = Vec::new();
        let mut current = target;

        loop {
            path.push(current);

            match self.predecessors.get(current as usize).and_then(|p| *p) {
                Some(pred) => current = pred,
                None => break,
            }
        }

        path.reverse();
        Some(path)
    }
}

/// Breadth-First Search (BFS) for unweighted shortest paths.
///
/// Implements NetworKit's BFS algorithm for finding shortest paths in unweighted graphs.
/// Runtime: O(n + m) where n = nodes, m = edges
///
/// Reference: NetworKit BFS class
pub struct BFS<'a> {
    graph: &'a Graph,
    source: NodeId,
    target: Option<NodeId>,
    store_paths: bool,
}

impl<'a> BFS<'a> {
    /// Create a new BFS instance.
    pub fn new(graph: &'a Graph, source: NodeId) -> Self {
        Self {
            graph,
            source,
            target: None,
            store_paths: true,
        }
    }

    /// Set a specific target node (stops search when found).
    pub fn with_target(mut self, target: NodeId) -> Self {
        self.target = Some(target);
        self
    }

    /// Configure whether to store predecessor information for path reconstruction.
    pub fn store_paths(mut self, store: bool) -> Self {
        self.store_paths = store;
        self
    }

    /// Run BFS and return results.
    pub fn run(&self) -> ShortestPathResult {
        let n = self.graph.upper_node_id_bound() as usize;
        let mut distances = vec![None; n];
        let mut predecessors = if self.store_paths {
            vec![None; n]
        } else {
            Vec::new()
        };

        let mut queue = VecDeque::new();
        queue.push_back(self.source);
        distances[self.source as usize] = Some(0.0);

        let mut found = self.target.is_none(); // True if no specific target

        while let Some(current) = queue.pop_front() {
            let current_dist = distances[current as usize].unwrap();

            // Check if we found the target
            if let Some(target) = self.target {
                if current == target {
                    found = true;
                    break;
                }
            }

            // Explore neighbors
            for neighbor in self.graph.out_neighbors(current) {
                let next = neighbor.target;

                if distances[next as usize].is_none() {
                    distances[next as usize] = Some(current_dist + 1.0);

                    if self.store_paths {
                        predecessors[next as usize] = Some(current);
                    }

                    queue.push_back(next);
                }
            }
        }

        ShortestPathResult {
            source: self.source,
            target: self.target,
            distances,
            predecessors,
            found,
        }
    }
}

/// Priority queue element for Dijkstra's algorithm.
#[derive(Copy, Clone, PartialEq)]
struct DijkstraState {
    node: NodeId,
    distance: f64,
}

impl Eq for DijkstraState {}

impl Ord for DijkstraState {
    fn cmp(&self, other: &Self) -> Ordering {
        // Min-heap: reverse comparison
        other.distance.partial_cmp(&self.distance)
            .unwrap_or(Ordering::Equal)
            .then_with(|| self.node.cmp(&other.node))
    }
}

impl PartialOrd for DijkstraState {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

/// Dijkstra's algorithm for weighted shortest paths.
///
/// Implements NetworKit's Dijkstra algorithm for finding shortest paths in weighted graphs.
/// Runtime: O((n + m) log n) with binary heap
///
/// Reference: NetworKit Dijkstra class
pub struct Dijkstra<'a> {
    graph: &'a Graph,
    source: NodeId,
    target: Option<NodeId>,
    store_paths: bool,
}

impl<'a> Dijkstra<'a> {
    /// Create a new Dijkstra instance.
    pub fn new(graph: &'a Graph, source: NodeId) -> Self {
        Self {
            graph,
            source,
            target: None,
            store_paths: true,
        }
    }

    /// Set a specific target node (stops search when found).
    pub fn with_target(mut self, target: NodeId) -> Self {
        self.target = Some(target);
        self
    }

    /// Configure whether to store paths.
    pub fn store_paths(mut self, store: bool) -> Self {
        self.store_paths = store;
        self
    }

    /// Run Dijkstra and return results.
    pub fn run(&self) -> ShortestPathResult {
        let n = self.graph.upper_node_id_bound() as usize;
        let mut distances = vec![None; n];
        let mut predecessors = if self.store_paths {
            vec![None; n]
        } else {
            Vec::new()
        };

        let mut heap = BinaryHeap::new();
        distances[self.source as usize] = Some(0.0);
        heap.push(DijkstraState {
            node: self.source,
            distance: 0.0,
        });

        let mut found = self.target.is_none();

        while let Some(DijkstraState { node, distance }) = heap.pop() {
            // Skip if we already found a better path
            if let Some(current_dist) = distances[node as usize] {
                if distance > current_dist {
                    continue;
                }
            }

            // Check if we found the target
            if let Some(target) = self.target {
                if node == target {
                    found = true;
                    break;
                }
            }

            // Explore neighbors
            for neighbor in self.graph.out_neighbors(node) {
                let next = neighbor.target;
                let weight = neighbor.weight.unwrap_or(1.0);
                let next_dist = distance + weight;

                let is_better = match distances[next as usize] {
                    None => true,
                    Some(current) => next_dist < current,
                };

                if is_better {
                    distances[next as usize] = Some(next_dist);

                    if self.store_paths {
                        predecessors[next as usize] = Some(node);
                    }

                    heap.push(DijkstraState {
                        node: next,
                        distance: next_dist,
                    });
                }
            }
        }

        ShortestPathResult {
            source: self.source,
            target: self.target,
            distances,
            predecessors,
            found,
        }
    }
}

/// Bidirectional Dijkstra for faster single-pair shortest paths.
///
/// Explores from both source and target simultaneously.
/// Runtime: Often faster than unidirectional Dijkstra when target is known.
///
/// Reference: NetworKit BidirectionalDijkstra class
pub struct BidirectionalDijkstra<'a> {
    graph: &'a Graph,
    source: NodeId,
    target: NodeId,
}

impl<'a> BidirectionalDijkstra<'a> {
    /// Create a new bidirectional Dijkstra instance.
    pub fn new(graph: &'a Graph, source: NodeId, target: NodeId) -> Self {
        Self {
            graph,
            source,
            target,
        }
    }

    /// Run bidirectional search.
    pub fn run(&self) -> Option<(f64, Vec<NodeId>)> {
        let n = self.graph.upper_node_id_bound() as usize;

        // Forward search from source
        let mut forward_dist = vec![None; n];
        let mut forward_pred = vec![None; n];
        let mut forward_heap = BinaryHeap::new();

        // Backward search from target
        let mut backward_dist = vec![None; n];
        let mut backward_pred = vec![None; n];
        let mut backward_heap = BinaryHeap::new();

        forward_dist[self.source as usize] = Some(0.0);
        backward_dist[self.target as usize] = Some(0.0);

        forward_heap.push(DijkstraState {
            node: self.source,
            distance: 0.0,
        });
        backward_heap.push(DijkstraState {
            node: self.target,
            distance: 0.0,
        });

        let mut best_distance = f64::INFINITY;
        let mut meeting_node = None;

        // Alternate between forward and backward search
        while !forward_heap.is_empty() || !backward_heap.is_empty() {
            // Forward step
            if let Some(DijkstraState { node, distance }) = forward_heap.pop() {
                if distance > best_distance {
                    break; // Proven optimal
                }

                for neighbor in self.graph.out_neighbors(node) {
                    let next = neighbor.target;
                    let weight = neighbor.weight.unwrap_or(1.0);
                    let next_dist = distance + weight;

                    let is_better = match forward_dist[next as usize] {
                        None => true,
                        Some(current) => next_dist < current,
                    };

                    if is_better {
                        forward_dist[next as usize] = Some(next_dist);
                        forward_pred[next as usize] = Some(node);

                        forward_heap.push(DijkstraState {
                            node: next,
                            distance: next_dist,
                        });

                        // Check if backward search reached this node
                        if let Some(backward_d) = backward_dist[next as usize] {
                            let total = next_dist + backward_d;
                            if total < best_distance {
                                best_distance = total;
                                meeting_node = Some(next);
                            }
                        }
                    }
                }
            }

            // Backward step
            if let Some(DijkstraState { node, distance }) = backward_heap.pop() {
                if distance > best_distance {
                    break; // Proven optimal
                }

                for neighbor in self.graph.in_neighbors(node) {
                    let next = neighbor.target;
                    let weight = neighbor.weight.unwrap_or(1.0);
                    let next_dist = distance + weight;

                    let is_better = match backward_dist[next as usize] {
                        None => true,
                        Some(current) => next_dist < current,
                    };

                    if is_better {
                        backward_dist[next as usize] = Some(next_dist);
                        backward_pred[next as usize] = Some(node);

                        backward_heap.push(DijkstraState {
                            node: next,
                            distance: next_dist,
                        });

                        // Check if forward search reached this node
                        if let Some(forward_d) = forward_dist[next as usize] {
                            let total = next_dist + forward_d;
                            if total < best_distance {
                                best_distance = total;
                                meeting_node = Some(next);
                            }
                        }
                    }
                }
            }
        }

        // Reconstruct path
        meeting_node.map(|meeting| {
            let mut path = Vec::new();

            // Forward path (source to meeting)
            let mut current = meeting;
            while let Some(pred) = forward_pred[current as usize] {
                path.push(current);
                current = pred;
            }
            path.push(self.source);
            path.reverse();

            // Backward path (meeting to target)
            current = meeting;
            while let Some(pred) = backward_pred[current as usize] {
                current = pred;
                path.push(current);
            }

            (best_distance, path)
        })
    }
}

/// All-Pairs Shortest Paths (APSP).
///
/// Computes shortest distances between all pairs of nodes.
/// Runtime: O(n * (n + m) log n) for weighted, O(n * (n + m)) for unweighted
///
/// Reference: NetworKit APSP class
pub struct APSP<'a> {
    graph: &'a Graph,
    weighted: bool,
}

impl<'a> APSP<'a> {
    /// Create a new APSP instance.
    pub fn new(graph: &'a Graph) -> Self {
        Self {
            graph,
            weighted: false, // Auto-detect or configure
        }
    }

    /// Specify whether to use weighted algorithm.
    pub fn weighted(mut self, weighted: bool) -> Self {
        self.weighted = weighted;
        self
    }

    /// Run APSP and return distance matrix.
    ///
    /// Returns a 2D vector where `result[i][j]` is the distance from node i to node j.
    pub fn run(&self) -> Vec<Vec<Option<f64>>> {
        let n = self.graph.upper_node_id_bound() as usize;
        let mut distances = vec![vec![None; n]; n];

        // Run single-source shortest path from each node
        for (source, dist_row) in distances.iter_mut().enumerate() {
            let source_id = source as NodeId;

            if !self.graph.has_node(source_id) {
                continue;
            }

            let result = if self.weighted {
                Dijkstra::new(self.graph, source_id).run()
            } else {
                BFS::new(self.graph, source_id).run()
            };

            *dist_row = result.distances;
        }

        distances
    }

    /// Get distance between two specific nodes (on-demand computation).
    pub fn distance(&self, source: NodeId, target: NodeId) -> Option<f64> {
        let result = if self.weighted {
            Dijkstra::new(self.graph, source)
                .with_target(target)
                .run()
        } else {
            BFS::new(self.graph, source)
                .with_target(target)
                .run()
        };

        result.distance(target)
    }
}

/// Compute the diameter of a graph (longest shortest path).
///
/// Reference: NetworKit Diameter class
pub fn diameter(graph: &Graph) -> Option<f64> {
    let mut max_dist = 0.0;

    for source in graph.nodes() {
        let result = BFS::new(graph, source).run();

        for dist in result.distances.iter().flatten() {
            if *dist > max_dist {
                max_dist = *dist;
            }
        }
    }

    if max_dist > 0.0 {
        Some(max_dist)
    } else {
        None
    }
}

/// Compute eccentricity of a node (maximum distance to any other node).
///
/// Reference: NetworKit Eccentricity class
pub fn eccentricity(graph: &Graph, node: NodeId) -> Option<(NodeId, f64)> {
    let result = BFS::new(graph, node).run();

    let mut max_dist = 0.0;
    let mut farthest = node;

    for (id, dist) in result.distances.iter().enumerate() {
        if let Some(d) = dist {
            if *d > max_dist {
                max_dist = *d;
                farthest = id as NodeId;
            }
        }
    }

    if max_dist > 0.0 {
        Some((farthest, max_dist))
    } else {
        None
    }
}

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

    fn create_test_graph() -> Graph {
        let mut graph = Graph::new(GraphConfig::simple());

        // Create a simple chain: 0 - 1 - 2 - 3
        for _ in 0..4 {
            graph.add_node();
        }

        graph.add_edge(0, 1, None);
        graph.add_edge(1, 2, None);
        graph.add_edge(2, 3, None);

        graph
    }

    #[test]
    fn test_bfs() {
        let graph = create_test_graph();
        let result = BFS::new(&graph, 0).run();

        assert_eq!(result.distance(0), Some(0.0));
        assert_eq!(result.distance(1), Some(1.0));
        assert_eq!(result.distance(2), Some(2.0));
        assert_eq!(result.distance(3), Some(3.0));
    }

    #[test]
    fn test_bfs_path() {
        let graph = create_test_graph();
        let result = BFS::new(&graph, 0).run();

        let path = result.path(3).unwrap();
        assert_eq!(path, vec![0, 1, 2, 3]);
    }

    #[test]
    fn test_bfs_with_target() {
        let graph = create_test_graph();
        let result = BFS::new(&graph, 0).with_target(2).run();

        assert_eq!(result.distance(2), Some(2.0));
        assert!(result.found);
    }

    #[test]
    fn test_dijkstra_unweighted() {
        let graph = create_test_graph();
        let result = Dijkstra::new(&graph, 0).run();

        assert_eq!(result.distance(0), Some(0.0));
        assert_eq!(result.distance(1), Some(1.0));
        assert_eq!(result.distance(2), Some(2.0));
        assert_eq!(result.distance(3), Some(3.0));
    }

    #[test]
    fn test_dijkstra_weighted() {
        let mut graph = Graph::new(GraphConfig::weighted());

        for _ in 0..4 {
            graph.add_node();
        }

        graph.add_edge(0, 1, Some(2.0));
        graph.add_edge(1, 2, Some(3.0));
        graph.add_edge(2, 3, Some(1.0));
        graph.add_edge(0, 3, Some(10.0)); // Longer direct path

        let result = Dijkstra::new(&graph, 0).run();

        assert_eq!(result.distance(3), Some(6.0)); // Via 1,2 is shorter
    }

    #[test]
    fn test_bidirectional_dijkstra() {
        let graph = create_test_graph();
        let result = BidirectionalDijkstra::new(&graph, 0, 3).run();

        assert!(result.is_some());
        let (distance, path) = result.unwrap();
        assert_eq!(distance, 3.0);
        assert_eq!(path, vec![0, 1, 2, 3]);
    }

    #[test]
    fn test_apsp() {
        let graph = create_test_graph();
        let apsp = APSP::new(&graph);

        assert_eq!(apsp.distance(0, 3), Some(3.0));
        assert_eq!(apsp.distance(1, 3), Some(2.0));
        assert_eq!(apsp.distance(0, 0), Some(0.0));
    }

    #[test]
    fn test_diameter() {
        let graph = create_test_graph();
        let diam = diameter(&graph);

        assert_eq!(diam, Some(3.0)); // Longest path is 0->3
    }

    #[test]
    fn test_eccentricity() {
        let graph = create_test_graph();
        let (farthest, dist) = eccentricity(&graph, 0).unwrap();

        assert_eq!(farthest, 3);
        assert_eq!(dist, 3.0);
    }
}

// ─── Average Shortest Path Length ─────────────────────────────────────────────

/// Compute the average unweighted shortest-path length across all reachable pairs.
///
/// Returns `None` for empty graphs or when no pairs are reachable.
/// For disconnected graphs, only reachable pairs are counted.
///
/// Runtime: O(n * (n + m))
pub fn unweighted_average_shortest_path_length(graph: &Graph) -> Option<f64> {
    let nodes: Vec<NodeId> = graph.nodes().collect();
    let n = nodes.len();
    if n <= 1 { return None; }

    let bound = graph.upper_node_id_bound() as usize;
    let mut total = 0u64;
    let mut count = 0u64;

    for &src in &nodes {
        let mut dist = vec![u64::MAX; bound];
        dist[src as usize] = 0;
        let mut queue = VecDeque::new();
        queue.push_back(src);
        while let Some(u) = queue.pop_front() {
            for e in graph.out_neighbors(u).iter() {
                let v = e.target;
                if dist[v as usize] == u64::MAX {
                    dist[v as usize] = dist[u as usize] + 1;
                    queue.push_back(v);
                }
            }
        }
        for &tgt in &nodes {
            if tgt != src && dist[tgt as usize] != u64::MAX {
                total += dist[tgt as usize];
                count += 1;
            }
        }
    }

    if count == 0 { None } else { Some(total as f64 / count as f64) }
}

// ─── Longest Simple Path ──────────────────────────────────────────────────────

/// Find the longest simple path in a graph (maximum number of edges).
///
/// For DAGs uses dynamic programming in topological order (O(n + m)).
/// For general graphs falls back to DFS exhaustive search (exponential worst-case;
/// practical only for small graphs).
///
/// Returns the path as a `Vec<NodeId>`, or an empty vec for empty graphs.
pub fn longest_simple_path(graph: &Graph) -> Vec<NodeId> {
    let nodes: Vec<NodeId> = graph.nodes().collect();
    if nodes.is_empty() { return vec![]; }

    // Try DAG DP first
    if graph.is_directed() {
        if let Some(path) = longest_simple_path_dag(graph) {
            return path;
        }
    }

    // General: exhaustive DFS over all start nodes
    let bound = graph.upper_node_id_bound() as usize;
    let mut best: Vec<NodeId> = Vec::new();

    for &start in &nodes {
        let mut visited = vec![false; bound];
        let mut current = vec![start];
        visited[start as usize] = true;
        dfs_longest(graph, start, &mut visited, &mut current, &mut best);
    }

    best
}

fn longest_simple_path_dag(graph: &Graph) -> Option<Vec<NodeId>> {
    use std::collections::HashMap;
    // Kahn topological sort
    let n = graph.upper_node_id_bound() as usize;
    let nodes: Vec<NodeId> = graph.nodes().collect();
    let mut in_deg = vec![0usize; n];
    for &u in &nodes {
        for e in graph.out_neighbors(u).iter() {
            in_deg[e.target as usize] += 1;
        }
    }
    let mut queue = VecDeque::new();
    for &u in &nodes { if in_deg[u as usize] == 0 { queue.push_back(u); } }

    let mut topo = Vec::new();
    while let Some(u) = queue.pop_front() {
        topo.push(u);
        for e in graph.out_neighbors(u).iter() {
            in_deg[e.target as usize] -= 1;
            if in_deg[e.target as usize] == 0 { queue.push_back(e.target); }
        }
    }
    if topo.len() != nodes.len() { return None; } // cycle detected → not a DAG

    // dp[u] = (length, predecessor) in longest path ending at u
    let mut dp: HashMap<NodeId, (usize, Option<NodeId>)> = HashMap::new();
    for &u in &topo { dp.insert(u, (0, None)); }
    for &u in &topo {
        let len_u = dp[&u].0;
        for e in graph.out_neighbors(u).iter() {
            let v = e.target;
            if len_u + 1 > dp[&v].0 {
                dp.insert(v, (len_u + 1, Some(u)));
            }
        }
    }

    let (&end, &(len, _)) = dp.iter().max_by_key(|(_, (l, _))| *l)?;
    if len == 0 { return Some(vec![end]); }

    // Reconstruct
    let mut path = Vec::new();
    let mut cur = end;
    loop {
        path.push(cur);
        match dp[&cur].1 {
            Some(prev) => cur = prev,
            None => break,
        }
    }
    path.reverse();
    Some(path)
}

fn dfs_longest(
    graph: &Graph,
    u: NodeId,
    visited: &mut Vec<bool>,
    current: &mut Vec<NodeId>,
    best: &mut Vec<NodeId>,
) {
    if current.len() > best.len() {
        best.clone_from(current);
    }
    let neighbors: Vec<NodeId> = graph.out_neighbors(u).iter().map(|e| e.target).collect();
    for v in neighbors {
        if !visited[v as usize] {
            visited[v as usize] = true;
            current.push(v);
            dfs_longest(graph, v, visited, current, best);
            current.pop();
            visited[v as usize] = false;
        }
    }
}

#[cfg(test)]
mod extra_distance_tests {
    use super::*;
    use crate::graph::backends::networkit_rust::graph::GraphConfig;

    // ── unweighted_average_shortest_path_length ───────────────────────────────

    #[test]
    fn test_avg_path_length_path4() {
        // Path 0-1-2-3: distances 1,2,3,1,2,1 (both directions) = 20/12 ≈ 1.666
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..4 { g.add_node(); }
        for i in 0..3u64 { g.add_edge(i, i+1, None); }
        let avg = unweighted_average_shortest_path_length(&g).unwrap();
        // Sum of all pairwise distances in P4: 1+2+3+1+2+1 = 10, counted twice = 20 ordered pairs
        assert!((avg - 20.0/12.0).abs() < 1e-9, "avg={avg}");
    }

    #[test]
    fn test_avg_path_length_complete() {
        // K3: all distances = 1
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..3 { g.add_node(); }
        for i in 0..3u64 { for j in (i+1)..3 { g.add_edge(i, j, None); } }
        let avg = unweighted_average_shortest_path_length(&g).unwrap();
        assert!((avg - 1.0).abs() < 1e-9);
    }

    #[test]
    fn test_avg_path_length_single() {
        let mut g = Graph::new(GraphConfig::simple());
        g.add_node();
        assert!(unweighted_average_shortest_path_length(&g).is_none());
    }

    #[test]
    fn test_avg_path_length_disconnected() {
        // Two isolated nodes: no reachable pairs
        let mut g = Graph::new(GraphConfig::simple());
        g.add_node(); g.add_node();
        assert!(unweighted_average_shortest_path_length(&g).is_none());
    }

    // ── longest_simple_path ───────────────────────────────────────────────────

    #[test]
    fn test_longest_path_dag() {
        // DAG: 0→1→2→3, also 0→3 shortcut
        let mut g = Graph::new(GraphConfig::directed());
        for _ in 0..4 { g.add_node(); }
        g.add_edge(0, 1, None); g.add_edge(1, 2, None); g.add_edge(2, 3, None);
        g.add_edge(0, 3, None);
        let path = longest_simple_path(&g);
        assert_eq!(path.len(), 4, "longest path should have 4 nodes: {:?}", path);
    }

    #[test]
    fn test_longest_path_undirected() {
        // Path graph P4: longest = all 4 nodes
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..4 { g.add_node(); }
        for i in 0..3u64 { g.add_edge(i, i+1, None); }
        let path = longest_simple_path(&g);
        assert_eq!(path.len(), 4, "P4 longest path = 4 nodes: {:?}", path);
    }

    #[test]
    fn test_longest_path_single_node() {
        let mut g = Graph::new(GraphConfig::simple());
        g.add_node();
        let path = longest_simple_path(&g);
        assert_eq!(path.len(), 1);
    }

    #[test]
    fn test_longest_path_empty() {
        let g = Graph::new(GraphConfig::simple());
        assert!(longest_simple_path(&g).is_empty());
    }
}
