// Copyright 2026 Gregorio Momm
// Licensed under the Apache License, Version 2.0
//
// Ported from RustworkxCore (IBM Qiskit) — Apache License 2.0
// Source: https://github.com/Qiskit/rustworkx/tree/main/rustworkx-core/src/generators

//! Graph generator functions.
//!
//! Create commonly-used graph structures directly as NetworKit `Graph` objects:
//!
//! - `complete_graph` — K_n: every node connected to every other
//! - `cycle_graph` — C_n: ring of n nodes
//! - `path_graph` — P_n: linear chain of n nodes
//! - `star_graph` — S_n: one center connected to n leaves
//! - `grid_graph` — m×n rectangular lattice
//! - `petersen_graph` — the classic Petersen graph (10 nodes)
//! - `barbell_graph` — two K_m connected by a path of length n
//! - `random_graph` — Erdős–Rényi G(n, p) random graph
//! - `karate_club` — Zachary's karate club benchmark (34 nodes)
//! - `full_rary_tree` — complete r-ary tree of given depth

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

// ─── Complete graph ───────────────────────────────────────────────────────────

/// Create a complete graph K_n (undirected by default).
///
/// Every pair of distinct nodes is connected by an edge.
/// n(n-1)/2 edges total.
pub fn complete_graph(n: usize, directed: bool) -> Graph {
    let cfg = if directed { GraphConfig::directed() } else { GraphConfig::simple() };
    let mut g = Graph::new(cfg);
    let ids: Vec<NodeId> = (0..n).map(|_| g.add_node()).collect();

    if directed {
        for &u in &ids {
            for &v in &ids {
                if u != v { g.add_edge(u, v, None); }
            }
        }
    } else {
        for i in 0..ids.len() {
            for j in (i+1)..ids.len() {
                g.add_edge(ids[i], ids[j], None);
            }
        }
    }
    g
}

// ─── Cycle graph ─────────────────────────────────────────────────────────────

/// Create a cycle graph C_n.
///
/// n nodes arranged in a ring: 0-1-2-…-(n-1)-0.
pub fn cycle_graph(n: usize, directed: bool) -> Graph {
    let cfg = if directed { GraphConfig::directed() } else { GraphConfig::simple() };
    let mut g = Graph::new(cfg);
    let ids: Vec<NodeId> = (0..n).map(|_| g.add_node()).collect();
    for i in 0..n {
        g.add_edge(ids[i], ids[(i + 1) % n], None);
    }
    g
}

// ─── Path graph ──────────────────────────────────────────────────────────────

/// Create a path graph P_n.
///
/// n nodes in a linear chain: 0-1-2-…-(n-1).
pub fn path_graph(n: usize, directed: bool) -> Graph {
    let cfg = if directed { GraphConfig::directed() } else { GraphConfig::simple() };
    let mut g = Graph::new(cfg);
    let ids: Vec<NodeId> = (0..n).map(|_| g.add_node()).collect();
    for i in 0..n.saturating_sub(1) {
        g.add_edge(ids[i], ids[i + 1], None);
    }
    g
}

// ─── Star graph ───────────────────────────────────────────────────────────────

/// Create a star graph S_n with `n` leaves (n+1 nodes total).
///
/// Node 0 is the center; nodes 1..=n are the leaves.
pub fn star_graph(n: usize, directed: bool) -> Graph {
    let cfg = if directed { GraphConfig::directed() } else { GraphConfig::simple() };
    let mut g = Graph::new(cfg);
    let center = g.add_node();
    for _ in 0..n {
        let leaf = g.add_node();
        g.add_edge(center, leaf, None);
    }
    g
}

// ─── Grid graph ──────────────────────────────────────────────────────────────

/// Create a 2D grid (lattice) graph with `rows` × `cols` nodes.
///
/// Nodes are connected to horizontal and vertical neighbors.
/// Node id = row * cols + col.
pub fn grid_graph(rows: usize, cols: usize, directed: bool) -> Graph {
    let cfg = if directed { GraphConfig::directed() } else { GraphConfig::simple() };
    let mut g = Graph::new(cfg);
    let mut ids: Vec<Vec<NodeId>> = Vec::new();
    for _ in 0..rows {
        let row: Vec<NodeId> = (0..cols).map(|_| g.add_node()).collect();
        ids.push(row);
    }

    for r in 0..rows {
        for c in 0..cols {
            // Right neighbor
            if c + 1 < cols {
                g.add_edge(ids[r][c], ids[r][c + 1], None);
            }
            // Down neighbor
            if r + 1 < rows {
                g.add_edge(ids[r][c], ids[r + 1][c], None);
            }
        }
    }
    g
}

// ─── Petersen graph ───────────────────────────────────────────────────────────

/// Create the Petersen graph (10 nodes, 15 edges).
///
/// Classic graph theory benchmark: 3-regular, vertex-transitive, non-planar.
/// Outer 5-cycle: 0-1-2-3-4-0
/// Inner pentagram: 5-7-9-6-8-5
/// Spokes: 0-5, 1-6, 2-7, 3-8, 4-9
pub fn petersen_graph() -> Graph {
    let mut g = Graph::new(GraphConfig::simple());
    let ids: Vec<NodeId> = (0..10).map(|_| g.add_node()).collect();

    // Outer cycle
    for i in 0..5usize {
        g.add_edge(ids[i], ids[(i + 1) % 5], None);
    }
    // Inner pentagram (skip-2 connections)
    for i in 0..5usize {
        g.add_edge(ids[i + 5], ids[(i + 2) % 5 + 5], None);
    }
    // Spokes
    for i in 0..5usize {
        g.add_edge(ids[i], ids[i + 5], None);
    }
    g
}

// ─── Barbell graph ────────────────────────────────────────────────────────────

/// Create a barbell graph: two complete graphs K_m connected by a path of length k.
///
/// Total nodes: 2*m + k
pub fn barbell_graph(m: usize, k: usize) -> Graph {
    let mut g = Graph::new(GraphConfig::simple());

    // First clique
    let clique1: Vec<NodeId> = (0..m).map(|_| g.add_node()).collect();
    for i in 0..m { for j in (i+1)..m { g.add_edge(clique1[i], clique1[j], None); } }

    // Path
    let mut prev = *clique1.last().unwrap();
    let path_nodes: Vec<NodeId> = (0..k).map(|_| {
        let n = g.add_node();
        g.add_edge(prev, n, None);
        prev = n;
        n
    }).collect();

    // Second clique
    let clique2_start = path_nodes.last().copied().unwrap_or(prev);
    let mut clique2: Vec<NodeId> = (0..m).map(|_| g.add_node()).collect();
    g.add_edge(clique2_start, clique2[0], None);
    for i in 0..m { for j in (i+1)..m { g.add_edge(clique2[i], clique2[j], None); } }

    let _ = clique2.pop(); // suppress unused warning
    g
}

// ─── Random graph (Erdős–Rényi) ───────────────────────────────────────────────

/// Create an Erdős–Rényi random graph G(n, p).
///
/// Each edge is included independently with probability `p`.
/// Uses a simple linear congruential PRNG (seed-based, no external deps).
///
/// Runtime: O(n²)
pub fn random_graph(n: usize, p: f64, seed: u64, directed: bool) -> Graph {
    let cfg = if directed { GraphConfig::directed() } else { GraphConfig::simple() };
    let mut g = Graph::new(cfg);
    let ids: Vec<NodeId> = (0..n).map(|_| g.add_node()).collect();

    // Simple LCG PRNG
    let mut state = seed;
    let lcg_next = |s: &mut u64| -> f64 {
        *s = s.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        (*s >> 33) as f64 / (u32::MAX as f64)
    };

    if directed {
        for &u in &ids {
            for &v in &ids {
                if u != v && lcg_next(&mut state) < p {
                    g.add_edge(u, v, None);
                }
            }
        }
    } else {
        for i in 0..ids.len() {
            for j in (i+1)..ids.len() {
                if lcg_next(&mut state) < p {
                    g.add_edge(ids[i], ids[j], None);
                }
            }
        }
    }
    g
}

// ─── Full r-ary tree ─────────────────────────────────────────────────────────

/// Create a complete r-ary tree of given `depth`.
///
/// The tree has (r^(depth+1) - 1) / (r - 1) nodes for r > 1.
/// Level 0 = root, level k = children at depth k.
pub fn full_rary_tree(r: usize, depth: usize) -> Graph {
    let mut g = Graph::new(GraphConfig::simple());
    if r == 0 || depth == 0 { g.add_node(); return g; }

    let root = g.add_node();
    let mut current_level = vec![root];

    for _ in 0..depth {
        let mut next_level = Vec::new();
        for &parent in &current_level {
            for _ in 0..r {
                let child = g.add_node();
                g.add_edge(parent, child, None);
                next_level.push(child);
            }
        }
        current_level = next_level;
    }
    g
}

// ─── Karate club ─────────────────────────────────────────────────────────────

/// Create Zachary's karate club graph (34 nodes, 78 edges).
///
/// Classic social network benchmark used in community detection.
/// Reference: Zachary, W. W. (1977). "An information flow model for conflict
/// and fission in small groups." Journal of Anthropological Research, 33(4).
pub fn karate_club() -> Graph {
    let mut g = Graph::new(GraphConfig::simple());
    for _ in 0..34 { g.add_node(); }

    // Zachary's karate club edges (0-indexed)
    let edges: &[(NodeId, NodeId)] = &[
        (0,1),(0,2),(0,3),(0,4),(0,5),(0,6),(0,7),(0,8),(0,10),(0,11),(0,12),(0,13),
        (0,17),(0,19),(0,21),(0,31),(1,2),(1,3),(1,7),(1,13),(1,17),(1,19),(1,21),
        (1,30),(2,3),(2,7),(2,8),(2,9),(2,13),(2,27),(2,28),(2,32),(3,7),(3,12),
        (3,13),(4,6),(4,10),(5,6),(5,10),(5,16),(6,16),(8,30),(8,32),(8,33),
        (9,33),(13,33),(14,32),(14,33),(15,32),(15,33),(18,32),(18,33),(19,33),
        (20,32),(20,33),(22,32),(22,33),(23,25),(23,27),(23,29),(23,32),(23,33),
        (24,25),(24,27),(24,31),(25,31),(26,29),(26,33),(27,33),(28,31),(28,33),
        (29,32),(29,33),(30,32),(30,33),(31,32),(31,33),(32,33),
    ];

    for &(u, v) in edges {
        g.add_edge(u, v, None);
    }
    g
}

// ─── Lollipop graph ───────────────────────────────────────────────────────────

/// Create a lollipop graph: K_m connected by a path of length n to a single end node.
///
/// Total nodes: m + n. The complete graph occupies nodes 0..m, the path tail m..m+n.
/// Node m-1 connects to node m (the "stick").
pub fn lollipop_graph(m: usize, n: usize) -> Graph {
    let mut g = Graph::new(GraphConfig::simple());
    if m == 0 { return g; }

    // Create the clique K_m
    let clique: Vec<NodeId> = (0..m).map(|_| g.add_node()).collect();
    for i in 0..clique.len() {
        for j in (i+1)..clique.len() {
            g.add_edge(clique[i], clique[j], None);
        }
    }

    // Create the path tail of length n
    let mut prev = *clique.last().unwrap();
    for _ in 0..n {
        let next = g.add_node();
        g.add_edge(prev, next, None);
        prev = next;
    }
    g
}

// ─── Barabási–Albert preferential attachment ──────────────────────────────────

/// Create a Barabási–Albert scale-free graph using preferential attachment.
///
/// Start with a complete graph of `m` nodes. Then add `n - m` new nodes,
/// each connecting to `m` existing nodes chosen with probability proportional to degree.
///
/// The resulting graph has power-law degree distribution P(k) ~ k^(-3).
///
/// # Arguments
/// - `n`: total number of nodes
/// - `m`: number of edges each new node attaches (initial clique size)
/// - `seed`: random seed for reproducibility
pub fn barabasi_albert_graph(n: usize, m: usize, seed: u64) -> Graph {
    let mut g = Graph::new(GraphConfig::simple());
    if n == 0 || m == 0 { return g; }
    let m = m.min(n);

    // Start with complete graph of m nodes
    let init: Vec<NodeId> = (0..m).map(|_| g.add_node()).collect();
    for i in 0..init.len() {
        for j in (i+1)..init.len() {
            g.add_edge(init[i], init[j], None);
        }
    }

    // LCG random number generator
    let mut state = seed.wrapping_add(12345);
    let lcg_next = |s: &mut u64| -> f64 {
        *s = s.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        (*s >> 33) as f64 / (u32::MAX as f64)
    };

    // Repeated targets list for preferential attachment (each node appears degree times)
    let mut repeated_nodes: Vec<NodeId> = Vec::new();
    for &u in &init {
        let d = g.degree(u) as usize;
        for _ in 0..d { repeated_nodes.push(u); }
    }

    for _ in m..n {
        let new_node = g.add_node();
        let mut targets: std::collections::HashSet<NodeId> = std::collections::HashSet::new();

        // Choose m distinct targets by preferential attachment
        let attempts = m * 20 + 100; // bounded attempts
        for _ in 0..attempts {
            if targets.len() >= m { break; }
            if repeated_nodes.is_empty() { break; }
            let idx = (lcg_next(&mut state) * repeated_nodes.len() as f64) as usize;
            let idx = idx.min(repeated_nodes.len() - 1);
            let target = repeated_nodes[idx];
            if target != new_node {
                targets.insert(target);
            }
        }

        for &t in &targets {
            g.add_edge(new_node, t, None);
            // Add both endpoints to repeated_nodes
            repeated_nodes.push(new_node);
            repeated_nodes.push(t);
        }
    }

    g
}

// ─── G(n,m) random graph ──────────────────────────────────────────────────────

/// Create a random graph with exactly `m` edges (Erdős–Rényi G(n,m) model).
///
/// Edges are sampled uniformly at random without replacement from all possible edges.
/// If `m` exceeds the number of possible edges (n*(n-1)/2), all edges are added.
pub fn gnm_random_graph(n: usize, m: usize, seed: u64) -> Graph {
    let mut g = Graph::new(GraphConfig::simple());
    for _ in 0..n { g.add_node(); }
    if n < 2 || m == 0 { return g; }

    let max_edges = n * (n - 1) / 2;
    let m = m.min(max_edges);

    // Collect all possible edges
    let mut all_edges: Vec<(NodeId, NodeId)> = Vec::with_capacity(max_edges);
    for i in 0..n as NodeId {
        for j in (i+1)..n as NodeId {
            all_edges.push((i, j));
        }
    }

    // Fisher-Yates shuffle to pick m edges
    let mut state = seed.wrapping_add(99999);
    let lcg_next = |s: &mut u64| -> usize {
        *s = s.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        (*s >> 33) as usize
    };

    for i in 0..m {
        let j = i + lcg_next(&mut state) % (all_edges.len() - i);
        all_edges.swap(i, j);
    }

    for &(u, v) in &all_edges[..m] {
        g.add_edge(u, v, None);
    }
    g
}

// ─── Random bipartite graph ───────────────────────────────────────────────────

/// Create a random bipartite graph G(n1, n2, p).
///
/// Nodes 0..n1 form set A, nodes n1..n1+n2 form set B.
/// Each edge between A and B is included with probability p.
pub fn random_bipartite_graph(n1: usize, n2: usize, p: f64, seed: u64) -> Graph {
    let mut g = Graph::new(GraphConfig::simple());
    let a: Vec<NodeId> = (0..n1).map(|_| g.add_node()).collect();
    let b: Vec<NodeId> = (0..n2).map(|_| g.add_node()).collect();

    let mut state = seed.wrapping_add(77777);
    let lcg_next = |s: &mut u64| -> f64 {
        *s = s.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        (*s >> 33) as f64 / (u32::MAX as f64)
    };

    for &u in &a {
        for &v in &b {
            if lcg_next(&mut state) < p {
                g.add_edge(u, v, None);
            }
        }
    }
    g
}

// ─── Random regular graph ─────────────────────────────────────────────────────

/// Create a random d-regular graph on n nodes (each node has exactly degree d).
///
/// Requires `n * d` to be even. Uses the pairing model (configuration model):
/// create `n * d` half-edges, randomly pair them, remove self-loops and duplicates.
///
/// For large d this may require multiple retries. Returns a best-effort result.
pub fn random_regular_graph(n: usize, d: usize, seed: u64) -> Option<Graph> {
    if n == 0 { return Some(Graph::new(GraphConfig::simple())); }
    if (n * d) % 2 != 0 { return None; } // must be even
    if d >= n { return None; }

    let mut state = seed.wrapping_add(54321);
    let lcg_next = |s: &mut u64| -> usize {
        *s = s.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        (*s >> 33) as usize
    };

    // Try configuration model up to 100 times
    for _attempt in 0..100 {
        // Create stubs (half-edges): node u appears d times
        let mut stubs: Vec<NodeId> = (0..n as NodeId)
            .flat_map(|u| std::iter::repeat(u).take(d))
            .collect();

        // Shuffle stubs
        let m = stubs.len();
        for i in (1..m).rev() {
            let j = lcg_next(&mut state) % (i + 1);
            stubs.swap(i, j);
        }

        // Pair consecutive stubs
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..n { g.add_node(); }

        let mut edges: std::collections::HashSet<(NodeId, NodeId)> = std::collections::HashSet::new();
        let mut valid = true;
        for pair in stubs.chunks(2) {
            let (u, v) = (pair[0], pair[1]);
            if u == v { valid = false; break; }
            let key = (u.min(v), u.max(v));
            if !edges.insert(key) { valid = false; break; }
        }

        if valid {
            for (u, v) in edges {
                g.add_edge(u, v, None);
            }
            return Some(g);
        }
    }
    None
}

// ─── Hexagonal lattice graph ──────────────────────────────────────────────────

/// Create a hexagonal (honeycomb) lattice graph of `rows` × `cols` hexagons.
///
/// The hexagonal lattice is bipartite. Useful for crystal/material structure modeling.
///
/// For `rows` rows and `cols` columns of hexagons:
/// - Nodes per column pair: 2*(rows+1)
/// - Total nodes ≈ 2*(rows+1)*(cols+1)
pub fn hexagonal_lattice_graph(rows: usize, cols: usize) -> Graph {
    let mut g = Graph::new(GraphConfig::simple());
    if rows == 0 || cols == 0 { return g; }

    // Build a (rows+1) × (cols+1) grid of hexagon corners using two offset sublattices
    // Simpler: treat as a brick-wall lattice (each row offset by half)
    // Node numbering: row r, col c → node id r*(cols+1)+c
    // Vertical edges: (r,c) - (r+1,c)
    // Horizontal edges: alternating based on parity

    let nrows = rows + 1;
    let ncols = cols + 1;
    let mut grid: Vec<Vec<NodeId>> = Vec::new();
    for _ in 0..nrows {
        let row: Vec<NodeId> = (0..ncols).map(|_| g.add_node()).collect();
        grid.push(row);
    }

    // Vertical edges (backbone)
    for r in 0..rows {
        for c in 0..ncols {
            g.add_edge(grid[r][c], grid[r+1][c], None);
        }
    }

    // Horizontal edges: in each row, alternate connecting left-right
    for r in 0..nrows {
        // Even rows: connect even-index cols; odd rows: odd-index cols
        let start = if r % 2 == 0 { 0 } else { 1 };
        let mut c = start;
        while c + 1 < ncols {
            g.add_edge(grid[r][c], grid[r][c+1], None);
            c += 2;
        }
    }

    g
}

// ─── Heavy Hex lattice graph ──────────────────────────────────────────────────

/// Create a Heavy Hex lattice graph for `d` qubits (quantum computing topology).
///
/// The Heavy Hex lattice is used in IBM's quantum processors. It consists of
/// a hexagonal lattice with extra "heavy" edges added.
///
/// This creates a 2D patch of the Heavy Hex topology:
/// rows of qubits connected in a brick-wall pattern with extra bridge qubits.
///
/// # Arguments
/// - `d`: code distance / number of rows (must be ≥ 2)
pub fn heavy_hex_graph(d: usize) -> Graph {
    // Heavy Hex: linear chains connected by "flag" bridge qubits
    // For code distance d, we have d rows of d qubits each
    // plus bridge qubits between alternate rows
    let mut g = Graph::new(GraphConfig::simple());
    if d < 2 { g.add_node(); return g; }

    // Create d × d grid of data qubits
    let mut data: Vec<Vec<NodeId>> = Vec::new();
    for _ in 0..d {
        let row: Vec<NodeId> = (0..d).map(|_| g.add_node()).collect();
        data.push(row);
    }

    // Connect qubits within rows (horizontal edges)
    for r in 0..d {
        for c in 0..(d-1) {
            // Bridge qubit between data[r][c] and data[r][c+1]
            let bridge = g.add_node();
            g.add_edge(data[r][c], bridge, None);
            g.add_edge(bridge, data[r][c+1], None);
        }
    }

    // Connect alternating rows with bridge qubits (vertical edges)
    for r in 0..(d-1) {
        // Only connect some columns (heavy hex pattern)
        let col_parity = r % 2;
        let mut c = col_parity;
        while c < d {
            let bridge = g.add_node();
            g.add_edge(data[r][c], bridge, None);
            g.add_edge(bridge, data[r+1][c], None);
            c += 2;
        }
    }

    g
}

// ─── Heavy Square lattice ─────────────────────────────────────────────────────

/// Create a Heavy Square lattice graph (IBM quantum topology variant).
///
/// Similar to Heavy Hex but with denser connectivity using bridge qubits
/// on all edges of a 2D grid.
///
/// # Arguments
/// - `d`: grid dimension (d × d grid of data qubits)
pub fn heavy_square_graph(d: usize) -> Graph {
    let mut g = Graph::new(GraphConfig::simple());
    if d < 2 { g.add_node(); return g; }

    // d × d data qubits
    let mut data: Vec<Vec<NodeId>> = Vec::new();
    for _ in 0..d {
        let row: Vec<NodeId> = (0..d).map(|_| g.add_node()).collect();
        data.push(row);
    }

    // Bridge qubits on every horizontal edge
    for r in 0..d {
        for c in 0..(d-1) {
            let bridge = g.add_node();
            g.add_edge(data[r][c], bridge, None);
            g.add_edge(bridge, data[r][c+1], None);
        }
    }

    // Bridge qubits on every vertical edge
    for r in 0..(d-1) {
        for c in 0..d {
            let bridge = g.add_node();
            g.add_edge(data[r][c], bridge, None);
            g.add_edge(bridge, data[r+1][c], None);
        }
    }

    g
}

// ─── Binomial tree ────────────────────────────────────────────────────────────

/// Create a binomial tree of order `n`.
///
/// A binomial tree B_n is defined recursively:
/// - B_0 = single node
/// - B_n = two copies of B_{n-1} connected at their roots
///
/// B_n has 2^n nodes and 2^n - 1 edges.
pub fn binomial_tree_graph(n: usize) -> Graph {
    let mut g = Graph::new(GraphConfig::simple());
    if n == 0 { g.add_node(); return g; }

    // Build iteratively: start with B_0, repeatedly combine
    let root0 = g.add_node(); // B_0

    let mut roots: Vec<NodeId> = vec![root0];

    for _ in 0..n {
        // Duplicate the current tree: add new nodes mapping old → new
        let existing_nodes: Vec<NodeId> = g.nodes().collect();
        let mut map: std::collections::HashMap<NodeId, NodeId> = std::collections::HashMap::new();
        for &old in &existing_nodes {
            let new_id = g.add_node();
            map.insert(old, new_id);
        }

        // Copy all existing edges to the new copy
        let existing_edges: Vec<(NodeId, NodeId)> = existing_nodes.iter()
            .flat_map(|&u| g.out_neighbors(u).iter().map(move |e| (u, e.target)))
            .filter(|&(u, v)| u < v)
            .collect();
        for (u, v) in existing_edges {
            g.add_edge(map[&u], map[&v], None);
        }

        // Connect old root to new root (new copy's root)
        let old_root = *roots.last().unwrap();
        let new_root = map[&old_root];
        g.add_edge(old_root, new_root, None);
        roots.push(new_root);
    }

    g
}

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

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

    // ── complete_graph ────────────────────────────────────────────────────────

    #[test]
    fn test_complete_graph_node_count() {
        for n in [3, 5, 7] {
            let g = complete_graph(n, false);
            assert_eq!(g.node_count(), n, "K{} should have {} nodes", n, n);
            let expected_edges = n * (n - 1) / 2;
            assert_eq!(g.edge_count(), expected_edges, "K{} should have {} edges", n, expected_edges);
        }
    }

    #[test]
    fn test_complete_graph_directed() {
        let g = complete_graph(4, true);
        assert_eq!(g.node_count(), 4);
        assert_eq!(g.edge_count(), 12); // 4*3
    }

    // ── cycle_graph ───────────────────────────────────────────────────────────

    #[test]
    fn test_cycle_graph() {
        let g = cycle_graph(5, false);
        assert_eq!(g.node_count(), 5);
        assert_eq!(g.edge_count(), 5);
        // Every node has degree 2
        for n in g.nodes() {
            assert_eq!(g.degree(n), 2, "cycle node {} should have degree 2", n);
        }
    }

    #[test]
    fn test_cycle_graph_directed() {
        let g = cycle_graph(4, true);
        assert_eq!(g.node_count(), 4);
        assert_eq!(g.edge_count(), 4);
    }

    // ── path_graph ────────────────────────────────────────────────────────────

    #[test]
    fn test_path_graph() {
        let g = path_graph(5, false);
        assert_eq!(g.node_count(), 5);
        assert_eq!(g.edge_count(), 4);
    }

    #[test]
    fn test_path_graph_single() {
        let g = path_graph(1, false);
        assert_eq!(g.node_count(), 1);
        assert_eq!(g.edge_count(), 0);
    }

    // ── star_graph ────────────────────────────────────────────────────────────

    #[test]
    fn test_star_graph() {
        let g = star_graph(4, false);
        assert_eq!(g.node_count(), 5); // center + 4 leaves
        assert_eq!(g.edge_count(), 4);
        // Center (node 0) has degree 4
        assert_eq!(g.degree(0), 4);
    }

    // ── grid_graph ────────────────────────────────────────────────────────────

    #[test]
    fn test_grid_graph_3x3() {
        let g = grid_graph(3, 3, false);
        assert_eq!(g.node_count(), 9);
        // Edges: rows*(cols-1) + (rows-1)*cols = 3*2 + 2*3 = 12
        assert_eq!(g.edge_count(), 12);
    }

    #[test]
    fn test_grid_graph_1xn() {
        // 1×4 grid = path graph
        let g = grid_graph(1, 4, false);
        assert_eq!(g.node_count(), 4);
        assert_eq!(g.edge_count(), 3);
    }

    // ── petersen_graph ────────────────────────────────────────────────────────

    #[test]
    fn test_petersen_graph() {
        let g = petersen_graph();
        assert_eq!(g.node_count(), 10);
        assert_eq!(g.edge_count(), 15);
        // Every node has degree 3
        for n in g.nodes() {
            assert_eq!(g.degree(n), 3, "petersen node {} should have degree 3", n);
        }
    }

    // ── full_rary_tree ────────────────────────────────────────────────────────

    #[test]
    fn test_full_rary_tree_binary() {
        // Binary tree depth 2: 1 + 2 + 4 = 7 nodes
        let g = full_rary_tree(2, 2);
        assert_eq!(g.node_count(), 7);
        assert_eq!(g.edge_count(), 6);
    }

    #[test]
    fn test_full_rary_tree_ternary_depth1() {
        // Ternary tree depth 1: 1 root + 3 children = 4 nodes
        let g = full_rary_tree(3, 1);
        assert_eq!(g.node_count(), 4);
        assert_eq!(g.edge_count(), 3);
    }

    // ── random_graph ──────────────────────────────────────────────────────────

    #[test]
    fn test_random_graph_node_count() {
        let g = random_graph(10, 0.5, 42, false);
        assert_eq!(g.node_count(), 10);
    }

    #[test]
    fn test_random_graph_deterministic() {
        // Same seed → same graph
        let g1 = random_graph(10, 0.3, 12345, false);
        let g2 = random_graph(10, 0.3, 12345, false);
        assert_eq!(g1.edge_count(), g2.edge_count());
    }

    #[test]
    fn test_random_graph_p0() {
        // p=0 → no edges
        let g = random_graph(5, 0.0, 1, false);
        assert_eq!(g.edge_count(), 0);
    }

    #[test]
    fn test_random_graph_p1() {
        // p=1 → complete graph
        let g = random_graph(5, 1.0, 1, false);
        assert_eq!(g.edge_count(), 10); // K5
    }

    // ── karate_club ───────────────────────────────────────────────────────────

    #[test]
    fn test_karate_club() {
        let g = karate_club();
        assert_eq!(g.node_count(), 34);
        assert_eq!(g.edge_count(), 78);
    }

    // ── barbell_graph ─────────────────────────────────────────────────────────

    #[test]
    fn test_barbell_graph() {
        // barbell(3, 2): K3 + path(2) + K3 = 3 + 2 + 3 = 8 nodes
        let g = barbell_graph(3, 2);
        assert!(g.node_count() >= 7);
    }

    // ── lollipop_graph ────────────────────────────────────────────────────────

    #[test]
    fn test_lollipop_graph() {
        // K3 (3 edges) + path of 3 edges: 6 nodes total, 6 edges
        let g = lollipop_graph(3, 3);
        assert_eq!(g.node_count(), 6);
        assert_eq!(g.edge_count(), 6, "lollipop(3,3) has 6 edges");
    }

    #[test]
    fn test_lollipop_empty() {
        let g = lollipop_graph(0, 5);
        assert_eq!(g.node_count(), 0);
    }

    // ── barabasi_albert_graph ─────────────────────────────────────────────────

    #[test]
    fn test_barabasi_albert() {
        let g = barabasi_albert_graph(20, 3, 42);
        assert_eq!(g.node_count(), 20);
        // Each new node (20-3=17 of them) connects to at most m=3 existing nodes
        assert!(g.edge_count() > 0);
    }

    #[test]
    fn test_barabasi_albert_small() {
        let g = barabasi_albert_graph(5, 2, 1);
        assert_eq!(g.node_count(), 5);
    }

    // ── gnm_random_graph ──────────────────────────────────────────────────────

    #[test]
    fn test_gnm_random_graph() {
        let g = gnm_random_graph(10, 15, 42);
        assert_eq!(g.node_count(), 10);
        assert_eq!(g.edge_count(), 15, "gnm should have exactly 15 edges");
    }

    #[test]
    fn test_gnm_random_graph_max_edges() {
        // Request more edges than possible: K4 has 6 edges
        let g = gnm_random_graph(4, 100, 42);
        assert_eq!(g.node_count(), 4);
        assert_eq!(g.edge_count(), 6, "capped at max possible edges");
    }

    // ── random_bipartite_graph ────────────────────────────────────────────────

    #[test]
    fn test_random_bipartite_graph() {
        let g = random_bipartite_graph(5, 5, 0.5, 42);
        assert_eq!(g.node_count(), 10);
        // With p=0.5, expect ~12-13 edges out of 25 possible
        assert!(g.edge_count() > 0);
    }

    #[test]
    fn test_random_bipartite_p0() {
        // p=0: no edges
        let g = random_bipartite_graph(4, 4, 0.0, 42);
        assert_eq!(g.node_count(), 8);
        assert_eq!(g.edge_count(), 0);
    }

    #[test]
    fn test_random_bipartite_p1() {
        // p=1: all edges → K_{3,3}
        let g = random_bipartite_graph(3, 3, 1.0, 42);
        assert_eq!(g.node_count(), 6);
        assert_eq!(g.edge_count(), 9, "K_3,3 has 9 edges");
    }

    // ── random_regular_graph ──────────────────────────────────────────────────

    #[test]
    fn test_random_regular_graph_3regular() {
        // 3-regular on 4 nodes: K4 (every degree = 3)
        let g = random_regular_graph(4, 3, 42).expect("4-node 3-regular exists");
        assert_eq!(g.node_count(), 4);
        // Every node has degree 3
        for u in g.nodes() {
            assert_eq!(g.degree(u), 3, "node {} should have degree 3", u);
        }
    }

    #[test]
    fn test_random_regular_graph_odd_fails() {
        // n*d must be even; n=3, d=1 → 3 is odd → None
        let result = random_regular_graph(3, 1, 42);
        assert!(result.is_none());
    }

    #[test]
    fn test_random_regular_graph_2regular() {
        // 2-regular on 6 nodes: two triangles or hexagon
        let g = random_regular_graph(6, 2, 99).expect("6-node 2-regular exists");
        assert_eq!(g.node_count(), 6);
        for u in g.nodes() {
            assert_eq!(g.degree(u), 2);
        }
    }

    // ── hexagonal_lattice_graph ───────────────────────────────────────────────

    #[test]
    fn test_hexagonal_lattice_graph() {
        let g = hexagonal_lattice_graph(2, 3);
        // 3×4 grid: 12 nodes + vertical edges + some horizontal
        assert!(g.node_count() > 0);
        assert!(g.edge_count() > 0);
    }

    #[test]
    fn test_hexagonal_lattice_nonempty() {
        let g = hexagonal_lattice_graph(1, 1);
        assert!(g.node_count() >= 4, "smallest hexagonal patch has ≥4 nodes");
    }

    #[test]
    fn test_hexagonal_lattice_empty() {
        let g = hexagonal_lattice_graph(0, 3);
        assert_eq!(g.node_count(), 0);
    }

    // ── heavy_hex_graph ───────────────────────────────────────────────────────

    #[test]
    fn test_heavy_hex_graph() {
        let g = heavy_hex_graph(3);
        // 3×3 data qubits (9) + bridges
        assert!(g.node_count() >= 9);
        assert!(g.edge_count() > 0);
    }

    #[test]
    fn test_heavy_hex_graph_small() {
        let g = heavy_hex_graph(2);
        // 2×2 data qubits (4) + bridge qubits
        assert!(g.node_count() > 4);
    }

    // ── heavy_square_graph ────────────────────────────────────────────────────

    #[test]
    fn test_heavy_square_graph() {
        let g = heavy_square_graph(3);
        // 3×3 = 9 data qubits + horizontal bridges (3 rows × 2 = 6) + vertical (2 rows × 3 = 6)
        assert_eq!(g.node_count(), 9 + 6 + 6, "heavy_square(3) node count");
    }

    #[test]
    fn test_heavy_square_graph_small() {
        let g = heavy_square_graph(2);
        // 2×2 = 4 data + 2 horizontal + 2 vertical = 8 total
        assert_eq!(g.node_count(), 8, "heavy_square(2) has 8 nodes");
    }

    // ── binomial_tree_graph ───────────────────────────────────────────────────

    #[test]
    fn test_binomial_tree_b0() {
        let g = binomial_tree_graph(0);
        assert_eq!(g.node_count(), 1);
        assert_eq!(g.edge_count(), 0);
    }

    #[test]
    fn test_binomial_tree_b1() {
        let g = binomial_tree_graph(1);
        assert_eq!(g.node_count(), 2);
        assert_eq!(g.edge_count(), 1);
    }

    #[test]
    fn test_binomial_tree_b2() {
        let g = binomial_tree_graph(2);
        assert_eq!(g.node_count(), 4, "B2 has 2^2=4 nodes");
        assert_eq!(g.edge_count(), 3, "B2 has 2^2-1=3 edges");
    }

    #[test]
    fn test_binomial_tree_b3() {
        let g = binomial_tree_graph(3);
        assert_eq!(g.node_count(), 8, "B3 has 2^3=8 nodes");
        assert_eq!(g.edge_count(), 7, "B3 has 2^3-1=7 edges");
    }
}

// ─── Generalized Petersen Graph ───────────────────────────────────────────────

/// Generate the generalized Petersen graph GP(n, k).
///
/// GP(n, k) has 2n nodes split into an outer n-cycle and an inner "star polygon"
/// connected by n spokes.  The classic Petersen graph is GP(5, 2).
///
/// Nodes 0..n are the outer ring; nodes n..2n are the inner ring.
///
/// Reference: Watkins, J.J. (1990) "Generalized Petersen graphs."
pub fn generalized_petersen_graph(n: usize, k: usize) -> Graph {
    assert!(n >= 3, "n must be ≥ 3");
    assert!(k >= 1 && k < n, "k must be in 1..n");

    let mut g = Graph::new(GraphConfig::simple());
    for _ in 0..(2 * n) { g.add_node(); }

    // Outer cycle: 0, 1, ..., n-1, 0
    for i in 0..n as NodeId {
        g.add_edge(i, (i + 1) % n as NodeId, None);
    }

    // Spokes: i ↔ i+n
    for i in 0..n as NodeId {
        g.add_edge(i, i + n as NodeId, None);
    }

    // Inner star polygon: i+n ↔ (i+k)%n + n
    // Use canonical min<max to avoid duplicate edges in undirected graph
    let mut inner_added = std::collections::HashSet::new();
    for i in 0..n as NodeId {
        let j = (i + k as NodeId) % n as NodeId;
        let (a, b) = if i < j { (i, j) } else { (j, i) };
        if inner_added.insert((a, b)) {
            g.add_edge(a + n as NodeId, b + n as NodeId, None);
        }
    }

    g
}

// ─── Watts–Strogatz small-world graph ────────────────────────────────────────

/// Generate a Watts–Strogatz small-world graph.
///
/// Starting from a ring lattice where each of the `n` nodes is connected to
/// its `k` nearest neighbours (k/2 on each side), each edge is rewired with
/// probability `p` to a random node (excluding self-loops and parallel edges).
///
/// Classic small-world networks arise for `p` in the range (0.001, 0.1).
///
/// # Parameters
/// - `n` — number of nodes
/// - `k` — each node is joined to `k` neighbours (must be even, ≥ 2, < n)
/// - `p` — rewiring probability ∈ [0, 1]
/// - `seed` — optional RNG seed for reproducibility
///
/// # Panics
/// Does not panic; silently clamps parameters to valid ranges.
pub fn watts_strogatz_graph(n: usize, k: usize, p: f64, seed: Option<u64>) -> Graph {
    let cfg = GraphConfig::simple();
    let mut g = Graph::new(cfg);
    if n == 0 { return g; }
    let ids: Vec<NodeId> = (0..n).map(|_| g.add_node()).collect();
    let k = k.min(n - 1);   // k < n
    let k_half = k / 2;

    // Build ring lattice
    for i in 0..n {
        for j in 1..=k_half {
            let u = ids[i];
            let v = ids[(i + j) % n];
            if u != v && !g.has_edge(u, v) {
                g.add_edge(u, v, None);
            }
        }
    }

    let p = p.clamp(0.0, 1.0);
    if p == 0.0 { return g; }

    // LCG RNG
    let mut state = seed.unwrap_or(42u64);
    let mut rng = || -> f64 {
        state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        (state >> 33) as f64 / (u64::MAX >> 33) as f64
    };

    // Rewire edges
    for i in 0..n {
        for j in 1..=k_half {
            if rng() < p {
                let u = ids[i];
                let v = ids[(i + j) % n];
                // Remove old edge
                g.remove_edge(u, v);
                // Pick a random target (not u, no self-loop, no parallel)
                let mut attempts = 0usize;
                loop {
                    attempts += 1;
                    if attempts > n * 4 { break; } // safety
                    let w = ids[(rng() * n as f64) as usize % n];
                    if w != u && !g.has_edge(u, w) {
                        g.add_edge(u, w, None);
                        break;
                    }
                }
            }
        }
    }
    g
}

// ─── Configuration model ─────────────────────────────────────────────────────

/// Generate a random graph with a given degree sequence (configuration model).
///
/// Matches a random undirected graph to the supplied `degree_sequence`.
/// Returns `None` if the sum of degrees is odd (no such graph can exist).
///
/// The result may contain self-loops and parallel edges (as is standard for
/// the configuration model). Use `has_parallel_edges` to check afterwards
/// if you need a simple graph.
///
/// # Parameters
/// - `degree_sequence` — desired degree for each node
/// - `seed` — optional RNG seed
pub fn configuration_model(degree_sequence: &[usize], seed: Option<u64>) -> Option<Graph> {
    let total: usize = degree_sequence.iter().sum();
    if total % 2 != 0 { return None; }

    let cfg = GraphConfig::simple();
    let mut g = Graph::new(cfg);
    if degree_sequence.is_empty() { return Some(g); }

    let ids: Vec<NodeId> = (0..degree_sequence.len()).map(|_| g.add_node()).collect();

    // Build stub list: for each node, add `degree` entries
    let mut stubs: Vec<NodeId> = Vec::with_capacity(total);
    for (i, &deg) in degree_sequence.iter().enumerate() {
        for _ in 0..deg { stubs.push(ids[i]); }
    }

    // Fisher-Yates shuffle
    let mut state = seed.unwrap_or(42u64);
    let mut rng_usize = |bound: usize| -> usize {
        state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        ((state >> 33) as usize) % bound
    };

    let m = stubs.len();
    for i in (1..m).rev() {
        let j = rng_usize(i + 1);
        stubs.swap(i, j);
    }

    // Pair up stubs
    let mut i = 0;
    while i + 1 < stubs.len() {
        let u = stubs[i];
        let v = stubs[i + 1];
        g.add_edge(u, v, None);
        i += 2;
    }

    Some(g)
}

// ─── Expected degree sequence (Chung-Lu) ─────────────────────────────────────

/// Generate a random graph where node `i` has expected degree `w[i]`
/// (Chung–Lu model).
///
/// Each pair (i, j) is connected with probability `p_ij = w_i * w_j / S`
/// where `S = sum(w)`.  If `p_ij > 1` the edge is always present.
///
/// # Parameters
/// - `weights` — expected degree for each node (non-negative)
/// - `seed` — optional RNG seed
pub fn expected_degree_graph(weights: &[f64], seed: Option<u64>) -> Graph {
    let cfg = GraphConfig::simple();
    let mut g = Graph::new(cfg);
    if weights.is_empty() { return g; }

    let ids: Vec<NodeId> = (0..weights.len()).map(|_| g.add_node()).collect();
    let s: f64 = weights.iter().sum();
    if s <= 0.0 { return g; }

    let mut state = seed.unwrap_or(42u64);
    let mut rng = || -> f64 {
        state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        (state >> 33) as f64 / (u64::MAX >> 33) as f64
    };

    for i in 0..weights.len() {
        for j in (i + 1)..weights.len() {
            let p = (weights[i] * weights[j] / s).min(1.0);
            if rng() < p {
                g.add_edge(ids[i], ids[j], None);
            }
        }
    }
    g
}

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

    // ── generalized_petersen_graph ────────────────────────────────────────────

    #[test]
    fn test_petersen_classic() {
        // GP(5,2) = classic Petersen: 10 nodes, 15 edges
        let g = generalized_petersen_graph(5, 2);
        assert_eq!(g.node_count(), 10);
        assert_eq!(g.edge_count(), 15);
    }

    #[test]
    fn test_petersen_gp_4_1() {
        // GP(4,1) = cube graph: 8 nodes, 12 edges
        let g = generalized_petersen_graph(4, 1);
        assert_eq!(g.node_count(), 8);
        assert_eq!(g.edge_count(), 12);
    }

    #[test]
    fn test_petersen_gp_6_2() {
        // GP(6,2): 12 nodes, 18 edges
        let g = generalized_petersen_graph(6, 2);
        assert_eq!(g.node_count(), 12);
        assert_eq!(g.edge_count(), 18);
    }

    // ── watts_strogatz_graph ──────────────────────────────────────────────────

    #[test]
    fn test_ws_ring_lattice() {
        // p=0 → pure ring lattice: n nodes, n*k/2 edges
        let g = watts_strogatz_graph(10, 4, 0.0, None);
        assert_eq!(g.node_count(), 10);
        assert_eq!(g.edge_count(), 20); // 10 * 4/2
    }

    #[test]
    fn test_ws_full_rewire() {
        // p=1 → fully rewired random graph: same node count
        let g = watts_strogatz_graph(10, 4, 1.0, Some(1));
        assert_eq!(g.node_count(), 10);
        // Edge count can vary, just ensure it's non-zero
        assert!(g.edge_count() > 0);
    }

    #[test]
    fn test_ws_reproducible() {
        let g1 = watts_strogatz_graph(20, 4, 0.3, Some(99));
        let g2 = watts_strogatz_graph(20, 4, 0.3, Some(99));
        assert_eq!(g1.edge_count(), g2.edge_count());
    }

    // ── configuration_model ───────────────────────────────────────────────────

    #[test]
    fn test_config_model_basic() {
        // Triangle: each node degree 2
        let g = configuration_model(&[2, 2, 2], Some(0)).unwrap();
        assert_eq!(g.node_count(), 3);
        // 3 nodes with degree sequence [2,2,2] → 3 edges
        assert_eq!(g.edge_count(), 3);
    }

    #[test]
    fn test_config_model_odd_sum() {
        // Sum of degrees 1+1+1 = 3 is odd → None
        assert!(configuration_model(&[1, 1, 1], None).is_none());
    }

    #[test]
    fn test_config_model_empty() {
        let g = configuration_model(&[], None).unwrap();
        assert_eq!(g.node_count(), 0);
    }

    // ── expected_degree_graph ─────────────────────────────────────────────────

    #[test]
    fn test_expected_degree_empty() {
        let g = expected_degree_graph(&[], None);
        assert_eq!(g.node_count(), 0);
    }

    #[test]
    fn test_expected_degree_all_zero() {
        let g = expected_degree_graph(&[0.0, 0.0, 0.0], None);
        assert_eq!(g.node_count(), 3);
        assert_eq!(g.edge_count(), 0);
    }

    #[test]
    fn test_expected_degree_complete_weights() {
        // All weights equal to n-1 → should produce something near complete
        let g = expected_degree_graph(&[3.0, 3.0, 3.0, 3.0], Some(42));
        assert_eq!(g.node_count(), 4);
        // In expectation, edge prob = 3*3/12 = 0.75 per pair; 6 pairs
        // At least some edges should be present
        assert!(g.edge_count() > 0);
    }
}
