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

//! Clique enumeration and maximum clique algorithms.
//!
//! - `find_cliques` — enumerate all maximal cliques (Bron–Kerbosch)
//! - `max_clique`   — find one maximum clique (largest by node count)
//! - `clique_number` — return the size of the maximum clique
//! - `graph_clique_number` — alias for `clique_number`
//! - `node_clique_number` — largest clique containing a given node
//! - `cliques_containing_node` — all cliques that include a given node

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

// ─── Bron–Kerbosch with pivoting ─────────────────────────────────────────────

/// Enumerate all maximal cliques in an **undirected** graph.
///
/// Uses the Bron–Kerbosch algorithm with pivot selection (Tomita et al.)
/// for improved performance on sparse graphs.
///
/// **Note**: For directed graphs the underlying undirected adjacency is used
/// (an edge u↔v exists if u→v or v→u).
///
/// Returns a `Vec` of cliques; each clique is a sorted `Vec<NodeId>`.
///
/// Runtime: O(3^(n/3)) worst case (exponential, unavoidable for NP-hard problem).
pub fn find_cliques(graph: &Graph) -> Vec<Vec<NodeId>> {
    // Build undirected adjacency as HashSet for O(1) neighbour checks
    let nodes: Vec<NodeId> = graph.nodes().collect();
    let mut adj: HashMap<NodeId, HashSet<NodeId>> = HashMap::new();
    for &u in &nodes {
        adj.entry(u).or_default();
        for nb in graph.out_neighbors(u) {
            adj.entry(u).or_default().insert(nb.target);
            adj.entry(nb.target).or_default().insert(u);
        }
    }

    if nodes.is_empty() { return vec![]; }

    let mut cliques: Vec<Vec<NodeId>> = Vec::new();
    let r: HashSet<NodeId> = HashSet::new();
    let p: HashSet<NodeId> = nodes.iter().copied().collect();
    let x: HashSet<NodeId> = HashSet::new();

    bron_kerbosch(&adj, r, p, x, &mut cliques);

    // Sort for determinism
    cliques.iter_mut().for_each(|c| c.sort());
    cliques.sort();
    cliques
}

fn bron_kerbosch(
    adj: &HashMap<NodeId, HashSet<NodeId>>,
    r: HashSet<NodeId>,
    mut p: HashSet<NodeId>,
    mut x: HashSet<NodeId>,
    out: &mut Vec<Vec<NodeId>>,
) {
    if p.is_empty() && x.is_empty() {
        out.push(r.iter().copied().collect());
        return;
    }

    // Choose pivot u ∈ P ∪ X to maximise |N(u) ∩ P|
    let pivot = p.iter().chain(x.iter())
        .max_by_key(|&&v| adj.get(&v).map_or(0, |nb| nb.intersection(&p).count()))
        .copied();

    let pivot_nbrs: HashSet<NodeId> = if let Some(u) = pivot {
        adj.get(&u).cloned().unwrap_or_default()
    } else {
        HashSet::new()
    };

    let candidates: Vec<NodeId> = p.difference(&pivot_nbrs).copied().collect();

    let empty_set: HashSet<NodeId> = HashSet::new();

    for v in candidates {
        let nbrs: &HashSet<NodeId> = adj.get(&v).unwrap_or(&empty_set);

        let new_r: HashSet<NodeId> = r.iter().copied().chain(std::iter::once(v)).collect();
        let new_p: HashSet<NodeId> = p.intersection(nbrs).copied().collect();
        let new_x: HashSet<NodeId> = x.intersection(nbrs).copied().collect();

        bron_kerbosch(adj, new_r, new_p, new_x, out);

        p.remove(&v);
        x.insert(v);
    }
}

// ─── Maximum clique ───────────────────────────────────────────────────────────

/// Find a maximum clique (largest by node count).
///
/// Returns the node set of one maximum clique, or an empty vec for empty graphs.
/// If multiple cliques tie for largest, an arbitrary one is returned.
pub fn max_clique(graph: &Graph) -> Vec<NodeId> {
    find_cliques(graph)
        .into_iter()
        .max_by_key(|c| c.len())
        .unwrap_or_default()
}

/// Return the clique number (size of the largest clique).
pub fn clique_number(graph: &Graph) -> usize {
    max_clique(graph).len()
}

/// Alias for `clique_number`.
pub fn graph_clique_number(graph: &Graph) -> usize {
    clique_number(graph)
}

// ─── Node-specific clique queries ─────────────────────────────────────────────

/// Return all maximal cliques that contain `node`.
pub fn cliques_containing_node(graph: &Graph, node: NodeId) -> Vec<Vec<NodeId>> {
    find_cliques(graph)
        .into_iter()
        .filter(|c| c.contains(&node))
        .collect()
}

/// Return the size of the largest clique containing `node`.
pub fn node_clique_number(graph: &Graph, node: NodeId) -> usize {
    cliques_containing_node(graph, node)
        .iter()
        .map(|c| c.len())
        .max()
        .unwrap_or(0)
}

// ─── 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 petersen_graph() -> Graph {
        // Petersen graph: known clique number = 2 (no triangles)
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..10 { g.add_node(); }
        // Outer 5-cycle
        for i in 0u64..5 { g.add_edge(i, (i + 1) % 5, None); }
        // Spokes
        for i in 0u64..5 { g.add_edge(i, i + 5, None); }
        // Inner star polygon (step 2)
        for i in 0u64..5 { g.add_edge(i + 5, (i + 2) % 5 + 5, None); }
        g
    }

    // ── find_cliques ──────────────────────────────────────────────────────────

    #[test]
    fn test_find_cliques_triangle() {
        let g = triangle();
        let cliques = find_cliques(&g);
        // Only one maximal clique: the whole triangle
        assert_eq!(cliques.len(), 1);
        assert_eq!(cliques[0].len(), 3);
    }

    #[test]
    fn test_find_cliques_path() {
        // Path 0-1-2: maximal cliques are edges {0,1} and {1,2}
        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 cliques = find_cliques(&g);
        assert_eq!(cliques.len(), 2);
        assert!(cliques.iter().all(|c| c.len() == 2));
    }

    #[test]
    fn test_find_cliques_k4() {
        // K4: exactly one maximal clique of size 4
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..4 { g.add_node(); }
        for i in 0u64..4 {
            for j in (i+1)..4 {
                g.add_edge(i, j, None);
            }
        }
        let cliques = find_cliques(&g);
        assert_eq!(cliques.len(), 1);
        assert_eq!(cliques[0].len(), 4);
    }

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

    #[test]
    fn test_find_cliques_no_edges() {
        // 3 isolated nodes: each is its own maximal clique
        let mut g = Graph::new(GraphConfig::simple());
        for _ in 0..3 { g.add_node(); }
        let cliques = find_cliques(&g);
        assert_eq!(cliques.len(), 3);
        assert!(cliques.iter().all(|c| c.len() == 1));
    }

    // ── max_clique / clique_number ────────────────────────────────────────────

    #[test]
    fn test_max_clique_triangle() {
        let g = triangle();
        assert_eq!(max_clique(&g).len(), 3);
    }

    #[test]
    fn test_clique_number_petersen() {
        // Petersen graph is triangle-free: clique number = 2
        let g = petersen_graph();
        assert_eq!(clique_number(&g), 2);
    }

    #[test]
    fn test_clique_number_empty() {
        let g = Graph::new(GraphConfig::simple());
        assert_eq!(clique_number(&g), 0);
    }

    // ── node_clique_number / cliques_containing_node ──────────────────────────

    #[test]
    fn test_node_clique_number_triangle() {
        let g = triangle();
        // Every node is in the triangle clique of size 3
        for n in 0u64..3 {
            assert_eq!(node_clique_number(&g, n), 3);
        }
    }

    #[test]
    fn test_cliques_containing_node_path() {
        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);
        // Node 1 is in both edge cliques
        let cc = cliques_containing_node(&g, 1);
        assert_eq!(cc.len(), 2);
        // Nodes 0 and 2 are in exactly one clique each
        assert_eq!(cliques_containing_node(&g, 0).len(), 1);
        assert_eq!(cliques_containing_node(&g, 2).len(), 1);
    }
}
