"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSpecAddCommand = createSpecAddCommand;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const errors_1 = require("../../../lib/errors");
const global_format_1 = require("../global-format");
const output_1 = require("../../../lib/output");
const runtime_contract_1 = require("../../../lib/runtime/runtime-contract");
const spec_registry_1 = require("../../../lib/spec-registry");
const transaction_1 = require("../../../lib/spec-registry/transaction");
const path_resolution_1 = require("../../../lib/spec-registry/path-resolution");
async function createSpecAddCommand() {
    const { Command } = await import("commander");
    const command = new Command("add");
    command
        .description("Register a validated spec entry by normalizing and writing it into the spec index.")
        .option("--entry-json <json>", "Candidate spec registry entry serialized as JSON text.")
        .option("--entry-file <path>", "Path to a JSON file containing a candidate spec registry entry.")
        .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.", [`Received: ${String((0, global_format_1.getGlobalFormat)(commandInstance))}`]);
        }
        const repositoryContext = (0, runtime_contract_1.createRepositoryContext)((0, runtime_contract_1.detectRepositoryRoot)(), false);
        const candidateEntry = loadCandidateEntry(commandInstance.opts(), repositoryContext.root);
        const specIndexPaths = (0, path_resolution_1.resolveSpecIndexPaths)(repositoryContext);
        const currentSnapshot = (0, transaction_1.readSpecRegistrySnapshotSync)(repositoryContext.root);
        const currentIndex = currentSnapshot.document;
        const addResult = (0, spec_registry_1.applySpecRegistryAdd)({
            proposedEntry: candidateEntry,
            currentDocument: currentIndex
        });
        if (!addResult.ok) {
            throw new errors_1.RuntimeError({
                message: "Spec add validation failed.",
                code: "SPEC_ADD_VALIDATION_FAILED",
                exitCode: errors_1.RuntimeExitCode.ERROR,
                details: addResult.findings.map((finding) => finding.message),
                findings: [...addResult.findings]
            });
        }
        requireCommittedRegistryMutation("add", (0, transaction_1.commitSpecRegistryDocumentSync)(repositoryContext.root, {
            expectedRevision: currentSnapshot.revision,
            document: addResult.document
        }));
        (0, output_1.emitSuccess)({
            command: "spec add",
            format,
            text: renderSpecAddWriteText(addResult.entry.id, currentIndex.entries.length, addResult.document.entries.length),
            data: {
                repositoryRoot: repositoryContext.root,
                detectionSource: repositoryContext.detectionSource,
                specIndexPath: specIndexPaths.relativePath,
                currentIndexEntryCount: currentIndex.entries.length,
                nextIndexEntryCount: addResult.document.entries.length,
                entry: addResult.entry
            }
        });
    });
    return command;
}
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 loadCandidateEntry(options, repositoryRoot) {
    const hasEntryJson = typeof options.entryJson === "string" && options.entryJson.trim() !== "";
    const hasEntryFile = typeof options.entryFile === "string" && options.entryFile.trim() !== "";
    if (hasEntryJson && hasEntryFile) {
        throw (0, errors_1.createUsageError)("Provide either --entry-json or --entry-file, not both.", ["The spec add validation seam accepts one candidate input source at a time."]);
    }
    if (!hasEntryJson && !hasEntryFile) {
        throw (0, errors_1.createUsageError)("Provide a candidate registry entry via --entry-json or --entry-file.", ["Example: day-shift spec add --entry-json '{\"id\":\"...\"}'"]);
    }
    if (hasEntryJson) {
        try {
            return JSON.parse(options.entryJson);
        }
        catch (error) {
            throw (0, errors_1.createUsageError)("--entry-json must contain valid JSON.", [error instanceof Error ? error.message : "Could not parse entry JSON."]);
        }
    }
    const filePath = node_path_1.default.isAbsolute(options.entryFile)
        ? options.entryFile
        : node_path_1.default.resolve(repositoryRoot, options.entryFile);
    try {
        return JSON.parse(node_fs_1.default.readFileSync(filePath, "utf8"));
    }
    catch (error) {
        throw (0, errors_1.createUsageError)(`--entry-file '${options.entryFile}' must point to a readable JSON file.`, [error instanceof Error ? error.message : "Could not read candidate entry file."]);
    }
}
function renderSpecAddWriteText(entryId, currentIndexEntryCount, nextIndexEntryCount) {
    return [
        "Registered spec entry.",
        `Entry id: ${entryId}`,
        `Previous registry entries: ${currentIndexEntryCount}`,
        `Updated registry entries: ${nextIndexEntryCount}`
    ].join("\n");
}
