"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SPEC_SOURCE_ADD_RUNTIME_SEMANTICS = void 0;
exports.executeSpecSourceAdd = executeSpecSourceAdd;
exports.createSpecSourceAddCommand = createSpecSourceAddCommand;
const errors_1 = require("../../../lib/errors");
const output_1 = require("../../../lib/output");
const spec_registry_1 = require("../../../lib/spec-registry");
const transaction_1 = require("../../../lib/spec-registry/transaction");
const runtime_contract_1 = require("../../../lib/runtime/runtime-contract");
const cli_constants_1 = require("../../../lib/config/cli-constants");
exports.SPEC_SOURCE_ADD_RUNTIME_SEMANTICS = Object.freeze({
    conditionalInputs: Object.freeze([]),
    dependentInputs: Object.freeze([]),
    conflictingInputs: Object.freeze([]),
    mutableFields: Object.freeze(['source.ref', 'source_history', 'updated_at']),
    genericGuards: Object.freeze([
        Object.freeze({
            id: 'append-only-source-reference',
            description: 'The previous source is appended to chronological history before the current ref changes.',
        }),
    ]),
    failureTaxonomy: Object.freeze({
        missingRequiredInput: 'invalid-input',
        invalidField: 'invalid-input',
        invalidRepositoryState: 'invalid-config',
    }),
    recoveries: Object.freeze([]),
    exactExamples: Object.freeze([
        Object.freeze([
            'day-shift',
            'spec',
            'source',
            'add',
            '--id',
            'spec-registry-commands',
            '--ref',
            'revised-spec.md',
            '--reason',
            'Replace superseded source reference',
        ]),
    ]),
});
function executeSpecSourceAdd(input) {
    const entryId = requireInput(input.entryId, '--id');
    const nextRef = requireInput(input.nextRef, '--ref');
    const reason = requireInput(input.reason, '--reason');
    let snapshot;
    try {
        snapshot = (0, transaction_1.readSpecRegistrySnapshotSync)(input.repositoryRoot);
    }
    catch (error) {
        const detail = error instanceof Error ? error.message : 'Could not parse the spec registry.';
        throw (0, errors_1.createBlockingInvalidConfigError)({
            message: 'Spec source transition requires a valid spec registry.',
            detail,
            findingCode: 'SPEC_SOURCE_REGISTRY_INVALID',
            blockingReason: 'invalid-command-input',
            path: cli_constants_1.CLI_SPEC_INDEX_PATH,
            recovery: 'Repair the spec registry structure and rerun spec source add.',
        });
    }
    let transition;
    try {
        transition = (0, spec_registry_1.applySpecSourceTransition)({
            currentDocument: snapshot.document,
            entryId,
            nextRef,
            reason,
            now: input.now,
        });
    }
    catch (error) {
        if (error instanceof RangeError) {
            throw (0, errors_1.createUsageError)(error.message, ['Use spec list or spec search to select an existing id.']);
        }
        throw error;
    }
    if (transition.changed) {
        requireCommittedRegistryMutation('source add', (0, transaction_1.commitSpecRegistryDocumentSync)(input.repositoryRoot, {
            expectedRevision: snapshot.revision,
            document: transition.document,
        }, input.transactionOptions));
    }
    return {
        ...transition,
        specIndexPath: cli_constants_1.CLI_SPEC_INDEX_PATH,
        writeApplied: transition.changed,
    };
}
function requireCommittedRegistryMutation(operation, mutation) {
    if (mutation.outcome === 'committed')
        return;
    if (mutation.outcome === 'conflict') {
        throw new errors_1.RuntimeError({
            message: `Spec ${operation} could not commit because the registry changed concurrently.`,
            code: 'SPEC_REGISTRY_CONCURRENCY_CONFLICT',
            exitCode: errors_1.RuntimeExitCode.CONCURRENCY_CONFLICT,
            details: [mutation.conflict?.message ?? 'Refresh the registry and retry the command.'],
        });
    }
    if (mutation.outcome === 'incomplete') {
        throw new errors_1.RuntimeError({
            message: `Spec ${operation} registry transaction is incomplete.`,
            code: 'SPEC_REGISTRY_TRANSACTION_INCOMPLETE',
            exitCode: errors_1.RuntimeExitCode.PARTIAL_MUTATION,
            details: [mutation.failure ?? 'Inspect retained registry transaction evidence before recovery.'],
            recoveryActions: [(0, errors_1.createNonCallableRecoveryAction)({
                    id: 'recovery.spec-registry.transaction.inspect',
                    command: null,
                    explanation: 'Inspect the retained registry transaction before retrying the mutation.',
                    reason: mutation.recoveryPath === null
                        ? 'No callable registry recovery command is currently available.'
                        : `Retained transaction evidence: ${mutation.recoveryPath}.`,
                })],
        });
    }
    throw new errors_1.RuntimeError({
        message: `Spec ${operation} registry transaction did not write.`,
        code: 'SPEC_REGISTRY_WRITE_FAILED',
        exitCode: errors_1.RuntimeExitCode.ERROR,
        details: [mutation.failure ?? 'Refresh the registry and retry the command.'],
    });
}
async function createSpecSourceAddCommand() {
    const { Command } = await import('commander');
    const command = new Command('add');
    command
        .description('Append the current spec source to history and atomically install a new source reference.')
        .requiredOption('--id <id>', 'Existing spec registry entry id.')
        .requiredOption('--ref <ref>', 'New source reference.')
        .requiredOption('--reason <reason>', 'Reviewable reason for the source transition.')
        .action((_, commandInstance) => {
        const format = (0, output_1.resolveOutputFormat)(commandInstance.optsWithGlobals?.().format);
        if (format === null)
            throw (0, errors_1.createUsageError)('Output format must be one of: text, json.');
        const options = commandInstance.opts();
        const repositoryContext = (0, runtime_contract_1.createRepositoryContext)((0, runtime_contract_1.detectRepositoryRoot)(), false);
        const result = executeSpecSourceAdd({
            repositoryRoot: repositoryContext.root,
            entryId: options.id,
            nextRef: options.ref,
            reason: options.reason,
        });
        (0, output_1.emitSuccess)({
            command: 'spec source add',
            format,
            text: [
                result.changed
                    ? 'Spec source transition applied.'
                    : 'Spec source reference already matched; no write applied.',
                `Entry id: ${result.entry.id}`,
                `Current source ref: ${result.entry.source.ref}`,
                result.changed
                    ? (0, output_1.renderTerminalBoundary)({ kind: 'write-applied', target: result.specIndexPath })
                    : (0, output_1.renderTerminalBoundary)({
                        kind: 'mutating-no-op',
                        reason: 'Requested source ref is already current.',
                        target: result.specIndexPath,
                    }),
            ].join('\n'),
            data: {
                repositoryRoot: repositoryContext.root,
                detectionSource: repositoryContext.detectionSource,
                specIndexPath: result.specIndexPath,
                changed: result.changed,
                writeApplied: result.writeApplied,
                transitionTimestamp: result.transitionTimestamp,
                entry: result.entry,
            },
        });
    });
    (0, output_1.attachCommandRuntimeSemantics)(command, exports.SPEC_SOURCE_ADD_RUNTIME_SEMANTICS);
    return command;
}
function requireInput(value, option) {
    if (typeof value !== 'string' || value.trim() === '') {
        throw (0, errors_1.createUsageError)(`${option} is required and must be non-empty.`);
    }
    return value.trim();
}
