//! Python bindings for Louvain community detection

use pyo3::prelude::*;
use pyo3::exceptions::PyValueError;
use numpy::{PyReadonlyArray2, PyUntypedArrayMethods};

/// Run Louvain community detection on a weighted adjacency list.
///
/// Args:
///     n: Number of nodes
///     edges: List of (src, dst, weight) tuples
///     resolution: Modularity resolution parameter (default 1.0)
///
/// Returns:
///     Tuple of (community_ids as list, num_communities)
#[pyfunction]
#[pyo3(signature = (n, edges, resolution=1.0))]
pub fn py_louvain_communities(
    n: usize,
    edges: Vec<(u32, u32, f32)>,
    resolution: f32,
) -> PyResult<(Vec<u32>, u32)> {
    // Build adjacency list from edge list
    let mut adj: Vec<Vec<(u32, f32)>> = vec![Vec::new(); n];
    for &(src, dst, weight) in &edges {
        if src as usize >= n || dst as usize >= n {
            return Err(PyValueError::new_err(format!(
                "Edge ({src}, {dst}) out of bounds for n={n}"
            )));
        }
        adj[src as usize].push((dst, weight));
        adj[dst as usize].push((src, weight));
    }

    let (labels, k) = dyf_core::louvain_communities(n, &adj, resolution);
    Ok((labels, k))
}

/// Build a KNN graph from embeddings and run Louvain community detection.
///
/// This is the main entry point for computing communities from leaf centroids.
///
/// Args:
///     centroids: 2D numpy array of L2-normalized centroids, shape (n, dim)
///     k: Number of nearest neighbors for KNN graph (default 15)
///     resolution: Modularity resolution parameter (default 1.0)
///
/// Returns:
///     Tuple of (community_ids as list, num_communities)
#[pyfunction]
#[pyo3(signature = (centroids, k=15, resolution=1.0))]
pub fn py_louvain_from_centroids(
    centroids: PyReadonlyArray2<'_, f32>,
    k: usize,
    resolution: f32,
) -> PyResult<(Vec<u32>, u32)> {
    let shape = centroids.shape();
    let n = shape[0];
    let dim = shape[1];

    if n == 0 {
        return Ok((Vec::new(), 0));
    }

    let data = centroids.as_slice().map_err(|_| {
        PyValueError::new_err("Centroids array must be contiguous")
    })?;

    let adj = dyf_core::build_knn_graph(data, n, dim, k);
    let (labels, num) = dyf_core::louvain_communities(n, &adj, resolution);
    Ok((labels, num))
}

/// Build a KNN graph from embeddings.
///
/// Args:
///     centroids: 2D numpy array of L2-normalized centroids, shape (n, dim)
///     k: Number of nearest neighbors (default 15)
///
/// Returns:
///     List of lists of (neighbor_index, weight) tuples
#[pyfunction]
#[pyo3(signature = (centroids, k=15))]
pub fn py_build_knn_graph(
    centroids: PyReadonlyArray2<'_, f32>,
    k: usize,
) -> PyResult<Vec<Vec<(u32, f32)>>> {
    let shape = centroids.shape();
    let n = shape[0];
    let dim = shape[1];

    let data = centroids.as_slice().map_err(|_| {
        PyValueError::new_err("Centroids array must be contiguous")
    })?;

    Ok(dyf_core::build_knn_graph(data, n, dim, k))
}
