"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSpecDispositionCommand = createSpecDispositionCommand;
exports.renderSpecificationDispositionText = renderSpecificationDispositionText;
exports.createSpecificationReadinessOutcomeError = createSpecificationReadinessOutcomeError;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const errors_1 = require("../../../lib/errors");
const output_1 = require("../../../lib/output");
const spec_readiness_1 = require("../../../lib/spec-readiness");
const runtime_contract_1 = require("../../../lib/runtime/runtime-contract");
const cli_constants_1 = require("../../../lib/config/cli-constants");
const global_format_1 = require("../global-format");
async function createSpecDispositionCommand() {
    const { Command } = await import('commander');
    const command = new Command('disposition');
    command
        .description('Preview or apply explicit revision-bound specification planning-readiness evidence.')
        .requiredOption('--spec <path>', 'Repository-relative canonical registered specification path.')
        .requiredOption('--planning-readiness <disposition>', 'Disposition: ready, waived, or revoked.')
        .option('--review-result <path>', 'Repository-local UTF-8 JSON file containing a complete spec review result; required for ready.')
        .option('--review-digest <sha256>', 'Optional expected SHA-256 digest for the canonical imported review JSON.')
        .option('--reason <text>', 'Disposition reason; required for waived and revoked.')
        .requiredOption('--recorded-by <actor>', 'Non-empty disposition actor; also records the waiver actor.')
        .requiredOption('--recorded-at <timestamp>', 'ISO-8601 disposition time; also records the waiver time.')
        .option('--waiver-id <id>', 'Stable waiver identity; required for waived.')
        .option('--waiver-subject <spec-id>', 'Specification identity authorized by the waiver; required for waived.')
        .option('--waiver-status <status>', 'Waiver status; a new waived disposition requires active.')
        .option('--waiver-expires-at <timestamp>', 'Optional ISO-8601 waiver expiry.')
        .option('--waiver-authority-reference <reference>', 'Optional waiver authority reference.')
        .option('--waiver-attempt-id <id>', 'Optional waiver attempt identity.')
        .option('--dry-run', 'Preview intended canonical writes and preconditions without mutation; omit to apply.')
        .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 repository = (0, runtime_contract_1.createRepositoryContext)((0, runtime_contract_1.detectRepositoryRoot)(), false);
        const options = commandInstance.opts();
        let request;
        try {
            request = normalizeRequest(repository.root, options);
        }
        catch (error) {
            throw normalizeDispositionError(error, options.spec);
        }
        let result;
        try {
            const preview = (0, spec_readiness_1.previewSpecificationReadinessDispositionSync)(request);
            result = options.dryRun === true ? preview : (0, spec_readiness_1.applySpecificationReadinessDispositionSync)(preview);
        }
        catch (error) {
            throw normalizeDispositionError(error, options.spec);
        }
        if (result.writeOutcome !== 'previewed' && result.writeOutcome !== 'committed') {
            throw createSpecificationReadinessOutcomeError(result);
        }
        const data = publicResult(result);
        (0, output_1.emitSuccess)({
            command: 'spec disposition',
            format,
            text: renderSpecificationDispositionText(data),
            data,
        });
    });
    return command;
}
function normalizeRequest(repositoryRoot, options) {
    const disposition = options.planningReadiness;
    if (disposition !== 'ready' && disposition !== 'waived' && disposition !== 'revoked') {
        throw (0, errors_1.createUsageError)('--planning-readiness must be one of: ready, waived, revoked.');
    }
    const waiverOptions = [
        options.waiverId,
        options.waiverSubject,
        options.waiverStatus,
        options.waiverExpiresAt,
        options.waiverAuthorityReference,
        options.waiverAttemptId,
    ];
    const hasWaiverOption = waiverOptions.some((value) => value !== undefined);
    if (disposition === 'ready') {
        if (options.reviewResult === undefined) {
            throw (0, errors_1.createUsageError)('A ready disposition requires --review-result <path> with complete review JSON.');
        }
        if (hasWaiverOption) {
            throw (0, errors_1.createUsageError)('Waiver options are valid only with --planning-readiness waived.');
        }
        return {
            repositoryRoot,
            specPath: options.spec,
            planningReadiness: 'ready',
            reviewResult: readReviewResult(repositoryRoot, options.reviewResult),
            ...(options.reviewDigest === undefined ? {} : { reviewDigest: options.reviewDigest }),
            ...(options.reason === undefined ? {} : { reason: options.reason }),
            recordedAt: options.recordedAt,
            recordedBy: options.recordedBy,
        };
    }
    if (options.reviewResult !== undefined || options.reviewDigest !== undefined) {
        throw (0, errors_1.createUsageError)('Review import options are valid only with --planning-readiness ready.');
    }
    if (disposition === 'revoked') {
        if (hasWaiverOption) {
            throw (0, errors_1.createUsageError)('Waiver options are valid only with --planning-readiness waived.');
        }
        if (options.reason === undefined || options.reason.trim() === '') {
            throw (0, errors_1.createUsageError)('A revoked disposition requires --reason <text>.');
        }
        return {
            repositoryRoot,
            specPath: options.spec,
            planningReadiness: 'revoked',
            reason: options.reason,
            recordedAt: options.recordedAt,
            recordedBy: options.recordedBy,
        };
    }
    const missing = [
        ['--waiver-id', options.waiverId],
        ['--waiver-subject', options.waiverSubject],
        ['--waiver-status', options.waiverStatus],
        ['--reason', options.reason],
    ]
        .filter(([, value]) => typeof value !== 'string' || value.trim() === '')
        .map(([flag]) => flag);
    if (missing.length > 0) {
        throw (0, errors_1.createUsageError)(`A waived disposition requires: ${missing.join(', ')}.`);
    }
    if (options.waiverStatus !== 'active') {
        throw (0, errors_1.createUsageError)('A new waived disposition requires --waiver-status active.');
    }
    const waiver = {
        waiver_id: options.waiverId,
        waiver_type: 'planning-readiness',
        subject_id: options.waiverSubject,
        ...(options.waiverAttemptId === undefined ? {} : { attempt_id: options.waiverAttemptId }),
        reason: options.reason,
        recorded_by: options.recordedBy,
        recorded_at: options.recordedAt,
        ...(options.waiverExpiresAt === undefined ? {} : { expires_at: options.waiverExpiresAt }),
        ...(options.waiverAuthorityReference === undefined
            ? {}
            : { authority_reference: options.waiverAuthorityReference }),
        status: 'active',
    };
    return {
        repositoryRoot,
        specPath: options.spec,
        planningReadiness: 'waived',
        waiver,
        reason: options.reason,
        recordedAt: options.recordedAt,
        recordedBy: options.recordedBy,
    };
}
function readReviewResult(repositoryRoot, requestedPath) {
    const root = node_fs_1.default.realpathSync(repositoryRoot);
    const candidate = node_path_1.default.isAbsolute(requestedPath) ? requestedPath : node_path_1.default.resolve(root, requestedPath);
    let resolved;
    let stats;
    try {
        resolved = node_fs_1.default.realpathSync(candidate);
        stats = node_fs_1.default.statSync(resolved);
    }
    catch (error) {
        throw (0, errors_1.createUsageError)(`Review result file '${requestedPath}' is not readable.`, [errorMessage(error)]);
    }
    const relative = node_path_1.default.relative(root, resolved);
    if (relative === '' || relative.startsWith('..') || node_path_1.default.isAbsolute(relative)) {
        throw (0, errors_1.createUsageError)('--review-result must resolve to a file inside the effective repository.');
    }
    if (!stats.isFile())
        throw (0, errors_1.createUsageError)('--review-result must identify a regular UTF-8 JSON file.');
    if (stats.size > cli_constants_1.CLI_MAX_REVIEW_RESULT_BYTES) {
        throw (0, errors_1.createUsageError)(`--review-result exceeds the ${cli_constants_1.CLI_MAX_REVIEW_RESULT_BYTES}-byte import limit.`);
    }
    try {
        return JSON.parse(node_fs_1.default.readFileSync(resolved, 'utf8'));
    }
    catch (error) {
        throw (0, errors_1.createUsageError)('--review-result must contain valid UTF-8 JSON.', [errorMessage(error)]);
    }
}
function publicResult(result) {
    return {
        specId: result.specId,
        specPath: result.specPath,
        specRevision: result.specRevision,
        planningReadiness: result.planningReadiness,
        dispositionPath: result.dispositionPath,
        writeOutcome: result.writeOutcome,
        intendedPaths: result.intendedPaths,
        changedPaths: result.changedPaths,
        expected: result.expected,
        reviewReference: result.review,
        waiver: result.waiver,
        transactionId: result.transactionId,
        conflicts: result.conflicts,
        recoveryPath: result.recoveryPath,
        failure: result.failure,
        nextAction: nextAction(result),
    };
}
function renderSpecificationDispositionText(result) {
    return [
        (0, output_1.renderTerminalHeading)('Specification planning-readiness disposition', 'title').trimEnd(),
        '',
        `specId: ${result.specId}`,
        `specPath: ${result.specPath}`,
        `specRevision: ${result.specRevision}`,
        `planningReadiness: ${result.planningReadiness}`,
        `dispositionPath: ${result.dispositionPath}`,
        `writeOutcome: ${result.writeOutcome}`,
        `intendedPaths: ${JSON.stringify(result.intendedPaths)}`,
        `changedPaths: ${JSON.stringify(result.changedPaths)}`,
        `expected: ${JSON.stringify(result.expected)}`,
        `reviewReference: ${JSON.stringify(result.reviewReference)}`,
        `waiver: ${JSON.stringify(result.waiver)}`,
        `transactionId: ${result.transactionId ?? 'null'}`,
        `conflicts: ${JSON.stringify(result.conflicts)}`,
        `recoveryPath: ${result.recoveryPath ?? 'null'}`,
        `failure: ${result.failure ?? 'null'}`,
        `nextAction: ${result.nextAction}`,
    ].join('\n');
}
function createSpecificationReadinessOutcomeError(result) {
    const details = [
        `planningReadiness: ${result.planningReadiness}`,
        `specId: ${result.specId}`,
        `specPath: ${result.specPath}`,
        `specRevision: ${result.specRevision}`,
        `writeOutcome: ${result.writeOutcome}`,
        `intendedPaths: ${JSON.stringify(result.intendedPaths)}`,
        `changedPaths: ${JSON.stringify(result.changedPaths)}`,
        `expected: ${JSON.stringify(result.expected)}`,
        `reviewReference: ${JSON.stringify(result.review)}`,
        `conflicts: ${JSON.stringify(result.conflicts)}`,
        `recoveryPath: ${result.recoveryPath ?? 'null'}`,
        `nextAction: ${nextAction(result)}`,
    ];
    if (result.writeOutcome === 'conflict') {
        return new errors_1.RuntimeError({
            message: 'Specification readiness disposition preview is stale or conflicted.',
            code: 'SPEC_DISPOSITION_CONFLICT',
            exitCode: errors_1.RuntimeExitCode.CONCURRENCY_CONFLICT,
            details,
            recoveryActions: [
                (0, errors_1.createNonCallableRecoveryAction)({
                    id: 'recovery.refresh-spec-disposition-preview',
                    command: 'spec disposition',
                    explanation: 'Refresh review and destination evidence, then preview the disposition again.',
                    reason: 'The original conditional option values must be supplied again by the operator.',
                }),
            ],
        });
    }
    if (result.writeOutcome === 'incomplete') {
        return new errors_1.RuntimeError({
            message: 'Specification readiness disposition stopped with incomplete repository state.',
            code: 'SPEC_DISPOSITION_INCOMPLETE',
            exitCode: errors_1.RuntimeExitCode.PARTIAL_MUTATION,
            writeOutcome: 'partial',
            details,
            recoveryActions: [
                (0, errors_1.createNonCallableRecoveryAction)({
                    id: 'recovery.inspect-spec-disposition-transaction',
                    explanation: 'Inspect retained transaction evidence before retrying or repairing readiness evidence.',
                    reason: result.recoveryPath ?? 'The retained transaction path was unavailable.',
                }),
            ],
        });
    }
    return new errors_1.RuntimeError({
        message: 'Specification readiness disposition did not change canonical evidence.',
        code: 'SPEC_DISPOSITION_NO_WRITE',
        exitCode: errors_1.RuntimeExitCode.ERROR,
        details,
    });
}
function normalizeDispositionError(error, specPath) {
    if (error instanceof errors_1.RuntimeError)
        return error;
    if (error instanceof spec_readiness_1.SpecificationReadinessError) {
        const exitCode = readinessExitCode(error.code);
        return new errors_1.RuntimeError({
            message: 'Specification readiness disposition was rejected.',
            code: `SPEC_DISPOSITION_${error.code.replace(/-/g, '_').toUpperCase()}`,
            exitCode,
            details: [
                error.message,
                'No canonical readiness, review, state, or planning files were written.',
                exitCode === errors_1.RuntimeExitCode.STALE_EVIDENCE
                    ? 'Refresh the specification review and disposition preview before retrying.'
                    : 'Correct the disposition input and preview it again before applying.',
            ],
            findings: [
                {
                    code: `SPEC_DISPOSITION_${error.code.replace(/-/g, '_').toUpperCase()}`,
                    severity: 'error',
                    message: error.message,
                    path: specPath,
                    noWriteBoundary: 'Rejected specification disposition requests do not write canonical evidence.',
                },
            ],
        });
    }
    throw error;
}
function readinessExitCode(code) {
    if (code === 'unsupported-protocol')
        return errors_1.RuntimeExitCode.UNSUPPORTED_CAPABILITY;
    if (code === 'review-mismatch' || code === 'digest-mismatch' || code === 'stale-preview') {
        return errors_1.RuntimeExitCode.STALE_EVIDENCE;
    }
    if (code === 'spec-unregistered')
        return errors_1.RuntimeExitCode.ERROR;
    return errors_1.RuntimeExitCode.USAGE;
}
function nextAction(result) {
    if (result.writeOutcome === 'previewed') {
        return 'Confirm the expected conditions, then rerun without --dry-run to apply this disposition.';
    }
    if (result.writeOutcome === 'committed') {
        return result.planningReadiness === 'revoked'
            ? 'Planning is ineligible until a new explicit ready or waived disposition is recorded.'
            : 'Planning-readiness evidence is current; planning may proceed only through an explicit planning command.';
    }
    if (result.writeOutcome === 'conflict') {
        return 'Refresh the specification review and disposition preview against current repository evidence before retrying.';
    }
    if (result.writeOutcome === 'incomplete') {
        return 'Inspect the retained transaction recovery path before retrying or repairing readiness evidence.';
    }
    return 'Inspect existing readiness evidence; no canonical file changed.';
}
function errorMessage(error) {
    return error instanceof Error && error.message.trim() !== '' ? error.message : 'Unknown file import error.';
}
