"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRuntimeProvenance = createRuntimeProvenance;
const node_child_process_1 = require("node:child_process");
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const automation_protocol_1 = require("../automation-protocol");
const build_input_manifest_1 = require("./build-input-manifest");
function createRuntimeProvenance(options) {
    const runtimeRoot = node_path_1.default.resolve(options.runtimeRoot);
    const runtimeSource = classifyRuntimeSource({
        runtimeRoot,
        runtimeModulePath: options.runtimeModulePath,
        releaseMetadataPresent: options.releaseMetadataPresent
    });
    const gitEvidence = runtimeSource === "repository-source" || runtimeSource === "repository-dist"
        ? inspectGitEvidence(runtimeRoot)
        : { sourceRevision: null, repositoryDirtyState: "not-applicable" };
    return Object.freeze({
        runtimeSource,
        cliVersion: options.cliVersion,
        protocolVersion: (0, automation_protocol_1.getSelectedAutomationProtocolVersion)(),
        nodeVersion: options.nodeVersion ?? process.versions.node,
        buildIdentity: createBuildIdentity(runtimeRoot, runtimeSource),
        sourceRevision: gitEvidence.sourceRevision,
        repositoryDirtyState: gitEvidence.repositoryDirtyState,
        distributionFreshness: inspectDistributionFreshness(runtimeRoot, runtimeSource),
        packageIntegrity: packageIntegrityStatus(runtimeSource)
    });
}
function classifyRuntimeSource(options) {
    if (options.releaseMetadataPresent)
        return "installed-package";
    const relativeModulePath = node_path_1.default.relative(options.runtimeRoot, node_path_1.default.resolve(options.runtimeModulePath));
    if (!isContainedRelativePath(relativeModulePath))
        return "unknown";
    const normalized = relativeModulePath.replace(/\\/g, "/");
    if (normalized === "dist" || normalized.startsWith("dist/"))
        return "repository-dist";
    if (normalized === "src" || normalized.startsWith("src/"))
        return "repository-source";
    return "unknown";
}
function createBuildIdentity(runtimeRoot, runtimeSource) {
    const runtimeDirectory = runtimeSource === "repository-dist" || runtimeSource === "installed-package"
        ? existingDirectory(node_path_1.default.join(runtimeRoot, "dist")) ?? runtimeRoot
        : runtimeSource === "repository-source"
            ? existingDirectory(node_path_1.default.join(runtimeRoot, "src"))
            : null;
    if (runtimeDirectory === null)
        return "unknown";
    try {
        const files = collectFiles(runtimeDirectory);
        if (files.length === 0)
            return "unknown";
        const hash = (0, node_crypto_1.createHash)("sha256");
        hash.update(`runtime-source:${runtimeSource}\n`);
        for (const file of files) {
            const relativePath = node_path_1.default.relative(runtimeDirectory, file).replace(/\\/g, "/");
            const bytes = node_fs_1.default.readFileSync(file);
            hash.update(`${relativePath.length}:${relativePath}:${bytes.byteLength}:`);
            hash.update(bytes);
            hash.update("\n");
        }
        return `sha256:${hash.digest("hex")}`;
    }
    catch {
        return "unknown";
    }
}
function collectFiles(root) {
    const files = [];
    const pending = [root];
    let totalBytes = 0;
    while (pending.length > 0) {
        const directory = pending.pop();
        for (const name of node_fs_1.default.readdirSync(directory).sort((left, right) => right.localeCompare(left, "en"))) {
            const entryPath = node_path_1.default.join(directory, name);
            const stat = node_fs_1.default.lstatSync(entryPath);
            if (stat.isSymbolicLink())
                continue;
            if (stat.isDirectory()) {
                pending.push(entryPath);
                continue;
            }
            if (!stat.isFile())
                continue;
            files.push(entryPath);
            totalBytes += stat.size;
            if (files.length > 50_000 || totalBytes > 256_000_000) {
                throw new Error("Runtime identity input exceeds the bounded local hashing budget.");
            }
        }
    }
    return files.sort((left, right) => left.localeCompare(right, "en"));
}
function inspectGitEvidence(runtimeRoot) {
    const topLevel = runGit(runtimeRoot, ["rev-parse", "--show-toplevel"]);
    if (topLevel === null || node_path_1.default.resolve(topLevel) !== runtimeRoot) {
        return { sourceRevision: null, repositoryDirtyState: "unknown" };
    }
    const sourceRevision = runGit(runtimeRoot, ["rev-parse", "--verify", "HEAD"]);
    const status = runGit(runtimeRoot, ["status", "--porcelain=v1", "--untracked-files=normal"]);
    return {
        sourceRevision: sourceRevision !== null && /^[0-9a-f]{40,64}$/i.test(sourceRevision)
            ? sourceRevision.toLowerCase()
            : null,
        repositoryDirtyState: status === null ? "unknown" : status.length === 0 ? "clean" : "dirty"
    };
}
function runGit(cwd, args) {
    const result = (0, node_child_process_1.spawnSync)("git", ["-C", cwd, ...args], {
        encoding: "utf8",
        stdio: ["ignore", "pipe", "ignore"],
        timeout: 2_000,
        maxBuffer: 1_000_000
    });
    if (result.status !== 0 || typeof result.stdout !== "string")
        return null;
    return result.stdout.trim();
}
function inspectDistributionFreshness(runtimeRoot, runtimeSource) {
    if (runtimeSource !== "repository-dist")
        return "not-applicable";
    try {
        const inspection = (0, build_input_manifest_1.inspectBuildInputManifest)(runtimeRoot);
        if (inspection.status === "malformed")
            return "incompatible";
        return inspection.status;
    }
    catch {
        return "unknown";
    }
}
function packageIntegrityStatus(runtimeSource) {
    if (runtimeSource === "repository-dist")
        return "computed-unverified";
    if (runtimeSource === "installed-package")
        return "metadata-unverified";
    if (runtimeSource === "repository-source")
        return "not-applicable";
    return "unknown";
}
function existingDirectory(input) {
    try {
        return node_fs_1.default.statSync(input).isDirectory() ? input : null;
    }
    catch {
        return null;
    }
}
function isContainedRelativePath(input) {
    return input !== ""
        && input !== ".."
        && !input.startsWith(`..${node_path_1.default.sep}`)
        && !node_path_1.default.isAbsolute(input);
}
