"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.requireReleaseVersion = requireReleaseVersion;
exports.releaseSubcommandHasOption = releaseSubcommandHasOption;
exports.parseInstalledCommandName = parseInstalledCommandName;
exports.createReleaseCommandInvocationMapping = createReleaseCommandInvocationMapping;
exports.createReleaseCommandInvocation = createReleaseCommandInvocation;
exports.createReleaseInvocationArgs = createReleaseInvocationArgs;
exports.resolveReleaseOutputDirOption = resolveReleaseOutputDirOption;
exports.resolveStagedReleaseExecutablePath = resolveStagedReleaseExecutablePath;
exports.createReleasePackageBoundaryMetadata = createReleasePackageBoundaryMetadata;
exports.normalizeStringRecord = normalizeStringRecord;
exports.readOptionalString = readOptionalString;
exports.rejectInstalledCommandName = rejectInstalledCommandName;
exports.requireInstalledCommandName = requireInstalledCommandName;
// Responsibility-focused extraction from index-command-surface.ts.
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const errors_1 = require("../../../lib/errors");
const version_1 = require("./version");
const index_command_contracts_1 = require("./index-command-contracts");
function requireReleaseVersion(releaseSubcommand, value) {
    if (!releaseSubcommandHasOption(releaseSubcommand, 'version')) {
        return { ok: false, input: null, reason: 'missing', message: '', correctiveAction: '' };
    }
    const parsedVersion = (0, version_1.parseReleaseVersion)(value);
    if (!parsedVersion.ok) {
        throw (0, errors_1.createUsageError)(parsedVersion.message, [
            `Command: release ${releaseSubcommand.name}`,
            `Rejected version: ${parsedVersion.input ?? '(not provided)'}`,
            `Reason: ${parsedVersion.reason}`,
            `Recovery: ${parsedVersion.correctiveAction}`,
            'Exit behavior: exits before generated release output directories or artifacts are created.',
        ]);
    }
    return parsedVersion;
}
function releaseSubcommandHasOption(releaseSubcommand, option) {
    return releaseSubcommand.options.includes(option);
}
function parseInstalledCommandName(value) {
    const input = value ?? index_command_contracts_1.DEFAULT_INSTALLED_COMMAND_NAME;
    if (typeof input !== 'string') {
        return rejectInstalledCommandName(input, 'invalid-type', 'Use a non-empty executable basename such as day-shift.');
    }
    const commandName = input.trim();
    if (commandName.length === 0) {
        return rejectInstalledCommandName(input, 'missing', 'Use a non-empty executable basename such as day-shift.');
    }
    if (commandName === '.' || commandName === '..') {
        return rejectInstalledCommandName(input, 'reserved-path-segment', 'Use an executable basename, not a relative path segment.');
    }
    if (/[\\/]/.test(commandName)) {
        return rejectInstalledCommandName(input, 'path-like', 'Use only the executable basename, without directories or path separators.');
    }
    if (/\s/.test(commandName)) {
        return rejectInstalledCommandName(input, 'contains-whitespace', 'Use a single executable basename without spaces or shell quoting.');
    }
    if (/[;&|<>`$(){}[\]*?!'"=:,]/.test(commandName)) {
        return rejectInstalledCommandName(input, 'unsafe-shell-character', 'Use only ASCII letters, numbers, dots, underscores, and hyphens.');
    }
    if (/\.(?:bat|cmd|ps1|sh)$/i.test(commandName)) {
        return rejectInstalledCommandName(input, 'script-file-name', 'Use the installed executable basename, not a platform script filename.');
    }
    if (!index_command_contracts_1.INSTALLED_COMMAND_NAME_PATTERN.test(commandName)) {
        return rejectInstalledCommandName(input, 'unsafe-basename', 'Use a basename that starts with a letter or number and contains only letters, numbers, dots, underscores, and hyphens.');
    }
    return {
        ok: true,
        input,
        commandName,
    };
}
function createReleaseCommandInvocationMapping(options) {
    const parsedCommandName = parseInstalledCommandName(options.installedCommandName);
    if (!parsedCommandName.ok) {
        throw (0, errors_1.createUsageError)(parsedCommandName.message, [
            `Rejected command name: ${typeof parsedCommandName.input === 'string' ? parsedCommandName.input : String(parsedCommandName.input)}`,
            `Reason: ${parsedCommandName.reason}`,
            `Recovery: ${parsedCommandName.correctiveAction}`,
        ]);
    }
    const args = options.args.map((arg) => String(arg));
    return {
        args: [...args],
        development: createReleaseCommandInvocation('development', 'node', ['dist/cli/index.js', ...args]),
        stagedRelease: createReleaseCommandInvocation('staged-release', options.stagedExecutablePath, args),
        installedUser: createReleaseCommandInvocation('installed-user', parsedCommandName.commandName, args),
    };
}
function createReleaseCommandInvocation(mode, executable, args) {
    return {
        mode,
        executable,
        args: [...args],
        command: [executable, ...args],
    };
}
function createReleaseInvocationArgs(releaseSubcommand, parsedVersion, options, effectiveOutputDir) {
    const args = ['release', releaseSubcommand.name];
    if (parsedVersion.ok) {
        args.push('--version', parsedVersion.version);
    }
    if (releaseSubcommandHasOption(releaseSubcommand, 'outputDir')) {
        args.push('--output-dir', effectiveOutputDir);
    }
    if (releaseSubcommandHasOption(releaseSubcommand, 'keep')) {
        args.push('--keep', String(options.keep ?? '3'));
    }
    if (releaseSubcommandHasOption(releaseSubcommand, 'confirm') && options.confirm === true) {
        args.push('--confirm');
    }
    if (releaseSubcommandHasOption(releaseSubcommand, 'dryRun') && options.dryRun === true) {
        args.push('--dry-run');
    }
    if (releaseSubcommandHasOption(releaseSubcommand, 'dirty') && options.dirty === true) {
        args.push('--dirty');
    }
    return args;
}
function resolveReleaseOutputDirOption(options) {
    if (typeof options.outputDir === 'string' && options.outputDir.trim().length > 0) {
        return options.outputDir;
    }
    return options.dirty ? 'dirty-release' : 'release';
}
function resolveStagedReleaseExecutablePath(outputState, commandName) {
    const releaseRoot = outputState.versionDirectoryRelative ?? outputState.releaseRootRelative;
    return `${releaseRoot}/bin/${commandName}`;
}
function createReleasePackageBoundaryMetadata(options) {
    const packageJsonPath = node_path_1.default.join(options.repositoryRoot, 'package.json');
    if (!node_fs_1.default.existsSync(packageJsonPath)) {
        throw (0, errors_1.createUsageError)('Release command requires package.json metadata.', [
            'Reason: missing-package-metadata',
            'Recovery: restore package.json before running release commands.',
            'Exit behavior: exits before generated release output directories or artifacts are created.',
        ]);
    }
    const packageJson = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8'));
    const bin = normalizeStringRecord(packageJson.bin);
    const localReleaseScripts = Object.fromEntries(Object.entries(normalizeStringRecord(packageJson.scripts))
        .filter(([scriptName, script]) => scriptName.startsWith('release:') && script.trim().startsWith('node dist/cli/index.js release '))
        .sort(([left], [right]) => left.localeCompare(right)));
    const packageBinName = Object.keys(bin).find((binName) => binName === index_command_contracts_1.DEFAULT_INSTALLED_COMMAND_NAME) ?? Object.keys(bin)[0] ?? null;
    return {
        packageJsonPath: 'package.json',
        name: readOptionalString(packageJson.name),
        version: readOptionalString(packageJson.version),
        private: packageJson.private === true,
        main: readOptionalString(packageJson.main),
        files: Array.isArray(packageJson.files)
            ? packageJson.files.filter((entry) => typeof entry === 'string')
            : [],
        bin,
        installedCommandName: options.installedCommandName,
        packageBinName,
        packageBinPath: packageBinName === null ? null : (bin[packageBinName] ?? null),
        engines: {
            node: typeof packageJson.engines === 'object' && packageJson.engines !== null
                ? readOptionalString(packageJson.engines.node)
                : null,
        },
        packageManager: readOptionalString(packageJson.packageManager),
        localReleaseScripts,
        publication: {
            publicNpmPublication: 'out-of-scope',
            publicRegistryInstall: 'out-of-scope',
        },
    };
}
function normalizeStringRecord(value) {
    if (typeof value !== 'object' || value === null || Array.isArray(value)) {
        return {};
    }
    return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === 'string'));
}
function readOptionalString(value) {
    return typeof value === 'string' ? value : null;
}
function rejectInstalledCommandName(input, reason, correctiveAction) {
    const received = typeof input === 'string' ? JSON.stringify(input) : String(input);
    return {
        ok: false,
        input,
        reason,
        message: `Rejected installed command name ${received}: ${correctiveAction}`,
        correctiveAction,
    };
}
function requireInstalledCommandName(releaseSubcommand, value) {
    const parsedCommandName = parseInstalledCommandName(value);
    if (!parsedCommandName.ok) {
        throw (0, errors_1.createUsageError)(parsedCommandName.message, [
            `Command: release ${releaseSubcommand.name}`,
            `Rejected command name: ${typeof parsedCommandName.input === 'string' ? parsedCommandName.input : String(parsedCommandName.input)}`,
            `Reason: ${parsedCommandName.reason}`,
            `Recovery: ${parsedCommandName.correctiveAction}`,
            'Exit behavior: exits before generated release output directories, launchers, manifests, installers, validation reports, or snippets are created.',
        ]);
    }
    return parsedCommandName.commandName;
}
