from collections import Counter
from collections.abc import Iterable
from itertools import chain

from numpy.typing import ArrayLike

from ..classes.graph import Graph
from ..classes.graphviews import reverse_view, subgraph_view
from ..utils import not_implemented_for, pairwise

__all__ = [
    "nodes",
    "edges",
    "degree",
    "degree_histogram",
    "neighbors",
    "number_of_nodes",
    "number_of_edges",
    "density",
    "is_directed",
    "info",
    "freeze",
    "is_frozen",
    "subgraph",
    "subgraph_view",
    "induced_subgraph",
    "reverse_view",
    "edge_subgraph",
    "restricted_view",
    "to_directed",
    "to_undirected",
    "add_star",
    "add_path",
    "add_cycle",
    "create_empty_copy",
    "set_node_attributes",
    "get_node_attributes",
    "set_edge_attributes",
    "get_edge_attributes",
    "all_neighbors",
    "non_neighbors",
    "non_edges",
    "common_neighbors",
    "is_weighted",
    "is_negatively_weighted",
    "is_empty",
    "selfloop_edges",
    "nodes_with_selfloops",
    "number_of_selfloops",
    "path_weight",
    "is_path",
]

def nodes(G: Graph): ...
def edges(G: Graph, nbunch=None): ...
def degree(G: Graph, nbunch=None, weight=None): ...
def neighbors(G: Graph, n): ...
def number_of_nodes(G: Graph): ...
def number_of_edges(G: Graph): ...
def density(G: Graph): ...
def degree_histogram(G: Graph) -> ArrayLike: ...
def is_directed(G: Graph): ...
def frozen(*args, **kwargs): ...
def freeze(G: Graph): ...
def is_frozen(G: Graph): ...
def add_star(G_to_add_to, nodes_for_star, **attr): ...
def add_path(G_to_add_to, nodes_for_path, **attr): ...
def add_cycle(G_to_add_to, nodes_for_cycle, **attr): ...
def subgraph(G: Graph, nbunch: ArrayLike | Iterable): ...
def induced_subgraph(G: Graph, nbunch): ...
def edge_subgraph(G: Graph, edges: Iterable): ...
def restricted_view(G: Graph, nodes: Iterable, edges: Iterable): ...
def to_directed(graph): ...
def to_undirected(graph): ...
def create_empty_copy(G: Graph, with_data=True): ...
def info(G: Graph, n=None) -> str: ...
def set_node_attributes(G: Graph, values, name=None): ...
def get_node_attributes(G: Graph, name: str): ...
def set_edge_attributes(G: Graph, values, name=None): ...
def get_edge_attributes(G: Graph, name: str): ...
def all_neighbors(graph, node): ...
def non_neighbors(graph, node): ...
def non_edges(graph): ...
def common_neighbors(G: Graph, u, v): ...
def is_weighted(G: Graph, edge: tuple | None = None, weight: str = "weight") -> bool: ...
def is_negatively_weighted(G: Graph, edge: tuple | None = None, weight: str = "weight") -> bool: ...
def is_empty(G: Graph) -> bool: ...
def nodes_with_selfloops(G: Graph): ...
def selfloop_edges(G: Graph, data=False, keys=False, default=None): ...
def number_of_selfloops(G: Graph) -> int: ...
def is_path(G: Graph, path: ArrayLike) -> bool: ...
def path_weight(G: Graph, path: ArrayLike, weight: str) -> int | float: ...
