"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeReleaseLocalValidationReport = writeReleaseLocalValidationReport;
exports.resolveExistingReleaseValidationInputs = resolveExistingReleaseValidationInputs;
exports.readExistingReleaseManifest = readExistingReleaseManifest;
exports.isReleaseArchiveManifestArtifact = isReleaseArchiveManifestArtifact;
exports.stripReleaseArchiveExtension = stripReleaseArchiveExtension;
exports.collectRelativeFilePaths = collectRelativeFilePaths;
// Responsibility-focused extraction from index-local-validation.ts.
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 index_local_validation_execution_1 = require("./index-local-validation-execution");
function writeReleaseLocalValidationReport(options) {
    if (options.dryRun) {
        return {
            status: 'skipped-dry-run',
            reportPath: null,
            reportPathRelative: options.outputState.versionDirectoryRelative === null
                ? null
                : `${options.outputState.versionDirectoryRelative}/release-validation-report.json`,
        };
    }
    if (options.releaseSubcommand.name !== 'check' ||
        options.version === null ||
        options.outputState.versionDirectory === null ||
        options.outputState.versionDirectoryRelative === null ||
        options.localValidation.status !== 'passed') {
        return {
            status: 'not-applicable',
            reportPath: null,
            reportPathRelative: options.outputState.versionDirectoryRelative === null
                ? null
                : `${options.outputState.versionDirectoryRelative}/release-validation-report.json`,
        };
    }
    const reportPath = node_path_1.default.join(options.outputState.versionDirectory, 'release-validation-report.json');
    const reportPathRelative = `${options.outputState.versionDirectoryRelative}/release-validation-report.json`;
    const executableValidations = [
        options.localValidation.staging,
        ...options.localValidation.archives.map((archive) => archive.execution),
    ].filter((validation) => validation !== null);
    const noWriteIsolation = executableValidations.map((validation) => ({
        executable: validation.executable,
        artifactRoot: validation.artifactRoot,
        validationScratchRoot: validation.validationScratchRoot,
        validationScratchCleaned: validation.validationScratchCleaned,
        stateDir: validation.isolatedEnvironment.stateDir,
        configHome: validation.isolatedEnvironment.configHome,
        licenseFile: validation.isolatedEnvironment.licenseFile,
        commandCount: validation.commands.length,
    }));
    const report = {
        schemaVersion: 1,
        generatedBy: 'day-shift-release-check',
        command: 'release check',
        version: options.version,
        installedCommandName: options.commandName,
        generatedAt: new Date().toISOString(),
        reportPath: reportPathRelative,
        publication: {
            websiteUpload: 'out-of-scope',
            publicNpmPublication: 'out-of-scope',
        },
        evidence: {
            releaseCheck: {
                status: options.localValidation.status,
                stagingStatus: options.localValidation.staging?.status ?? null,
                archiveStatuses: options.localValidation.archives.map((archive) => ({
                    target: archive.target,
                    status: archive.status,
                    archivePath: archive.archivePath,
                    requiredFiles: archive.requiredFiles,
                    missingFiles: archive.missingFiles,
                    executionStatus: archive.execution?.status ?? null,
                    executionSkippedReason: archive.executionSkippedReason,
                    temporaryCleaned: archive.temporaryCleaned,
                })),
            },
            stagedExecution: options.localValidation.staging,
            archiveValidation: options.localValidation.archives,
            runtimeDiagnostics: executableValidations.map((validation) => validation.diagnostics),
            installerSafetyValidation: options.installerSafetyValidation,
            noWriteIsolation,
            installerFailureEvidence: [
                {
                    reason: 'artifact-checksum-mismatch',
                    boundary: 'fails before existing installation replacement',
                },
                {
                    reason: 'installer-manifest-archive-missing',
                    boundary: 'fails before existing installation replacement',
                },
                {
                    reason: 'missing-node',
                    boundary: 'fails before existing installation replacement',
                },
                {
                    reason: 'unsupported-node-version',
                    boundary: 'fails before existing installation replacement',
                },
                {
                    reason: 'missing-executable-shim',
                    boundary: 'fails before existing installation replacement',
                },
                {
                    reason: 'installer-replacement-failed',
                    boundary: 'restores existing installation before returning failure',
                },
            ],
            managedSandboxLauncherStdoutLimitation: {
                status: 'documented',
                summary: 'Generated release launcher execution can emit blank stdout inside the managed sandbox; authoritative executable validation is the post-build release check harness outside that limitation.',
            },
        },
    };
    node_fs_1.default.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
    return {
        status: 'written',
        reportPath,
        reportPathRelative,
    };
}
function resolveExistingReleaseValidationInputs(options) {
    if (options.version === null ||
        options.outputState.versionDirectory === null ||
        options.outputState.versionDirectoryRelative === null) {
        return null;
    }
    const versionDirectory = options.outputState.versionDirectory;
    const stagingArtifactRoot = node_path_1.default.join(versionDirectory, 'staging');
    const stagedExecutable = node_path_1.default.join(stagingArtifactRoot, 'bin', options.commandName);
    const bundledManifestPath = node_path_1.default.join(versionDirectory, cli_constants_1.RELEASE_DOWNLOAD_BUNDLE_DIRECTORY, cli_constants_1.RELEASE_MANIFEST_FILENAME);
    const manifestPath = node_fs_1.default.existsSync(bundledManifestPath)
        ? bundledManifestPath
        : node_path_1.default.join(versionDirectory, cli_constants_1.RELEASE_MANIFEST_FILENAME);
    if (!node_fs_1.default.existsSync(stagedExecutable) && !node_fs_1.default.existsSync(manifestPath)) {
        return null;
    }
    if (!node_fs_1.default.existsSync(stagedExecutable)) {
        throw (0, errors_1.createUsageError)('Release check requires the staged executable produced by release build.', [
            'Reason: release-check-staged-executable-missing',
            `Expected executable: ${(0, index_local_validation_execution_1.toReleaseValidationPath)(stagedExecutable, options.repositoryRoot)}`,
            'Recovery: run release build before release check, or repair the staged release payload.',
            'Exit behavior: exits before successful release validation is reported.',
        ]);
    }
    if (!node_fs_1.default.existsSync(manifestPath)) {
        throw (0, errors_1.createUsageError)('Release check requires the release manifest produced by release build.', [
            'Reason: release-check-manifest-missing',
            `Expected manifest: ${(0, index_local_validation_execution_1.toReleaseValidationPath)(manifestPath, options.repositoryRoot)}`,
            'Recovery: run release build before release check, or repair the release manifest.',
            'Exit behavior: exits before successful release validation is reported.',
        ]);
    }
    const manifest = readExistingReleaseManifest(manifestPath, options.repositoryRoot);
    const archiveArtifacts = manifest.artifacts
        .filter(isReleaseArchiveManifestArtifact)
        .sort((a, b) => a.target.localeCompare(b.target));
    if (archiveArtifacts.length !== 2) {
        throw (0, errors_1.createUsageError)('Release check requires macOS/Linux and Windows archive records in the release manifest.', [
            'Reason: release-check-archive-manifest-records-missing',
            `Manifest: ${(0, index_local_validation_execution_1.toReleaseValidationPath)(manifestPath, options.repositoryRoot)}`,
            `Archive records: ${archiveArtifacts.map((artifact) => artifact.target).join(', ') || '(none)'}`,
            'Recovery: rerun release build so the manifest records both archive targets.',
            'Exit behavior: exits before successful release validation is reported.',
        ]);
    }
    const archivePayloads = {
        status: 'created',
        rootDirectory: `${options.outputState.versionDirectoryRelative}/archive-payloads`,
        targets: archiveArtifacts.map((artifact) => {
            const rootDirectoryName = stripReleaseArchiveExtension(artifact.filename);
            const payloadDirectory = `${options.outputState.versionDirectoryRelative}/archive-payloads/${rootDirectoryName}`;
            const payloadDirectoryAbsolute = node_path_1.default.join(options.repositoryRoot, ...payloadDirectory.split('/'));
            return {
                target: artifact.target,
                runtimeModel: artifact.runtimeModel,
                rootDirectoryName,
                payloadDirectory,
                archiveFilename: artifact.filename,
                archivePath: artifact.path,
                launcherPaths: artifact.target === 'macos-linux'
                    ? [`bin/${options.commandName}`]
                    : [`bin/${options.commandName}.cmd`, `bin/${options.commandName}.ps1`],
                files: node_fs_1.default.existsSync(payloadDirectoryAbsolute) ? collectRelativeFilePaths(payloadDirectoryAbsolute) : [],
                validation: null,
            };
        }),
    };
    return {
        version: options.version,
        versionDirectory,
        stagingArtifactRoot,
        stagedExecutable,
        archivePayloads,
    };
}
function readExistingReleaseManifest(manifestPath, repositoryRoot) {
    try {
        return JSON.parse(node_fs_1.default.readFileSync(manifestPath, 'utf8'));
    }
    catch (error) {
        throw (0, errors_1.createUsageError)('Release check requires a parseable release manifest.', [
            'Reason: release-check-manifest-unparseable',
            `Manifest: ${(0, index_local_validation_execution_1.toReleaseValidationPath)(manifestPath, repositoryRoot)}`,
            `Error: ${error instanceof Error ? error.message : String(error)}`,
            'Recovery: rerun release build so the manifest is regenerated.',
            'Exit behavior: exits before successful release validation is reported.',
        ]);
    }
}
function isReleaseArchiveManifestArtifact(artifact) {
    return artifact.kind === 'archive' && (artifact.target === 'macos-linux' || artifact.target === 'windows');
}
function stripReleaseArchiveExtension(filename) {
    if (filename.endsWith('.tar.gz')) {
        return filename.slice(0, -'.tar.gz'.length);
    }
    if (filename.endsWith('.zip')) {
        return filename.slice(0, -'.zip'.length);
    }
    return filename;
}
function collectRelativeFilePaths(rootDirectory) {
    const files = [];
    const visit = (directory) => {
        for (const entry of node_fs_1.default.readdirSync(directory, { withFileTypes: true })) {
            const entryPath = node_path_1.default.join(directory, entry.name);
            if (entry.isDirectory()) {
                visit(entryPath);
                continue;
            }
            if (entry.isFile()) {
                files.push(node_path_1.default.relative(rootDirectory, entryPath).split(node_path_1.default.sep).join('/'));
            }
        }
    };
    visit(rootDirectory);
    return files.sort();
}
