"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EVIDENCE_FRESHNESS_INDEX_SCHEMA_VERSION = void 0;
exports.createEvidenceFreshnessIndex = createEvidenceFreshnessIndex;
exports.hashEvidenceContent = hashEvidenceContent;
exports.recordSourceSnapshot = recordSourceSnapshot;
exports.recordGeneratedEvidence = recordGeneratedEvidence;
exports.inspectGeneratedEvidence = inspectGeneratedEvidence;
exports.listGeneratedEvidence = listGeneratedEvidence;
exports.clearGeneratedEvidence = clearGeneratedEvidence;
exports.clearSourceSnapshot = clearSourceSnapshot;
const node_crypto_1 = require("node:crypto");
const paths_1 = require("../paths");
__exportStar(require("./cache-lifecycle"), exports);
__exportStar(require("./decision-trace"), exports);
__exportStar(require("./performance-benchmark"), exports);
exports.EVIDENCE_FRESHNESS_INDEX_SCHEMA_VERSION = 1;
function createEvidenceFreshnessIndex() {
    return { schemaVersion: exports.EVIDENCE_FRESHNESS_INDEX_SCHEMA_VERSION, revision: 0, sources: {}, evidence: {} };
}
function hashEvidenceContent(content) {
    return `sha256:${(0, node_crypto_1.createHash)("sha256").update(content).digest("hex")}`;
}
function recordSourceSnapshot(index, snapshot) {
    assertIndex(index);
    const normalized = normalizeSnapshot(snapshot);
    const current = index.sources[normalized.identity];
    if (current !== undefined && normalized.revision < current.revision) {
        throw new Error(`Source revision for '${normalized.identity}' cannot move backwards.`);
    }
    if (current !== undefined && normalized.revision === current.revision && normalized.hash !== current.hash) {
        throw new Error(`Source hash for '${normalized.identity}' changed without a revision change.`);
    }
    if (current !== undefined && current.hash === normalized.hash && current.revision === normalized.revision) {
        return { changed: false, revision: index.revision, source: cloneSnapshot(current) };
    }
    index.sources[normalized.identity] = normalized;
    index.revision += 1;
    return { changed: true, revision: index.revision, source: cloneSnapshot(normalized) };
}
function recordGeneratedEvidence(index, input) {
    assertIndex(index);
    const id = normalizeId(input.id);
    const generatedHash = normalizeHash(input.generatedHash, "Generated evidence hash");
    if (input.sources.length === 0)
        throw new Error("Generated evidence must reference at least one source snapshot.");
    const sources = input.sources.map(normalizeSnapshot).sort((left, right) => left.identity.localeCompare(right.identity));
    if (new Set(sources.map((source) => source.identity)).size !== sources.length) {
        throw new Error("Generated evidence source identities must be unique.");
    }
    const current = index.evidence[id];
    const next = { id, generatedHash, sources, complete: input.complete ?? true, supersededBy: null };
    const supersedes = input.supersedes === undefined ? null : normalizeId(input.supersedes);
    if (supersedes === id)
        throw new Error("Generated evidence cannot supersede itself.");
    if (supersedes !== null && index.evidence[supersedes] === undefined) {
        throw new Error(`Superseded evidence '${supersedes}' does not exist.`);
    }
    const changed = JSON.stringify(current) !== JSON.stringify(next)
        || (supersedes !== null && index.evidence[supersedes].supersededBy !== id);
    if (!changed)
        return { changed: false, revision: index.revision, evidence: cloneEvidence(current) };
    index.evidence[id] = next;
    if (supersedes !== null)
        index.evidence[supersedes].supersededBy = id;
    index.revision += 1;
    return { changed: true, revision: index.revision, evidence: cloneEvidence(next) };
}
function inspectGeneratedEvidence(index, evidenceId) {
    assertIndex(index);
    const record = index.evidence[normalizeId(evidenceId)];
    if (record === undefined)
        return null;
    const reasons = new Set();
    if (!record.complete)
        reasons.add("generation-incomplete");
    if (record.supersededBy !== null)
        reasons.add("evidence-superseded");
    for (const expected of record.sources) {
        const current = index.sources[expected.identity];
        if (current === undefined) {
            reasons.add("source-missing");
            continue;
        }
        if (current.hash !== expected.hash)
            reasons.add("source-hash-mismatch");
        if (current.revision !== expected.revision)
            reasons.add("source-revision-mismatch");
    }
    const orderedReasons = [...reasons].sort();
    const status = reasons.has("generation-incomplete")
        ? "incomplete"
        : reasons.has("evidence-superseded")
            ? "superseded"
            : orderedReasons.length > 0
                ? "stale"
                : "fresh";
    return {
        id: record.id,
        status,
        reasons: orderedReasons,
        writeAuthorized: status === "fresh",
        generatedHash: record.generatedHash,
        sources: record.sources.map(cloneSnapshot),
        supersededBy: record.supersededBy,
        indexRevision: index.revision
    };
}
function listGeneratedEvidence(index) {
    assertIndex(index);
    return Object.keys(index.evidence).sort().map((id) => inspectGeneratedEvidence(index, id));
}
function clearGeneratedEvidence(index, evidenceId) {
    assertIndex(index);
    const ids = evidenceId === undefined
        ? Object.keys(index.evidence).sort()
        : index.evidence[normalizeId(evidenceId)] === undefined ? [] : [normalizeId(evidenceId)];
    if (ids.length === 0)
        return { changed: false, revision: index.revision, clearedIds: [] };
    for (const id of ids)
        delete index.evidence[id];
    for (const record of Object.values(index.evidence)) {
        if (record.supersededBy !== null && ids.includes(record.supersededBy))
            record.supersededBy = null;
    }
    index.revision += 1;
    return { changed: true, revision: index.revision, clearedIds: ids };
}
function clearSourceSnapshot(index, identity) {
    assertIndex(index);
    const normalized = normalizeIdentity(identity);
    if (index.sources[normalized] === undefined)
        return { changed: false, revision: index.revision };
    delete index.sources[normalized];
    index.revision += 1;
    return { changed: true, revision: index.revision };
}
function assertIndex(index) {
    if (index.schemaVersion !== exports.EVIDENCE_FRESHNESS_INDEX_SCHEMA_VERSION || !Number.isSafeInteger(index.revision) || index.revision < 0) {
        throw new Error("Evidence freshness index state is invalid or unsupported.");
    }
}
function normalizeSnapshot(snapshot) {
    if (!Number.isSafeInteger(snapshot.revision) || snapshot.revision < 0)
        throw new Error("Source revision must be a non-negative safe integer.");
    return { identity: normalizeIdentity(snapshot.identity), hash: normalizeHash(snapshot.hash, "Source hash"), revision: snapshot.revision };
}
function normalizeIdentity(identity) {
    return (0, paths_1.parseRepositoryRelativePath)(identity).normalizedPath;
}
function normalizeId(id) {
    const normalized = id.trim();
    if (!/^[a-z0-9][a-z0-9._:-]{0,127}$/i.test(normalized))
        throw new Error("Evidence id must be a bounded opaque identifier.");
    return normalized;
}
function normalizeHash(hash, label) {
    const normalized = hash.toLowerCase();
    if (!/^sha256:[a-f0-9]{64}$/.test(normalized))
        throw new Error(`${label} must use the sha256:<hex> format.`);
    return normalized;
}
function cloneSnapshot(snapshot) {
    return { ...snapshot };
}
function cloneEvidence(record) {
    return { ...record, sources: record.sources.map(cloneSnapshot) };
}
