"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_CACHE_RETENTION_MS = exports.DERIVED_CACHE_SCHEMA_VERSION = void 0;
exports.createDerivedCache = createDerivedCache;
exports.createDeterministicCacheKey = createDeterministicCacheKey;
exports.putDerivedCacheEntry = putDerivedCacheEntry;
exports.inspectDerivedCacheEntry = inspectDerivedCacheEntry;
exports.getDerivedCacheValue = getDerivedCacheValue;
exports.listDerivedCacheEntries = listDerivedCacheEntries;
exports.clearDerivedCache = clearDerivedCache;
const node_crypto_1 = require("node:crypto");
exports.DERIVED_CACHE_SCHEMA_VERSION = 1;
exports.DEFAULT_CACHE_RETENTION_MS = 7 * 24 * 60 * 60 * 1_000;
function createDerivedCache() {
    return { schemaVersion: exports.DERIVED_CACHE_SCHEMA_VERSION, revision: 0, entries: {} };
}
function createDeterministicCacheKey(namespace, inputs) {
    const normalizedNamespace = normalizeNamespace(namespace);
    const digest = (0, node_crypto_1.createHash)("sha256").update(stableSerialize(inputs)).digest("hex");
    return `${normalizedNamespace}:sha256:${digest}`;
}
function putDerivedCacheEntry(cache, input) {
    assertCache(cache);
    const key = createDeterministicCacheKey(input.namespace, input.inputs);
    const dependencies = normalizeDependencies(input.dependencies);
    const nowMs = normalizeTime(input.nowMs);
    const next = {
        key,
        namespace: normalizeNamespace(input.namespace),
        value: clone(input.value),
        dependencies,
        createdAtMs: nowMs,
        lastAccessedAtMs: nowMs
    };
    const current = cache.entries[key];
    if (current !== undefined && stableSerialize(current) === stableSerialize(next)) {
        return { changed: false, revision: cache.revision, key };
    }
    cache.entries[key] = next;
    cache.revision += 1;
    return { changed: true, revision: cache.revision, key };
}
function inspectDerivedCacheEntry(cache, key, currentDependencies) {
    assertCache(cache);
    const entry = cache.entries[normalizeKey(key)];
    if (entry === undefined)
        return null;
    const current = new Map(normalizeDependencies(currentDependencies).map((dependency) => [dependency.identity, dependency]));
    const reasons = new Set();
    for (const expected of entry.dependencies) {
        const observed = current.get(expected.identity);
        if (observed === undefined)
            reasons.add("dependency-missing");
        else {
            if (observed.hash !== expected.hash)
                reasons.add("dependency-hash-mismatch");
            if (observed.revision !== expected.revision)
                reasons.add("dependency-revision-mismatch");
        }
    }
    const orderedReasons = [...reasons].sort();
    return {
        ...clone(entry),
        status: orderedReasons.length === 0 ? "fresh" : "invalid",
        reasons: orderedReasons,
        cacheRevision: cache.revision,
        canonical: false
    };
}
function getDerivedCacheValue(cache, key, currentDependencies, nowMs) {
    const inspection = inspectDerivedCacheEntry(cache, key, currentDependencies);
    if (inspection === null || inspection.status !== "fresh")
        return { hit: false, inspection };
    const entry = cache.entries[key];
    entry.lastAccessedAtMs = normalizeTime(nowMs);
    cache.revision += 1;
    return { hit: true, value: clone(inspection.value), inspection: { ...inspection, lastAccessedAtMs: entry.lastAccessedAtMs } };
}
function listDerivedCacheEntries(cache, currentDependencies) {
    return Object.keys(cache.entries).sort().map((key) => inspectDerivedCacheEntry(cache, key, currentDependencies));
}
function clearDerivedCache(cache, options = {}) {
    assertCache(cache);
    if (options.olderThanMs !== undefined && options.nowMs === undefined)
        throw new Error("Retention cleanup requires nowMs.");
    const key = options.key === undefined ? null : normalizeKey(options.key);
    const dependency = options.dependencyIdentity?.replaceAll("\\", "/");
    const threshold = options.olderThanMs === undefined ? null : normalizeTime(options.nowMs) - normalizeDuration(options.olderThanMs);
    const filtered = Object.values(cache.entries).filter((entry) => (key === null || entry.key === key)
        && (dependency === undefined || entry.dependencies.some((item) => item.identity === dependency))
        && (threshold === null || entry.lastAccessedAtMs < threshold));
    const clearedKeys = filtered.map((entry) => entry.key).sort();
    if (clearedKeys.length === 0)
        return { changed: false, revision: cache.revision, clearedKeys };
    for (const clearedKey of clearedKeys)
        delete cache.entries[clearedKey];
    cache.revision += 1;
    return { changed: true, revision: cache.revision, clearedKeys };
}
function normalizeDependencies(dependencies) {
    const normalized = dependencies.map((item) => ({ ...item, identity: item.identity.replaceAll("\\", "/") }))
        .sort((left, right) => left.identity.localeCompare(right.identity));
    if (new Set(normalized.map((item) => item.identity)).size !== normalized.length)
        throw new Error("Cache dependencies must be unique.");
    for (const item of normalized) {
        if (!item.identity || item.identity.startsWith("/") || item.identity.includes(".."))
            throw new Error("Cache dependency identities must be repository-relative.");
        if (!/^sha256:[a-f0-9]{64}$/.test(item.hash) || !Number.isSafeInteger(item.revision) || item.revision < 0)
            throw new Error("Cache dependency evidence is invalid.");
    }
    return normalized;
}
function stableSerialize(value) {
    if (value === undefined)
        return '"<undefined>"';
    if (value === null || typeof value !== "object")
        return JSON.stringify(value);
    if (Array.isArray(value))
        return `[${value.map(stableSerialize).join(",")}]`;
    const record = value;
    return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableSerialize(record[key])}`).join(",")}}`;
}
function clone(value) { return structuredClone(value); }
function normalizeNamespace(value) { if (!/^[a-z0-9][a-z0-9._-]{0,63}$/i.test(value))
    throw new Error("Cache namespace is invalid."); return value.toLowerCase(); }
function normalizeKey(value) { if (!/^[a-z0-9._-]+:sha256:[a-f0-9]{64}$/.test(value))
    throw new Error("Cache key is invalid."); return value; }
function normalizeTime(value) { if (!Number.isSafeInteger(value) || value < 0)
    throw new Error("Cache time must be a non-negative safe integer."); return value; }
function normalizeDuration(value) { return normalizeTime(value); }
function assertCache(cache) { if (cache.schemaVersion !== 1 || !Number.isSafeInteger(cache.revision) || cache.revision < 0)
    throw new Error("Derived cache state is invalid."); }
