/**
 * Internal helpers and constants shared by checkpoint creation and loading.
 *
 * @module domain/services/state/checkpointHelpers
 */
import { CONTENT_PROPERTY_KEY, decodePropKey, isEdgePropKey, decodeEdgePropKey } from "../KeyCodec.js";
import WarpError from "../../errors/WarpError.js";
/** Current shipped checkpoint schema: envelope tree with state subtree. */
export const CURRENT_CHECKPOINT_SCHEMA = 5;
/** Supported shipped runtime checkpoint schemas. */
export const SUPPORTED_CHECKPOINT_SCHEMAS = [CURRENT_CHECKPOINT_SCHEMA];
/**
 * Returns true when the schema number identifies the current runtime
 * checkpoint envelope.
 */
export function isCurrentCheckpointSchema(schema) {
    return schema === CURRENT_CHECKPOINT_SCHEMA;
}
/**
 * Number of unique content storage OIDs to hold before folding a batch into the
 * accumulated sorted anchor list. This keeps checkpoint creation from building
 * one monolithic Set of every content reference before tree serialization.
 */
const CONTENT_ANCHOR_BATCH_SIZE = 256;
/**
 * Writes index tree shards as blobs and creates a subtree.
 */
export async function writeIndexSubtree(indexTree, persistence) {
    const paths = Object.keys(indexTree).sort();
    const oids = await Promise.all(paths.map((path) => {
        const blob = indexTree[path];
        if (blob === undefined) {
            throw new WarpError(`Missing index blob for path: ${path}`, 'E_CHECKPOINT_MISSING_INDEX_BLOB', { context: { path } });
        }
        return persistence.writeBlob(blob);
    }));
    const entries = paths.map((path, i) => {
        const oid = oids[i];
        if (oid === undefined) {
            throw new WarpError(`Missing index blob OID for path: ${path}`, 'E_CHECKPOINT_MISSING_INDEX_BLOB_OID', { context: { path } });
        }
        return `100644 blob ${oid}\t${path}`;
    });
    return await persistence.writeTree(entries);
}
/**
 * Partitions readTreeOids output into core entries and index shard OIDs.
 *
 * Entries prefixed with `index/` are stripped and collected separately.
 */
export function partitionTreeOids(rawOids) {
    const treeOids = new Map();
    const indexShardOids = new Map();
    for (const [path, oid] of Object.entries(rawOids)) {
        if (path.startsWith('index/')) {
            indexShardOids.set(path.slice(6), oid);
        }
        else {
            treeOids.set(path, oid);
        }
    }
    return {
        treeOids: Object.fromEntries(treeOids),
        indexShardOids: Object.fromEntries(indexShardOids),
    };
}
/**
 * Compares git tree entry lines by path segment (content after the tab).
 */
export function compareTreeEntriesByPath(left, right) {
    const leftPath = left.slice(left.indexOf('\t') + 1);
    const rightPath = right.slice(right.indexOf('\t') + 1);
    return leftPath < rightPath ? -1 : leftPath > rightPath ? 1 : 0;
}
/**
 * Merges two sorted string arrays into one sorted unique array.
 */
export function mergeSortedUniqueStrings(existing, incoming) {
    const merged = [];
    let i = 0;
    let j = 0;
    while (i < existing.length && j < incoming.length) {
        const left = existing[i];
        const right = incoming[j];
        if (left === right) {
            merged.push(left);
            i++;
            j++;
            continue;
        }
        if (left < right) {
            merged.push(left);
            i++;
            continue;
        }
        merged.push(right);
        j++;
    }
    while (i < existing.length) {
        merged.push(existing[i++]);
    }
    while (j < incoming.length) {
        merged.push(incoming[j++]);
    }
    return merged;
}
/**
 * Collects sorted, de-duplicated content storage anchor entries for a
 * checkpoint tree without holding all content OIDs in one monolithic Set at
 * once. Current content storage OIDs identify CAS trees, so checkpoint trees
 * must anchor them as tree entries.
 */
export async function collectContentAnchorEntries(propMap, // nosemgrep: ts-no-unknown-outside-adapters -- 0025B
readObjectType = defaultContentAnchorObjectType) {
    let sortedOids = [];
    let batch = new Set();
    const flushBatch = () => {
        if (batch.size === 0) {
            return;
        }
        const sortedBatch = Array.from(batch).sort();
        batch = new Set();
        sortedOids = mergeSortedUniqueStrings(sortedOids, sortedBatch);
    };
    for (const [propKey, register] of propMap) {
        const { propKey: decodedKey } = isEdgePropKey(propKey)
            ? decodeEdgePropKey(propKey)
            : decodePropKey(propKey);
        if (decodedKey !== CONTENT_PROPERTY_KEY || typeof register.value !== 'string') {
            continue;
        }
        batch.add(register.value);
        if (batch.size >= CONTENT_ANCHOR_BATCH_SIZE) {
            flushBatch();
        }
    }
    flushBatch();
    const anchorEntries = [];
    for (const oid of sortedOids) {
        const objectType = await readObjectType(oid);
        anchorEntries.push(contentAnchorEntry(oid, objectType));
    }
    return anchorEntries;
}
function defaultContentAnchorObjectType() {
    return Promise.resolve('tree');
}
function contentAnchorEntry(oid, objectType) {
    if (objectType === 'blob') {
        return `100644 blob ${oid}\t_content_${oid}`;
    }
    if (objectType === 'tree') {
        return `040000 tree ${oid}\t_content_${oid}`;
    }
    return unsupportedContentAnchorObjectType(oid, objectType);
}
function unsupportedContentAnchorObjectType(oid, objectType) {
    throw new WarpError('Unsupported content anchor object type', 'E_CHECKPOINT_CONTENT_ANCHOR_OBJECT_TYPE', { context: { oid, objectType } });
}
