"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSpecCreateCommand = createSpecCreateCommand;
exports.parseStructuredDocument = parseStructuredDocument;
exports.parseBoundedInteractiveAnswers = parseBoundedInteractiveAnswers;
exports.createSpecificationCreationOutcomeError = createSpecificationCreationOutcomeError;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const promises_1 = require("node:readline/promises");
const errors_1 = require("../../../lib/errors");
const output_1 = require("../../../lib/output");
const spec_creation_1 = require("../../../lib/spec-creation");
const runtime_contract_1 = require("../../../lib/runtime/runtime-contract");
const global_format_1 = require("../global-format");
async function createSpecCreateCommand() {
    const { Command } = await import("commander");
    const command = new Command("create");
    command
        .description("Preview or create a canonical draft specification and planning-optional registry entry.")
        .option("--title <title>", "Specification title; required for flag authoring and optional as an import override.")
        .option("--spec-id <id>", "Explicit lowercase, hyphen-separated specification identity.")
        .requiredOption("--implementation-order <order>", "Required positive specification order (zero-padded to at least two digits); use 'next' for the lowest unused order.")
        .option("--source <reference>", "Source reference; repeat to retain multiple sources.", collectValue, [])
        .option("--dry-run", "Preview identity, paths, and registry action without writing.")
        .option("--markdown-file <path>", "Import a complete Markdown specification body.")
        .option("--input-file <path>", "Import a structured JSON or bounded YAML authoring document.")
        .option("--stdin", "Read Markdown, JSON, or bounded YAML authoring input from standard input.")
        .option("--interactive", "Prompt on a TTY for the bounded title and purpose draft fields.")
        .option("--purpose <text>", "Canonical Purpose narrative.")
        .option("--problem-statement <text>", "Canonical Problem Statement narrative.")
        .option("--desired-outcome <text>", "Canonical Desired Outcome item; repeat for multiple outcomes.", collectValue, [])
        .option("--non-goal <text>", "Flag-authored non-goal; repeat for multiple non-goals.", collectValue, [])
        .option("--scope <text>", "Canonical Scope item; repeat for multiple items.", collectValue, [])
        .option("--constraint <text>", "Flag-authored constraint; repeat for multiple constraints.", collectValue, [])
        .option("--success-criterion <text>", "Flag-authored success criterion; repeat for multiple criteria.", collectValue, [])
        .option("--open-question <text>", "Canonical Open Questions item; repeat for multiple questions.", collectValue, [])
        .option("--summary <text>", "Deprecated Day Shift 0.2.0 compatibility alias for --purpose.")
        .option("--problem <text>", "Deprecated Day Shift 0.2.0 compatibility alias for --problem-statement.")
        .option("--goal <text>", "Deprecated Day Shift 0.2.0 compatibility alias for --desired-outcome; repeatable.", collectValue, [])
        .option("--risk <text>", "Deprecated Day Shift 0.2.0 compatibility input retained under Constraints; repeatable.", collectValue, [])
        .action(async (_, 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 = await normalizeRequest(repository.root, options, format);
        }
        catch (error) {
            throw normalizeCreateError(error);
        }
        let result;
        try {
            const preview = (0, spec_creation_1.previewSpecificationCreationSync)(request);
            result = options.dryRun === true
                ? preview
                : (0, spec_creation_1.applySpecificationCreationSync)(preview);
        }
        catch (error) {
            throw normalizeCreateError(error);
        }
        if (result.writeOutcome !== "previewed" && result.writeOutcome !== "committed") {
            throw createSpecificationCreationOutcomeError(result);
        }
        (0, output_1.emitSuccess)({
            command: "spec create",
            format,
            text: renderSuccessText(result),
            data: publicResult(result)
        });
    });
    return command;
}
async function normalizeRequest(repositoryRoot, options, format) {
    const selectedModes = [options.markdownFile, options.inputFile, options.stdin, options.interactive]
        .filter(Boolean).length;
    if (selectedModes > 1) {
        throw (0, errors_1.createUsageError)("Choose only one authoring mode: --markdown-file, --input-file, --stdin, or --interactive.", ["Omit all four to author a structured draft with flags."]);
    }
    assertCompatibleAuthoringFlags(options);
    const hasFlagBody = options.purpose !== undefined
        || options.problemStatement !== undefined
        || options.desiredOutcome.length > 0
        || options.scope.length > 0
        || options.openQuestion.length > 0
        || options.summary !== undefined
        || options.problem !== undefined
        || options.goal.length > 0
        || options.nonGoal.length > 0
        || options.constraint.length > 0
        || options.risk.length > 0
        || options.successCriterion.length > 0;
    if (selectedModes > 0 && hasFlagBody) {
        throw (0, errors_1.createUsageError)("Body authoring flags cannot be combined with file, stdin, or interactive modes.", ["Use --title, --spec-id, and --source only as supported import overrides."]);
    }
    if (options.interactive === true) {
        if (format === "json") {
            throw (0, errors_1.createUsageError)("Interactive authoring is unavailable with JSON output.");
        }
        if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
            throw (0, errors_1.createUsageError)("Interactive authoring requires an attached TTY.", ["Use flags, --markdown-file, --input-file, or --stdin for automation."]);
        }
        if (options.title !== undefined) {
            throw (0, errors_1.createUsageError)("--interactive cannot be combined with --title; the bounded prompt collects it.");
        }
        const answers = await promptForBoundedDraft();
        return {
            repositoryRoot,
            title: answers.title,
            implementationOrder: options.implementationOrder,
            ...(options.specId === undefined ? {} : { specId: options.specId }),
            sourceReferences: options.source,
            authoring: { kind: "structured", purpose: answers.purpose }
        };
    }
    if (options.markdownFile !== undefined) {
        const markdown = readInputFile(repositoryRoot, options.markdownFile, "Markdown");
        const title = options.title ?? inferMarkdownTitle(markdown);
        return createRequest(repositoryRoot, title, options, { kind: "markdown", markdown }, [
            { type: "markdown", ref: options.markdownFile }
        ]);
    }
    if (options.inputFile !== undefined) {
        const text = readInputFile(repositoryRoot, options.inputFile, "Structured input");
        const document = parseStructuredDocument(text, options.inputFile);
        return createRequest(repositoryRoot, options.title ?? document.title, { ...options, specId: options.specId ?? document.specId }, document.authoring, document.sourceReferences ?? [{ type: "structured", ref: options.inputFile }]);
    }
    if (options.stdin === true) {
        const text = node_fs_1.default.readFileSync(0, "utf8");
        if (looksStructured(text)) {
            const document = parseStructuredDocument(text, "stdin");
            return createRequest(repositoryRoot, options.title ?? document.title, { ...options, specId: options.specId ?? document.specId }, document.authoring, document.sourceReferences ?? [{ type: "stdin", ref: "standard input" }]);
        }
        return createRequest(repositoryRoot, options.title ?? inferMarkdownTitle(text), options, { kind: "markdown", markdown: text }, [{ type: "stdin", ref: "standard input" }]);
    }
    return createRequest(repositoryRoot, options.title, options, {
        kind: "structured",
        ...((options.purpose ?? options.summary) === undefined ? {} : { purpose: options.purpose ?? options.summary }),
        ...((options.problemStatement ?? options.problem) === undefined
            ? {}
            : { problemStatement: options.problemStatement ?? options.problem }),
        desiredOutcome: options.desiredOutcome.length > 0 ? options.desiredOutcome : options.goal,
        nonGoals: options.nonGoal,
        scope: options.scope,
        constraints: options.constraint,
        risks: options.risk,
        successCriteria: options.successCriterion,
        openQuestions: options.openQuestion
    });
}
function assertCompatibleAuthoringFlags(options) {
    const conflicts = [
        [options.purpose !== undefined, options.summary !== undefined, "--purpose", "--summary"],
        [options.problemStatement !== undefined, options.problem !== undefined, "--problem-statement", "--problem"],
        [options.desiredOutcome.length > 0, options.goal.length > 0, "--desired-outcome", "--goal"]
    ];
    for (const [hasCanonical, hasLegacy, canonical, legacy] of conflicts) {
        if (hasCanonical && hasLegacy) {
            throw (0, errors_1.createUsageError)(`${canonical} cannot be combined with its deprecated compatibility alias ${legacy}.`, [`Use ${canonical} for new authoring.`]);
        }
    }
}
function createRequest(repositoryRoot, title, options, authoring, inferredSources = []) {
    if (title === undefined || title.trim() === "") {
        throw (0, errors_1.createUsageError)("Specification title is required.", ["Provide --title or include a top-level title / Markdown H1 in the selected input."]);
    }
    return {
        repositoryRoot,
        title,
        implementationOrder: options.implementationOrder,
        ...(options.specId === undefined ? {} : { specId: options.specId }),
        sourceReferences: options.source.length > 0 ? options.source : inferredSources,
        authoring
    };
}
function readInputFile(repositoryRoot, inputPath, label) {
    const absolutePath = node_path_1.default.isAbsolute(inputPath) ? inputPath : node_path_1.default.resolve(repositoryRoot, inputPath);
    try {
        return node_fs_1.default.readFileSync(absolutePath, "utf8");
    }
    catch (error) {
        throw (0, errors_1.createUsageError)(`${label} file '${inputPath}' is not readable.`, [error instanceof Error ? error.message : "Could not read input file."]);
    }
}
function inferMarkdownTitle(markdown) {
    return /^#\s+(.+?)\s*$/m.exec(markdown)?.[1]?.trim();
}
function looksStructured(text) {
    const trimmed = text.trimStart();
    return trimmed.startsWith("{") || /^title\s*:/m.test(trimmed);
}
function parseStructuredDocument(text, sourceLabel = "input") {
    let value;
    try {
        value = text.trimStart().startsWith("{") ? JSON.parse(text) : parseBoundedYaml(text);
    }
    catch (error) {
        throw (0, errors_1.createUsageError)(`Structured ${sourceLabel} must contain valid JSON or bounded YAML.`, [error instanceof Error ? error.message : "Could not parse structured input."]);
    }
    if (typeof value !== "object" || value === null || Array.isArray(value)) {
        throw (0, errors_1.createUsageError)(`Structured ${sourceLabel} must be an object.`);
    }
    const document = value;
    const sources = readSourceInputs(document.sourceReferences ?? document.sources, sourceLabel);
    return {
        title: readOptionalString(document.title, "title"),
        specId: readOptionalString(document.specId ?? document.spec_id, "specId"),
        ...(sources === undefined ? {} : { sourceReferences: sources }),
        authoring: {
            kind: "structured",
            ...readOptionalNarrative(document, ["purpose", "summary"], "purpose"),
            ...readOptionalNarrative(document, ["problemStatement", "problem_statement", "problem"], "problemStatement"),
            ...readOptionalList(document, ["desiredOutcome", "desired_outcome", "goals"], "desiredOutcome"),
            ...readOptionalList(document, ["nonGoals", "non_goals"], "nonGoals"),
            ...readOptionalList(document, ["scope"], "scope"),
            ...readOptionalList(document, ["constraints"], "constraints"),
            ...readOptionalList(document, ["risks"], "risks"),
            ...readOptionalList(document, ["successCriteria", "success_criteria"], "successCriteria"),
            ...readOptionalList(document, ["openQuestions", "open_questions"], "openQuestions")
        }
    };
}
function parseBoundedYaml(text) {
    const result = {};
    let activeList = null;
    for (const [index, rawLine] of text.replace(/\r\n/g, "\n").split("\n").entries()) {
        const line = rawLine.trim();
        if (line === "" || line.startsWith("#"))
            continue;
        if (line.startsWith("- ")) {
            if (activeList === null) {
                throw new Error(`Line ${index + 1}: list item has no top-level key.`);
            }
            result[activeList].push(parseYamlScalar(line.slice(2)));
            continue;
        }
        const match = /^([A-Za-z][A-Za-z0-9_]*)\s*:\s*(.*)$/.exec(line);
        if (match === null) {
            throw new Error(`Line ${index + 1}: expected a top-level key and scalar or list.`);
        }
        const [, key, remainder] = match;
        if (Object.hasOwn(result, key)) {
            throw new Error(`Line ${index + 1}: duplicate key '${key}'.`);
        }
        if (remainder === "") {
            result[key] = [];
            activeList = key;
        }
        else {
            result[key] = parseYamlScalar(remainder);
            activeList = null;
        }
    }
    return result;
}
function parseYamlScalar(value) {
    const trimmed = value.trim();
    if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
        return trimmed.slice(1, -1);
    }
    return trimmed;
}
function readOptionalString(value, field) {
    if (value === undefined)
        return undefined;
    if (typeof value !== "string" || value.trim() === "") {
        throw (0, errors_1.createUsageError)(`Structured field '${field}' must be a non-empty string.`);
    }
    return value;
}
function readOptionalNarrative(document, inputFields, outputField) {
    const selected = selectStructuredField(document, inputFields, outputField);
    if (selected === undefined)
        return {};
    const value = readOptionalString(selected.value, selected.field);
    return value === undefined ? {} : { [outputField]: value };
}
function readOptionalList(document, inputFields, outputField) {
    const selected = selectStructuredField(document, inputFields, outputField);
    if (selected === undefined)
        return {};
    const value = selected.value;
    if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) {
        throw (0, errors_1.createUsageError)(`Structured field '${selected.field}' must be a list of non-empty strings.`);
    }
    return { [outputField]: value };
}
function selectStructuredField(document, inputFields, semanticField) {
    const present = inputFields.filter((field) => Object.hasOwn(document, field));
    if (present.length > 1) {
        throw (0, errors_1.createUsageError)(`Structured fields '${present.join("' and '")}' both provide '${semanticField}'.`, [`Use '${inputFields[0]}' for new authoring.`]);
    }
    const field = present[0];
    return field === undefined ? undefined : { field, value: document[field] };
}
function readSourceInputs(value, sourceLabel) {
    if (value === undefined)
        return undefined;
    if (!Array.isArray(value)) {
        throw (0, errors_1.createUsageError)(`Structured ${sourceLabel} sources must be a list.`);
    }
    return value.map((item) => {
        if (typeof item === "string" && item.trim() !== "")
            return item;
        if (typeof item === "object" && item !== null && !Array.isArray(item)
            && typeof item.ref === "string") {
            return item;
        }
        throw (0, errors_1.createUsageError)(`Structured ${sourceLabel} sources must contain strings or source objects with ref.`);
    });
}
function parseBoundedInteractiveAnswers(title, purpose) {
    if (title.trim() === "" || purpose.trim() === "") {
        throw (0, errors_1.createUsageError)("Interactive title and purpose must both be non-empty.");
    }
    return { title: title.trim(), purpose: purpose.trim() };
}
async function promptForBoundedDraft() {
    const readline = (0, promises_1.createInterface)({ input: process.stdin, output: process.stdout });
    try {
        const title = await readline.question("Specification title: ");
        const purpose = await readline.question("Purpose: ");
        return parseBoundedInteractiveAnswers(title, purpose);
    }
    finally {
        readline.close();
    }
}
function collectValue(value, previous) {
    return [...previous, value];
}
function normalizeCreateError(error) {
    if (error instanceof errors_1.RuntimeError)
        return error;
    if (error instanceof spec_creation_1.SpecificationCreationError) {
        const collision = error.code === "spec-id-collision" || error.code === "spec-path-collision";
        return new errors_1.RuntimeError({
            message: error.message,
            code: `SPEC_CREATE_${error.code.toUpperCase().replace(/-/g, "_")}`,
            exitCode: collision ? errors_1.RuntimeExitCode.ERROR : errors_1.RuntimeExitCode.USAGE,
            details: [collision
                    ? "Choose another explicit id or omit --spec-id for deterministic suffix allocation."
                    : "Refine the authoring input and preview the command again."]
        });
    }
    if (error instanceof Error) {
        return (0, errors_1.createUsageError)(error.message, ["No specification creation write was accepted."]);
    }
    return (0, errors_1.createUsageError)("Invalid specification creation input.");
}
function createSpecificationCreationOutcomeError(result) {
    const details = [
        `specId: ${result.specId}`,
        `specPath: ${result.specPath}`,
        `writeOutcome: ${result.writeOutcome}`,
        `nextAction: ${result.nextAction}`,
        ...(result.recoveryPath === null ? [] : [`recoveryPath: ${result.recoveryPath}`])
    ];
    if (result.writeOutcome === "conflict") {
        return new errors_1.RuntimeError({
            message: "Specification creation preview is stale.",
            code: "SPEC_CREATE_CONFLICT",
            exitCode: errors_1.RuntimeExitCode.CONCURRENCY_CONFLICT,
            details
        });
    }
    if (result.writeOutcome === "incomplete") {
        return new errors_1.RuntimeError({
            message: "Specification creation stopped after a partial repository mutation.",
            code: "SPEC_CREATE_INCOMPLETE",
            exitCode: errors_1.RuntimeExitCode.PARTIAL_MUTATION,
            writeOutcome: "partial",
            details,
            recoveryActions: [(0, errors_1.createNonCallableRecoveryAction)({
                    id: "recovery.inspect-spec-create-transaction",
                    explanation: "Inspect retained transaction evidence before retrying specification creation.",
                    reason: result.recoveryPath ?? "The retained transaction path was unavailable."
                })]
        });
    }
    return new errors_1.RuntimeError({
        message: "Specification creation did not write its intended paths.",
        code: "SPEC_CREATE_NO_WRITE",
        exitCode: errors_1.RuntimeExitCode.ERROR,
        details
    });
}
function publicResult(result) {
    return {
        specId: result.specId,
        implementationOrder: result.implementationOrder,
        specPath: result.specPath,
        status: result.status,
        sourceReferences: result.sourceReferences,
        registryAction: result.registryAction,
        writeOutcome: result.writeOutcome,
        changedPaths: result.changedPaths,
        intendedPaths: result.intendedPaths,
        expected: result.expected,
        nextAction: result.nextAction
    };
}
function renderSuccessText(result) {
    return [
        result.writeOutcome === "previewed" ? "Previewed specification creation." : "Created draft specification.",
        `Specification id: ${result.specId}`,
        `Implementation order: ${result.implementationOrder}`,
        `Specification path: ${result.specPath}`,
        `Status: ${result.status}`,
        `Source references: ${result.sourceReferences.map((source) => String(source.ref)).join(", ")}`,
        `Registry action: ${result.registryAction}`,
        `Write outcome: ${result.writeOutcome}`,
        `Changed paths: ${result.changedPaths.join(", ")}`,
        `Intended paths: ${result.intendedPaths.join(", ")}`,
        `Expected registry revision: ${result.expected.registryRevision}`,
        `Expected registry presence: ${result.expected.registryPresence}`,
        `Expected specification presence: ${result.expected.specificationPresence}`,
        `Next action: ${result.nextAction}`
    ].join("\n");
}
