"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.populateReleaseStagingPayload = populateReleaseStagingPayload;
exports.resolveProductionDependencyClosure = resolveProductionDependencyClosure;
exports.resolveInstalledDependencyDirectory = resolveInstalledDependencyDirectory;
exports.resolvePathWithinRepository = resolvePathWithinRepository;
exports.resolveReleaseDownloadBundleDirectory = resolveReleaseDownloadBundleDirectory;
exports.resolveReleaseDownloadBundleRelativeDirectory = resolveReleaseDownloadBundleRelativeDirectory;
exports.resolveRequiredDownloadBundleRelativeDirectory = resolveRequiredDownloadBundleRelativeDirectory;
exports.createRuntimePackageJson = createRuntimePackageJson;
exports.writeReleaseLaunchers = writeReleaseLaunchers;
exports.normalizeArchiveNamePart = normalizeArchiveNamePart;
exports.isExecutable = isExecutable;
exports.removeIfExists = removeIfExists;
// 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 errors_1 = require("../../../lib/errors");
const output_lifecycle_lifecycle_contracts_1 = require("./output-lifecycle-lifecycle-contracts");
const output_lifecycle_output_state_1 = require("./output-lifecycle-output-state");
const output_lifecycle_payload_policy_1 = require("./output-lifecycle-payload-policy");
function populateReleaseStagingPayload(options) {
    if (options.state.stagingDirectory === null || options.state.stagingDirectoryRelative === null) {
        throw (0, errors_1.createUsageError)('Release staging payload requires a resolved staging directory.', [
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    const packageJsonPath = node_path_1.default.join(options.repositoryRoot, 'package.json');
    const distPath = node_path_1.default.join(options.repositoryRoot, 'dist');
    const distEntrypointPath = node_path_1.default.join(options.repositoryRoot, 'dist', 'cli', 'index.js');
    if (!node_fs_1.default.existsSync(packageJsonPath)) {
        throw (0, errors_1.createUsageError)('Release staging payload requires package.json.', [
            'Reason: missing-package-metadata',
            'Recovery: restore package.json before running release build.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    if (!node_fs_1.default.existsSync(distPath) || !node_fs_1.default.statSync(distPath).isDirectory() || !node_fs_1.default.existsSync(distEntrypointPath)) {
        throw (0, errors_1.createUsageError)('Release staging payload requires compiled dist output.', [
            'Reason: missing-compiled-dist',
            'Required entrypoint: dist/cli/index.js',
            'Recovery: fix the production build so it emits compiled files under dist/.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    const packageJson = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8'));
    const dependencies = (0, output_lifecycle_payload_policy_1.normalizeStringRecord)(packageJson.dependencies);
    const productionDependencyPlan = resolveProductionDependencyClosure({
        repositoryRoot: options.repositoryRoot,
        dependencyNames: Object.keys(dependencies),
    });
    const productionDependencyNames = [...productionDependencyPlan.keys()].sort();
    const missingDependencies = productionDependencyNames.filter((dependencyName) => productionDependencyPlan.get(dependencyName) === null);
    if (missingDependencies.length > 0) {
        throw (0, errors_1.createUsageError)('Release staging payload requires installed production dependency contents.', [
            'Reason: missing-production-dependencies',
            `Missing dependencies: ${missingDependencies.join(', ')}`,
            'Recovery: install locked dependencies before running release build.',
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    node_fs_1.default.cpSync(distPath, node_path_1.default.join(options.state.stagingDirectory, 'dist'), { recursive: true });
    (0, output_lifecycle_payload_policy_1.removePublicCliExcludedPrivateRuntimeFiles)(options.state.stagingDirectory);
    node_fs_1.default.writeFileSync(node_path_1.default.join(options.state.stagingDirectory, 'package.json'), `${JSON.stringify(createRuntimePackageJson({
        packageJson,
        version: options.version,
        installedCommandName: options.installedCommandName,
    }), null, 2)}\n`);
    node_fs_1.default.writeFileSync(node_path_1.default.join(options.state.stagingDirectory, '.day-shift-release.json'), `${JSON.stringify({
        name: (0, output_lifecycle_payload_policy_1.readOptionalString)(packageJson.name),
        schemaVersion: 1,
        version: options.version,
        installedCommandName: options.installedCommandName,
        runtimeModel: 'system-node',
        requiredNodeVersion: typeof packageJson.engines === 'object' && packageJson.engines !== null
            ? (0, output_lifecycle_payload_policy_1.readOptionalString)(packageJson.engines.node)
            : null,
        compatibility: output_lifecycle_lifecycle_contracts_1.CURRENT_RELEASE_COMPATIBILITY,
        releaseNotesSourceSummary: options.releaseNotesSourceSummary ?? null,
    }, null, 2)}\n`);
    const launchers = writeReleaseLaunchers({
        stagingDirectory: options.state.stagingDirectory,
        stagingDirectoryRelative: options.state.stagingDirectoryRelative,
        installedCommandName: options.installedCommandName,
    });
    for (const dependencyName of productionDependencyNames) {
        const sourceDirectory = productionDependencyPlan.get(dependencyName);
        if (sourceDirectory === null || sourceDirectory === undefined) {
            continue;
        }
        (0, output_lifecycle_payload_policy_1.copyDirectoryFiltered)({
            sourceDirectory,
            destinationDirectory: node_path_1.default.join(options.state.stagingDirectory, 'node_modules', ...dependencyName.split('/')),
            payloadRoot: options.state.stagingDirectory,
        });
    }
    (0, output_lifecycle_payload_policy_1.copyRepositorySharedAuthoringDirectories)({
        repositoryRoot: options.repositoryRoot,
        stagingDirectory: options.state.stagingDirectory,
    });
    const noticeFiles = (0, output_lifecycle_payload_policy_1.copyRepositoryNoticeFiles)({
        repositoryRoot: options.repositoryRoot,
        stagingDirectory: options.state.stagingDirectory,
    });
    const files = (0, output_lifecycle_payload_policy_1.collectFiles)(options.state.stagingDirectory)
        .map((filePath) => (0, output_lifecycle_payload_policy_1.toRepositoryRelativePath)(options.state.stagingDirectory, filePath))
        .sort();
    const validation = (0, output_lifecycle_payload_policy_1.validateReleasePayloadContent)({
        payload: 'staging',
        files,
        requiredFiles: [
            'dist/cli/index.js',
            'package.json',
            '.day-shift-release.json',
            `bin/${options.installedCommandName}`,
            `bin/${options.installedCommandName}.cmd`,
            `bin/${options.installedCommandName}.ps1`,
        ],
    });
    return {
        status: 'created',
        stagingDirectory: options.state.stagingDirectoryRelative,
        files,
        launchers,
        productionDependencies: productionDependencyNames,
        noticeFiles,
        validation,
    };
}
function resolveProductionDependencyClosure(options) {
    const resolved = new Map();
    const pending = options.dependencyNames
        .sort()
        .map((dependencyName) => ({ dependencyName, parentDirectory: options.repositoryRoot }));
    while (pending.length > 0) {
        const candidate = pending.shift();
        const sourceDirectory = resolveInstalledDependencyDirectory({
            repositoryRoot: options.repositoryRoot,
            parentDirectory: candidate.parentDirectory,
            dependencyName: candidate.dependencyName,
        });
        const existing = resolved.get(candidate.dependencyName);
        if (sourceDirectory === null) {
            if (existing === undefined) {
                resolved.set(candidate.dependencyName, null);
            }
            continue;
        }
        if (existing !== undefined && existing !== null) {
            if (node_fs_1.default.realpathSync(existing) !== node_fs_1.default.realpathSync(sourceDirectory)) {
                throw (0, errors_1.createUsageError)('Release staging payload cannot flatten conflicting production dependency versions.', [
                    'Reason: conflicting-production-dependencies',
                    `Dependency: ${candidate.dependencyName}`,
                    'Recovery: use a production deployment layout that preserves nested dependency versions.',
                    'Exit behavior: exits before successful-release state is recorded.',
                ]);
            }
            continue;
        }
        resolved.set(candidate.dependencyName, sourceDirectory);
        const dependencyPackageJson = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(sourceDirectory, 'package.json'), 'utf8'));
        const childDependencies = Object.keys((0, output_lifecycle_payload_policy_1.normalizeStringRecord)(dependencyPackageJson.dependencies)).sort();
        for (const dependencyName of childDependencies) {
            pending.push({ dependencyName, parentDirectory: sourceDirectory });
        }
    }
    return resolved;
}
function resolveInstalledDependencyDirectory(options) {
    const dependencySegments = options.dependencyName.split('/');
    const candidates = [
        node_path_1.default.join(options.parentDirectory, 'node_modules', ...dependencySegments),
        node_path_1.default.join(node_path_1.default.dirname(options.parentDirectory), ...dependencySegments),
        node_path_1.default.join(options.repositoryRoot, 'node_modules', ...dependencySegments),
    ];
    for (const candidate of candidates) {
        if (node_fs_1.default.existsSync(node_path_1.default.join(candidate, 'package.json'))) {
            return node_fs_1.default.realpathSync(candidate);
        }
    }
    return null;
}
function resolvePathWithinRepository(options) {
    const raw = typeof options.value === 'string' && options.value.trim().length > 0 ? options.value.trim() : options.fallback;
    const resolved = node_path_1.default.resolve(options.repositoryRoot, raw);
    const relative = node_path_1.default.relative(options.repositoryRoot, resolved);
    if (relative === '..' || relative.startsWith(`..${node_path_1.default.sep}`) || node_path_1.default.isAbsolute(relative)) {
        throw (0, errors_1.createUsageError)(`Release ${options.label} must stay inside the repository: ${raw}`, [
            'Reason: output-root-outside-repository',
            'Recovery: choose a repository-relative generated output directory such as release.',
            'Exit behavior: exits before generated release output directories or artifacts are created.',
        ]);
    }
    return resolved;
}
function resolveReleaseDownloadBundleDirectory(state) {
    return state.versionDirectory === null ? null : node_path_1.default.join(state.versionDirectory, output_lifecycle_output_state_1.RELEASE_DOWNLOAD_BUNDLE_DIRECTORY);
}
function resolveReleaseDownloadBundleRelativeDirectory(state) {
    return state.versionDirectoryRelative === null
        ? null
        : `${state.versionDirectoryRelative}/${output_lifecycle_output_state_1.RELEASE_DOWNLOAD_BUNDLE_DIRECTORY}`;
}
function resolveRequiredDownloadBundleRelativeDirectory(state) {
    const bundleDirectory = resolveReleaseDownloadBundleRelativeDirectory(state);
    if (bundleDirectory === null) {
        throw (0, errors_1.createUsageError)('Release output requires a resolved download bundle directory.', [
            'Exit behavior: exits before successful-release state is recorded.',
        ]);
    }
    return bundleDirectory;
}
function createRuntimePackageJson(options) {
    const runtimePackageJson = {};
    const copiedFields = ['name', 'private', 'description', 'license', 'type', 'main', 'engines', 'dependencies'];
    for (const field of copiedFields) {
        if (Object.prototype.hasOwnProperty.call(options.packageJson, field)) {
            runtimePackageJson[field] = options.packageJson[field];
        }
    }
    runtimePackageJson.version = options.version;
    runtimePackageJson.bin = {
        [options.installedCommandName]: 'dist/cli/index.js',
    };
    return runtimePackageJson;
}
function writeReleaseLaunchers(options) {
    const binDirectory = node_path_1.default.join(options.stagingDirectory, 'bin');
    node_fs_1.default.mkdirSync(binDirectory, { recursive: true });
    const posixLauncherPath = node_path_1.default.join(binDirectory, options.installedCommandName);
    node_fs_1.default.writeFileSync(posixLauncherPath, [
        '#!/usr/bin/env sh',
        'set -eu',
        'SCRIPT_PATH=$0',
        'while [ -L "$SCRIPT_PATH" ]; do',
        '  SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$SCRIPT_PATH")" && pwd)',
        '  LINK_TARGET=$(readlink "$SCRIPT_PATH")',
        '  case "$LINK_TARGET" in',
        '    /*) SCRIPT_PATH=$LINK_TARGET ;;',
        '    *) SCRIPT_PATH=$SCRIPT_DIR/$LINK_TARGET ;;',
        '  esac',
        'done',
        'SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$SCRIPT_PATH")" && pwd)',
        'INSTALL_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)',
        'exec node "$INSTALL_ROOT/dist/cli/index.js" "$@"',
        '',
    ].join('\n'));
    node_fs_1.default.chmodSync(posixLauncherPath, 0o755);
    const cmdLauncherPath = node_path_1.default.join(binDirectory, `${options.installedCommandName}.cmd`);
    node_fs_1.default.writeFileSync(cmdLauncherPath, [
        '@echo off',
        'setlocal',
        'set "SCRIPT_DIR=%~dp0"',
        'node "%SCRIPT_DIR%..\\dist\\cli\\index.js" %*',
        'exit /b %ERRORLEVEL%',
        '',
    ].join('\r\n'));
    const powershellLauncherPath = node_path_1.default.join(binDirectory, `${options.installedCommandName}.ps1`);
    node_fs_1.default.writeFileSync(powershellLauncherPath, [
        '$ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path',
        '$InstallRoot = Split-Path -Parent $ScriptRoot',
        "$Entrypoint = Join-Path $InstallRoot 'dist\\cli\\index.js'",
        '& node $Entrypoint @args',
        'exit $LASTEXITCODE',
        '',
    ].join('\r\n'));
    return [
        {
            platform: 'posix',
            path: `${options.stagingDirectoryRelative}/bin/${options.installedCommandName}`,
            lineEndings: 'lf',
            executable: isExecutable(posixLauncherPath),
        },
        {
            platform: 'windows',
            path: `${options.stagingDirectoryRelative}/bin/${options.installedCommandName}.cmd`,
            lineEndings: 'crlf',
            executable: false,
        },
        {
            platform: 'windows',
            path: `${options.stagingDirectoryRelative}/bin/${options.installedCommandName}.ps1`,
            lineEndings: 'crlf',
            executable: false,
        },
    ];
}
function normalizeArchiveNamePart(value) {
    const normalized = value
        .trim()
        .toLowerCase()
        .replace(/^@/, '')
        .replace(/[\\/]+/g, '-')
        .replace(/[^a-z0-9._-]+/g, '-')
        .replace(/^-+|-+$/g, '');
    return normalized.length === 0 ? 'day-shift' : normalized;
}
function isExecutable(filePath) {
    return (node_fs_1.default.statSync(filePath).mode & 0o111) !== 0;
}
function removeIfExists(filePath) {
    node_fs_1.default.rmSync(filePath, { force: true });
}
