/**
 * IndexStalenessChecker - Detects stale bitmap indexes by comparing
 * frontier metadata stored at build time against current writer refs.
 */
import defaultCodec from "../../utils/defaultCodec.js";
import IndexError from "../../errors/IndexError.js";
function isNonNullObject(value) {
    return value !== null && value !== undefined && typeof value === 'object';
}
function isFrontierEnvelope(envelope) {
    if (!isNonNullObject(envelope)) {
        return false;
    }
    return 'frontier' in envelope && isNonNullObject(envelope['frontier']);
}
function validateEnvelope(envelope, label) {
    if (!isFrontierEnvelope(envelope)) {
        throw new IndexError(`invalid frontier envelope for ${label}`, { code: 'E_INDEX_INVALID_FRONTIER' });
    }
}
/**
 * Loads the frontier from an index tree's shard OIDs.
 *
 * @returns Frontier map, or null if not present (legacy index)
 */
export async function loadIndexFrontier(shardOids, storage, options) {
    const { codec, indexStore } = options ?? {};
    const deps = buildCborDeps(storage, codec, indexStore);
    return await loadCborFrontier(shardOids, deps)
        ?? await loadJsonFrontier(shardOids, storage)
        ?? null;
}
function buildCborDeps(storage, codec, indexStore) {
    const deps = { storage, codec: codec ?? defaultCodec };
    if (indexStore) {
        deps.indexStore = indexStore;
    }
    return deps;
}
async function loadCborFrontier(shardOids, { storage, codec, indexStore }) {
    const oid = shardOids['frontier.cbor'];
    if (typeof oid !== 'string' || oid.length === 0) {
        return null;
    }
    let envelope; // nosemgrep: ts-no-unknown-outside-adapters -- 0025B
    if (indexStore) {
        envelope = await indexStore.decodeShard(oid);
    }
    else {
        const buffer = await storage.readBlob(oid);
        envelope = codec.decode(buffer);
    }
    validateEnvelope(envelope, 'frontier.cbor');
    return new Map(Object.entries(envelope.frontier));
}
async function loadJsonFrontier(shardOids, storage) {
    const oid = shardOids['frontier.json'];
    if (typeof oid !== 'string' || oid.length === 0) {
        return null;
    }
    const buffer = await storage.readBlob(oid);
    const text = new TextDecoder().decode(buffer);
    const parsed = JSON.parse(text); // nosemgrep: ts-no-json-parse-in-core -- 0025B; nosemgrep: ts-no-unknown-outside-adapters -- 0025B
    validateEnvelope(parsed, 'frontier.json');
    return new Map(Object.entries(parsed.frontier));
}
function buildReason(opts) {
    const { stale, advancedWriters, newWriters, removedWriters } = opts;
    if (!stale) {
        return 'index is current';
    }
    const parts = [];
    if (advancedWriters.length > 0) {
        parts.push(`${advancedWriters.length} writer(s) advanced`);
    }
    if (newWriters.length > 0) {
        parts.push(`${newWriters.length} new writer(s)`);
    }
    if (removedWriters.length > 0) {
        parts.push(`${removedWriters.length} writer(s) removed`);
    }
    return parts.join(', ');
}
/**
 * Compares index frontier against current frontier to detect staleness.
 */
export function checkStaleness(indexFrontier, currentFrontier) {
    const advancedWriters = findAdvancedWriters(indexFrontier, currentFrontier);
    const newWriters = findNewWriters(indexFrontier, currentFrontier);
    const removedWriters = findRemovedWriters(indexFrontier, currentFrontier);
    const stale = advancedWriters.length > 0 || newWriters.length > 0 || removedWriters.length > 0;
    const reason = buildReason({ stale, advancedWriters, newWriters, removedWriters });
    return { stale, reason, advancedWriters, newWriters, removedWriters };
}
function findAdvancedWriters(indexFrontier, currentFrontier) {
    const result = [];
    for (const [writerId, tipSha] of currentFrontier) {
        const indexTip = indexFrontier.get(writerId);
        if (indexTip !== undefined && indexTip !== tipSha) {
            result.push(writerId);
        }
    }
    return result;
}
function findNewWriters(indexFrontier, currentFrontier) {
    const result = [];
    for (const writerId of currentFrontier.keys()) {
        if (!indexFrontier.has(writerId)) {
            result.push(writerId);
        }
    }
    return result;
}
function findRemovedWriters(indexFrontier, currentFrontier) {
    const result = [];
    for (const writerId of indexFrontier.keys()) {
        if (!currentFrontier.has(writerId)) {
            result.push(writerId);
        }
    }
    return result;
}
