"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SPEC_SOURCE_ADD_RUNTIME_SEMANTICS = void 0;
exports.executeSpecSourceAdd = executeSpecSourceAdd;
exports.createSpecSourceAddCommand = createSpecSourceAddCommand;
const node_fs_1 = __importDefault(require("node:fs"));
const errors_1 = require("../../../lib/errors");
const output_1 = require("../../../lib/output");
const spec_registry_1 = require("../../../lib/spec-registry");
const path_resolution_1 = require("../../../lib/spec-registry/path-resolution");
const atomic_write_1 = require("../../../lib/filesystem/atomic-write");
const runtime_contract_1 = require("../../../lib/runtime/runtime-contract");
const SPEC_INDEX_PATH = ".day-shift/specs/index.json";
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");
    const absolutePath = (0, path_resolution_1.resolveSpecIndexAbsolutePath)(input.repositoryRoot);
    const originalContent = node_fs_1.default.readFileSync(absolutePath, "utf8");
    let document;
    try {
        document = (0, spec_registry_1.parseSpecIndexDocumentText)(originalContent);
    }
    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: SPEC_INDEX_PATH,
            recovery: "Repair the spec registry structure and rerun spec source add."
        });
    }
    let transition;
    try {
        transition = (0, spec_registry_1.applySpecSourceTransition)({
            currentDocument: 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) {
        (0, atomic_write_1.atomicWriteFileSync)(absolutePath, (0, spec_registry_1.renderSpecIndexDocument)(transition.document), input.atomicWriteOptions);
    }
    return {
        ...transition,
        specIndexPath: SPEC_INDEX_PATH,
        writeApplied: transition.changed
    };
}
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();
}
