// Copyright 2026 Gregorio Momm
// Licensed under the Apache License, Version 2.0
//
// Ported from RustworkxCore (IBM Qiskit) — Apache License 2.0

//! Graph operations: complement, union, tensor product, cartesian product.

use super::super::graph::{Graph, GraphConfig, NodeId};
use std::collections::HashMap;

// ─── Complement ───────────────────────────────────────────────────────────────

/// Return the complement of a graph.
///
/// The complement contains an edge (u, v) for every pair of distinct nodes
/// where the original graph does NOT have that edge.  Self-loops are never
/// added.  The returned graph has the same directed/undirected configuration
/// as the input.
///
/// Runtime: O(n²)
pub fn complement(graph: &Graph) -> Graph {
    let mut out = Graph::new(GraphConfig {
        directed: graph.is_directed(),
        ..Default::default()
    });

    let nodes: Vec<NodeId> = graph.nodes().collect();
    let n = nodes.len();

    // Add all nodes
    for _ in 0..n {
        out.add_node();
    }

    // Map old NodeId → index in `nodes` vec (node IDs may not be contiguous)
    let id_to_idx: HashMap<NodeId, usize> =
        nodes.iter().enumerate().map(|(i, &v)| (v, i)).collect();

    if graph.is_directed() {
        for (i, &u) in nodes.iter().enumerate() {
            for (j, &v) in nodes.iter().enumerate() {
                if i != j && !graph.has_edge(u, v) {
                    out.add_edge(id_to_idx[&u] as NodeId, id_to_idx[&v] as NodeId, None);
                }
            }
        }
    } else {
        for (i, &u) in nodes.iter().enumerate() {
            for (j, &v) in nodes.iter().enumerate() {
                if i < j && !graph.has_edge(u, v) {
                    out.add_edge(id_to_idx[&u] as NodeId, id_to_idx[&v] as NodeId, None);
                }
            }
        }
    }

    out
}

// ─── Union ────────────────────────────────────────────────────────────────────

/// Result of a graph union operation.
pub struct UnionResult {
    /// The combined graph.
    pub graph: Graph,
    /// Map from node IDs in graph A to new node IDs in the union graph.
    pub node_map_a: HashMap<NodeId, NodeId>,
    /// Map from node IDs in graph B to new node IDs in the union graph.
    pub node_map_b: HashMap<NodeId, NodeId>,
}

/// Compute the disjoint union of two graphs.
///
/// Creates a new graph containing all nodes and edges from both input graphs,
/// with node IDs remapped so that the two vertex sets are disjoint.
///
/// Runtime: O(n_a + m_a + n_b + m_b)
pub fn graph_union(a: &Graph, b: &Graph) -> UnionResult {
    let directed = a.is_directed() || b.is_directed();
    let mut out = Graph::new(GraphConfig {
        directed,
        ..Default::default()
    });

    // Copy nodes from A
    let mut node_map_a: HashMap<NodeId, NodeId> = HashMap::new();
    for u in a.nodes() {
        let new_id = out.add_node();
        node_map_a.insert(u, new_id);
    }

    // Copy edges from A
    for u in a.nodes() {
        for e in a.out_neighbors(u).iter() {
            if a.is_directed() || u < e.target {
                out.add_edge(node_map_a[&u], node_map_a[&e.target], None);
            }
        }
    }

    // Copy nodes from B
    let mut node_map_b: HashMap<NodeId, NodeId> = HashMap::new();
    for v in b.nodes() {
        let new_id = out.add_node();
        node_map_b.insert(v, new_id);
    }

    // Copy edges from B
    for v in b.nodes() {
        for e in b.out_neighbors(v).iter() {
            if b.is_directed() || v < e.target {
                out.add_edge(node_map_b[&v], node_map_b[&e.target], None);
            }
        }
    }

    UnionResult { graph: out, node_map_a, node_map_b }
}

// ─── Tensor Product ───────────────────────────────────────────────────────────

/// Compute the tensor product (categorical product) of two graphs.
///
/// Nodes in the product are pairs (u, v) where u ∈ A and v ∈ B.
/// An edge ((u1,v1), (u2,v2)) exists iff edge (u1,u2) ∈ A AND (v1,v2) ∈ B.
///
/// Runtime: O(n_a * n_b + m_a * m_b)
pub fn tensor_product(a: &Graph, b: &Graph) -> (Graph, HashMap<(NodeId, NodeId), NodeId>) {
    let directed = a.is_directed() || b.is_directed();
    let mut out = Graph::new(GraphConfig {
        directed,
        ..Default::default()
    });

    let nodes_a: Vec<NodeId> = a.nodes().collect();
    let nodes_b: Vec<NodeId> = b.nodes().collect();

    // Create product nodes: one per (u, v) pair
    let mut pair_to_id: HashMap<(NodeId, NodeId), NodeId> = HashMap::new();
    for &u in &nodes_a {
        for &v in &nodes_b {
            let id = out.add_node();
            pair_to_id.insert((u, v), id);
        }
    }

    // Add edges: (u1,v1)→(u2,v2) iff u1→u2 ∈ A and v1→v2 ∈ B
    for &u1 in &nodes_a {
        for e_a in a.out_neighbors(u1).iter() {
            let u2 = e_a.target;
            for &v1 in &nodes_b {
                for e_b in b.out_neighbors(v1).iter() {
                    let v2 = e_b.target;
                    if !directed && (u1 > u2 || (u1 == u2 && v1 > v2)) {
                        continue; // canonical direction for undirected
                    }
                    let src = pair_to_id[&(u1, v1)];
                    let dst = pair_to_id[&(u2, v2)];
                    if src != dst {
                        out.add_edge(src, dst, None);
                    }
                }
            }
        }
    }

    (out, pair_to_id)
}

// ─── Cartesian Product ────────────────────────────────────────────────────────

/// Compute the Cartesian product of two graphs.
///
/// Nodes are pairs (u, v). An edge exists between (u1,v1) and (u2,v2) iff:
/// - u1 == u2 and (v1, v2) ∈ B, OR
/// - v1 == v2 and (u1, u2) ∈ A
///
/// Runtime: O(n_a * n_b + n_a * m_b + n_b * m_a)
pub fn cartesian_product(a: &Graph, b: &Graph) -> (Graph, HashMap<(NodeId, NodeId), NodeId>) {
    let directed = a.is_directed() || b.is_directed();
    let mut out = Graph::new(GraphConfig {
        directed,
        ..Default::default()
    });

    let nodes_a: Vec<NodeId> = a.nodes().collect();
    let nodes_b: Vec<NodeId> = b.nodes().collect();

    let mut pair_to_id: HashMap<(NodeId, NodeId), NodeId> = HashMap::new();
    for &u in &nodes_a {
        for &v in &nodes_b {
            let id = out.add_node();
            pair_to_id.insert((u, v), id);
        }
    }

    // Edges from A-edges with fixed B-node
    for &u1 in &nodes_a {
        for e in a.out_neighbors(u1).iter() {
            let u2 = e.target;
            if !directed && u1 > u2 { continue; }
            for &v in &nodes_b {
                let src = pair_to_id[&(u1, v)];
                let dst = pair_to_id[&(u2, v)];
                out.add_edge(src, dst, None);
            }
        }
    }

    // Edges from B-edges with fixed A-node
    for &v1 in &nodes_b {
        for e in b.out_neighbors(v1).iter() {
            let v2 = e.target;
            if !directed && v1 > v2 { continue; }
            for &u in &nodes_a {
                let src = pair_to_id[&(u, v1)];
                let dst = pair_to_id[&(u, v2)];
                out.add_edge(src, dst, None);
            }
        }
    }

    (out, pair_to_id)
}

// ─── Adjacency / Distance matrices ───────────────────────────────────────────

/// Compute the adjacency matrix of a graph as a dense 2D vec.
///
/// Entry `[i][j]` is 1.0 if edge (nodes[i], nodes[j]) exists, else 0.0.
/// For weighted graphs the entry is the edge weight.
/// Returns `(matrix, node_order)` where `node_order[i]` is the NodeId at row i.
// ─── Strong product ───────────────────────────────────────────────────────────

/// Compute the strong product of two graphs.
///
/// The strong product G □⊗ H has:
/// - Node set: V(G) × V(H)
/// - Edges between (u1, v1) and (u2, v2) if:
///   - u1 = u2 and (v1, v2) ∈ E(H), **or**
///   - v1 = v2 and (u1, u2) ∈ E(G), **or**
///   - (u1, u2) ∈ E(G) and (v1, v2) ∈ E(H)  ← diagonal edges
///
/// The strong product is the union of the Cartesian and tensor products.
///
/// Returns `(product_graph, node_map)` where `node_map[(u, v)]` gives the
/// product node id for the pair `(u, v)`.
pub fn strong_product(a: &Graph, b: &Graph) -> (Graph, HashMap<(NodeId, NodeId), NodeId>) {
    let directed = a.is_directed() && b.is_directed();
    let cfg = if directed { GraphConfig::directed() } else { GraphConfig::simple() };
    let mut g = Graph::new(cfg);

    let a_nodes: Vec<NodeId> = a.nodes().collect();
    let b_nodes: Vec<NodeId> = b.nodes().collect();

    let mut node_map: HashMap<(NodeId, NodeId), NodeId> = HashMap::new();
    for &u in &a_nodes {
        for &v in &b_nodes {
            let id = g.add_node();
            node_map.insert((u, v), id);
        }
    }

    let has_a = |u: NodeId, v: NodeId| a.out_neighbors(u).iter().any(|e| e.target == v);
    let has_b = |u: NodeId, v: NodeId| b.out_neighbors(u).iter().any(|e| e.target == v);

    for &u1 in &a_nodes {
        for &v1 in &b_nodes {
            for &u2 in &a_nodes {
                for &v2 in &b_nodes {
                    if (u1, v1) == (u2, v2) { continue; }
                    let n1 = node_map[&(u1, v1)];
                    let n2 = node_map[&(u2, v2)];

                    // Cartesian: one coordinate equal, other has edge
                    let cart = (u1 == u2 && has_b(v1, v2)) || (v1 == v2 && has_a(u1, u2));
                    // Tensor: both coordinates have edges
                    let tens = has_a(u1, u2) && has_b(v1, v2);

                    if (cart || tens) && !g.has_edge(n1, n2) {
                        g.add_edge(n1, n2, None);
                    }
                }
            }
        }
    }
    (g, node_map)
}

// ─── Lexicographic product ────────────────────────────────────────────────────

/// Compute the lexicographic product (graph composition) of two graphs.
///
/// The lexicographic product G[H] has:
/// - Node set: V(G) × V(H)
/// - Edges between (u1, v1) and (u2, v2) if:
///   - (u1, u2) ∈ E(G), **or**
///   - u1 = u2 and (v1, v2) ∈ E(H)
///
/// Also known as the "graph substitution" — replace every node of G with a
/// copy of H, and connect all pairs across copies when the G-nodes are adjacent.
pub fn lexicographic_product(a: &Graph, b: &Graph) -> (Graph, HashMap<(NodeId, NodeId), NodeId>) {
    let directed = a.is_directed() && b.is_directed();
    let cfg = if directed { GraphConfig::directed() } else { GraphConfig::simple() };
    let mut g = Graph::new(cfg);

    let a_nodes: Vec<NodeId> = a.nodes().collect();
    let b_nodes: Vec<NodeId> = b.nodes().collect();

    let mut node_map: HashMap<(NodeId, NodeId), NodeId> = HashMap::new();
    for &u in &a_nodes {
        for &v in &b_nodes {
            let id = g.add_node();
            node_map.insert((u, v), id);
        }
    }

    let has_a = |u: NodeId, v: NodeId| a.out_neighbors(u).iter().any(|e| e.target == v);
    let has_b = |u: NodeId, v: NodeId| b.out_neighbors(u).iter().any(|e| e.target == v);

    for &u1 in &a_nodes {
        for &v1 in &b_nodes {
            for &u2 in &a_nodes {
                for &v2 in &b_nodes {
                    if (u1, v1) == (u2, v2) { continue; }
                    let n1 = node_map[&(u1, v1)];
                    let n2 = node_map[&(u2, v2)];

                    // Edge if (u1, u2) ∈ E(G), OR u1 = u2 and (v1, v2) ∈ E(H)
                    let connect = has_a(u1, u2) || (u1 == u2 && has_b(v1, v2));
                    if connect && !g.has_edge(n1, n2) {
                        g.add_edge(n1, n2, None);
                    }
                }
            }
        }
    }
    (g, node_map)
}

// ─── Graph power ──────────────────────────────────────────────────────────────

/// Compute the k-th power of a graph.
///
/// Two nodes are connected in G^k if and only if there is a path of length
/// ≤ k between them in G.
///
/// `k = 1` returns the original graph; `k ≥ diameter` gives the complete graph.
pub fn graph_power(graph: &Graph, k: usize) -> Graph {
    let directed = graph.is_directed();
    let cfg = if directed { GraphConfig::directed() } else { GraphConfig::simple() };
    let mut out = Graph::new(cfg);

    if k == 0 {
        // No edges — just nodes
        for _ in graph.nodes() { out.add_node(); }
        return out;
    }

    let nodes: Vec<NodeId> = graph.nodes().collect();
    let mut id_map: HashMap<NodeId, NodeId> = HashMap::new();
    for &n in &nodes {
        let id = out.add_node();
        id_map.insert(n, id);
    }

    // BFS from each node up to depth k
    use std::collections::VecDeque;
    for &src in &nodes {
        let mut dist: HashMap<NodeId, usize> = HashMap::new();
        let mut queue: VecDeque<NodeId> = VecDeque::new();
        dist.insert(src, 0);
        queue.push_back(src);
        while let Some(u) = queue.pop_front() {
            let d = dist[&u];
            if d >= k { continue; }
            for nb in graph.out_neighbors(u) {
                if !dist.contains_key(&nb.target) {
                    dist.insert(nb.target, d + 1);
                    queue.push_back(nb.target);
                }
            }
        }
        for (tgt, _) in dist {
            if tgt != src {
                let ns = id_map[&src];
                let nt = id_map[&tgt];
                if !out.has_edge(ns, nt) {
                    out.add_edge(ns, nt, None);
                }
            }
        }
    }
    out
}

///
/// Runtime: O(n² + m)
pub fn adjacency_matrix(graph: &Graph) -> (Vec<Vec<f64>>, Vec<NodeId>) {
    let nodes: Vec<NodeId> = graph.nodes().collect();
    let n = nodes.len();
    let id_to_idx: HashMap<NodeId, usize> =
        nodes.iter().enumerate().map(|(i, &v)| (v, i)).collect();
    let mut mat = vec![vec![0.0_f64; n]; n];

    for &u in &nodes {
        for e in graph.out_neighbors(u).iter() {
            let i = id_to_idx[&u];
            let j = id_to_idx[&e.target];
            let w = e.weight.unwrap_or(1.0) as f64;
            mat[i][j] = w;
            if !graph.is_directed() {
                mat[j][i] = w;
            }
        }
    }

    (mat, nodes)
}

/// Compute the all-pairs shortest-path distance matrix using BFS (unweighted).
///
/// Entry `[i][j]` is the shortest-path distance from nodes[i] to nodes[j],
/// or `f64::INFINITY` if unreachable.
///
/// Runtime: O(n * (n + m))
pub fn bfs_distance_matrix(graph: &Graph) -> (Vec<Vec<f64>>, Vec<NodeId>) {
    use std::collections::VecDeque;

    let nodes: Vec<NodeId> = graph.nodes().collect();
    let n = nodes.len();
    let bound = graph.upper_node_id_bound() as usize;

    let mut mat = vec![vec![f64::INFINITY; n]; n];
    for i in 0..n { mat[i][i] = 0.0; }

    for (si, &src) in nodes.iter().enumerate() {
        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 (ti, &tgt) in nodes.iter().enumerate() {
            if dist[tgt as usize] != u64::MAX {
                mat[si][ti] = dist[tgt as usize] as f64;
            }
        }
    }

    (mat, nodes)
}

// ─── Tests ────────────────────────────────────────────────────────────────────

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

    fn triangle() -> Graph {
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..3 { g.add_node(); }
        g.add_edge(0, 1, None);
        g.add_edge(1, 2, None);
        g.add_edge(0, 2, None);
        g
    }

    fn path3() -> Graph {
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..3 { g.add_node(); }
        g.add_edge(0, 1, None);
        g.add_edge(1, 2, None);
        g
    }

    /// K2: a single undirected edge between two nodes
    fn edge_graph() -> Graph {
        let mut g = Graph::new(GraphConfig::simple());
        g.add_node(); g.add_node();
        g.add_edge(0, 1, None);
        g
    }

    // ── complement ───────────────────────────────────────────────────────────

    #[test]
    fn test_complement_triangle() {
        // Triangle K3 complement = empty graph (no edges)
        let g = triangle();
        let c = complement(&g);
        assert_eq!(c.node_count(), 3);
        assert_eq!(c.edge_count(), 0, "complement of K3 has no edges");
    }

    #[test]
    fn test_complement_path3() {
        // Path 0-1-2 complement: only edge (0,2) missing
        let g = path3();
        let c = complement(&g);
        assert_eq!(c.node_count(), 3);
        assert_eq!(c.edge_count(), 1, "complement of P3 has 1 edge");
    }

    #[test]
    fn test_complement_empty() {
        // Complement of empty graph = empty graph
        let g = Graph::new(GraphConfig::simple());
        let c = complement(&g);
        assert_eq!(c.node_count(), 0);
        assert_eq!(c.edge_count(), 0);
    }

    #[test]
    fn test_complement_isolated_nodes() {
        // 3 isolated nodes: complement = K3
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..3 { g.add_node(); }
        let c = complement(&g);
        assert_eq!(c.edge_count(), 3);
    }

    // ── graph_union ───────────────────────────────────────────────────────────

    #[test]
    fn test_union_node_count() {
        let a = path3();
        let b = triangle();
        let res = graph_union(&a, &b);
        assert_eq!(res.graph.node_count(), 6);
    }

    #[test]
    fn test_union_edge_count() {
        let a = path3();   // 2 edges
        let b = triangle(); // 3 edges
        let res = graph_union(&a, &b);
        assert_eq!(res.graph.edge_count(), 5);
    }

    #[test]
    fn test_union_disjoint() {
        // Ensure node maps are disjoint
        let a = path3();
        let b = path3();
        let res = graph_union(&a, &b);
        for (_, &va) in &res.node_map_a {
            assert!(!res.node_map_b.values().any(|&vb| vb == va));
        }
    }

    // ── tensor_product ────────────────────────────────────────────────────────

    #[test]
    fn test_tensor_product_node_count() {
        // P3 × P3 → 9 nodes
        let (prod, _) = tensor_product(&path3(), &path3());
        assert_eq!(prod.node_count(), 9);
    }

    #[test]
    fn test_tensor_product_k2_k2() {
        // K2 × K2: nodes (0,0),(0,1),(1,0),(1,1); edges: (0,1)↔(1,0) diagonal
        let mut k2 = Graph::new(GraphConfig::simple());
        k2.add_node(); k2.add_node();
        k2.add_edge(0, 1, None);
        let (prod, _) = tensor_product(&k2, &k2);
        assert_eq!(prod.node_count(), 4);
        // K2×K2 for undirected = 2 nodes connected (the two diagonals)
        assert_eq!(prod.edge_count(), 2);
    }

    // ── cartesian_product ─────────────────────────────────────────────────────

    #[test]
    fn test_cartesian_product_node_count() {
        // P3 □ P3 → 9 nodes
        let (prod, _) = cartesian_product(&path3(), &path3());
        assert_eq!(prod.node_count(), 9);
    }

    #[test]
    fn test_cartesian_product_k2_k2() {
        // K2 □ K2 = C4 (4-cycle): 4 nodes, 4 edges
        let mut k2 = Graph::new(GraphConfig::simple());
        k2.add_node(); k2.add_node();
        k2.add_edge(0, 1, None);
        let (prod, _) = cartesian_product(&k2, &k2);
        assert_eq!(prod.node_count(), 4);
        assert_eq!(prod.edge_count(), 4);
    }

    // ── adjacency_matrix ──────────────────────────────────────────────────────

    #[test]
    fn test_adjacency_matrix_triangle() {
        let g = triangle();
        let (mat, nodes) = adjacency_matrix(&g);
        assert_eq!(nodes.len(), 3);
        // Diagonal should be 0
        for i in 0..3 { assert_eq!(mat[i][i], 0.0); }
        // All off-diagonal should be 1 (K3)
        for i in 0..3 {
            for j in 0..3 {
                if i != j { assert_eq!(mat[i][j], 1.0); }
            }
        }
    }

    #[test]
    fn test_adjacency_matrix_path3() {
        let g = path3();
        let (mat, _) = adjacency_matrix(&g);
        // Sum of all entries = 2 * edges = 4
        let total: f64 = mat.iter().flat_map(|r| r.iter()).sum();
        assert_eq!(total, 4.0);
    }

    // ── distance_matrix ───────────────────────────────────────────────────────

    #[test]
    fn test_distance_matrix_path3() {
        let g = path3();
        let (mat, nodes) = bfs_distance_matrix(&g);
        let idx: HashMap<NodeId, usize> = nodes.iter().enumerate().map(|(i, &v)| (v, i)).collect();
        assert_eq!(mat[idx[&0]][idx[&0]], 0.0);
        assert_eq!(mat[idx[&0]][idx[&1]], 1.0);
        assert_eq!(mat[idx[&0]][idx[&2]], 2.0);
        assert_eq!(mat[idx[&2]][idx[&0]], 2.0);
    }

    #[test]
    fn test_distance_matrix_disconnected() {
        // Two isolated nodes: distance should be infinity
        let mut g = Graph::new(GraphConfig::simple());
        g.add_node(); g.add_node();
        let (mat, _) = bfs_distance_matrix(&g);
        assert_eq!(mat[0][1], f64::INFINITY);
        assert_eq!(mat[1][0], f64::INFINITY);
    }

    // ── strong_product ────────────────────────────────────────────────────────

    #[test]
    fn test_strong_product_k2_k2() {
        // K2 □⊗ K2: 4 nodes
        // Strong product of two edges: should yield the cycle C4 + diagonal = K4
        let g1 = edge_graph();
        let g2 = edge_graph();
        let (sp, _) = strong_product(&g1, &g2);
        assert_eq!(sp.node_count(), 4);
        // K2 □⊗ K2 = K4 (complete graph on 4 nodes = 6 edges)
        assert_eq!(sp.edge_count(), 6);
    }

    #[test]
    fn test_strong_product_node_count() {
        let g1 = path3();
        let g2 = edge_graph();
        let (sp, nm) = strong_product(&g1, &g2);
        assert_eq!(sp.node_count(), 6); // 3 * 2
        assert!(!nm.is_empty());
    }

    // ── lexicographic_product ─────────────────────────────────────────────────

    #[test]
    fn test_lexicographic_product_node_count() {
        let g1 = path3();
        let g2 = edge_graph();
        let (lp, nm) = lexicographic_product(&g1, &g2);
        assert_eq!(lp.node_count(), 6);
        assert!(!nm.is_empty());
    }

    #[test]
    fn test_lexicographic_product_k2_k2() {
        // K2 [K2]: (a,0)-(a,1), (b,0)-(b,1) [from K2 in H]
        // plus all (a,*) ↔ (b,*) [from K2 in G]
        let g1 = edge_graph();
        let g2 = edge_graph();
        let (lp, _) = lexicographic_product(&g1, &g2);
        assert_eq!(lp.node_count(), 4);
        // 4 copies of H-edge (a,0)-(a,1), (b,0)-(b,1) + 4 cross edges = but undirected → 6 total
        // = K4, same as strong product here
        assert_eq!(lp.edge_count(), 6);
    }

    // ── graph_power ───────────────────────────────────────────────────────────

    #[test]
    fn test_graph_power_1() {
        // G^1 = G
        let g = path3();
        let p1 = graph_power(&g, 1);
        assert_eq!(p1.node_count(), 3);
        assert_eq!(p1.edge_count(), 2);
    }

    #[test]
    fn test_graph_power_2_path() {
        // Path 0-1-2-3: G^2 adds 0-2 and 1-3
        let mut g = Graph::new(GraphConfig::simple());
        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);
        let p2 = graph_power(&g, 2);
        assert_eq!(p2.node_count(), 4);
        // Original 3 + added 0-2 and 1-3 = 5
        assert_eq!(p2.edge_count(), 5);
    }

    #[test]
    fn test_graph_power_large_k_complete() {
        // G^k for k >= diameter gives complete graph
        let g = path3(); // diameter = 2
        let p3 = graph_power(&g, 3);
        assert_eq!(p3.node_count(), 3);
        assert_eq!(p3.edge_count(), 3); // K3
    }
}
