"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LIFECYCLE_EVIDENCE_OBJECT_DIRECTORY = exports.LIFECYCLE_EVIDENCE_OBJECT_ALGORITHM = exports.LIFECYCLE_EVIDENCE_REFERENCE_SCHEMA_VERSION = void 0;
exports.sha256LifecycleEvidence = sha256LifecycleEvidence;
exports.lifecycleEvidenceObjectPath = lifecycleEvidenceObjectPath;
exports.createLifecycleEvidenceObjectReference = createLifecycleEvidenceObjectReference;
exports.resolveLifecycleEvidenceObjectWritePathSync = resolveLifecycleEvidenceObjectWritePathSync;
exports.validateLifecycleEvidenceEntryReference = validateLifecycleEvidenceEntryReference;
exports.readLifecycleEvidenceBytesSync = readLifecycleEvidenceBytesSync;
exports.projectLifecycleEvidenceStorageMetrics = projectLifecycleEvidenceStorageMetrics;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const errors_1 = require("../errors");
const paths_1 = require("../paths");
exports.LIFECYCLE_EVIDENCE_REFERENCE_SCHEMA_VERSION = 1;
exports.LIFECYCLE_EVIDENCE_OBJECT_ALGORITHM = "sha256";
exports.LIFECYCLE_EVIDENCE_OBJECT_DIRECTORY = "objects/sha256";
const SHA256_PATTERN = /^sha256:[a-f0-9]{64}$/u;
function sha256LifecycleEvidence(bytes) {
    return `sha256:${(0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex")}`;
}
function lifecycleEvidenceObjectPath(runDirectoryValue, objectId) {
    const runDirectory = (0, paths_1.parseRepositoryRelativePath)(runDirectoryValue).normalizedPath;
    requireSha256(objectId, "Lifecycle evidence object identity");
    return node_path_1.default.posix.join(runDirectory, exports.LIFECYCLE_EVIDENCE_OBJECT_DIRECTORY, `${objectId.slice("sha256:".length)}.json`);
}
function createLifecycleEvidenceObjectReference(runDirectory, bytes) {
    const objectId = sha256LifecycleEvidence(bytes);
    return {
        schemaVersion: exports.LIFECYCLE_EVIDENCE_REFERENCE_SCHEMA_VERSION,
        kind: "content-addressed",
        algorithm: exports.LIFECYCLE_EVIDENCE_OBJECT_ALGORITHM,
        objectId,
        objectPath: lifecycleEvidenceObjectPath(runDirectory, objectId)
    };
}
function resolveLifecycleEvidenceObjectWritePathSync(repositoryRootValue, objectPath) {
    const repositoryRoot = node_fs_1.default.realpathSync(node_path_1.default.resolve(repositoryRootValue));
    const relativePath = (0, paths_1.parseRepositoryRelativePath)(objectPath).normalizedPath;
    const segments = relativePath.split("/");
    let current = repositoryRoot;
    for (let index = 0; index < segments.length; index += 1) {
        current = node_path_1.default.join(current, segments[index]);
        const stat = node_fs_1.default.lstatSync(current, { throwIfNoEntry: false });
        if (stat === undefined)
            continue;
        if (stat.isSymbolicLink()) {
            throw (0, errors_1.createUsageError)(`Lifecycle evidence path '${relativePath}' must not traverse a symbolic link.`);
        }
        if (index < segments.length - 1 && !stat.isDirectory()) {
            throw (0, errors_1.createUsageError)(`Lifecycle evidence path '${relativePath}' traverses a non-directory ancestor.`);
        }
        if (index === segments.length - 1 && !stat.isFile()) {
            throw (0, errors_1.createUsageError)(`Lifecycle evidence object '${relativePath}' must be a regular file when present.`);
        }
    }
    return current;
}
function validateLifecycleEvidenceEntryReference(runDirectory, entry, label = `Lifecycle entry '${entry.entryId}'`) {
    requireSha256(entry.evidenceHash, `${label} evidence hash`);
    if (!Number.isSafeInteger(entry.evidenceBytes) || entry.evidenceBytes <= 0) {
        throw (0, errors_1.createUsageError)(`${label} evidence byte count must be a positive safe integer.`);
    }
    const evidencePath = (0, paths_1.parseRepositoryRelativePath)(entry.evidencePath).normalizedPath;
    const runPrefix = `${(0, paths_1.parseRepositoryRelativePath)(runDirectory).normalizedPath}/`;
    if (!evidencePath.startsWith(runPrefix)) {
        throw (0, errors_1.createUsageError)(`${label} evidence path escapes its lifecycle run directory.`);
    }
    const reference = entry.evidenceStorage;
    if (reference === undefined)
        return;
    if (reference.schemaVersion !== exports.LIFECYCLE_EVIDENCE_REFERENCE_SCHEMA_VERSION
        || reference.kind !== "content-addressed"
        || reference.algorithm !== exports.LIFECYCLE_EVIDENCE_OBJECT_ALGORITHM) {
        throw (0, errors_1.createUsageError)(`${label} has an unsupported content-addressed evidence reference.`);
    }
    requireSha256(reference.objectId, `${label} object identity`);
    const expectedPath = lifecycleEvidenceObjectPath(runDirectory, reference.objectId);
    if (reference.objectPath !== expectedPath
        || entry.evidenceHash !== reference.objectId) {
        throw (0, errors_1.createUsageError)(`${label} content-addressed evidence identity is inconsistent.`);
    }
}
function readLifecycleEvidenceBytesSync(options) {
    const { repositoryRoot, runDirectory, entry, maximumBytes } = options;
    validateLifecycleEvidenceEntryReference(runDirectory, entry);
    const relativePath = (0, paths_1.parseRepositoryRelativePath)(entry.evidenceStorage?.objectPath ?? entry.evidencePath).normalizedPath;
    const absolutePath = resolveLifecycleEvidenceObjectWritePathSync(repositoryRoot, relativePath);
    const stat = readRegularFileStat(absolutePath, `Lifecycle evidence '${relativePath}'`);
    if (stat.size <= 0 || stat.size > maximumBytes) {
        throw (0, errors_1.createUsageError)(`Lifecycle evidence '${relativePath}' must contain 1-${maximumBytes} bytes.`);
    }
    if (stat.size !== entry.evidenceBytes) {
        throw (0, errors_1.createUsageError)(`Lifecycle evidence '${relativePath}' byte count does not match its manifest entry.`);
    }
    const bytes = node_fs_1.default.readFileSync(absolutePath);
    const hash = sha256LifecycleEvidence(bytes);
    if (hash !== entry.evidenceHash) {
        throw (0, errors_1.createUsageError)(`Lifecycle evidence '${relativePath}' digest does not match its manifest entry.`);
    }
    return {
        bytes,
        hash,
        path: relativePath,
        storageKind: entry.evidenceStorage === undefined ? "legacy" : "content-addressed",
        objectId: entry.evidenceStorage?.objectId ?? hash
    };
}
function projectLifecycleEvidenceStorageMetrics(entries) {
    const unique = new Map();
    let logicalEntryBytes = 0;
    for (const entry of entries) {
        logicalEntryBytes += entry.evidenceBytes;
        const identity = entry.evidenceStorage?.objectId
            ?? `${entry.evidenceHash}\u0000${entry.evidencePath}`;
        if (!unique.has(identity))
            unique.set(identity, entry.evidenceBytes);
    }
    const physicalObjectBytes = [...unique.values()].reduce((total, bytes) => total + bytes, 0);
    return {
        logicalEntryBytes,
        physicalObjectBytes,
        logicalEntryCount: entries.length,
        uniqueObjectCount: unique.size,
        duplicateCount: entries.length - unique.size,
        savingsBytes: logicalEntryBytes - physicalObjectBytes
    };
}
function readRegularFileStat(absolutePath, label) {
    let stat;
    try {
        stat = node_fs_1.default.lstatSync(absolutePath);
    }
    catch {
        throw (0, errors_1.createUsageError)(`${label} is missing.`);
    }
    if (stat.isSymbolicLink() || !stat.isFile() || stat.nlink !== 1) {
        throw (0, errors_1.createUsageError)(`${label} must be a non-symbolic, non-hard-linked regular file.`);
    }
    return stat;
}
function requireSha256(value, label) {
    if (!SHA256_PATTERN.test(value)) {
        throw (0, errors_1.createUsageError)(`${label} must be a canonical sha256:<64-lowercase-hex> identity.`);
    }
}
