"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWorkPromoteCommand = createWorkPromoteCommand;
const errors_1 = require("../../../lib/errors");
const output_1 = require("../../../lib/output");
const operational_recovery_1 = require("../../../lib/filesystem/operational-recovery");
const promotion_application_1 = require("../../../lib/planning/promotion-application");
const promotion_planning_1 = require("../../../lib/planning/promotion-planning");
const runtime_contract_1 = require("../../../lib/runtime/runtime-contract");
const task_promote_1 = require("../task-promote");
const global_format_1 = require("../global-format");
async function createWorkPromoteCommand() {
    const { Command } = await import('commander');
    const command = new Command('promote');
    command
        .description('Preview/apply Structured-to-Governed promotion, or recover exact promotion evidence.')
        .option('--overview <path>', 'Canonical repository-relative Structured overview.md path.')
        .option('--to <level>', 'Explicit promotion target; governed is the only supported work target.')
        .option('--implementation-order <order>', 'Two-digit Governed slice implementation order.')
        .option('--slice-id <id>', "Optional explicit Governed '<ordered-prefix>-<slice-slug>' identity.")
        .option('--slice <slug>', 'Canonical lowercase kebab-case Governed slice slug.')
        .option('--phase <slug>', 'Ordered Governed destination phase slug.')
        .option('--task-mapping <task-id=milestone-id>', 'Explicit Structured task to Governed milestone mapping; repeat once per direct task.', collect, [])
        .option('--dry-run', 'Explicit no-write promotion preview.')
        .option('--apply', 'Transactionally apply the exact preview identified by --plan-id.')
        .option('--plan-id <id>', 'Exact promotion plan id required by --apply.')
        .option('--actor <identity>', 'Non-empty audit actor required by --apply or --recover.')
        .option('--reason <text>', 'Non-empty audit reason required by --apply or --recover.')
        .option('--applied-at <timestamp>', 'Optional apply-only ISO-8601 application time.')
        .option('--recover <action>', 'Explicit exact-evidence recovery: roll-forward or restore.')
        .option('--transaction-id <id>', 'Exact retained promotion transaction id required by --recover.')
        .option('--evidence-fingerprint <sha256>', 'Exact current read-only inspection fingerprint required by --recover.')
        .option('--recovered-at <timestamp>', 'Optional recovery-only ISO-8601 recovery time.')
        .action((_, commandInstance) => {
        const format = (0, output_1.resolveOutputFormat)((0, global_format_1.getGlobalFormat)(commandInstance));
        if (format === null)
            throw (0, errors_1.createUsageError)('Output format must be one of: text, json.');
        const options = commandInstance.opts();
        const repository = (0, runtime_contract_1.createRepositoryContext)((0, runtime_contract_1.detectRepositoryRoot)(), false);
        if (hasRecoveryInput(options)) {
            const proof = requireRecoveryProof(options);
            let recovery;
            try {
                recovery = (0, operational_recovery_1.recoverOperationalTransactionSync)(repository.root, proof);
            }
            catch (error) {
                throw (0, task_promote_1.normalizeOperationalRecoveryError)(error);
            }
            if (recovery.outcome !== 'recovered') {
                throw (0, task_promote_1.createPromotionRecoveryOutcomeError)('work promote', repository.root, proof, recovery);
            }
            (0, output_1.emitSuccess)({
                command: 'work promote',
                format,
                text: (0, task_promote_1.renderPromotionRecoveryText)(recovery),
                data: {
                    detectionSource: repository.detectionSource,
                    recoveryRequested: true,
                    recovery: (0, task_promote_1.publicPromotionRecoveryResult)(recovery),
                },
            });
            return;
        }
        const overview = requirePlanningOption(options.overview, '--overview');
        const target = requirePlanningOption(options.to, '--to');
        const implementationOrder = requirePlanningOption(options.implementationOrder, '--implementation-order');
        const slice = requirePlanningOption(options.slice, '--slice');
        const phase = requirePlanningOption(options.phase, '--phase');
        if (target !== 'governed') {
            throw (0, errors_1.createUsageError)("work promote supports only the explicit target '--to governed'.", [
                'No source or destination artifact was written.',
            ]);
        }
        validateApplicationOptions(options);
        let plan;
        let application = null;
        try {
            const mapping = parseTaskMappings(options.taskMapping);
            const governed = {
                sliceSlug: slice,
                implementationOrders: [implementationOrder],
                requestedSliceId: optionalText(options.sliceId),
                phaseSlug: phase,
                milestoneSlugs: mapping.milestones,
                taskToMilestone: mapping.taskToMilestone,
            };
            if (options.apply === true) {
                const preview = (0, promotion_application_1.previewStructuredPromotionSync)({
                    repositoryRoot: repository.root,
                    sourceOverviewPath: overview,
                    governed,
                });
                application = (0, promotion_application_1.applyStructuredPromotionSync)(preview, {
                    planId: requireApplyOption(options.planId, '--plan-id'),
                    actor: requireApplyOption(options.actor, '--actor'),
                    reason: requireApplyOption(options.reason, '--reason'),
                    appliedAt: options.appliedAt,
                });
                plan = application.plan;
            }
            else {
                plan = (0, promotion_planning_1.planPromotionSync)({
                    repositoryRoot: repository.root,
                    sourceLevel: 'structured',
                    targetLevel: 'governed',
                    sourceOverviewPath: overview,
                    governed,
                });
            }
        }
        catch (error) {
            throw (0, task_promote_1.normalizePromotionError)(error);
        }
        if (application !== null && (application.writeOutcome !== 'committed' || application.cleanupErrors.length > 0)) {
            throw createPromotionApplicationOutcomeError(application, repository.root);
        }
        (0, output_1.emitSuccess)({
            command: 'work promote',
            format,
            text: application === null ? (0, task_promote_1.renderPromotionPlanText)(plan) : renderPromotionApplicationText(application),
            data: {
                detectionSource: repository.detectionSource,
                requestedDryRun: options.dryRun === true,
                applyRequested: options.apply === true,
                recoveryRequested: false,
                noWriteBoundary: application === null ? promotionNoWriteBoundary() : null,
                ...plan,
                ...(application === null ? {} : { application: publicApplicationResult(application) }),
            },
        });
    });
    return command;
}
function parseTaskMappings(values) {
    const taskToMilestone = {};
    const milestones = [];
    for (const value of values) {
        const separator = value.indexOf('=');
        const taskId = separator < 0 ? '' : value.slice(0, separator).trim();
        const milestoneId = separator < 0 ? '' : value.slice(separator + 1).trim();
        if (taskId === '' || milestoneId === '') {
            throw (0, errors_1.createUsageError)('--task-mapping values must use <task-id>=<milestone-id>.', [
                'No source or destination artifact was written.',
            ]);
        }
        milestones.push(milestoneId);
        const existing = taskToMilestone[taskId];
        if (existing === undefined) {
            taskToMilestone[taskId] = milestoneId;
        }
        else {
            taskToMilestone[taskId] = Array.isArray(existing) ? [...existing, milestoneId] : [existing, milestoneId];
        }
    }
    return {
        taskToMilestone,
        milestones: [...new Set(milestones)].sort(),
    };
}
function requirePlanningOption(value, name) {
    if (typeof value === 'string' && value.trim() !== '')
        return value.trim();
    throw (0, errors_1.createUsageError)(`${name} is required for promotion planning and apply.`, [
        promotionNoWriteBoundary(),
        'Exact-evidence recovery uses --recover with transaction id, fingerprint, actor, and reason.',
    ]);
}
function requireApplyOption(value, name) {
    if (typeof value === 'string' && value.trim() !== '')
        return value.trim();
    throw (0, errors_1.createUsageError)(`${name} is required when --apply is selected.`, [
        'Run the no-write preview first, then apply its exact plan id with actor and reason.',
    ]);
}
function validateApplicationOptions(options) {
    if (options.apply === true && options.dryRun === true) {
        throw (0, errors_1.createUsageError)('--dry-run and --apply cannot be selected together.', [
            'Use --dry-run for a no-write preview or --apply for the explicitly authorized transaction.',
        ]);
    }
    const applicationOnly = [
        ['--plan-id', options.planId],
        ['--actor', options.actor],
        ['--reason', options.reason],
        ['--applied-at', options.appliedAt],
    ];
    if (options.apply !== true) {
        const supplied = applicationOnly.filter(([, value]) => value !== undefined).map(([name]) => name);
        if (supplied.length > 0) {
            throw (0, errors_1.createUsageError)(`${supplied.join(', ')} require --apply.`, [promotionNoWriteBoundary()]);
        }
    }
}
function hasRecoveryInput(options) {
    return [options.recover, options.transactionId, options.evidenceFingerprint, options.recoveredAt].some((value) => value !== undefined);
}
function requireRecoveryProof(options) {
    const contradictory = [
        ['--overview', options.overview],
        ['--to', options.to],
        ['--implementation-order', options.implementationOrder],
        ['--slice-id', options.sliceId],
        ['--slice', options.slice],
        ['--phase', options.phase],
        ['--task-mapping', options.taskMapping.length > 0 ? 'supplied' : undefined],
        ['--dry-run', options.dryRun],
        ['--apply', options.apply],
        ['--plan-id', options.planId],
        ['--applied-at', options.appliedAt],
    ].filter(([, value]) => value !== undefined && value !== false);
    if (contradictory.length > 0) {
        throw (0, errors_1.createUsageError)(`Recovery cannot be combined with planning/apply inputs: ${contradictory.map(([name]) => name).join(', ')}.`, ['No recovery or promotion write was attempted.']);
    }
    if (options.recover !== 'roll-forward' && options.recover !== 'restore') {
        throw (0, errors_1.createUsageError)('--recover must be one of: roll-forward, restore.', [
            'No recovery or promotion write was attempted.',
        ]);
    }
    return {
        action: options.recover,
        transactionId: requireRecoveryOption(options.transactionId, '--transaction-id'),
        evidenceFingerprint: requireRecoveryOption(options.evidenceFingerprint, '--evidence-fingerprint'),
        actor: requireRecoveryOption(options.actor, '--actor'),
        reason: requireRecoveryOption(options.reason, '--reason'),
        recoveredAt: options.recoveredAt ?? new Date().toISOString(),
    };
}
function optionalText(value) {
    return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
}
function requireRecoveryOption(value, name) {
    if (typeof value === 'string' && value.trim() !== '')
        return value.trim();
    throw (0, errors_1.createUsageError)(`${name} is required when exact-evidence recovery is selected.`, [
        'Recovery inputs are all-or-nothing; no write was attempted.',
    ]);
}
function publicApplicationResult(result) {
    return {
        planId: result.planId,
        writeOutcome: result.writeOutcome,
        transactionId: result.transactionId,
        sourceWorkId: result.sourceWorkId,
        sourceTaskIds: result.sourceTaskIds,
        sourceOverviewPath: result.sourceOverviewPath,
        sourcePaths: result.sourcePaths,
        destinationPaths: result.destinationPaths,
        createdParentPaths: result.createdParentPaths,
        intendedRetiredPaths: result.intendedRetiredPaths,
        intendedPaths: result.intendedPaths,
        changedPaths: result.changedPaths,
        retiredPaths: result.retiredPaths,
        conflicts: result.conflicts,
        promotionRecordPath: result.promotionRecordPath,
        recoveryPath: result.recoveryPath,
        recoveryFingerprint: result.recoveryFingerprint,
        cleanupErrors: result.cleanupErrors,
        failure: result.failure,
        actor: result.actor,
        reason: result.reason,
        appliedAt: result.appliedAt,
        nextAction: result.nextAction,
    };
}
function renderPromotionApplicationText(result) {
    return [
        'Applied Structured promotion to governed.',
        '',
        `Plan id: ${result.planId}`,
        `Write outcome: ${result.writeOutcome}`,
        `Transaction id: ${result.transactionId ?? 'none'}`,
        `Source overview: ${result.sourceOverviewPath}`,
        `Source tasks: ${renderList(result.sourceTaskIds)}`,
        `Source paths: ${renderList(result.sourcePaths)}`,
        `Destination paths: ${renderList(result.destinationPaths)}`,
        `Created parents: ${renderList(result.createdParentPaths)}`,
        `Changed paths: ${renderList(result.changedPaths)}`,
        `Retired paths: ${renderList(result.retiredPaths)}`,
        `Promotion record: ${result.promotionRecordPath}`,
        `Actor: ${result.actor ?? 'none'}`,
        `Reason: ${result.reason ?? 'none'}`,
        `Applied at: ${result.appliedAt ?? 'none'}`,
        `Recovery path: ${result.recoveryPath ?? 'none'}`,
        `Recovery fingerprint: ${result.recoveryFingerprint ?? 'none'}`,
        `Cleanup errors: ${renderList(result.cleanupErrors)}`,
        `Next action: ${result.nextAction}`,
    ].join('\n');
}
function createPromotionApplicationOutcomeError(result, repositoryRoot) {
    const details = [
        `planId: ${result.planId}`,
        `writeOutcome: ${result.writeOutcome}`,
        `sourcePaths: ${JSON.stringify(result.sourcePaths)}`,
        `destinationPaths: ${JSON.stringify(result.destinationPaths)}`,
        `createdParentPaths: ${JSON.stringify(result.createdParentPaths)}`,
        `intendedRetiredPaths: ${JSON.stringify(result.intendedRetiredPaths)}`,
        `changedPaths: ${JSON.stringify(result.changedPaths)}`,
        `retiredPaths: ${JSON.stringify(result.retiredPaths)}`,
        `conflicts: ${JSON.stringify(result.conflicts)}`,
        `promotionRecordPath: ${result.promotionRecordPath}`,
        `recoveryPath: ${result.recoveryPath ?? 'null'}`,
        `recoveryFingerprint: ${result.recoveryFingerprint ?? 'null'}`,
        `cleanupErrors: ${JSON.stringify(result.cleanupErrors)}`,
        `nextAction: ${result.nextAction}`,
    ];
    if (result.writeOutcome === 'conflict') {
        return new errors_1.RuntimeError({
            message: 'Structured promotion preview is stale or conflicted.',
            code: 'PROMOTION_APPLICATION_CONFLICT',
            exitCode: errors_1.RuntimeExitCode.CONCURRENCY_CONFLICT,
            details,
            recoveryActions: [
                (0, errors_1.createNonCallableRecoveryAction)({
                    id: 'recovery.refresh-structured-promotion-preview',
                    command: 'work promote',
                    explanation: 'Inspect source and destination evidence, then build a fresh no-write promotion plan.',
                    reason: 'The original slice, phase, and task mappings must be supplied again by the operator.',
                }),
            ],
        });
    }
    if (result.writeOutcome === 'incomplete' || result.recoveryPath !== null || result.cleanupErrors.length > 0) {
        return new errors_1.RuntimeError({
            message: result.writeOutcome === 'committed'
                ? 'Structured promotion committed, but operational cleanup remains incomplete.'
                : 'Structured promotion stopped with retained incomplete repository state.',
            code: 'PROMOTION_APPLICATION_INCOMPLETE',
            exitCode: errors_1.RuntimeExitCode.PARTIAL_MUTATION,
            writeOutcome: 'partial',
            details,
            recoveryActions: (0, task_promote_1.promotionApplicationRecoveryActions)('work promote', repositoryRoot, result),
        });
    }
    return new errors_1.RuntimeError({
        message: 'Structured promotion did not commit its intended destination and retirement changes.',
        code: 'PROMOTION_APPLICATION_NO_WRITE',
        exitCode: errors_1.RuntimeExitCode.ERROR,
        details,
    });
}
function promotionNoWriteBoundary() {
    return 'Structured promotion planning without --apply never writes, moves, copies, retires, or transactionally applies source or destination artifacts.';
}
function renderList(values) {
    return values.length === 0 ? 'none' : values.join(', ');
}
function collect(value, previous) {
    previous.push(value);
    return previous;
}
