import { AbstractEdge, AbstractGraph, AbstractVertex } from './abstract-graph';
import { arrayRemove } from '../../utils';
export class UndirectedVertex extends AbstractVertex {
    /**
     * The constructor function initializes a vertex with an optional value.
     * @param {VertexKey} key - The `key` parameter is of type `VertexKey` and represents the identifier of the vertex. It is
     * used to uniquely identify the vertex within a graph or network.
     * @param {V} [value] - The "value" parameter is an optional parameter of type V. It is used to initialize the value of the
     * vertex. If no value is provided, the vertex will be initialized with a default value.
     */
    constructor(key, value) {
        super(key, value);
    }
}
export class UndirectedEdge extends AbstractEdge {
    endpoints;
    /**
     * The constructor function creates an instance of a class with two vertex IDs, an optional weight, and an optional
     * value.
     * @param {VertexKey} v1 - The first vertex ID of the edge.
     * @param {VertexKey} v2 - The parameter `v2` is a `VertexKey`, which represents the identifier of the second vertex in a
     * graph edge.
     * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge.
     * @param {E} [value] - The "value" parameter is an optional parameter of type E. It is used to store a value associated
     * with the edge.
     */
    constructor(v1, v2, weight, value) {
        super(weight, value);
        this.endpoints = [v1, v2];
    }
}
/**
 *
 */
export class UndirectedGraph extends AbstractGraph {
    /**
     * The constructor initializes a new Map object to store edgeMap.
     */
    constructor() {
        super();
        this._edgeMap = new Map();
    }
    _edgeMap;
    get edgeMap() {
        return this._edgeMap;
    }
    set edgeMap(v) {
        this._edgeMap = v;
    }
    /**
     * The function creates a new vertex with an optional value and returns it.
     * @param {VertexKey} key - The `key` parameter is the unique identifier for the vertex. It is used to distinguish one
     * vertex from another in the graph.
     * @param [value] - The `value` parameter is an optional value that can be assigned to the vertex. If a value is provided,
     * it will be used as the value of the vertex. If no value is provided, the `key` parameter will be used as the value of
     * the vertex.
     * @returns The method is returning a new instance of the `UndirectedVertex` class, casted as type `VO`.
     */
    createVertex(key, value) {
        return new UndirectedVertex(key, value ?? key);
    }
    /**
     * The function creates an undirected edge between two vertexMap with an optional weight and value.
     * @param {VertexKey} v1 - The parameter `v1` represents the first vertex of the edge.
     * @param {VertexKey} v2 - The parameter `v2` represents the second vertex of the edge.
     * @param {number} [weight] - The `weight` parameter is an optional number that represents the weight of the edge. If
     * no weight is provided, it defaults to 1.
     * @param [value] - The `value` parameter is an optional value that can be assigned to the edge. It can be of any type and
     * is used to store additional information or data associated with the edge.
     * @returns a new instance of the `UndirectedEdge` class, which is casted as type `EO`.
     */
    createEdge(v1, v2, weight, value) {
        return new UndirectedEdge(v1, v2, weight ?? 1, value);
    }
    /**
     * Time Complexity: O(|E|), where |E| is the number of edgeMap incident to the given vertex.
     * Space Complexity: O(1)
     *
     * The function `getEdge` returns the first edge that connects two endpoints, or undefined if no such edge exists.
     * @param {VO | VertexKey | undefined} v1 - The parameter `v1` represents a vertex or vertex ID. It can be of type `VO` (vertex
     * object), `undefined`, or `VertexKey` (a string or number representing the ID of a vertex).
     * @param {VO | VertexKey | undefined} v2 - The parameter `v2` represents a vertex or vertex ID. It can be of type `VO` (vertex
     * object), `undefined`, or `VertexKey` (vertex ID).
     * @returns an edge (EO) or undefined.
     */
    getEdge(v1, v2) {
        let edgeMap = [];
        if (v1 !== undefined && v2 !== undefined) {
            const vertex1 = this._getVertex(v1);
            const vertex2 = this._getVertex(v2);
            if (vertex1 && vertex2) {
                edgeMap = this._edgeMap.get(vertex1)?.filter(e => e.endpoints.includes(vertex2.key));
            }
        }
        return edgeMap ? edgeMap[0] || undefined : undefined;
    }
    /**
     * Time Complexity: O(|E|), where |E| is the number of edgeMap incident to the given vertex.
     * Space Complexity: O(1)
     *
     * The function removes an edge between two vertexMap in a graph and returns the removed edge.
     * @param {VO | VertexKey} v1 - The parameter `v1` represents either a vertex object (`VO`) or a vertex ID (`VertexKey`).
     * @param {VO | VertexKey} v2 - VO | VertexKey - This parameter can be either a vertex object (VO) or a vertex ID
     * (VertexKey). It represents the second vertex of the edge that needs to be removed.
     * @returns the removed edge (EO) if it exists, or undefined if either of the endpoints (VO) does not exist.
     */
    deleteEdgeBetween(v1, v2) {
        const vertex1 = this._getVertex(v1);
        const vertex2 = this._getVertex(v2);
        if (!vertex1 || !vertex2) {
            return undefined;
        }
        const v1Edges = this._edgeMap.get(vertex1);
        let removed = undefined;
        if (v1Edges) {
            removed = arrayRemove(v1Edges, (e) => e.endpoints.includes(vertex2.key))[0] || undefined;
        }
        const v2Edges = this._edgeMap.get(vertex2);
        if (v2Edges) {
            arrayRemove(v2Edges, (e) => e.endpoints.includes(vertex1.key));
        }
        return removed;
    }
    /**
     * Time Complexity: O(E), where E is the number of edgeMap incident to the given vertex.
     * Space Complexity: O(1)
     *
     * The function `deleteEdge` deletes an edge between two endpoints in a graph.
     * @param {EO | VertexKey} edgeOrOneSideVertexKey - The parameter `edgeOrOneSideVertexKey` can be
     * either an edge object or a vertex key.
     * @param {VertexKey} [otherSideVertexKey] - The parameter `otherSideVertexKey` is an optional
     * parameter that represents the key of the vertex on the other side of the edge. It is used when the
     * `edgeOrOneSideVertexKey` parameter is a vertex key, and it specifies the key of the vertex on the
     * other side of the
     * @returns The `deleteEdge` function returns either the deleted edge object (EO) or `undefined`.
     */
    deleteEdge(edgeOrOneSideVertexKey, otherSideVertexKey) {
        let oneSide, otherSide;
        if (this.isVertexKey(edgeOrOneSideVertexKey)) {
            if (this.isVertexKey(otherSideVertexKey)) {
                oneSide = this._getVertex(edgeOrOneSideVertexKey);
                otherSide = this._getVertex(otherSideVertexKey);
            }
            else {
                return;
            }
        }
        else {
            oneSide = this._getVertex(edgeOrOneSideVertexKey.endpoints[0]);
            otherSide = this._getVertex(edgeOrOneSideVertexKey.endpoints[1]);
        }
        if (oneSide && otherSide) {
            return this.deleteEdgeBetween(oneSide, otherSide);
        }
        else {
            return;
        }
    }
    /**
     * Time Complexity: O(1) - Constant time for Map operations.
     * Space Complexity: O(1) - Constant space, as it creates only a few variables.
     *
     * The `deleteVertex` function removes a vertex from a graph by its ID or by the vertex object itself.
     * @param {VO | VertexKey} vertexOrKey - The parameter `vertexOrKey` can be either a vertex object (`VO`) or a vertex ID
     * (`VertexKey`).
     * @returns The method is returning a boolean value.
     */
    deleteVertex(vertexOrKey) {
        let vertexKey;
        let vertex;
        if (this.isVertexKey(vertexOrKey)) {
            vertex = this.getVertex(vertexOrKey);
            vertexKey = vertexOrKey;
        }
        else {
            vertex = vertexOrKey;
            vertexKey = this._getVertexKey(vertexOrKey);
        }
        const neighbors = this.getNeighbors(vertexOrKey);
        if (vertex) {
            neighbors.forEach(neighbor => {
                const neighborEdges = this._edgeMap.get(neighbor);
                if (neighborEdges) {
                    const restEdges = neighborEdges.filter(edge => {
                        return !edge.endpoints.includes(vertexKey);
                    });
                    this._edgeMap.set(neighbor, restEdges);
                }
            });
            this._edgeMap.delete(vertex);
        }
        return this._vertexMap.delete(vertexKey);
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function `degreeOf` returns the degree of a vertex in a graph, which is the number of edgeMap connected to that
     * vertex.
     * @param {VertexKey | VO} vertexOrKey - The parameter `vertexOrKey` can be either a `VertexKey` or a `VO`.
     * @returns The function `degreeOf` returns the degree of a vertex in a graph. The degree of a vertex is the number of
     * edgeMap connected to that vertex.
     */
    degreeOf(vertexOrKey) {
        const vertex = this._getVertex(vertexOrKey);
        if (vertex) {
            return this._edgeMap.get(vertex)?.length || 0;
        }
        else {
            return 0;
        }
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function returns the edgeMap of a given vertex or vertex ID.
     * @param {VertexKey | VO} vertexOrKey - The parameter `vertexOrKey` can be either a `VertexKey` or a `VO`. A `VertexKey` is a
     * unique identifier for a vertex in a graph, while `VO` represents the type of the vertex.
     * @returns an array of edgeMap.
     */
    edgesOf(vertexOrKey) {
        const vertex = this._getVertex(vertexOrKey);
        if (vertex) {
            return this._edgeMap.get(vertex) || [];
        }
        else {
            return [];
        }
    }
    /**
     * Time Complexity: O(|V| + |E|), where |V| is the number of vertexMap and |E| is the number of edgeMap.
     * Space Complexity: O(|E|)
     *
     * The function "edgeSet" returns an array of unique edgeMap from a set of edgeMap.
     * @returns The method `edgeSet()` returns an array of type `EO[]`.
     */
    edgeSet() {
        const edgeSet = new Set();
        this._edgeMap.forEach(edgeMap => {
            edgeMap.forEach(edge => {
                edgeSet.add(edge);
            });
        });
        return [...edgeSet];
    }
    /**
     * Time Complexity: O(|V| + |E|), where |V| is the number of vertexMap and |E| is the number of edgeMap.
     * Space Complexity: O(|E|)
     *
     * The function "getNeighbors" returns an array of neighboring endpoints for a given vertex or vertex ID.
     * @param {VO | VertexKey} vertexOrKey - The parameter `vertexOrKey` can be either a vertex object (`VO`) or a vertex ID
     * (`VertexKey`).
     * @returns an array of vertexMap (VO[]).
     */
    getNeighbors(vertexOrKey) {
        const neighbors = [];
        const vertex = this._getVertex(vertexOrKey);
        if (vertex) {
            const neighborEdges = this.edgesOf(vertex);
            for (const edge of neighborEdges) {
                const neighbor = this._getVertex(edge.endpoints.filter(e => e !== vertex.key)[0]);
                if (neighbor) {
                    neighbors.push(neighbor);
                }
            }
        }
        return neighbors;
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function "getEndsOfEdge" returns the endpoints at the ends of an edge if the edge exists in the graph, otherwise
     * it returns undefined.
     * @param {EO} edge - The parameter "edge" is of type EO, which represents an edge in a graph.
     * @returns The function `getEndsOfEdge` returns an array containing two endpoints `[VO, VO]` if the edge exists in the
     * graph. If the edge does not exist, it returns `undefined`.
     */
    getEndsOfEdge(edge) {
        if (!this.hasEdge(edge.endpoints[0], edge.endpoints[1])) {
            return undefined;
        }
        const v1 = this._getVertex(edge.endpoints[0]);
        const v2 = this._getVertex(edge.endpoints[1]);
        if (v1 && v2) {
            return [v1, v2];
        }
        else {
            return undefined;
        }
    }
    /**
     * The isEmpty function checks if the graph is empty.
     * @return True if the graph is empty and false otherwise
     */
    isEmpty() {
        return this.vertexMap.size === 0 && this.edgeMap.size === 0;
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The clear function resets the vertex and edge maps to empty maps.
     */
    clear() {
        this._vertexMap = new Map();
        this._edgeMap = new Map();
    }
    /**
     * The clone function creates a new UndirectedGraph object and copies the
     * vertexMap and edgeMap from this graph to the new one. This is done by
     * assigning each of these properties to their respective counterparts in the
     * cloned graph. The clone function returns a reference to this newly created,
     * cloned UndirectedGraph object.
     *
     * @return A new instance of the undirectedgraph class
     */
    clone() {
        const cloned = new UndirectedGraph();
        cloned.vertexMap = new Map(this.vertexMap);
        cloned.edgeMap = new Map(this.edgeMap);
        return cloned;
    }
    /**
     *  Time Complexity: O(V + E)
     *  Space Complexity: O(V)
     *   Tarjan is an algorithm based on dfs,which is used to solve the connectivity problem of graphs.
     *  1. Tarjan can find the articulation points and bridges(critical edgeMap) of undirected graphs in linear time
     *
     * The function `tarjan` implements the Tarjan's algorithm to find bridges and cut vertices in a
     * graph.
     * @returns The function `tarjan()` returns an object with the following properties:
     */
    tarjan() {
        const dfnMap = new Map();
        const lowMap = new Map();
        const bridges = [];
        const cutVertices = [];
        let time = 0;
        const dfs = (vertex, parent) => {
            dfnMap.set(vertex, time);
            lowMap.set(vertex, time);
            time++;
            const neighbors = this.getNeighbors(vertex);
            let childCount = 0;
            for (const neighbor of neighbors) {
                if (!dfnMap.has(neighbor)) {
                    childCount++;
                    dfs(neighbor, vertex);
                    lowMap.set(vertex, Math.min(lowMap.get(vertex), lowMap.get(neighbor)));
                    if (lowMap.get(neighbor) > dfnMap.get(vertex)) {
                        // Found a bridge
                        const edge = this.getEdge(vertex, neighbor);
                        if (edge) {
                            bridges.push(edge);
                        }
                    }
                    if (parent !== undefined && lowMap.get(neighbor) >= dfnMap.get(vertex)) {
                        // Found an articulation point
                        cutVertices.push(vertex);
                    }
                }
                else if (neighbor !== parent) {
                    lowMap.set(vertex, Math.min(lowMap.get(vertex), dfnMap.get(neighbor)));
                }
            }
            if (parent === undefined && childCount > 1) {
                // Special case for root in DFS tree
                cutVertices.push(vertex);
            }
        };
        for (const vertex of this.vertexMap.values()) {
            if (!dfnMap.has(vertex)) {
                dfs(vertex, undefined);
            }
        }
        return {
            dfnMap,
            lowMap,
            bridges,
            cutVertices
        };
    }
    /**
     * The function "getBridges" returns an array of bridges in a graph using the Tarjan's algorithm.
     * @returns The function `getBridges()` is returning the bridges found using the Tarjan's algorithm.
     */
    getBridges() {
        return this.tarjan().bridges;
    }
    /**
     * The function "getCutVertices" returns an array of cut vertices using the Tarjan's algorithm.
     * @returns the cut vertices found using the Tarjan's algorithm.
     */
    getCutVertices() {
        return this.tarjan().cutVertices;
    }
    /**
     * The function returns the dfnMap property of the result of the tarjan() function.
     * @returns the `dfnMap` property of the result of calling the `tarjan()` function.
     */
    getDFNMap() {
        return this.tarjan().dfnMap;
    }
    /**
     * The function returns the lowMap property of the result of the tarjan() function.
     * @returns the lowMap property of the result of calling the tarjan() function.
     */
    getLowMap() {
        return this.tarjan().lowMap;
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function adds an edge to the graph by updating the adjacency list with the vertexMap of the edge.
     * @param {EO} edge - The parameter "edge" is of type EO, which represents an edge in a graph.
     * @returns a boolean value.
     */
    _addEdge(edge) {
        for (const end of edge.endpoints) {
            const endVertex = this._getVertex(end);
            if (endVertex === undefined)
                return false;
            if (endVertex) {
                const edgeMap = this._edgeMap.get(endVertex);
                if (edgeMap) {
                    edgeMap.push(edge);
                }
                else {
                    this._edgeMap.set(endVertex, [edge]);
                }
            }
        }
        return true;
    }
}
//# sourceMappingURL=undirected-graph.js.map