/**
 * Multi-node graph function
 * @template N
 * @param {Graph<MultiNode<N>>} graph
 * @param {WeightedEdge<MultiNode<N>>} edge
 * @param {number} source_limit
 * @param {BinaryHeap<Edge>} heap
 * @returns {boolean} whether collapse was performed or rejected
 */
export function mn_graph_collapse_weighted_edge(
    graph,
    edge,
    source_limit,
    heap
) {

    const successor_node = edge.first;
    const victim_node = edge.second;

    // new count
    const total_source_count = victim_node.source_nodes.length + successor_node.source_nodes.length;

    if (total_source_count > source_limit) {
        return false;
    }

    // remove original edge from the graph
    graph.removeEdge(edge);

    // join multi-nodes
    const b_ctx = graph.getNodeContainer(victim_node);

    // patch graph
    const b_edges = b_ctx.getEdges();

    const b_edge_count = b_edges.length;

    for (let i = b_edge_count - 1; i >= 0; i--) {

        const b_edge = b_edges[i];

        const other = b_edge.other(victim_node);

        graph.removeEdge(b_edge);

        let target_edge = graph.getAnyEdgeBetween(successor_node, other);

        if (target_edge === undefined) {
            // re-use edge
            if (b_edge.first === other) {
                b_edge.second = successor_node;
            } else {
                b_edge.first = successor_node;
            }

            graph.addEdge(b_edge);

            target_edge = b_edge;
        } else {
            target_edge.weight += b_edge.weight;

            heap.delete(b_edge);
        }

        const heap_index = heap.data.indexOf(target_edge);

        if (heap_index !== -1) {
            // updated edge is in the heap, re-score
            heap.bubbleDown(heap_index);
        }

    }

    // move sources from victim to successor
    successor_node.add(victim_node);

    // remove victim node
    graph.removeNode(victim_node);

    return true;
}
