"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SPEC_UPDATE_RUNTIME_SEMANTICS = exports.SUPPORTED_SPEC_UPDATE_FIELDS = void 0;
exports.createSpecUpdateCommand = createSpecUpdateCommand;
exports.persistSpecRegistryMetadataUpdate = persistSpecRegistryMetadataUpdate;
const node_crypto_1 = require("node:crypto");
const runtime_contract_1 = require("../../../lib/runtime/runtime-contract");
const cli_constants_1 = require("../../../lib/config/cli-constants");
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 metadata_update_operation_1 = require("../metadata-update-operation");
exports.SUPPORTED_SPEC_UPDATE_FIELDS = Object.freeze([
    'title',
    'depth',
    'status',
    'implementation_order',
    'registered_at',
    'updated_at',
    'archived_at',
    'linked_slice.slice',
    'linked_slice.path',
]);
const SUPPORTED_SPEC_UPDATE_FIELD_SET = new Set(exports.SUPPORTED_SPEC_UPDATE_FIELDS);
exports.SPEC_UPDATE_RUNTIME_SEMANTICS = Object.freeze({
    conditionalInputs: Object.freeze([]),
    dependentInputs: Object.freeze([]),
    conflictingInputs: Object.freeze([]),
    mutableFields: exports.SUPPORTED_SPEC_UPDATE_FIELDS,
    genericGuards: Object.freeze([
        Object.freeze({
            id: 'append-only-source-reference',
            description: 'source.ref is append-only and may be changed only through spec source add.',
        }),
    ]),
    failureTaxonomy: Object.freeze({
        missingRequiredInput: 'invalid-input',
        invalidField: 'invalid-input',
        invalidRepositoryState: 'invalid-config',
    }),
    recoveries: Object.freeze([
        Object.freeze({
            command: 'spec source add',
            requiredInputs: Object.freeze(['id', 'ref', 'reason']),
            callableWhen: 'all-required-inputs-present',
            availability: 'callable',
        }),
    ]),
    exactExamples: Object.freeze([
        Object.freeze([
            'day-shift',
            'spec',
            'update',
            '--spec-id',
            'spec-registry-commands',
            '--field',
            'title=Updated spec title',
        ]),
    ]),
});
async function createSpecUpdateCommand() {
    const { Command } = await import('commander');
    const command = new Command('update');
    command
        .description('Apply helper-backed field updates to one spec-registry entry.')
        .requiredOption('--spec-id <id>', 'Spec registry entry id to update.')
        .requiredOption('--field <key=value>', 'Direct intended metadata update. Repeat for multiple fields.', collectFieldAssignment, [])
        .action((_, commandInstance) => {
        const repositoryContext = (0, runtime_contract_1.createRepositoryContext)((0, runtime_contract_1.detectRepositoryRoot)(), false);
        const commandOptions = commandInstance.opts();
        const specId = readRequiredSpecId(commandOptions.specId);
        const fieldAssignments = readAndValidateFieldAssignments(commandOptions.field, specId);
        const prefixedAssignments = fieldAssignments.map((assignment) => `entry:${specId}:${assignment}`);
        (0, metadata_update_operation_1.executeMetadataUpdateOperation)({
            commandName: 'spec update',
            write: true,
            commandInstance,
            repositoryRoot: repositoryContext.root,
            detectionSource: repositoryContext.detectionSource,
            artifactPathOverride: cli_constants_1.CLI_SPEC_INDEX_PATH,
            fieldAssignmentsOverride: prefixedAssignments,
            expectedFamilies: ['spec-registry'],
            persistContent: ({ originalContent, nextContent }) => {
                persistSpecRegistryMetadataUpdate({
                    repositoryRoot: repositoryContext.root,
                    originalContent,
                    nextContent,
                });
            },
        });
    });
    (0, output_1.attachCommandRuntimeSemantics)(command, exports.SPEC_UPDATE_RUNTIME_SEMANTICS);
    return command;
}
function persistSpecRegistryMetadataUpdate(input, transactionOptions = {}) {
    const mutation = (0, transaction_1.commitSpecRegistryDocumentSync)(input.repositoryRoot, {
        expectedRevision: (0, node_crypto_1.createHash)('sha256').update(input.originalContent).digest('hex'),
        document: (0, spec_registry_1.parseSpecIndexDocumentText)(input.nextContent),
    }, transactionOptions);
    requireCommittedRegistryMutation('update', mutation);
}
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.'],
    });
}
function readAndValidateFieldAssignments(value, specId) {
    if (!Array.isArray(value) || value.length === 0) {
        throw (0, errors_1.createUsageError)('--field is required and must be provided at least once.');
    }
    return value.map((assignment) => {
        if (typeof assignment !== 'string') {
            throw (0, errors_1.createUsageError)('Each --field value must use <key>=<value>.');
        }
        const separatorIndex = assignment.indexOf('=');
        const field = separatorIndex < 0 ? assignment.trim() : assignment.slice(0, separatorIndex).trim();
        if (separatorIndex <= 0 || field === '') {
            throw (0, errors_1.createUsageError)(`Invalid --field assignment '${assignment}'.`, [
                'Each assignment must use <key>=<value>.',
            ]);
        }
        if (!SUPPORTED_SPEC_UPDATE_FIELD_SET.has(field)) {
            if (field === 'source.ref') {
                throw (0, errors_1.createUsageError)('Spec source references are append-only and cannot be changed through spec update.', [
                    `Supported alternative: day-shift spec source add --id ${specId} --ref <ref> --reason <reason>.`,
                    'Recovery is not executable until id, ref, and reason are all provided.',
                ]);
            }
            throw (0, errors_1.createUsageError)(`Unsupported spec update field '${field}'.`, [
                `Supported fields: ${exports.SUPPORTED_SPEC_UPDATE_FIELDS.join(', ')}.`,
            ]);
        }
        return assignment;
    });
}
function collectFieldAssignment(value, previous) {
    previous.push(value);
    return previous;
}
function readRequiredSpecId(value) {
    if (typeof value !== 'string' || value.trim() === '') {
        throw (0, errors_1.createUsageError)('--spec-id is required.');
    }
    return value.trim();
}
