declare class Graph<Node = string, LinkProps = never> {
    /**
     * Contains all the nodes added to the graph.
     */
    nodes: Set<Node>;
    /**
     * The adjacency list of the graph.
     */
    edges: Map<Node, Set<Node>>;
    /**
     * The weights of edges.
     *
     * Map<SourceNode, Map<TargetNode, EdgeWeight>>
     */
    edgeWeights: Map<Node, Map<Node, EdgeWeight>>;
    /**
     * Arbitrary properties of edges.
     * Map<SourceNode, Map<TargetNode, EdgeProperties>>
     */
    edgeProperties: Map<Node, Map<Node, LinkProps>>;
    /**
     * Adds a node to the graph.
     * If node was already added, this function does nothing.
     * If node was not already added, this function sets up an empty adjacency list.
     */
    addNode(node: Node): this;
    /**
     * Removes a node from the graph.
     * Also removes incoming and outgoing edges.
     */
    removeNode(node: Node): this;
    /**
     * Gets the adjacent nodes set for the given node.
     */
    adjacent(node: Node): Set<Node> | undefined;
    /**
     * Sets the weight of the given edge.
     */
    setEdgeWeight(source: Node, target: Node, weight: EdgeWeight): this;
    /**
     * Gets the weight of the given edge or `1` if not set.
     */
    getEdgeWeight(source: Node, target: Node): number;
    /**
     * Set the properties of the given edge.
     */
    setEdgeProperties(source: Node, target: Node, props: LinkProps): this;
    /**
     * Get the properties of the given edge or undefined if the edge doesn't exist .
     */
    getEdgeProperties(source: Node, target: Node): LinkProps | undefined;
    /**
     * Adds an edge from the `source` node to `target` node.
     * This method will create the nodes if they were not already added.
     */
    addEdge(source: Node, target: Node, ...args: AddEdgeArgs<LinkProps>): this;
    /**
     * Removes the edge from the `source` node to `target` node.
     * Does not remove the nodes themselves.
     * Does nothing if the edge does not exist.
     */
    removeEdge(source: Node, target: Node): this;
    /**
     * Returns true if there is an edge from the `source` node to `target` node..
     */
    hasEdge(source: Node, target: Node): boolean;
}
type AddEdgeArgs<LinkProps> = [LinkProps] extends [never] ? [weight?: EdgeWeight] | [opts?: {
    weight?: EdgeWeight;
}] : [opts: {
    weight?: EdgeWeight;
    props: LinkProps;
}];

type EdgeWeight = number;
type Edge<NodeIdentity = unknown, Props = unknown> = {
    source: NodeIdentity;
    target: NodeIdentity;
    weight?: EdgeWeight;
    props: Props;
};
type Serialized<Node = unknown, LinkProps = unknown, NodeIdentity = Node> = {
    nodes: Node[];
    links: Edge<NodeIdentity, LinkProps>[];
};
type SerializedInput<Node = unknown, LinkProps = unknown> = {
    nodes: ReadonlyArray<Node>;
    links: ReadonlyArray<Edge<Node, LinkProps>>;
};
type NoInfer<T> = [T][T extends any ? 0 : never];
type NextWeightFnParams<Node = unknown, LinkProps = unknown> = {
    edgeWeight: EdgeWeight;
    currentPathWeight: EdgeWeight | undefined;
    hop: number;
    graph: Graph<Node, LinkProps>;
    path: readonly [NoInfer<Node>, NoInfer<Node>, ...NoInfer<Node>[]];
    previousNode: NoInfer<Node> | undefined;
    currentNode: NoInfer<Node>;
    props: LinkProps;
};

declare class CycleError extends Error {
    constructor(message: string);
}

type DepthFirstSearchOptions<Node, LinkProps> = {
    /**
     * Use those nodes as source nodes.
     * @default all the nodes in the graph are used as source nodes.
     */
    sourceNodes?: Node[];
    /**
     * Include or exclude the source nodes from the result.
     * @default true
     */
    includeSourceNodes?: boolean;
    /**
     * Throw an error when a cycle is detected.
     * @default false
     */
    errorOnCycle?: boolean;
    /**
     * A function that is executed to determine if the branch should be visited or not.
     * @param source the current node
     * @param target the next node to explore
     * @param graph the graph instance being explored
     * @returns boolean
     */
    shouldFollow?: (args: {
        source: Node;
        target: Node;
        props: LinkProps;
        graph: Graph<Node, LinkProps>;
    }) => boolean;
};

/**
 * Depth First Search algorithm, inspired by
 * Cormen et al. "Introduction to Algorithms" 3rd Ed. p. 604
 */
declare function depthFirstSearch<Node, LinkProps>(graph: Graph<Node, LinkProps>, opts?: DepthFirstSearchOptions<NoInfer<Node>, NoInfer<LinkProps>>): Node[];

/**
 * Dijkstra's Shortest Path Algorithm.
 * Cormen et al. "Introduction to Algorithms" 3rd Ed. p. 658
 * Variable and function names correspond to names in the book.
 */
declare function shortestPath<Node, LinkProps>(graph: Graph<Node, LinkProps>, source: NoInfer<Node>, destination: NoInfer<Node>, nextWeightFn?: (params: NextWeightFnParams) => number): {
    nodes: [Node, Node, ...Node[]];
    weight: number | undefined;
};

declare function shortestPaths<Node, LinkProps>(graph: Graph<Node, LinkProps>, source: NoInfer<Node>, destination: NoInfer<Node>): {
    nodes: [Node, Node, ...Node[]];
    weight: number | undefined;
}[];

/**
 * The topological sort algorithm yields a list of visited nodes
 * such that for each visited edge (u, v), u comes before v in the list.
 * Amazingly, this comes from just reversing the result from depth first search.
 * Cormen et al. "Introduction to Algorithms" 3rd Ed. p. 613
 */

type TopologicalSortOptions<Node, LinkProps> = {
    /**
     * Run the first on those nodes.
     * @default all the nodes of the graph.
     */
    sourceNodes?: Node[];
    /**
     * Include the source nodes from the result.
     * @default true
     */
    includeSourceNodes?: boolean;
    /**
     * A function that is executed to determine if the branch should be visited or not.
     * @param source the current node
     * @param target the next node to explore
     * @param graph the graph instance being explored
     * @returns boolean
     */
    shouldFollow?: (args: {
        source: Node;
        target: Node;
        props: LinkProps;
        graph: Graph<Node, LinkProps>;
    }) => boolean;
};
declare function topologicalSort<Node, LinkProps>(graph: Graph<Node, LinkProps>, opts?: TopologicalSortOptions<NoInfer<Node>, NoInfer<LinkProps>>): Node[];

/**
 * Return an array containing the lowest common ancestors.
 *
 * Inspired by https://github.com/relaxedws/lca/blob/master/src/LowestCommonAncestor.php code
 * but uses depth search instead of breadth. Also uses some optimizations.
 */
declare function lowestCommonAncestors<Node, LinkProps>(graph: Graph<Node, LinkProps>, node1: NoInfer<Node>, node2: NoInfer<Node>): Node[];

/**
 * Computes the indegree for the given node.
 * Not very efficient, costs O(E) where E = number of edges.
 */
declare function indegree<Node>(graph: Graph<Node>, node: NoInfer<Node>): number;

declare function outdegree<Node>(graph: Graph<Node>, node: NoInfer<Node>): number;

/**
 * Clone the graph data structures.
 * Nodes references are preserves.
 */
declare function cloneGraph<Node, LinkProps>(graph: Graph<Node, LinkProps>): Graph<Node, LinkProps>;

/**
 * Perform a depth first search to detect an eventual cycle.
 *
 * You can provide a `shouldFollow` function to constrain the traversing and
 * provide `sourceNodes` to explore a particular sub-graphs.
 */
declare function hasCycle<Node, LinkProps>(graph: Graph<Node, LinkProps>, opts?: Pick<DepthFirstSearchOptions<Node, LinkProps>, 'shouldFollow' | 'sourceNodes'>): boolean;

/**
 * Serializes the graph.
 */

type SerializeGraphOptions<IncludeDefaultWeight extends boolean = false> = {
    /**
     * If no weight is set on an edge, the default weight is `1`.
     * When `includeDefaultWeight: true`, the serialization will include the edge's weight even when none is set or
     * the value has been set to `1`.
     *
     * @default false
     */
    includeDefaultWeight?: IncludeDefaultWeight;
};
/**
 * Serialize the graph data set : nodes, edges, edges weight & properties.
 *
 * Optionally, you can pass a function that returns a unique value for a given node.
 * When provided, the function will be used to avoid data duplication in the serialized object.
 */
declare function serializeGraph<Node, LinkProps, IncludeDefaultWeight extends boolean, NodeIdentity = Node>(graph: Graph<Node, LinkProps>, ...args: [
    identityFn: (node: NoInfer<Node>) => NodeIdentity,
    SerializeGraphOptions<IncludeDefaultWeight>?
] | [SerializeGraphOptions<IncludeDefaultWeight>?]): Serialized<Node, LinkProps, NodeIdentity>;

declare function deserializeGraph<Node, LinkProps, NodeIdentity>(...args: Node extends object ? [
    data: SerializedInput<Node, LinkProps>,
    identityFn: (node: NoInfer<Node>) => NodeIdentity
] : [data: SerializedInput<Node, LinkProps>]): Graph<Node, LinkProps>;

/**
 * Returns all the nodes matching your function.
 */
declare function findNodes<Node>(graph: Graph<Node, any>, fn: (node: NoInfer<Node>) => boolean): Node[];

/**
 * Return the node matching your function. Throws if none is found or if more than one node if found.
 */
declare function getNode<Node>(graph: Graph<Node, any>, fn: (node: NoInfer<Node>) => boolean): Node;

/**
 * Return the first node matching your function and throws if none is found.
 */
declare function getFirstNode<Node>(graph: Graph<Node, any>, fn: (node: NoInfer<Node>) => boolean): Node;

export { CycleError, type Edge, type EdgeWeight, Graph, type NextWeightFnParams, type Serialized, type SerializedInput, cloneGraph, depthFirstSearch, deserializeGraph, findNodes, getFirstNode, getNode, hasCycle, indegree, lowestCommonAncestors, outdegree, serializeGraph, shortestPath, shortestPaths, topologicalSort };
