"""
Type stubs for the OCG PyO3 extension module (_ocg).

This module is compiled from Rust (src/python.rs) and exposes four graph
backend classes plus a top-level convenience function.
"""

from __future__ import annotations

from collections.abc import Callable
from typing import Any, Optional

__version__: str

# ---------------------------------------------------------------------------
# Return-type aliases (for documentation clarity)
# ---------------------------------------------------------------------------

# Community detection result dict:
#   {"node_to_community": dict[int, int], "communities": list[list[int]],
#    "modularity": float, "num_communities": int}
_CommunityResult = dict[str, Any]

# ---------------------------------------------------------------------------
# Graph — PropertyGraph backend (petgraph StableDiGraph)
# ---------------------------------------------------------------------------

class Graph:
    """
    OpenCypher graph backed by a petgraph StableDiGraph (default backend).

    Supports the full openCypher query language and 30+ graph algorithms.
    """

    def __init__(self) -> None: ...
    def __repr__(self) -> str: ...

    # -- Query execution ---------------------------------------------------

    def execute(
        self,
        query: str,
        params: Optional[dict[str, Any]] = None,
    ) -> list[dict[str, Any]]:
        """Execute an openCypher query, optionally with parameters."""
        ...

    def on_execute(self, callback: Callable[[str, float], None]) -> None:
        """Register a callback invoked after each execute() call.

        Args:
            callback: Called with (query: str, elapsed_seconds: float)
        """
        ...

    def metrics(self) -> dict[str, int]:
        """Return accumulated execution metrics.

        Returns a dict with keys:
            query_total, error_total, nodes_created, edges_created,
            nodes_deleted, edges_deleted, execute_nanos_total
        """
        ...

    # -- Graph inspection --------------------------------------------------

    def node_count(self) -> int: ...
    def edge_count(self) -> int: ...

    # -- Single-element creation -------------------------------------------

    def create_node(
        self,
        labels: list[str],
        properties: Optional[dict[str, Any]] = None,
    ) -> int:
        """Create a node and return its ID."""
        ...

    def create_relationship(
        self,
        from_node: int,
        to_node: int,
        rel_type: str,
        properties: Optional[dict[str, Any]] = None,
    ) -> int:
        """Create a relationship and return its ID."""
        ...

    # -- Bulk loader -------------------------------------------------------

    def bulk_create_nodes(
        self,
        nodes: list[tuple[list[str], dict[str, Any]]],
    ) -> list[int]:
        """Bulk-create nodes. Each tuple is (labels, properties). Returns list of node IDs."""
        ...

    def bulk_create_relationships(
        self,
        relationships: list[tuple[int, int, str, dict[str, Any]]],
    ) -> list[int]:
        """Bulk-create relationships. Each tuple is (from_id, to_id, rel_type, properties). Returns list of edge IDs."""
        ...

    # -- Serialization -----------------------------------------------------

    def save(self, path: str) -> None:
        """Save graph to a JSON file."""
        ...

    @staticmethod
    def load(path: str) -> Graph:
        """Load graph from a JSON file previously saved with save()."""
        ...

    # -- Centrality --------------------------------------------------------

    def degree_centrality(self) -> dict[int, float]:
        """Degree centrality for every node. Returns dict[node_id, score]."""
        ...

    def betweenness_centrality(self, normalized: bool = True) -> dict[int, float]:
        """Betweenness centrality (Brandes 2001). Returns dict[node_id, score]."""
        ...

    def closeness_centrality(self, normalized: bool = True) -> dict[int, float]:
        """Closeness centrality. Returns dict[node_id, score]."""
        ...

    def pagerank(self, damping: float = 0.85, max_iter: int = 100) -> dict[int, float]:
        """PageRank importance scores. Returns dict[node_id, score]."""
        ...

    # -- Pathfinding -------------------------------------------------------

    def bfs_path(self, source: int, target: int) -> Optional[list[int]]:
        """BFS shortest path. Returns list of node IDs or None if unreachable."""
        ...

    def dijkstra_path(
        self, source: int, target: int
    ) -> Optional[tuple[float, list[int]]]:
        """Dijkstra shortest path. Returns (cost, path) or None if unreachable."""
        ...

    def astar_path(
        self,
        source: int,
        target: int,
        heuristic: Optional[Callable[[int], float]] = None,
    ) -> Optional[tuple[float, list[int]]]:
        """A* shortest path with optional heuristic. Returns (cost, path) or None."""
        ...

    def bellman_ford_distances(
        self, source: int
    ) -> Optional[dict[int, float]]:
        """Bellman-Ford single-source distances. Returns dict or None if negative cycle."""
        ...

    def floyd_warshall_distances(self) -> dict[tuple[int, int], float]:
        """Floyd-Warshall all-pairs shortest distances. Returns dict[(src, tgt), dist]."""
        ...

    def all_pairs_distances(self) -> dict[int, dict[int, float]]:
        """All-pairs shortest distances via Dijkstra. Returns dict[src, dict[tgt, dist]]."""
        ...

    # -- Spanning Trees ----------------------------------------------------

    def minimum_spanning_tree(self) -> list[tuple[int, int, float]]:
        """Minimum spanning tree (Kruskal). Returns list of (src, tgt, weight) triples."""
        ...

    def maximum_spanning_tree(self) -> list[tuple[int, int, float]]:
        """Maximum spanning tree (Kruskal). Returns list of (src, tgt, weight) triples."""
        ...

    # -- DAG Algorithms ----------------------------------------------------

    def topological_sort(self) -> list[int]:
        """Topological sort. Returns ordered list of node IDs. Raises RuntimeError on cycle."""
        ...

    def is_dag(self) -> bool:
        """Return True if the graph has no directed cycles."""
        ...

    def find_cycles(self) -> list[list[int]]:
        """Find all directed cycles. Returns list of cycles (each a list of node IDs)."""
        ...

    def dag_longest_path(self) -> Optional[tuple[int, list[int]]]:
        """Longest path in DAG (edge count). Returns (length, path) or None if cyclic."""
        ...

    def dag_longest_path_weighted(self) -> Optional[tuple[float, list[int]]]:
        """Longest weighted path in DAG. Returns (cost, path) or None if cyclic."""
        ...

    def transitive_closure(self) -> list[tuple[int, int]]:
        """Transitive closure. Returns list of (src, tgt) reachable pairs."""
        ...

    # -- Network Flow ------------------------------------------------------

    def max_flow(
        self, source: int, sink: int
    ) -> Optional[tuple[float, dict[tuple[int, int], float]]]:
        """Edmonds-Karp max flow. Returns (max_flow, edge_flows) or None."""
        ...

    def min_cut_capacity(self, source: int, sink: int) -> Optional[float]:
        """Minimum cut capacity. Returns float or None if source/sink not in graph."""
        ...

    # -- Graph Coloring ----------------------------------------------------

    def node_coloring(self) -> dict[int, int]:
        """Greedy vertex coloring. Returns dict[node_id, color]."""
        ...

    def edge_coloring(self) -> dict[tuple[int, int], int]:
        """Greedy edge coloring. Returns dict[(src, tgt), color]."""
        ...

    def chromatic_number(self) -> int:
        """Approximate chromatic number (minimum colors to color all nodes)."""
        ...

    # -- Matching ----------------------------------------------------------

    def max_weight_matching(self) -> list[tuple[int, int]]:
        """Maximum weight matching (greedy). Returns list of (node_id, node_id) pairs."""
        ...

    def max_cardinality_matching(self) -> list[tuple[int, int]]:
        """Maximum cardinality matching. Returns list of (node_id, node_id) pairs."""
        ...

    # -- Community Detection -----------------------------------------------

    def louvain_communities(
        self, resolution: Optional[float] = None
    ) -> _CommunityResult:
        """Louvain community detection. Returns dict with node_to_community, communities, modularity, num_communities."""
        ...

    def label_propagation_communities(self) -> _CommunityResult:
        """Label propagation community detection."""
        ...

    def girvan_newman_communities(self, k: int) -> _CommunityResult:
        """Girvan-Newman community detection. k = number of communities."""
        ...

    # -- Components --------------------------------------------------------

    def connected_components(self) -> list[list[int]]:
        """Weakly connected components. Returns list of lists of node IDs."""
        ...

    def strongly_connected_components(self) -> list[list[int]]:
        """Strongly connected components (Tarjan). Returns list of lists of node IDs."""
        ...


# ---------------------------------------------------------------------------
# NetworKitGraph — NetworKit-inspired adjacency list backend
# ---------------------------------------------------------------------------

class NetworKitGraph:
    """
    OpenCypher graph backed by a pure-Rust NetworKit-inspired adjacency list.

    Native algorithm implementations operate directly on internal node IDs.
    """

    def __init__(self) -> None: ...
    def __repr__(self) -> str: ...

    def execute(
        self,
        query: str,
        params: Optional[dict[str, Any]] = None,
    ) -> list[dict[str, Any]]: ...

    def on_execute(self, callback: Callable[[str, float], None]) -> None:
        """Register a callback invoked after each execute() call.

        Args:
            callback: Called with (query: str, elapsed_seconds: float)
        """
        ...

    def metrics(self) -> dict[str, int]:
        """Return accumulated execution metrics.

        Returns a dict with keys:
            query_total, error_total, nodes_created, edges_created,
            nodes_deleted, edges_deleted, execute_nanos_total
        """
        ...

    def node_count(self) -> int: ...
    def edge_count(self) -> int: ...

    def create_node(
        self,
        labels: list[str],
        properties: Optional[dict[str, Any]] = None,
    ) -> int: ...

    def create_relationship(
        self,
        from_node: int,
        to_node: int,
        rel_type: str,
        properties: Optional[dict[str, Any]] = None,
    ) -> int: ...

    def save(self, path: str) -> None: ...

    @staticmethod
    def load(path: str) -> NetworKitGraph: ...

    # Centrality
    def degree_centrality(self) -> dict[int, float]: ...
    def betweenness_centrality(self, normalized: bool = True) -> dict[int, float]: ...
    def closeness_centrality(self, normalized: bool = True) -> dict[int, float]: ...
    def pagerank(self, damping: float = 0.85, max_iter: int = 100) -> dict[int, float]: ...

    # Pathfinding
    def bfs_path(self, source: int, target: int) -> Optional[list[int]]: ...
    def dijkstra_path(self, source: int, target: int) -> Optional[tuple[float, list[int]]]: ...
    def astar_path(
        self,
        source: int,
        target: int,
        heuristic: Optional[Callable[[int], float]] = None,
    ) -> Optional[tuple[float, list[int]]]: ...
    def bellman_ford_distances(self, source: int) -> Optional[dict[int, float]]: ...
    def floyd_warshall_distances(self) -> dict[tuple[int, int], float]: ...
    def all_pairs_distances(self) -> dict[int, dict[int, float]]: ...

    # Spanning Trees
    def minimum_spanning_tree(self) -> list[tuple[int, int, float]]: ...
    def maximum_spanning_tree(self) -> list[tuple[int, int, float]]: ...

    # DAG Algorithms
    def topological_sort(self) -> list[int]: ...
    def is_dag(self) -> bool: ...
    def find_cycles(self) -> list[list[int]]: ...
    def dag_longest_path(self) -> Optional[tuple[int, list[int]]]: ...
    def dag_longest_path_weighted(self) -> Optional[tuple[float, list[int]]]: ...
    def transitive_closure(self) -> list[tuple[int, int]]: ...

    # Network Flow
    def max_flow(self, source: int, sink: int) -> Optional[tuple[float, dict[tuple[int, int], float]]]: ...
    def min_cut_capacity(self, source: int, sink: int) -> Optional[float]: ...

    # Graph Coloring
    def node_coloring(self) -> dict[int, int]: ...
    def edge_coloring(self) -> dict[tuple[int, int], int]: ...
    def chromatic_number(self) -> int: ...

    # Matching
    def max_weight_matching(self) -> list[tuple[int, int]]: ...
    def max_cardinality_matching(self) -> list[tuple[int, int]]: ...

    # Community Detection
    def louvain_communities(self, resolution: Optional[float] = None) -> _CommunityResult: ...
    def label_propagation_communities(self) -> _CommunityResult: ...
    def girvan_newman_communities(self, k: int) -> _CommunityResult: ...

    # Components
    def connected_components(self) -> list[list[int]]: ...
    def strongly_connected_components(self) -> list[list[int]]: ...


# ---------------------------------------------------------------------------
# RustworkxGraph — petgraph StableDiGraph + rustworkx-core algorithms
# ---------------------------------------------------------------------------

class RustworkxGraph:
    """
    OpenCypher graph backed by petgraph StableDiGraph with IBM Qiskit
    rustworkx-core algorithms.
    """

    def __init__(self) -> None: ...
    def __repr__(self) -> str: ...

    def execute(
        self,
        query: str,
        params: Optional[dict[str, Any]] = None,
    ) -> list[dict[str, Any]]: ...

    def on_execute(self, callback: Callable[[str, float], None]) -> None:
        """Register a callback invoked after each execute() call.

        Args:
            callback: Called with (query: str, elapsed_seconds: float)
        """
        ...

    def metrics(self) -> dict[str, int]:
        """Return accumulated execution metrics.

        Returns a dict with keys:
            query_total, error_total, nodes_created, edges_created,
            nodes_deleted, edges_deleted, execute_nanos_total
        """
        ...

    def node_count(self) -> int: ...
    def edge_count(self) -> int: ...

    def create_node(
        self,
        labels: list[str],
        properties: Optional[dict[str, Any]] = None,
    ) -> int: ...

    def create_relationship(
        self,
        from_node: int,
        to_node: int,
        rel_type: str,
        properties: Optional[dict[str, Any]] = None,
    ) -> int: ...

    def save(self, path: str) -> None: ...

    @staticmethod
    def load(path: str) -> RustworkxGraph: ...

    # Centrality
    def degree_centrality(self) -> dict[int, float]: ...
    def betweenness_centrality(self, normalized: bool = True) -> dict[int, float]: ...
    def closeness_centrality(self, normalized: bool = True) -> dict[int, float]: ...
    def pagerank(self, damping: float = 0.85, max_iter: int = 100) -> dict[int, float]: ...

    # Pathfinding
    def bfs_path(self, source: int, target: int) -> Optional[list[int]]: ...
    def dijkstra_path(self, source: int, target: int) -> Optional[tuple[float, list[int]]]: ...
    def astar_path(
        self,
        source: int,
        target: int,
        heuristic: Optional[Callable[[int], float]] = None,
    ) -> Optional[tuple[float, list[int]]]: ...
    def bellman_ford_distances(self, source: int) -> Optional[dict[int, float]]: ...
    def floyd_warshall_distances(self) -> dict[tuple[int, int], float]: ...
    def all_pairs_distances(self) -> dict[int, dict[int, float]]: ...

    # Spanning Trees
    def minimum_spanning_tree(self) -> list[tuple[int, int, float]]: ...
    def maximum_spanning_tree(self) -> list[tuple[int, int, float]]: ...

    # DAG Algorithms
    def topological_sort(self) -> list[int]: ...
    def is_dag(self) -> bool: ...
    def find_cycles(self) -> list[list[int]]: ...
    def dag_longest_path(self) -> Optional[tuple[int, list[int]]]: ...
    def dag_longest_path_weighted(self) -> Optional[tuple[float, list[int]]]: ...
    def transitive_closure(self) -> list[tuple[int, int]]: ...

    # Network Flow
    def max_flow(self, source: int, sink: int) -> Optional[tuple[float, dict[tuple[int, int], float]]]: ...
    def min_cut_capacity(self, source: int, sink: int) -> Optional[float]: ...

    # Graph Coloring
    def node_coloring(self) -> dict[int, int]: ...
    def edge_coloring(self) -> dict[tuple[int, int], int]: ...
    def chromatic_number(self) -> int: ...

    # Matching
    def max_weight_matching(self) -> list[tuple[int, int]]: ...
    def max_cardinality_matching(self) -> list[tuple[int, int]]: ...

    # Community Detection
    def louvain_communities(self, resolution: Optional[float] = None) -> _CommunityResult: ...
    def label_propagation_communities(self) -> _CommunityResult: ...
    def girvan_newman_communities(self, k: int) -> _CommunityResult: ...

    # Components
    def connected_components(self) -> list[list[int]]: ...
    def strongly_connected_components(self) -> list[list[int]]: ...


# ---------------------------------------------------------------------------
# GraphrsGraph — petgraph StableDiGraph + graphrs community detection
# ---------------------------------------------------------------------------

class GraphrsGraph:
    """
    OpenCypher graph backed by petgraph StableDiGraph with graphrs community
    detection algorithms (Louvain, Leiden) and the full PetgraphAlgorithms suite.
    """

    def __init__(self) -> None: ...
    def __repr__(self) -> str: ...

    def execute(
        self,
        query: str,
        params: Optional[dict[str, Any]] = None,
    ) -> list[dict[str, Any]]: ...

    def on_execute(self, callback: Callable[[str, float], None]) -> None:
        """Register a callback invoked after each execute() call.

        Args:
            callback: Called with (query: str, elapsed_seconds: float)
        """
        ...

    def metrics(self) -> dict[str, int]:
        """Return accumulated execution metrics.

        Returns a dict with keys:
            query_total, error_total, nodes_created, edges_created,
            nodes_deleted, edges_deleted, execute_nanos_total
        """
        ...

    def node_count(self) -> int: ...
    def edge_count(self) -> int: ...

    def create_node(
        self,
        labels: list[str],
        properties: Optional[dict[str, Any]] = None,
    ) -> int: ...

    def create_relationship(
        self,
        from_node: int,
        to_node: int,
        rel_type: str,
        properties: Optional[dict[str, Any]] = None,
    ) -> int: ...

    def save(self, path: str) -> None: ...

    @staticmethod
    def load(path: str) -> GraphrsGraph: ...

    # Centrality
    def degree_centrality(self) -> dict[int, float]: ...
    def betweenness_centrality(self, normalized: bool = True) -> dict[int, float]: ...
    def closeness_centrality(self, normalized: bool = True) -> dict[int, float]: ...
    def pagerank(self, damping: float = 0.85, max_iter: int = 100) -> dict[int, float]: ...

    # Pathfinding
    def bfs_path(self, source: int, target: int) -> Optional[list[int]]: ...
    def dijkstra_path(self, source: int, target: int) -> Optional[tuple[float, list[int]]]: ...
    def astar_path(
        self,
        source: int,
        target: int,
        heuristic: Optional[Callable[[int], float]] = None,
    ) -> Optional[tuple[float, list[int]]]: ...
    def bellman_ford_distances(self, source: int) -> Optional[dict[int, float]]: ...
    def floyd_warshall_distances(self) -> dict[tuple[int, int], float]: ...
    def all_pairs_distances(self) -> dict[int, dict[int, float]]: ...

    # Spanning Trees
    def minimum_spanning_tree(self) -> list[tuple[int, int, float]]: ...
    def maximum_spanning_tree(self) -> list[tuple[int, int, float]]: ...

    # DAG Algorithms
    def topological_sort(self) -> list[int]: ...
    def is_dag(self) -> bool: ...
    def find_cycles(self) -> list[list[int]]: ...
    def dag_longest_path(self) -> Optional[tuple[int, list[int]]]: ...
    def dag_longest_path_weighted(self) -> Optional[tuple[float, list[int]]]: ...
    def transitive_closure(self) -> list[tuple[int, int]]: ...

    # Network Flow
    def max_flow(self, source: int, sink: int) -> Optional[tuple[float, dict[tuple[int, int], float]]]: ...
    def min_cut_capacity(self, source: int, sink: int) -> Optional[float]: ...

    # Graph Coloring
    def node_coloring(self) -> dict[int, int]: ...
    def edge_coloring(self) -> dict[tuple[int, int], int]: ...
    def chromatic_number(self) -> int: ...

    # Matching
    def max_weight_matching(self) -> list[tuple[int, int]]: ...
    def max_cardinality_matching(self) -> list[tuple[int, int]]: ...

    # Community Detection
    def louvain_communities(self, resolution: Optional[float] = None) -> _CommunityResult: ...
    def label_propagation_communities(self) -> _CommunityResult: ...
    def girvan_newman_communities(self, k: int) -> _CommunityResult: ...

    # Components
    def connected_components(self) -> list[list[int]]: ...
    def strongly_connected_components(self) -> list[list[int]]: ...


# ---------------------------------------------------------------------------
# Top-level convenience function
# ---------------------------------------------------------------------------

def execute(
    query: str,
    params: Optional[dict[str, Any]] = None,
) -> list[dict[str, Any]]:
    """
    Execute an openCypher query on a fresh temporary graph.

    This is a convenience wrapper that creates a new Graph, runs the query,
    and returns the result rows.

    Args:
        query:  openCypher query string.
        params: Optional dict of query parameters.

    Returns:
        List of result row dicts.

    Example:
        >>> from ocg import execute
        >>> result = execute("RETURN 1 AS num")
        >>> print(result[0]['num'])
        1
    """
    ...
