"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CRC32_TABLE = void 0;
exports.populateReleaseArchivePayloads = populateReleaseArchivePayloads;
exports.writeDeterministicTarGzipArchive = writeDeterministicTarGzipArchive;
exports.createTarHeader = createTarHeader;
exports.splitTarPath = splitTarPath;
exports.writeTarString = writeTarString;
exports.writeTarOctal = writeTarOctal;
exports.writeDeterministicZipArchive = writeDeterministicZipArchive;
exports.computeCrc32 = computeCrc32;
// Mechanically extracted from output-lifecycle.ts to keep release responsibilities bounded.
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const node_zlib_1 = __importDefault(require("node:zlib"));
const errors_1 = require("../../../lib/errors");
const output_lifecycle_installation_readme_1 = require("./output-lifecycle-installation-readme");
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 populateReleaseArchivePayloads(options) {
    if (options.state.versionDirectory === null ||
        options.state.versionDirectoryRelative === null ||
        options.state.stagingDirectory === null) {
        throw (0, errors_1.createUsageError)('Release archive payloads require resolved version and staging directories.', [
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    if (options.stagingPayload.status !== 'created') {
        return (0, output_lifecycle_output_state_1.createNotApplicableArchivePayloads)();
    }
    const packageSlug = (0, output_lifecycle_staging_1.normalizeArchiveNamePart)(options.packageName ?? 'day-shift');
    const downloadBundleDirectory = (0, output_lifecycle_staging_1.resolveReleaseDownloadBundleDirectory)(options.state);
    const downloadBundleRelativeDirectory = (0, output_lifecycle_staging_1.resolveReleaseDownloadBundleRelativeDirectory)(options.state);
    if (downloadBundleDirectory === null || downloadBundleRelativeDirectory === null) {
        throw (0, errors_1.createUsageError)('Release archive payloads require a resolved download bundle directory.', [
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    const archivePayloadRoot = node_path_1.default.join(options.state.versionDirectory, 'archive-payloads');
    const archivePayloadRootRelative = `${options.state.versionDirectoryRelative}/archive-payloads`;
    const targets = [
        {
            target: 'macos-linux',
            runtimeModel: 'system-node',
            rootDirectoryName: `${packageSlug}-${options.version}-macos-linux-system-node`,
            payloadDirectory: '',
            archiveFilename: `${packageSlug}-${options.version}-macos-linux-system-node.tar.gz`,
            archivePath: '',
            launcherPaths: [`bin/${options.installedCommandName}`],
            files: [],
            validation: null,
        },
        {
            target: 'windows',
            runtimeModel: 'system-node',
            rootDirectoryName: `${packageSlug}-${options.version}-windows-system-node`,
            payloadDirectory: '',
            archiveFilename: `${packageSlug}-${options.version}-windows-system-node.zip`,
            archivePath: '',
            launcherPaths: [`bin/${options.installedCommandName}.cmd`, `bin/${options.installedCommandName}.ps1`],
            files: [],
            validation: null,
        },
    ];
    for (const target of targets) {
        const targetDirectory = node_path_1.default.join(archivePayloadRoot, target.rootDirectoryName);
        node_fs_1.default.rmSync(targetDirectory, { recursive: true, force: true });
        node_fs_1.default.mkdirSync(node_path_1.default.dirname(targetDirectory), { recursive: true });
        node_fs_1.default.cpSync(options.state.stagingDirectory, targetDirectory, { recursive: true });
        if (target.target === 'macos-linux') {
            (0, output_lifecycle_staging_1.removeIfExists)(node_path_1.default.join(targetDirectory, 'bin', `${options.installedCommandName}.cmd`));
            (0, output_lifecycle_staging_1.removeIfExists)(node_path_1.default.join(targetDirectory, 'bin', `${options.installedCommandName}.ps1`));
        }
        else {
            (0, output_lifecycle_staging_1.removeIfExists)(node_path_1.default.join(targetDirectory, 'bin', options.installedCommandName));
        }
        (0, output_lifecycle_installation_readme_1.copyArchiveRootDocumentation)({
            versionDirectory: options.state.versionDirectory,
            targetDirectory,
        });
        target.payloadDirectory = `${archivePayloadRootRelative}/${target.rootDirectoryName}`;
        target.files = (0, output_lifecycle_payload_policy_1.collectFiles)(targetDirectory)
            .map((filePath) => (0, output_lifecycle_payload_policy_1.toRepositoryRelativePath)(targetDirectory, filePath))
            .sort();
        target.validation = (0, output_lifecycle_payload_policy_1.validateReleasePayloadContent)({
            payload: 'archive',
            files: target.files,
            requiredFiles: [
                'dist/cli/index.js',
                'package.json',
                '.day-shift-release.json',
                output_lifecycle_output_state_1.RELEASE_INSTALLATION_README_FILENAME,
                output_lifecycle_output_state_1.RELEASE_NOTES_FILENAME,
                ...target.launcherPaths,
            ],
        });
        target.archivePath = `${downloadBundleRelativeDirectory}/${target.archiveFilename}`;
        node_fs_1.default.mkdirSync(downloadBundleDirectory, { recursive: true });
        const archivePath = node_path_1.default.join(downloadBundleDirectory, target.archiveFilename);
        if (target.target === 'macos-linux') {
            writeDeterministicTarGzipArchive({
                sourceDirectory: targetDirectory,
                rootDirectoryName: target.rootDirectoryName,
                filePaths: target.files,
                archivePath,
            });
        }
        else {
            writeDeterministicZipArchive({
                sourceDirectory: targetDirectory,
                rootDirectoryName: target.rootDirectoryName,
                filePaths: target.files,
                archivePath,
            });
        }
    }
    return {
        status: 'created',
        rootDirectory: archivePayloadRootRelative,
        targets,
    };
}
function writeDeterministicTarGzipArchive(options) {
    const blocks = [];
    for (const filePath of options.filePaths) {
        const absoluteFilePath = node_path_1.default.join(options.sourceDirectory, ...filePath.split('/'));
        const fileContents = node_fs_1.default.readFileSync(absoluteFilePath);
        const stats = node_fs_1.default.statSync(absoluteFilePath);
        const entryName = `${options.rootDirectoryName}/${filePath}`;
        blocks.push(createTarHeader({
            name: entryName,
            size: fileContents.length,
            mode: stats.mode & 0o777,
        }));
        blocks.push(fileContents);
        const paddingLength = (512 - (fileContents.length % 512)) % 512;
        if (paddingLength > 0) {
            blocks.push(Buffer.alloc(paddingLength));
        }
    }
    blocks.push(Buffer.alloc(1024));
    node_fs_1.default.writeFileSync(options.archivePath, node_zlib_1.default.gzipSync(Buffer.concat(blocks), { level: 9 }));
}
function createTarHeader(options) {
    const header = Buffer.alloc(512);
    const nameParts = splitTarPath(options.name);
    writeTarString(header, 0, 100, nameParts.name);
    writeTarOctal(header, 100, 8, options.mode);
    writeTarOctal(header, 108, 8, 0);
    writeTarOctal(header, 116, 8, 0);
    writeTarOctal(header, 124, 12, options.size);
    writeTarOctal(header, 136, 12, 0);
    header.fill(0x20, 148, 156);
    header[156] = '0'.charCodeAt(0);
    writeTarString(header, 257, 6, 'ustar');
    writeTarString(header, 263, 2, '00');
    writeTarString(header, 265, 32, 'root');
    writeTarString(header, 297, 32, 'root');
    if (nameParts.prefix.length > 0) {
        writeTarString(header, 345, 155, nameParts.prefix);
    }
    let checksum = 0;
    for (const byte of header) {
        checksum += byte;
    }
    writeTarOctal(header, 148, 8, checksum);
    return header;
}
function splitTarPath(value) {
    if (Buffer.byteLength(value) <= 100) {
        return { name: value, prefix: '' };
    }
    const slashIndex = value.lastIndexOf('/');
    const prefix = slashIndex === -1 ? '' : value.slice(0, slashIndex);
    const name = slashIndex === -1 ? value : value.slice(slashIndex + 1);
    if (Buffer.byteLength(name) > 100 || Buffer.byteLength(prefix) > 155) {
        throw (0, errors_1.createUsageError)('Release tar archive entry path is too long.', [
            'Reason: archive-entry-path-too-long',
            `Entry: ${value}`,
            'Recovery: shorten generated release payload paths before archive materialization.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    return { name, prefix };
}
function writeTarString(buffer, offset, length, value) {
    buffer.write(value, offset, length, 'utf8');
}
function writeTarOctal(buffer, offset, length, value) {
    const text = value
        .toString(8)
        .padStart(length - 1, '0')
        .slice(-(length - 1));
    buffer.write(text, offset, length - 1, 'ascii');
    buffer[offset + length - 1] = 0;
}
function writeDeterministicZipArchive(options) {
    const localRecords = [];
    const centralRecords = [];
    let offset = 0;
    for (const filePath of options.filePaths) {
        const absoluteFilePath = node_path_1.default.join(options.sourceDirectory, ...filePath.split('/'));
        const fileContents = node_fs_1.default.readFileSync(absoluteFilePath);
        const stats = node_fs_1.default.statSync(absoluteFilePath);
        const entryName = `${options.rootDirectoryName}/${filePath}`;
        const nameBuffer = Buffer.from(entryName, 'utf8');
        const crc = computeCrc32(fileContents);
        const mode = stats.mode & 0o777;
        const localHeader = Buffer.alloc(30);
        localHeader.writeUInt32LE(0x04034b50, 0);
        localHeader.writeUInt16LE(20, 4);
        localHeader.writeUInt16LE(0x0800, 6);
        localHeader.writeUInt16LE(0, 8);
        localHeader.writeUInt16LE(0, 10);
        localHeader.writeUInt16LE(0x21, 12);
        localHeader.writeUInt32LE(crc, 14);
        localHeader.writeUInt32LE(fileContents.length, 18);
        localHeader.writeUInt32LE(fileContents.length, 22);
        localHeader.writeUInt16LE(nameBuffer.length, 26);
        localHeader.writeUInt16LE(0, 28);
        localRecords.push(localHeader, nameBuffer, fileContents);
        const centralHeader = Buffer.alloc(46);
        centralHeader.writeUInt32LE(0x02014b50, 0);
        centralHeader.writeUInt16LE(0x0314, 4);
        centralHeader.writeUInt16LE(20, 6);
        centralHeader.writeUInt16LE(0x0800, 8);
        centralHeader.writeUInt16LE(0, 10);
        centralHeader.writeUInt16LE(0, 12);
        centralHeader.writeUInt16LE(0x21, 14);
        centralHeader.writeUInt32LE(crc, 16);
        centralHeader.writeUInt32LE(fileContents.length, 20);
        centralHeader.writeUInt32LE(fileContents.length, 24);
        centralHeader.writeUInt16LE(nameBuffer.length, 28);
        centralHeader.writeUInt16LE(0, 30);
        centralHeader.writeUInt16LE(0, 32);
        centralHeader.writeUInt16LE(0, 34);
        centralHeader.writeUInt16LE(0, 36);
        centralHeader.writeUInt32LE((mode << 16) >>> 0, 38);
        centralHeader.writeUInt32LE(offset, 42);
        centralRecords.push(centralHeader, nameBuffer);
        offset += localHeader.length + nameBuffer.length + fileContents.length;
    }
    const centralDirectory = Buffer.concat(centralRecords);
    const endRecord = Buffer.alloc(22);
    endRecord.writeUInt32LE(0x06054b50, 0);
    endRecord.writeUInt16LE(0, 4);
    endRecord.writeUInt16LE(0, 6);
    endRecord.writeUInt16LE(options.filePaths.length, 8);
    endRecord.writeUInt16LE(options.filePaths.length, 10);
    endRecord.writeUInt32LE(centralDirectory.length, 12);
    endRecord.writeUInt32LE(offset, 16);
    endRecord.writeUInt16LE(0, 20);
    node_fs_1.default.writeFileSync(options.archivePath, Buffer.concat([...localRecords, centralDirectory, endRecord]));
}
function computeCrc32(buffer) {
    let crc = 0xffffffff;
    for (const byte of buffer) {
        crc = exports.CRC32_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
    }
    return (crc ^ 0xffffffff) >>> 0;
}
exports.CRC32_TABLE = Array.from({ length: 256 }, (_unused, index) => {
    let value = index;
    for (let bit = 0; bit < 8; bit += 1) {
        value = (value & 1) === 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
    }
    return value >>> 0;
});
