"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.populateReleaseArtifactChecksums = populateReleaseArtifactChecksums;
exports.validateReleaseArtifactChecksums = validateReleaseArtifactChecksums;
exports.createChecksumRecord = createChecksumRecord;
exports.resolveArtifactPathInsideVersionDirectory = resolveArtifactPathInsideVersionDirectory;
exports.normalizeChecksumRecord = normalizeChecksumRecord;
// Mechanically extracted from output-lifecycle.ts to keep release responsibilities bounded.
const node_crypto_1 = __importDefault(require("node:crypto"));
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const cli_constants_1 = require("../../../lib/config/cli-constants");
const errors_1 = require("../../../lib/errors");
const output_lifecycle_manifest_1 = require("./output-lifecycle-manifest");
const output_lifecycle_output_state_1 = require("./output-lifecycle-output-state");
const output_lifecycle_payload_policy_1 = require("./output-lifecycle-payload-policy");
const output_lifecycle_staging_1 = require("./output-lifecycle-staging");
function populateReleaseArtifactChecksums(options) {
    if (options.state.versionDirectory === null || options.state.versionDirectoryRelative === null) {
        throw (0, errors_1.createUsageError)('Release artifact checksums require a resolved version directory.', [
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    if (options.archivePayloads.status !== 'created') {
        return (0, output_lifecycle_output_state_1.createNotApplicableArtifactChecksums)();
    }
    const archiveRecords = options.archivePayloads.targets.map((target) => createChecksumRecord({
        versionDirectory: options.state.versionDirectory,
        versionDirectoryRelative: options.state.versionDirectoryRelative,
        kind: 'archive',
        target: target.target,
        artifactPath: target.archivePath,
    }));
    const installerRecords = (0, output_lifecycle_manifest_1.collectInstallerAssetPaths)(options.state.versionDirectory).map((artifactPath) => createChecksumRecord({
        versionDirectory: options.state.versionDirectory,
        versionDirectoryRelative: options.state.versionDirectoryRelative,
        kind: 'installer',
        target: (0, output_lifecycle_manifest_1.classifyInstallerAssetTarget)(node_path_1.default.basename(artifactPath)),
        artifactPath,
    }));
    const artifacts = [...archiveRecords, ...installerRecords].sort((left, right) => left.path.localeCompare(right.path));
    const bundleDirectory = (0, output_lifecycle_staging_1.resolveReleaseDownloadBundleDirectory)(options.state);
    const bundleDirectoryRelative = (0, output_lifecycle_staging_1.resolveReleaseDownloadBundleRelativeDirectory)(options.state);
    if (bundleDirectory === null || bundleDirectoryRelative === null) {
        throw (0, errors_1.createUsageError)('Release artifact checksums require a resolved download bundle directory.', [
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    node_fs_1.default.mkdirSync(bundleDirectory, { recursive: true });
    const checksumJsonPath = node_path_1.default.join(bundleDirectory, cli_constants_1.RELEASE_ARTIFACT_CHECKSUMS_FILENAME);
    const checksumTextPath = node_path_1.default.join(bundleDirectory, cli_constants_1.RELEASE_CHECKSUMS_FILENAME);
    const checksumJsonPathRelative = `${bundleDirectoryRelative}/${cli_constants_1.RELEASE_ARTIFACT_CHECKSUMS_FILENAME}`;
    const checksumTextPathRelative = `${bundleDirectoryRelative}/${cli_constants_1.RELEASE_CHECKSUMS_FILENAME}`;
    node_fs_1.default.writeFileSync(checksumJsonPath, `${JSON.stringify({
        generatedBy: 'day-shift-release',
        schemaVersion: 1,
        artifacts,
    }, null, 2)}\n`);
    node_fs_1.default.writeFileSync(checksumTextPath, artifacts.map((artifact) => `${artifact.sha256}  ${artifact.path}`).join('\n') + '\n');
    validateReleaseArtifactChecksums({
        versionDirectory: options.state.versionDirectory,
        versionDirectoryRelative: options.state.versionDirectoryRelative,
        checksumJsonPath,
        expectedArtifactPaths: artifacts.map((artifact) => artifact.path),
    });
    return {
        status: 'created',
        checksumJsonPath: checksumJsonPathRelative,
        checksumTextPath: checksumTextPathRelative,
        artifacts,
    };
}
function validateReleaseArtifactChecksums(options) {
    const checksumPath = resolveArtifactPathInsideVersionDirectory({
        versionDirectory: options.versionDirectory,
        versionDirectoryRelative: options.versionDirectoryRelative,
        artifactPath: options.checksumJsonPath,
        label: 'checksum record',
    });
    if (!node_fs_1.default.existsSync(checksumPath) || !node_fs_1.default.statSync(checksumPath).isFile()) {
        throw (0, errors_1.createUsageError)('Release artifact checksum record is missing.', [
            'Reason: missing-checksum-record',
            `Checksum record: ${options.checksumJsonPath}`,
            'Recovery: regenerate checksum evidence after archive and installer assets are finalized.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    const parsed = JSON.parse(node_fs_1.default.readFileSync(checksumPath, 'utf8'));
    if (!Array.isArray(parsed.artifacts)) {
        throw (0, errors_1.createUsageError)('Release artifact checksum record is malformed.', [
            'Reason: malformed-checksum-record',
            `Checksum record: ${options.checksumJsonPath}`,
            'Recovery: regenerate checksum evidence with the release checksum generator.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    const records = parsed.artifacts.map((entry) => normalizeChecksumRecord(entry));
    const recordsByPath = new Map(records.map((record) => [record.path, record]));
    const missingRecords = options.expectedArtifactPaths.filter((artifactPath) => !recordsByPath.has(artifactPath));
    if (missingRecords.length > 0) {
        throw (0, errors_1.createUsageError)('Release artifact checksum record is missing expected artifacts.', [
            'Reason: absent-checksum-record',
            `Missing artifacts: ${missingRecords.join(', ')}`,
            'Recovery: regenerate checksum evidence after all expected archive and installer assets are present.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    for (const record of records) {
        const artifactPath = resolveArtifactPathInsideVersionDirectory({
            versionDirectory: options.versionDirectory,
            versionDirectoryRelative: options.versionDirectoryRelative,
            artifactPath: record.path,
            label: 'artifact',
        });
        if (!node_fs_1.default.existsSync(artifactPath) || !node_fs_1.default.statSync(artifactPath).isFile()) {
            throw (0, errors_1.createUsageError)('Release artifact referenced by checksum record is missing.', [
                'Reason: missing-artifact',
                `Artifact: ${record.path}`,
                'Recovery: regenerate release archives or installer assets before checksum validation.',
                'Exit behavior: exits before successful-release state is recorded.',
            ]);
        }
        const artifactBytes = node_fs_1.default.readFileSync(artifactPath);
        const actualSha256 = node_crypto_1.default.createHash('sha256').update(artifactBytes).digest('hex');
        if (record.byteSize !== artifactBytes.length) {
            throw (0, errors_1.createUsageError)('Release artifact byte size does not match checksum record.', [
                'Reason: artifact-byte-size-mismatch',
                `Artifact: ${record.path}`,
                `Expected byte size: ${record.byteSize}`,
                `Actual byte size: ${artifactBytes.length}`,
                'Recovery: regenerate checksum evidence after final artifact writes complete.',
                'Exit behavior: exits before successful-release state is recorded.',
            ]);
        }
        if (record.sha256 !== actualSha256) {
            throw (0, errors_1.createUsageError)('Release artifact SHA-256 does not match checksum record.', [
                'Reason: artifact-checksum-mismatch',
                `Artifact: ${record.path}`,
                'Recovery: regenerate checksum evidence after final artifact writes complete.',
                'Exit behavior: exits before successful-release state is recorded.',
            ]);
        }
    }
    return records;
}
function createChecksumRecord(options) {
    const normalizedArtifactPath = (0, output_lifecycle_payload_policy_1.normalizePayloadPath)(options.artifactPath);
    const resolvedArtifactPath = resolveArtifactPathInsideVersionDirectory({
        versionDirectory: options.versionDirectory,
        versionDirectoryRelative: options.versionDirectoryRelative,
        artifactPath: options.artifactPath,
        label: 'artifact',
    });
    const relativePath = node_path_1.default.relative(options.versionDirectory, resolvedArtifactPath);
    const relativeArtifactPath = `${options.versionDirectoryRelative}/${relativePath.split(node_path_1.default.sep).join('/')}`;
    if (!node_fs_1.default.existsSync(resolvedArtifactPath) || !node_fs_1.default.statSync(resolvedArtifactPath).isFile()) {
        throw (0, errors_1.createUsageError)('Release artifact checksum input is missing.', [
            'Reason: missing-artifact',
            `Artifact path: ${normalizedArtifactPath}`,
            'Recovery: generate release archives or installer assets before checksum generation.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    const fileContents = node_fs_1.default.readFileSync(resolvedArtifactPath);
    return {
        kind: options.kind,
        target: options.target,
        filename: node_path_1.default.basename(resolvedArtifactPath),
        path: relativeArtifactPath,
        byteSize: fileContents.length,
        sha256: node_crypto_1.default.createHash('sha256').update(fileContents).digest('hex'),
    };
}
function resolveArtifactPathInsideVersionDirectory(options) {
    const normalizedArtifactPath = (0, output_lifecycle_payload_policy_1.normalizePayloadPath)(options.artifactPath);
    const versionPrefix = `${options.versionDirectoryRelative}/`;
    const resolvedArtifactPath = node_path_1.default.isAbsolute(options.artifactPath)
        ? node_path_1.default.resolve(options.artifactPath)
        : normalizedArtifactPath.startsWith(versionPrefix)
            ? node_path_1.default.join(options.versionDirectory, ...normalizedArtifactPath.slice(versionPrefix.length).split('/'))
            : node_path_1.default.resolve(options.versionDirectory, options.artifactPath);
    const relativePath = node_path_1.default.relative(options.versionDirectory, resolvedArtifactPath);
    if (relativePath === '..' || relativePath.startsWith(`..${node_path_1.default.sep}`) || node_path_1.default.isAbsolute(relativePath)) {
        throw (0, errors_1.createUsageError)(`Release ${options.label} path must stay inside the versioned release directory.`, [
            `Reason: ${options.label}-path-outside-version-directory`,
            `${options.label[0].toUpperCase()}${options.label.slice(1)} path: ${options.artifactPath}`,
            'Recovery: keep archive, installer, and checksum artifacts under the resolved release/<version>/ directory.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    return resolvedArtifactPath;
}
function normalizeChecksumRecord(value) {
    if (typeof value !== 'object' || value === null || Array.isArray(value)) {
        throw (0, errors_1.createUsageError)('Release artifact checksum record contains an invalid artifact entry.', [
            'Reason: malformed-checksum-artifact-record',
            'Recovery: regenerate checksum evidence with the release checksum generator.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    const record = value;
    const kind = record.kind;
    const target = record.target;
    const filename = record.filename;
    const artifactPath = record.path;
    const byteSize = record.byteSize;
    const sha256 = record.sha256;
    if ((kind !== 'archive' && kind !== 'installer') ||
        !['macos-linux', 'windows', 'posix-installer', 'windows-installer'].includes(String(target)) ||
        typeof filename !== 'string' ||
        typeof artifactPath !== 'string' ||
        typeof byteSize !== 'number' ||
        !Number.isSafeInteger(byteSize) ||
        byteSize < 0 ||
        typeof sha256 !== 'string' ||
        !/^[a-f0-9]{64}$/.test(sha256)) {
        throw (0, errors_1.createUsageError)('Release artifact checksum record contains invalid artifact metadata.', [
            'Reason: malformed-checksum-artifact-record',
            'Recovery: regenerate checksum evidence with the release checksum generator.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    return {
        kind,
        target: target,
        filename,
        path: artifactPath,
        byteSize,
        sha256,
    };
}
