/**
 * NeighborProvider backed by bitmap indexes.
 *
 * Two modes:
 * 1. **Commit DAG** (`indexReader`): Wraps BitmapIndexReader for parent/child
 *    relationships. Edges use label = '' (empty string sentinel).
 * 2. **Logical graph** (`logicalIndex`): Wraps CBOR-based logical bitmap index
 *    with labeled edges, per-label bitmap filtering, and alive bitmap checks.
 *
 * @module domain/services/index/BitmapNeighborProvider
 */
import NeighborProviderPort from "../../../ports/NeighborProviderPort.js";
import IndexError from "../../errors/IndexError.js";
function sortEdges(edges) {
    return edges.sort((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 dedupSorted(edges) {
    if (edges.length <= 1) {
        return edges;
    }
    const first = edges[0];
    if (first === undefined || first === null) {
        return edges;
    }
    const result = [first];
    for (let i = 1; i < edges.length; i++) {
        const prev = result[result.length - 1];
        const curr = edges[i];
        if (prev !== undefined && prev !== null &&
            curr !== undefined && curr !== null &&
            (curr.neighborId !== prev.neighborId || curr.label !== prev.label)) {
            result.push(curr);
        }
    }
    return result;
}
export default class BitmapNeighborProvider extends NeighborProviderPort {
    _reader;
    _logical;
    constructor(params) {
        const { indexReader, logicalIndex } = params ?? {};
        super();
        this._reader = indexReader ?? null;
        this._logical = logicalIndex ?? null;
    }
    _assertReady() {
        if (!this._reader && !this._logical) {
            throw new IndexError('BitmapNeighborProvider requires either indexReader or logicalIndex', { code: 'E_NEIGHBOR_PROVIDER_NO_BACKEND' });
        }
    }
    async getNeighbors(nodeId, direction, options) {
        this._assertReady();
        if (this._logical) {
            return this._getLogicalNeighbors(nodeId, direction, options);
        }
        return await this._getDagNeighbors(nodeId, direction, options);
    }
    async hasNode(nodeId) {
        this._assertReady();
        if (this._logical) {
            return this._logical.isAlive(nodeId);
        }
        if (this._reader) {
            const id = await this._reader.lookupId(nodeId);
            return id !== undefined;
        }
        throw new IndexError('BitmapNeighborProvider readiness check passed without an active backend', { code: 'E_NEIGHBOR_PROVIDER_INVALID_BACKEND' });
    }
    get latencyClass() {
        return 'async-local';
    }
    // ── Commit DAG mode ─────────────────────────────────────────────────
    async _getDagSingleDirection(reader, nodeId, dir) {
        const shas = dir === 'out'
            ? await reader.getChildren(nodeId)
            : await reader.getParents(nodeId);
        return sortEdges(shas.map((id) => ({ neighborId: id, label: '' })));
    }
    _dagLabelsExcluded(options) {
        return options?.labels !== undefined && options?.labels !== null && !options.labels.has('');
    }
    async _getDagNeighbors(nodeId, direction, options) {
        if (!this._reader) {
            return [];
        }
        if (this._dagLabelsExcluded(options)) {
            return [];
        }
        if (direction === 'out' || direction === 'in') {
            return await this._getDagSingleDirection(this._reader, nodeId, direction);
        }
        return await this._getDagBothDirections(this._reader, nodeId);
    }
    async _getDagBothDirections(reader, nodeId) {
        const [children, parents] = await Promise.all([
            reader.getChildren(nodeId),
            reader.getParents(nodeId),
        ]);
        const all = children.map((id) => ({ neighborId: id, label: '' }))
            .concat(parents.map((id) => ({ neighborId: id, label: '' })));
        return dedupSorted(sortEdges(all));
    }
    // ── Logical graph mode ──────────────────────────────────────────────
    _resolveLabelIds(logical, labels) {
        const registry = logical.getLabelRegistry();
        const ids = [];
        for (const label of labels) {
            const id = registry.get(label);
            if (id !== undefined) {
                ids.push(id);
            }
        }
        return ids;
    }
    _getLogicalNeighbors(nodeId, direction, options) {
        const logical = this._logical;
        let labelIds;
        if (options?.labels) {
            labelIds = this._resolveLabelIds(logical, options.labels);
            if (labelIds === undefined || labelIds.length === 0) {
                return [];
            }
        }
        if (direction === 'both') {
            const outEdges = logical.getEdges(nodeId, 'out', labelIds);
            const inEdges = logical.getEdges(nodeId, 'in', labelIds);
            return dedupSorted(sortEdges([...outEdges, ...inEdges]));
        }
        return sortEdges(logical.getEdges(nodeId, direction, labelIds));
    }
}
