import defaultCodec from "../../utils/defaultCodec.js";
import WarpError from "../../errors/WarpError.js";
import SchemaUnsupportedError from "../../errors/SchemaUnsupportedError.js";
/**
 * ProvenanceIndex - Node-to-Patch SHA Index
 *
 * Implements HG/IO/2: Build nodeId-to-patchSha index from I/O declarations.
 * This enables quick answers to "which patches affected node X?" without
 * replaying all patches.
 *
 * The index maps:
 * - nodeId -> Set<patchSha> (patches that read or wrote this node)
 *
 * @module domain/services/provenance/ProvenanceIndex
 */
class ProvenanceIndex {
    #index;
    constructor(initialIndex) {
        if (initialIndex) {
            this.#index = new Map();
            for (const [k, v] of initialIndex) {
                this.#index.set(k, new Set(v));
            }
        }
        else {
            this.#index = new Map();
        }
    }
    static empty() {
        return new ProvenanceIndex();
    }
    addPatch(patchSha, reads, writes) {
        this.#indexEntityList(reads, patchSha);
        this.#indexEntityList(writes, patchSha);
        return this;
    }
    #indexEntityList(entities, patchSha) {
        if (entities !== undefined && entities !== null && entities.length > 0) {
            for (const entityId of entities) {
                this.#addEntry(entityId, patchSha);
            }
        }
    }
    #addEntry(entityId, patchSha) {
        let shas = this.#index.get(entityId);
        if (!shas) {
            shas = new Set();
            this.#index.set(entityId, shas);
        }
        shas.add(patchSha);
    }
    patchesFor(entityId) {
        const shas = this.#index.get(entityId);
        if (!shas || shas.size === 0) {
            return [];
        }
        return [...shas].sort();
    }
    has(entityId) {
        const shas = this.#index.get(entityId);
        return shas !== undefined && shas.size > 0;
    }
    get size() {
        return this.#index.size;
    }
    entities() {
        return [...this.#index.keys()].sort();
    }
    clear() {
        this.#index.clear();
        return this;
    }
    merge(other) {
        for (const [entityId, shas] of other.#index) {
            for (const sha of shas) {
                this.#addEntry(entityId, sha);
            }
        }
        return this;
    }
    clone() {
        const clonedMap = new Map();
        for (const [entityId, shas] of this.#index) {
            clonedMap.set(entityId, new Set(shas));
        }
        return new ProvenanceIndex(clonedMap);
    }
    #sortedEntries() {
        const entries = [];
        for (const [entityId, shas] of this.#index) {
            entries.push([entityId, [...shas].sort()]);
        }
        entries.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
        return entries;
    }
    serialize({ codec } = {}) {
        const c = codec ?? defaultCodec;
        return c.encode({ version: 1, entries: this.#sortedEntries() });
    }
    static #buildIndex(entries) {
        const index = new Map();
        for (const [entityId, shas] of entries) {
            index.set(entityId, new Set(shas));
        }
        return index;
    }
    static deserialize(buffer, { codec } = {}) {
        const c = codec ?? defaultCodec;
        const obj = c.decode(buffer);
        ProvenanceIndex.#validateSerialized(obj);
        return new ProvenanceIndex(ProvenanceIndex.#buildIndex(obj.entries ?? []));
    }
    static #validateSerialized(obj) {
        if (obj.version !== 1) {
            const versionLabel = obj.version === undefined ? 'missing' : String(obj.version);
            throw new SchemaUnsupportedError(`Unsupported ProvenanceIndex version: ${versionLabel}`, { context: { version: obj.version } });
        }
        if (!Array.isArray(obj.entries)) {
            throw new WarpError('Missing or invalid ProvenanceIndex entries', 'E_PROVENANCE_INDEX_MALFORMED');
        }
    }
    toJSON() {
        return { version: 1, entries: this.#sortedEntries() };
    }
    static fromJSON(json) {
        ProvenanceIndex.#validateSerialized(json);
        return new ProvenanceIndex(ProvenanceIndex.#buildIndex(json.entries ?? []));
    }
    *[Symbol.iterator]() {
        for (const entry of this.#sortedEntries()) {
            yield entry;
        }
    }
}
export default ProvenanceIndex;
export { ProvenanceIndex };
