// Copyright 2026 Gregorio Momm
// Licensed under the Apache License, Version 2.0

//! Shared algorithm facade for petgraph-based backends.
//!
//! Both `RustworkxCoreBackend` and `GraphrsBackend` store their graphs as
//! `StableDiGraph<GraphNode, GraphEdge>`. This module provides a single
//! conversion layer (StableDiGraph → NetworKit Graph) that makes all 40+
//! ported NetworKit algorithms available to both backends with zero code duplication.

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

use petgraph::stable_graph::{NodeIndex, StableDiGraph};
use petgraph::visit::{EdgeRef as PetgraphEdgeRef, IntoEdgeReferences};

use crate::graph::types::{GraphEdge, GraphNode, PropertyValue};
use crate::graph::backends::networkit_rust::graph::{
    Graph as NkGraph, GraphConfig, NodeId as NkId,
};
use crate::graph::backends::networkit_rust::algorithms::{
    // Pathfinding
    astar, bellman_ford, floyd_warshall, all_pairs_dijkstra,
    // Spanning trees
    minimum_spanning_tree_kruskal, maximum_spanning_tree_kruskal,
    // DAG
    topological_sort, is_dag, find_cycles,
    dag_longest_path, dag_longest_path_weighted, transitive_closure,
    // Flow
    edmonds_karp, min_cut, FlowResult,
    // Coloring
    greedy_node_color, greedy_edge_color, chromatic_number_approx,
    // Matching
    max_weight_matching, max_cardinality_matching,
    // Community
    louvain, label_propagation, girvan_newman, CommunityResult,
    // Native NetworKit (builder pattern)
    BFS, Dijkstra, DegreeCentrality, BetweennessCentrality, ClosenessCentrality,
    PageRank, ConnectedComponents, StronglyConnectedComponents,
    // Euler
    has_euler_circuit, has_euler_path, euler_circuit, euler_path,
    // Planarity
    is_planar,
};

// ─────────────────────────────────────────────────────────────────────────────

/// Return type for max-flow: (max_flow_value, per-edge flow amounts)
type MaxFlowResult = Option<(f64, HashMap<(u64, u64), f64>)>;

// Conversion

/// Bidirectional mapping between petgraph NodeIndex and NetworKit NodeId.
pub struct GraphMapping {
    pub pg_to_nk: HashMap<NodeIndex, NkId>,
    /// `nk_to_stable[nk_id]` = stable u64 ID
    pub nk_to_stable: Vec<u64>,
    pub stable_to_nk: HashMap<u64, NkId>,
}

/// Convert a petgraph `StableDiGraph<GraphNode, GraphEdge>` to a NetworKit `Graph`.
pub fn build_networkit_graph(
    pg: &StableDiGraph<GraphNode, GraphEdge>,
) -> (NkGraph, GraphMapping) {
    let mut nk = NkGraph::new(GraphConfig::directed().with_edge_index());
    let mut pg_to_nk: HashMap<NodeIndex, NkId> = HashMap::new();
    let mut nk_to_stable: Vec<u64> = Vec::new();
    let mut stable_to_nk: HashMap<u64, NkId> = HashMap::new();

    for pg_idx in pg.node_indices() {
        let stable_id = pg[pg_idx].id;
        let nk_id = nk.add_node();
        pg_to_nk.insert(pg_idx, nk_id);
        stable_to_nk.insert(stable_id, nk_id);
        assert_eq!(nk_id as usize, nk_to_stable.len());
        nk_to_stable.push(stable_id);
    }

    for edge_ref in pg.edge_references() {
        let src_nk = pg_to_nk[&edge_ref.source()];
        let tgt_nk = pg_to_nk[&edge_ref.target()];
        let weight = edge_ref.weight().properties
            .get("weight")
            .and_then(|v| match v {
                PropertyValue::Integer(i) => Some(*i as f64),
                PropertyValue::Float(f)   => Some(*f),
                _ => None,
            });
        nk.add_edge(src_nk, tgt_nk, weight);
    }

    (nk, GraphMapping { pg_to_nk, nk_to_stable, stable_to_nk })
}

// ─── Helpers ─────────────────────────────────────────────────────────────────

fn nk_to_stable_vec(v: Vec<NkId>, m: &GraphMapping) -> Vec<u64> {
    v.into_iter().map(|id| m.nk_to_stable[id as usize]).collect()
}

fn nk_map_to_stable<V>(map: HashMap<NkId, V>, m: &GraphMapping) -> HashMap<u64, V> {
    map.into_iter()
        .map(|(id, v)| (m.nk_to_stable[id as usize], v))
        .collect()
}

/// Convert CentralityResult (Vec-indexed by NkId) → HashMap<u64, f64>
fn centrality_to_map(
    result: &crate::graph::backends::networkit_rust::algorithms::CentralityResult,
    nk: &NkGraph,
    m: &GraphMapping,
) -> HashMap<u64, f64> {
    nk.nodes()
        .map(|nk_id| {
            let stable = m.nk_to_stable[nk_id as usize];
            let score = result.scores.get(nk_id as usize).copied().unwrap_or(0.0);
            (stable, score)
        })
        .collect()
}

/// Convert ComponentResult (Vec<Option<usize>>) → Vec<Vec<u64>>
fn component_to_vecs(
    result: &crate::graph::backends::networkit_rust::algorithms::ComponentResult,
    nk: &NkGraph,
    m: &GraphMapping,
) -> Vec<Vec<u64>> {
    let mut map: HashMap<usize, Vec<u64>> = HashMap::new();
    for nk_id in nk.nodes() {
        if let Some(Some(comp_id)) = result.components.get(nk_id as usize) {
            let stable = m.nk_to_stable[nk_id as usize];
            map.entry(*comp_id).or_default().push(stable);
        }
    }
    map.into_values().collect()
}

// ─────────────────────────────────────────────────────────────────────────────
// The trait

/// All graph algorithms available on petgraph-backed OCG backends.
///
/// Automatically available on `RustworkxCoreBackend` and `GraphrsBackend`.
pub trait PetgraphAlgorithms {
    fn petgraph_inner(&self) -> &StableDiGraph<GraphNode, GraphEdge>;

    fn to_networkit(&self) -> (NkGraph, GraphMapping) {
        build_networkit_graph(self.petgraph_inner())
    }

    // ── Centrality ──────────────────────────────────────────────────────────

    fn degree_centrality(&self) -> HashMap<u64, f64> {
        let (nk, m) = self.to_networkit();
        let result = DegreeCentrality::new().run(&nk);
        centrality_to_map(&result, &nk, &m)
    }

    fn betweenness_centrality(&self, normalized: bool) -> HashMap<u64, f64> {
        let (nk, m) = self.to_networkit();
        let mut bc = BetweennessCentrality::new();
        if normalized { bc = bc.normalized(); }
        let result = bc.run(&nk);
        centrality_to_map(&result, &nk, &m)
    }

    fn closeness_centrality(&self, normalized: bool) -> HashMap<u64, f64> {
        let (nk, m) = self.to_networkit();
        let mut cc = ClosenessCentrality::new();
        if normalized { cc = cc.normalized(); }
        let result = cc.run(&nk);
        centrality_to_map(&result, &nk, &m)
    }

    fn pagerank(&self, damping: f64, _max_iter: usize) -> HashMap<u64, f64> {
        let (nk, m) = self.to_networkit();
        let result = PageRank::new().damping(damping).run(&nk);
        centrality_to_map(&result, &nk, &m)
    }

    // ── Pathfinding ─────────────────────────────────────────────────────────

    fn bfs_path(&self, source: u64, target: u64) -> Option<Vec<u64>> {
        let (nk, m) = self.to_networkit();
        let src = *m.stable_to_nk.get(&source)?;
        let tgt = *m.stable_to_nk.get(&target)?;
        let result = BFS::new(&nk, src).with_target(tgt).store_paths(true).run();
        result.path(tgt).map(|p| nk_to_stable_vec(p, &m))
    }

    fn dijkstra_path(&self, source: u64, target: u64) -> Option<(f64, Vec<u64>)> {
        let (nk, m) = self.to_networkit();
        let src = *m.stable_to_nk.get(&source)?;
        let tgt = *m.stable_to_nk.get(&target)?;
        let result = Dijkstra::new(&nk, src).with_target(tgt).store_paths(true).run();
        let cost = result.distance(tgt)?;
        let path = nk_to_stable_vec(result.path(tgt)?, &m);
        Some((cost, path))
    }

    fn astar_path<F>(&self, source: u64, target: u64, heuristic: F) -> Option<(f64, Vec<u64>)>
    where
        F: Fn(u64) -> f64,
    {
        let (nk, m) = self.to_networkit();
        let src = *m.stable_to_nk.get(&source)?;
        let tgt = *m.stable_to_nk.get(&target)?;
        let h = |nk_id: NkId| heuristic(m.nk_to_stable[nk_id as usize]);
        astar(&nk, src, tgt, h).map(|(cost, path)| (cost, nk_to_stable_vec(path, &m)))
    }

    fn bellman_ford_distances(&self, source: u64) -> Option<HashMap<u64, f64>> {
        let (nk, m) = self.to_networkit();
        let src = *m.stable_to_nk.get(&source)?;
        bellman_ford(&nk, src).ok().map(|d| nk_map_to_stable(d, &m))
    }

    fn floyd_warshall_distances(&self) -> HashMap<(u64, u64), f64> {
        let (nk, m) = self.to_networkit();
        floyd_warshall(&nk)
            .into_iter()
            .map(|((a, b), d)| ((m.nk_to_stable[a as usize], m.nk_to_stable[b as usize]), d))
            .collect()
    }

    fn all_pairs_distances(&self) -> HashMap<u64, HashMap<u64, f64>> {
        let (nk, m) = self.to_networkit();
        all_pairs_dijkstra(&nk)
            .into_iter()
            .map(|(src, dists)| {
                (m.nk_to_stable[src as usize], nk_map_to_stable(dists, &m))
            })
            .collect()
    }

    // ── Spanning Trees ───────────────────────────────────────────────────────

    fn minimum_spanning_tree(&self) -> Vec<(u64, u64, f64)> {
        let (nk, m) = self.to_networkit();
        minimum_spanning_tree_kruskal(&nk)
            .into_iter()
            .map(|(a, b, w)| (m.nk_to_stable[a as usize], m.nk_to_stable[b as usize], w))
            .collect()
    }

    fn maximum_spanning_tree(&self) -> Vec<(u64, u64, f64)> {
        let (nk, m) = self.to_networkit();
        maximum_spanning_tree_kruskal(&nk)
            .into_iter()
            .map(|(a, b, w)| (m.nk_to_stable[a as usize], m.nk_to_stable[b as usize], w))
            .collect()
    }

    // ── DAG Algorithms ───────────────────────────────────────────────────────

    fn topological_sort(&self) -> Result<Vec<u64>, Vec<u64>> {
        let (nk, m) = self.to_networkit();
        topological_sort(&nk)
            .map(|v| nk_to_stable_vec(v, &m))
            .map_err(|v| nk_to_stable_vec(v, &m))
    }

    fn is_dag(&self) -> bool {
        let (nk, _) = self.to_networkit();
        is_dag(&nk)
    }

    fn find_cycles(&self) -> Vec<Vec<u64>> {
        let (nk, m) = self.to_networkit();
        find_cycles(&nk)
            .into_iter()
            .map(|c| nk_to_stable_vec(c, &m))
            .collect()
    }

    fn dag_longest_path(&self) -> Option<(usize, Vec<u64>)> {
        let (nk, m) = self.to_networkit();
        dag_longest_path(&nk).map(|(len, p)| (len, nk_to_stable_vec(p, &m)))
    }

    fn dag_longest_path_weighted(&self) -> Option<(f64, Vec<u64>)> {
        let (nk, m) = self.to_networkit();
        dag_longest_path_weighted(&nk).map(|(cost, p)| (cost, nk_to_stable_vec(p, &m)))
    }

    fn transitive_closure(&self) -> HashSet<(u64, u64)> {
        let (nk, m) = self.to_networkit();
        transitive_closure(&nk)
            .into_iter()
            .map(|(a, b)| (m.nk_to_stable[a as usize], m.nk_to_stable[b as usize]))
            .collect()
    }

    // ── Network Flow ─────────────────────────────────────────────────────────

    fn max_flow(&self, source: u64, sink: u64) -> MaxFlowResult {
        let (nk, m) = self.to_networkit();
        let src = *m.stable_to_nk.get(&source)?;
        let snk = *m.stable_to_nk.get(&sink)?;
        let FlowResult { max_flow, edge_flows } = edmonds_karp(&nk, src, snk);
        let stable_flows = edge_flows.into_iter()
            .map(|((a, b), f)| ((m.nk_to_stable[a as usize], m.nk_to_stable[b as usize]), f))
            .collect();
        Some((max_flow, stable_flows))
    }

    fn min_cut_capacity(&self, source: u64, sink: u64) -> Option<f64> {
        let (nk, m) = self.to_networkit();
        let src = *m.stable_to_nk.get(&source)?;
        let snk = *m.stable_to_nk.get(&sink)?;
        Some(min_cut(&nk, src, snk).min_cut)
    }

    // ── Coloring ─────────────────────────────────────────────────────────────

    fn node_coloring(&self) -> HashMap<u64, usize> {
        let (nk, m) = self.to_networkit();
        nk_map_to_stable(greedy_node_color(&nk), &m)
    }

    fn edge_coloring(&self) -> HashMap<(u64, u64), usize> {
        let (nk, m) = self.to_networkit();
        greedy_edge_color(&nk)
            .into_iter()
            .map(|((a, b), c)| ((m.nk_to_stable[a as usize], m.nk_to_stable[b as usize]), c))
            .collect()
    }

    fn chromatic_number(&self) -> usize {
        let (nk, _) = self.to_networkit();
        chromatic_number_approx(&nk)
    }

    // ── Matching ─────────────────────────────────────────────────────────────

    fn max_weight_matching_edges(&self) -> HashSet<(u64, u64)> {
        let (nk, m) = self.to_networkit();
        max_weight_matching(&nk, false)
            .into_iter()
            .map(|(a, b)| (m.nk_to_stable[a as usize], m.nk_to_stable[b as usize]))
            .collect()
    }

    fn max_cardinality_matching_edges(&self) -> HashSet<(u64, u64)> {
        let (nk, m) = self.to_networkit();
        max_cardinality_matching(&nk)
            .into_iter()
            .map(|(a, b)| (m.nk_to_stable[a as usize], m.nk_to_stable[b as usize]))
            .collect()
    }

    // ── Community Detection ───────────────────────────────────────────────────

    fn louvain_communities(&self, resolution: Option<f64>) -> CommunityInfo {
        let (nk, m) = self.to_networkit();
        CommunityInfo::from_result(louvain(&nk, resolution, 100), &m)
    }

    fn label_propagation_communities(&self) -> CommunityInfo {
        let (nk, m) = self.to_networkit();
        CommunityInfo::from_result(label_propagation(&nk, 50), &m)
    }

    fn girvan_newman_communities(&self, k: usize) -> CommunityInfo {
        let (nk, m) = self.to_networkit();
        CommunityInfo::from_result(girvan_newman(&nk, k), &m)
    }

    // ── Components ───────────────────────────────────────────────────────────

    fn connected_components_list(&self) -> Vec<Vec<u64>> {
        let (nk, m) = self.to_networkit();
        component_to_vecs(&ConnectedComponents::run(&nk), &nk, &m)
    }

    fn strongly_connected_components_list(&self) -> Vec<Vec<u64>> {
        let (nk, m) = self.to_networkit();
        component_to_vecs(&StronglyConnectedComponents::run(&nk), &nk, &m)
    }

    // ── Eulerian ─────────────────────────────────────────────────────────────

    fn has_euler_circuit(&self) -> bool {
        let (nk, _) = self.to_networkit();
        has_euler_circuit(&nk)
    }

    fn has_euler_path(&self) -> bool {
        let (nk, _) = self.to_networkit();
        has_euler_path(&nk)
    }

    fn euler_circuit(&self) -> Option<Vec<u64>> {
        let (nk, m) = self.to_networkit();
        euler_circuit(&nk).map(|c| nk_to_stable_vec(c, &m))
    }

    fn euler_path(&self) -> Option<Vec<u64>> {
        let (nk, m) = self.to_networkit();
        euler_path(&nk).map(|p| nk_to_stable_vec(p, &m))
    }

    // ── Planarity ─────────────────────────────────────────────────────────────

    fn is_planar(&self) -> bool {
        let (nk, _) = self.to_networkit();
        is_planar(&nk)
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// CommunityInfo

/// Community detection result with stable u64 node IDs.
#[derive(Debug, Clone)]
pub struct CommunityInfo {
    pub node_to_community: HashMap<u64, usize>,
    pub communities: Vec<Vec<u64>>,
    pub modularity: f64,
}

impl CommunityInfo {
    pub(crate) fn from_result(result: CommunityResult, m: &GraphMapping) -> Self {
        let node_to_community = result.node_to_community.into_iter()
            .map(|(nk_id, comm)| (m.nk_to_stable[nk_id as usize], comm))
            .collect();
        let communities = result.communities.into_iter()
            .map(|comp| nk_to_stable_vec(comp, m))
            .collect();
        CommunityInfo { node_to_community, communities, modularity: result.modularity }
    }

    pub fn num_communities(&self) -> usize {
        self.communities.len()
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Blanket AlgorithmEngine impl for all PetgraphAlgorithms implementors
//
// Any type that implements PetgraphAlgorithms automatically gets AlgorithmEngine.
// This covers PropertyGraph, RustworkxCoreBackend, and GraphrsBackend.

impl<T: PetgraphAlgorithms> crate::graph::algorithm_engine::AlgorithmEngine for T {
    fn degree_centrality(&self) -> HashMap<u64, f64> {
        PetgraphAlgorithms::degree_centrality(self)
    }

    fn betweenness_centrality(&self, normalized: bool) -> HashMap<u64, f64> {
        PetgraphAlgorithms::betweenness_centrality(self, normalized)
    }

    fn closeness_centrality(&self, normalized: bool) -> HashMap<u64, f64> {
        PetgraphAlgorithms::closeness_centrality(self, normalized)
    }

    fn pagerank(&self, damping: f64, max_iter: usize) -> HashMap<u64, f64> {
        PetgraphAlgorithms::pagerank(self, damping, max_iter)
    }

    fn bfs_path(&self, source: u64, target: u64) -> Option<Vec<u64>> {
        PetgraphAlgorithms::bfs_path(self, source, target)
    }

    fn dijkstra_path(&self, source: u64, target: u64) -> Option<(f64, Vec<u64>)> {
        PetgraphAlgorithms::dijkstra_path(self, source, target)
    }

    fn astar_path<F>(&self, source: u64, target: u64, heuristic: F) -> Option<(f64, Vec<u64>)>
    where
        F: Fn(u64) -> f64,
    {
        PetgraphAlgorithms::astar_path(self, source, target, heuristic)
    }

    fn bellman_ford_distances(&self, source: u64) -> Option<HashMap<u64, f64>> {
        PetgraphAlgorithms::bellman_ford_distances(self, source)
    }

    fn floyd_warshall_distances(&self) -> HashMap<(u64, u64), f64> {
        PetgraphAlgorithms::floyd_warshall_distances(self)
    }

    fn all_pairs_distances(&self) -> HashMap<u64, HashMap<u64, f64>> {
        PetgraphAlgorithms::all_pairs_distances(self)
    }

    fn minimum_spanning_tree(&self) -> Vec<(u64, u64, f64)> {
        PetgraphAlgorithms::minimum_spanning_tree(self)
    }

    fn maximum_spanning_tree(&self) -> Vec<(u64, u64, f64)> {
        PetgraphAlgorithms::maximum_spanning_tree(self)
    }

    fn topological_sort(&self) -> Result<Vec<u64>, Vec<u64>> {
        PetgraphAlgorithms::topological_sort(self)
    }

    fn is_dag(&self) -> bool {
        PetgraphAlgorithms::is_dag(self)
    }

    fn find_cycles(&self) -> Vec<Vec<u64>> {
        PetgraphAlgorithms::find_cycles(self)
    }

    fn dag_longest_path(&self) -> Option<(usize, Vec<u64>)> {
        PetgraphAlgorithms::dag_longest_path(self)
    }

    fn dag_longest_path_weighted(&self) -> Option<(f64, Vec<u64>)> {
        PetgraphAlgorithms::dag_longest_path_weighted(self)
    }

    fn transitive_closure(&self) -> std::collections::HashSet<(u64, u64)> {
        PetgraphAlgorithms::transitive_closure(self)
    }

    fn max_flow(&self, source: u64, sink: u64) -> crate::graph::algorithm_engine::MaxFlowResult {
        PetgraphAlgorithms::max_flow(self, source, sink)
    }

    fn min_cut_capacity(&self, source: u64, sink: u64) -> Option<f64> {
        PetgraphAlgorithms::min_cut_capacity(self, source, sink)
    }

    fn node_coloring(&self) -> HashMap<u64, usize> {
        PetgraphAlgorithms::node_coloring(self)
    }

    fn edge_coloring(&self) -> HashMap<(u64, u64), usize> {
        PetgraphAlgorithms::edge_coloring(self)
    }

    fn chromatic_number(&self) -> usize {
        PetgraphAlgorithms::chromatic_number(self)
    }

    fn max_weight_matching_edges(&self) -> std::collections::HashSet<(u64, u64)> {
        PetgraphAlgorithms::max_weight_matching_edges(self)
    }

    fn max_cardinality_matching_edges(&self) -> std::collections::HashSet<(u64, u64)> {
        PetgraphAlgorithms::max_cardinality_matching_edges(self)
    }

    fn louvain_communities(&self, resolution: Option<f64>) -> CommunityInfo {
        PetgraphAlgorithms::louvain_communities(self, resolution)
    }

    fn label_propagation_communities(&self) -> CommunityInfo {
        PetgraphAlgorithms::label_propagation_communities(self)
    }

    fn girvan_newman_communities(&self, k: usize) -> CommunityInfo {
        PetgraphAlgorithms::girvan_newman_communities(self, k)
    }

    fn connected_components_list(&self) -> Vec<Vec<u64>> {
        PetgraphAlgorithms::connected_components_list(self)
    }

    fn strongly_connected_components_list(&self) -> Vec<Vec<u64>> {
        PetgraphAlgorithms::strongly_connected_components_list(self)
    }

    fn has_euler_circuit(&self) -> bool {
        PetgraphAlgorithms::has_euler_circuit(self)
    }

    fn has_euler_path(&self) -> bool {
        PetgraphAlgorithms::has_euler_path(self)
    }

    fn euler_circuit(&self) -> Option<Vec<u64>> {
        PetgraphAlgorithms::euler_circuit(self)
    }

    fn euler_path(&self) -> Option<Vec<u64>> {
        PetgraphAlgorithms::euler_path(self)
    }

    fn is_planar(&self) -> bool {
        PetgraphAlgorithms::is_planar(self)
    }
}
