/**
 * NeighborProvider backed by in-memory adjacency maps.
 *
 * Wraps the { outgoing, incoming } Maps produced by _buildAdjacency().
 * Adjacency lists are pre-sorted at construction by (neighborId, label)
 * using strict codepoint comparison. Label filtering via Set.has() in-memory.
 *
 * @module domain/services/query/AdjacencyNeighborProvider
 */
import NeighborProviderPort from "../../../ports/NeighborProviderPort.js";
import QueryError from "../../errors/QueryError.js";
function edgeCmp(a, b) {
    if (a.neighborId < b.neighborId) {
        return -1;
    }
    if (a.neighborId > b.neighborId) {
        return 1;
    }
    if (a.label < b.label) {
        return -1;
    }
    if (a.label > b.label) {
        return 1;
    }
    return 0;
}
function sortAdjacencyMap(adjMap) {
    const result = new Map();
    for (const [nodeId, edges] of adjMap) {
        result.set(nodeId, edges.slice().sort(edgeCmp));
    }
    return result;
}
function filterByLabels(edges, labels) {
    if (labels === undefined) {
        return edges;
    }
    return edges.filter((e) => labels.has(e.label));
}
function mergeSorted(a, b) {
    const result = [];
    let i = 0;
    let j = 0;
    while (i < a.length && j < b.length) {
        const ai = a[i];
        const bj = b[j];
        const cmp = edgeCmp(ai, bj);
        result.push(cmp <= 0 ? ai : bj);
        if (cmp <= 0) {
            i++;
        }
        if (cmp >= 0) {
            j++;
        }
    }
    for (let k = i; k < a.length; k++) {
        result.push(a[k]);
    }
    for (let k = j; k < b.length; k++) {
        result.push(b[k]);
    }
    return result;
}
export default class AdjacencyNeighborProvider extends NeighborProviderPort {
    _outgoing;
    _incoming;
    _aliveNodes;
    constructor(params) {
        super();
        const { outgoing, incoming, aliveNodes } = params;
        if (aliveNodes === undefined || aliveNodes === null) {
            throw new QueryError('AdjacencyNeighborProvider: aliveNodes is required', { code: 'E_ADJACENCY_NO_ALIVE_NODES' });
        }
        this._outgoing = sortAdjacencyMap(outgoing);
        this._incoming = sortAdjacencyMap(incoming);
        this._aliveNodes = aliveNodes;
    }
    getNeighbors(nodeId, direction, options) {
        const labels = options?.labels;
        return Promise.resolve(this._resolveEdges(nodeId, direction, labels));
    }
    _filteredEdges(adjMap, nodeId, labels) {
        return filterByLabels(adjMap.get(nodeId) ?? [], labels);
    }
    _resolveEdges(nodeId, direction, labels) {
        if (direction === 'out') {
            return this._filteredEdges(this._outgoing, nodeId, labels);
        }
        if (direction === 'in') {
            return this._filteredEdges(this._incoming, nodeId, labels);
        }
        return mergeSorted(this._filteredEdges(this._outgoing, nodeId, labels), this._filteredEdges(this._incoming, nodeId, labels));
    }
    hasNode(nodeId) {
        return Promise.resolve(this._aliveNodes.has(nodeId));
    }
    get latencyClass() {
        return 'sync';
    }
}
