// 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

//! Line graph construction.
//!
//! The line graph L(G) of a graph G has one node per edge of G.
//! Two nodes in L(G) are adjacent if the corresponding edges in G share an endpoint.
//!
//! Reference: Whitney, H. (1932). "Congruent Graphs and the Connectivity of Graphs."
//! American Journal of Mathematics, 54(1), 150-168.

use std::collections::HashMap;

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

/// Result of line graph construction.
pub struct LineGraphResult {
    /// The line graph L(G).
    pub graph: Graph,
    /// Map from node in L(G) → (u, v) edge in G.
    pub node_to_edge: Vec<(NodeId, NodeId)>,
    /// Map from (u, v) edge in G → node id in L(G).
    pub edge_to_node: HashMap<(NodeId, NodeId), NodeId>,
}

/// Construct the line graph L(G) of the given graph.
///
/// Each edge (u, v) in G becomes a node in L(G).
/// Two nodes in L(G) are connected if the corresponding edges share an endpoint.
///
/// For an undirected graph with m edges, L(G) has m nodes.
///
/// Runtime: O(m * Δ) where Δ = max degree
pub fn line_graph(graph: &Graph, directed: bool) -> LineGraphResult {
    let cfg = if directed { GraphConfig::directed() } else { GraphConfig::simple() };
    let mut lg = Graph::new(cfg);

    let mut node_to_edge: Vec<(NodeId, NodeId)> = Vec::new();
    let mut edge_to_node: HashMap<(NodeId, NodeId), NodeId> = HashMap::new();

    // Create one node in L(G) per edge in G
    for (u, v, _, _) in graph.edges() {
        let key = if directed { (u, v) } else { (u.min(v), u.max(v)) };
        if edge_to_node.contains_key(&key) { continue; }
        let lg_node = lg.add_node();
        node_to_edge.push(key);
        edge_to_node.insert(key, lg_node);
    }

    // Add adjacency in L(G): two edge-nodes are adjacent if edges share an endpoint
    // For each node w in G, all edges incident to w form a clique in L(G)
    for w in graph.nodes() {
        let incident: Vec<(NodeId, NodeId)> = graph.out_neighbors(w)
            .iter().map(|e| {
                let (u, v) = (w, e.target);
                if directed { (u, v) } else { (u.min(v), u.max(v)) }
            })
            .collect();

        for i in 0..incident.len() {
            for j in (i+1)..incident.len() {
                let a = edge_to_node[&incident[i]];
                let b = edge_to_node[&incident[j]];
                if a != b {
                    lg.add_edge(a, b, None);
                }
            }
        }
    }

    LineGraphResult { graph: lg, node_to_edge, edge_to_node }
}

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

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

    #[test]
    fn test_line_graph_path() {
        // Path P4: 0-1-2-3 has 3 edges → L(P4) has 3 nodes and 2 edges
        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 result = line_graph(&g, false);
        assert_eq!(result.graph.node_count(), 3, "L(P4) should have 3 nodes");
        assert_eq!(result.graph.edge_count(), 2, "L(P4) should have 2 edges");
    }

    #[test]
    fn test_line_graph_triangle() {
        // Triangle K3 has 3 edges → L(K3) = K3 (3 nodes, 3 edges)
        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(2, 0, None);

        let result = line_graph(&g, false);
        assert_eq!(result.graph.node_count(), 3, "L(K3) should have 3 nodes");
        assert_eq!(result.graph.edge_count(), 3, "L(K3) should have 3 edges");
    }

    #[test]
    fn test_line_graph_star() {
        // Star S4 (center + 4 leaves) has 4 edges → L(S4) = K4 (4 nodes, 6 edges)
        let mut g = Graph::new(GraphConfig::simple());
        let center = g.add_node();
        for _ in 0..4 {
            let leaf = g.add_node();
            g.add_edge(center, leaf, None);
        }

        let result = line_graph(&g, false);
        assert_eq!(result.graph.node_count(), 4);
        assert_eq!(result.graph.edge_count(), 6); // K4 has 6 edges
    }

    #[test]
    fn test_line_graph_edge_to_node_mapping() {
        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);

        let result = line_graph(&g, false);
        assert!(result.edge_to_node.contains_key(&(0, 1)));
        assert!(result.edge_to_node.contains_key(&(1, 2)));
        assert_eq!(result.node_to_edge.len(), 2);
    }

    #[test]
    fn test_line_graph_single_edge() {
        // Single edge → L(G) has 1 node, 0 edges
        let mut g = Graph::new(GraphConfig::simple());
        g.add_node();
        g.add_node();
        g.add_edge(0, 1, None);

        let result = line_graph(&g, false);
        assert_eq!(result.graph.node_count(), 1);
        assert_eq!(result.graph.edge_count(), 0);
    }
}
