//! Component algorithms ported from NetworKit.
//!
//! This module implements NetworKit's component detection:
//! - Connected Components (undirected graphs)
//! - Strongly Connected Components (directed graphs - Tarjan's algorithm)
//! - Weakly Connected Components (directed graphs)
//!
//! Reference: NetworKit components module

use std::collections::VecDeque;

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

/// Component membership result.
#[derive(Debug, Clone)]
pub struct ComponentResult {
    /// Component ID for each node.
    pub components: Vec<Option<usize>>,
    /// Number of components.
    pub num_components: usize,
    /// Sizes of each component.
    pub component_sizes: Vec<usize>,
}

impl ComponentResult {
    /// Get the component ID of a node.
    pub fn component(&self, node: NodeId) -> Option<usize> {
        self.components.get(node as usize).and_then(|c| *c)
    }

    /// Check if two nodes are in the same component.
    pub fn same_component(&self, a: NodeId, b: NodeId) -> bool {
        match (self.component(a), self.component(b)) {
            (Some(ca), Some(cb)) => ca == cb,
            _ => false,
        }
    }

    /// Get all nodes in a specific component.
    pub fn nodes_in_component(&self, component_id: usize) -> Vec<NodeId> {
        self.components
            .iter()
            .enumerate()
            .filter_map(|(id, &comp)| {
                if comp == Some(component_id) {
                    Some(id as NodeId)
                } else {
                    None
                }
            })
            .collect()
    }

    /// Get the largest component.
    pub fn largest_component(&self) -> Option<usize> {
        self.component_sizes
            .iter()
            .enumerate()
            .max_by_key(|(_, &size)| size)
            .map(|(id, _)| id)
    }
}

/// Connected Components for undirected graphs.
///
/// Uses BFS/DFS to find all connected components.
/// Runtime: O(n + m)
///
/// Reference: NetworKit ConnectedComponents class
pub struct ConnectedComponents;

impl ConnectedComponents {
    /// Find all connected components using BFS.
    pub fn run(graph: &Graph) -> ComponentResult {
        let n = graph.upper_node_id_bound() as usize;
        let mut components = vec![None; n];
        let mut component_sizes = Vec::new();
        let mut next_component_id = 0;

        for start_node in graph.nodes() {
            if components[start_node as usize].is_some() {
                continue; // Already visited
            }

            // BFS from this node
            let mut queue = VecDeque::new();
            let mut size = 0;

            queue.push_back(start_node);
            components[start_node as usize] = Some(next_component_id);

            while let Some(node) = queue.pop_front() {
                size += 1;

                for neighbor in graph.out_neighbors(node) {
                    let next = neighbor.target;

                    if components[next as usize].is_none() {
                        components[next as usize] = Some(next_component_id);
                        queue.push_back(next);
                    }
                }
            }

            component_sizes.push(size);
            next_component_id += 1;
        }

        ComponentResult {
            components,
            num_components: next_component_id,
            component_sizes,
        }
    }
}

/// Strongly Connected Components using Tarjan's algorithm.
///
/// Finds maximal strongly connected subgraphs in directed graphs.
/// Runtime: O(n + m)
///
/// Reference: NetworKit StronglyConnectedComponents class
/// Algorithm: Tarjan (1972)
pub struct StronglyConnectedComponents;

impl StronglyConnectedComponents {
    /// Find all strongly connected components using Tarjan's algorithm.
    pub fn run(graph: &Graph) -> ComponentResult {
        let n = graph.upper_node_id_bound() as usize;

        /// Mutable state shared across Tarjan's recursive calls.
        struct TarjanState {
            index: usize,
            indices: Vec<Option<usize>>,
            lowlinks: Vec<Option<usize>>,
            on_stack: Vec<bool>,
            stack: Vec<NodeId>,
            components: Vec<Option<usize>>,
            next_component_id: usize,
        }

        fn strongconnect(node: NodeId, graph: &Graph, s: &mut TarjanState) {
            // Set depth index
            s.indices[node as usize] = Some(s.index);
            s.lowlinks[node as usize] = Some(s.index);
            s.index += 1;
            s.stack.push(node);
            s.on_stack[node as usize] = true;

            // Consider successors
            for neighbor in graph.out_neighbors(node) {
                let next = neighbor.target;

                if s.indices[next as usize].is_none() {
                    // Successor not visited, recurse
                    strongconnect(next, graph, s);
                    s.lowlinks[node as usize] = Some(std::cmp::min(
                        s.lowlinks[node as usize].unwrap(),
                        s.lowlinks[next as usize].unwrap(),
                    ));
                } else if s.on_stack[next as usize] {
                    // Successor is on stack, part of current SCC
                    s.lowlinks[node as usize] = Some(std::cmp::min(
                        s.lowlinks[node as usize].unwrap(),
                        s.indices[next as usize].unwrap(),
                    ));
                }
            }

            // If node is a root, pop the stack and generate SCC
            if s.lowlinks[node as usize] == s.indices[node as usize] {
                loop {
                    let w = s.stack.pop().unwrap();
                    s.on_stack[w as usize] = false;
                    s.components[w as usize] = Some(s.next_component_id);

                    if w == node {
                        break;
                    }
                }
                s.next_component_id += 1;
            }
        }

        let mut state = TarjanState {
            index: 0,
            indices: vec![None; n],
            lowlinks: vec![None; n],
            on_stack: vec![false; n],
            stack: Vec::new(),
            components: vec![None; n],
            next_component_id: 0,
        };

        // Run Tarjan's algorithm from each unvisited node
        for node in graph.nodes() {
            if state.indices[node as usize].is_none() {
                strongconnect(node, graph, &mut state);
            }
        }

        let next_component_id = state.next_component_id;
        let components = state.components;

        // Calculate component sizes
        let mut sizes = vec![0; next_component_id];
        for comp_id in components.iter().flatten() {
            sizes[*comp_id] += 1;
        }

        ComponentResult {
            components,
            num_components: next_component_id,
            component_sizes: sizes,
        }
    }
}

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

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

        // Create two disconnected components
        for _ in 0..6 {
            graph.add_node();
        }

        // Component 1: 0-1-2
        graph.add_edge(0, 1, None);
        graph.add_edge(1, 2, None);

        // Component 2: 3-4-5
        graph.add_edge(3, 4, None);
        graph.add_edge(4, 5, None);

        let result = ConnectedComponents::run(&graph);

        assert_eq!(result.num_components, 2);
        assert_ne!(result.component(0), result.component(3));
        assert_eq!(result.component(0), result.component(1));
        assert_eq!(result.component(0), result.component(2));
    }

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

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

        // Create a cycle: 0->1->2->0 (SCC)
        graph.add_edge(0, 1, None);
        graph.add_edge(1, 2, None);
        graph.add_edge(2, 0, None);

        // Node 3 is separate
        graph.add_edge(2, 3, None);

        let result = StronglyConnectedComponents::run(&graph);

        // Should have 2 SCCs: {0,1,2} and {3}
        assert_eq!(result.num_components, 2);
        assert_eq!(result.component(0), result.component(1));
        assert_eq!(result.component(1), result.component(2));
        assert_ne!(result.component(0), result.component(3));
    }
}
