use std::collections::{HashSet, VecDeque};

use crate::error::LayoutError;
use crate::model::Graph;

pub fn compute_hierarchy_levels(graph: &Graph) -> Result<Vec<usize>, LayoutError> {
    validate_simple_dag(graph)?;

    let node_count = graph.nodes.len();
    if node_count == 0 {
        return Ok(Vec::new());
    }

    let mut indegree = vec![0usize; node_count];
    let mut outgoing = vec![Vec::new(); node_count];

    for edge in &graph.edges {
        indegree[edge.target] += 1;
        outgoing[edge.source].push(edge.target);
    }

    let mut queue = VecDeque::new();
    for (node_index, degree) in indegree.iter().enumerate() {
        if *degree == 0 {
            queue.push_back(node_index);
        }
    }

    let mut levels = vec![0usize; node_count];
    let mut visited = 0usize;

    while let Some(node) = queue.pop_front() {
        visited += 1;
        let next_level = levels[node] + 1;
        for &child in &outgoing[node] {
            if levels[child] < next_level {
                levels[child] = next_level;
            }
            indegree[child] -= 1;
            if indegree[child] == 0 {
                queue.push_back(child);
            }
        }
    }

    if visited != node_count {
        return Err(LayoutError::CycleDetected);
    }

    Ok(levels)
}

pub(crate) fn validate_simple_dag(graph: &Graph) -> Result<(), LayoutError> {
    let node_count = graph.nodes.len();
    let mut seen_edges = HashSet::new();

    for (edge_index, edge) in graph.edges.iter().enumerate() {
        if edge.source >= node_count {
            return Err(LayoutError::InvalidNodeIndex {
                edge_index,
                node_index: edge.source,
                node_count,
            });
        }
        if edge.target >= node_count {
            return Err(LayoutError::InvalidNodeIndex {
                edge_index,
                node_index: edge.target,
                node_count,
            });
        }
        if edge.source == edge.target {
            return Err(LayoutError::SelfLoop {
                edge_index,
                node: edge.source,
            });
        }
        if !seen_edges.insert((edge.source, edge.target)) {
            return Err(LayoutError::DuplicateEdge {
                edge_index,
                source: edge.source,
                target: edge.target,
            });
        }
    }

    Ok(())
}
