"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpecificationCreationError = void 0;
exports.previewSpecificationCreationSync = previewSpecificationCreationSync;
exports.applySpecificationCreationSync = applySpecificationCreationSync;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const cli_constants_1 = require("../config/cli-constants");
const mutation_preconditions_1 = require("../filesystem/mutation-preconditions");
const repository_transaction_1 = require("../filesystem/repository-transaction");
const canonical_write_target_1 = require("../paths/canonical-write-target");
const spec_registry_1 = require("../spec-registry");
const path_resolution_1 = require("../spec-registry/path-resolution");
const transaction_1 = require("../spec-registry/transaction");
const specification_contract_1 = require("../specification-contract");
class SpecificationCreationError extends Error {
    code;
    constructor(code, message) {
        super(message);
        this.name = "SpecificationCreationError";
        this.code = code;
    }
}
exports.SpecificationCreationError = SpecificationCreationError;
const plans = new WeakMap();
const SPEC_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
function previewSpecificationCreationSync(request) {
    const repositoryRoot = requireRepositoryRoot(request.repositoryRoot);
    const title = requireNonEmpty(request.title, "Specification title");
    const registryPath = (0, path_resolution_1.resolveSpecIndexAbsolutePath)(repositoryRoot);
    const registryRelativePath = node_path_1.default.relative(repositoryRoot, registryPath).replace(/\\/g, "/");
    const registryTarget = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, registryRelativePath);
    if (!registryTarget.exists) {
        throw new SpecificationCreationError("invalid-request", `Specification creation requires an existing ${cli_constants_1.CLI_SPEC_INDEX_PATH} registry.`);
    }
    const registrySnapshot = (0, transaction_1.readSpecRegistrySnapshotSync)(repositoryRoot);
    const currentDocument = registrySnapshot.document;
    const implementationOrder = resolveImplementationOrder(request.implementationOrder, currentDocument.entries.map((entry) => ({
        implementationOrder: entry.optional.implementation_order,
        specPath: entry.spec_path
    })), node_fs_1.default.readdirSync(node_path_1.default.dirname(registryTarget.runtimePath), { withFileTypes: true })
        .filter((entry) => entry.isDirectory())
        .map((entry) => entry.name));
    const specId = allocateSpecId({
        repositoryRoot,
        title,
        implementationOrder,
        explicitSpecId: request.specId,
        registeredIds: currentDocument.entries.map((entry) => entry.id),
        registeredPaths: currentDocument.entries.map((entry) => entry.spec_path)
    });
    const requestedSpecPath = `.day-shift/specs/${implementationOrder}-${specId}/${specId}.md`;
    const specificationTarget = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, requestedSpecPath);
    if (specificationTarget.exists) {
        throw new SpecificationCreationError("spec-path-collision", `Canonical specification path '${specificationTarget.repositoryRelativePath}' already exists.`);
    }
    const sources = normalizeSourceReferences(request.sourceReferences);
    const specificationContents = renderCanonicalSpecification({
        specId,
        implementationOrder,
        title,
        sources,
        authoring: request.authoring
    });
    const serializedSources = sources.map((source) => (0, spec_registry_1.serializeSpecRegistrySourceReference)(source));
    const proposedEntry = {
        id: specId,
        title,
        status: "draft",
        spec_path: specificationTarget.repositoryRelativePath,
        source: serializedSources[0],
        implementation_order: implementationOrder
    };
    if (serializedSources.length > 1) {
        proposedEntry.source_references = serializedSources;
    }
    const addResult = (0, spec_registry_1.applySpecRegistryAdd)({
        proposedEntry,
        currentDocument
    });
    if (!addResult.ok) {
        throw new SpecificationCreationError("registry-add-rejected", addResult.findings.map((finding) => finding.message).join(" "));
    }
    const registryPlan = (0, transaction_1.planSpecRegistryDocumentWriteSync)(repositoryRoot, {
        expectedRevision: registrySnapshot.revision,
        document: addResult.document
    });
    const specificationPrecondition = (0, mutation_preconditions_1.captureDestinationPresencePreconditionSync)(specificationTarget);
    const intendedPaths = [
        specificationTarget.repositoryRelativePath,
        registryTarget.repositoryRelativePath
    ].sort();
    const preview = {
        specId,
        implementationOrder,
        specPath: specificationTarget.repositoryRelativePath,
        status: "draft",
        sourceReferences: serializedSources,
        registryAction: "add",
        writeOutcome: "previewed",
        changedPaths: intendedPaths,
        intendedPaths,
        expected: {
            registryRevision: registryPlan.expectedRevision,
            registryPresence: "present",
            specificationPresence: "absent"
        },
        nextAction: "Refine the draft specification, then run read-only specification validation.",
        transactionId: null,
        conflicts: [],
        recoveryPath: null
    };
    plans.set(preview, {
        repositoryRoot,
        specificationContents,
        registryWrite: registryPlan.write,
        preconditions: [
            ...registryPlan.preconditions,
            specificationPrecondition
        ]
    });
    return preview;
}
function applySpecificationCreationSync(preview, options = {}) {
    const plan = plans.get(preview);
    if (plan === undefined) {
        throw new SpecificationCreationError("stale-preview", "Specification creation apply requires the original in-process preview. Refresh the preview.");
    }
    const transaction = (0, repository_transaction_1.executeRepositoryTransactionSync)(plan.repositoryRoot, {
        writes: [
            { path: preview.specPath, contents: plan.specificationContents },
            plan.registryWrite
        ],
        preconditions: plan.preconditions
    }, options.transaction);
    const changedPaths = transaction.entries
        .slice(0, transaction.canonicalWrites)
        .map((entry) => entry.path);
    return {
        ...preview,
        writeOutcome: transaction.outcome,
        changedPaths,
        transactionId: transaction.transactionId,
        conflicts: transaction.conflicts,
        recoveryPath: transaction.outcome === "incomplete" ? transaction.transactionPath : null,
        nextAction: nextActionForOutcome(transaction.outcome)
    };
}
function allocateSpecId(input) {
    const occupiedIds = new Set(input.registeredIds.map((id) => id.toLowerCase()));
    const occupiedPaths = new Set(input.registeredPaths.map((specPath) => specPath.toLowerCase()));
    if (input.explicitSpecId !== undefined) {
        const explicit = validateSpecId(input.explicitSpecId);
        if (occupiedIds.has(explicit.toLowerCase())) {
            throw new SpecificationCreationError("spec-id-collision", `Specification id '${explicit}' already exists (case-insensitive comparison).`);
        }
        assertPathAvailable(input.repositoryRoot, input.implementationOrder, explicit, occupiedPaths);
        return explicit;
    }
    const base = slugify(input.title);
    for (let suffix = 1; suffix < 10_000; suffix += 1) {
        const candidate = suffix === 1 ? base : `${base}-${suffix}`;
        if (occupiedIds.has(candidate.toLowerCase())) {
            continue;
        }
        if (pathIsAvailable(input.repositoryRoot, input.implementationOrder, candidate, occupiedPaths)) {
            return candidate;
        }
    }
    throw new SpecificationCreationError("spec-id-collision", `Could not allocate a collision-free specification id for '${input.title}'.`);
}
function assertPathAvailable(repositoryRoot, implementationOrder, specId, occupiedPaths) {
    if (!pathIsAvailable(repositoryRoot, implementationOrder, specId, occupiedPaths)) {
        throw new SpecificationCreationError("spec-path-collision", `Canonical specification path for '${specId}' already exists or is registered.`);
    }
}
function pathIsAvailable(repositoryRoot, implementationOrder, specId, occupiedPaths) {
    const requestedPath = `.day-shift/specs/${implementationOrder}-${specId}/${specId}.md`;
    if (occupiedPaths.has(requestedPath.toLowerCase())) {
        return false;
    }
    return !(0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, requestedPath).exists;
}
function resolveImplementationOrder(requestedOrder, registeredEntries, canonicalSpecDirectoryNames) {
    const normalized = requireNonEmpty(requestedOrder, "Specification implementation order").toLowerCase();
    if (normalized === "next") {
        const occupied = new Set(registeredEntries
            .flatMap((entry) => [
            parseStoredImplementationOrder(entry.implementationOrder),
            parseSpecPathImplementationOrder(entry.specPath)
        ])
            .concat(canonicalSpecDirectoryNames.map(parseSpecDirectoryImplementationOrder))
            .filter((value) => value !== null));
        for (let candidate = 1; Number.isSafeInteger(candidate); candidate += 1) {
            if (!occupied.has(candidate)) {
                return formatImplementationOrder(candidate);
            }
        }
        throw new SpecificationCreationError("invalid-implementation-order", "No unused specification implementation order is available within the supported integer range.");
    }
    if (!/^\d+$/.test(normalized)) {
        throw new SpecificationCreationError("invalid-implementation-order", "Specification implementation order must be a positive number or 'next'.");
    }
    const numericOrder = Number(normalized);
    if (!Number.isSafeInteger(numericOrder) || numericOrder < 1) {
        throw new SpecificationCreationError("invalid-implementation-order", "Specification implementation order must be a positive whole number; 00 remains reserved for emergent intake.");
    }
    return formatImplementationOrder(numericOrder);
}
function parseSpecDirectoryImplementationOrder(directoryName) {
    const match = /^(\d+)-/.exec(directoryName);
    return match === null ? null : parseStoredImplementationOrder(match[1]);
}
function parseSpecPathImplementationOrder(specPath) {
    const match = /^\.day-shift\/specs\/(\d+)-/.exec(specPath);
    return match === null ? null : parseStoredImplementationOrder(match[1]);
}
function parseStoredImplementationOrder(value) {
    const normalized = typeof value === "number" && Number.isSafeInteger(value)
        ? String(value)
        : typeof value === "string"
            ? value.trim()
            : "";
    if (!/^\d+$/.test(normalized))
        return null;
    const numericOrder = Number(normalized);
    return Number.isSafeInteger(numericOrder)
        && numericOrder >= 1
        ? numericOrder
        : null;
}
function formatImplementationOrder(value) {
    return String(value).padStart(2, "0");
}
function validateSpecId(value) {
    const normalized = requireNonEmpty(value, "Specification id");
    if (!SPEC_ID_PATTERN.test(normalized)) {
        throw new SpecificationCreationError("invalid-spec-id", "Specification ids must use lowercase letters, numbers, and hyphen-separated segments.");
    }
    return normalized;
}
function slugify(title) {
    const slug = title
        .normalize("NFKD")
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, "-")
        .replace(/^-+|-+$/g, "");
    if (slug === "") {
        throw new SpecificationCreationError("invalid-spec-id", "Specification title must contain at least one ASCII letter or number for generated identity.");
    }
    return slug;
}
function normalizeSourceReferences(inputs) {
    const candidates = inputs === undefined || inputs.length === 0
        ? [{ type: "user", ref: "interactive or direct authoring" }]
        : inputs;
    return candidates.map((input) => {
        if (typeof input === "string") {
            const ref = requireNonEmpty(input, "Specification source reference");
            return (0, spec_registry_1.parseSpecRegistrySourceReference)({
                type: inferSpecificationSourceType(ref),
                ref
            });
        }
        const { type = inferSpecificationSourceType(String(input.ref ?? "")), ...rest } = input;
        return (0, spec_registry_1.parseSpecRegistrySourceReference)({ type, ...rest });
    });
}
function inferSpecificationSourceType(ref) {
    if (/^https?:\/\//iu.test(ref))
        return "url";
    if (!/\s/u.test(ref) && (ref.endsWith(".md")
        || ref.includes("/")
        || ref.startsWith("."))) {
        return "markdown";
    }
    return "message";
}
function renderCanonicalSpecification(input) {
    const primarySource = input.sources[0];
    if (primarySource === undefined) {
        throw new SpecificationCreationError("invalid-request", "Canonical specification rendering requires at least one source reference.");
    }
    const frontMatter = [
        "---",
        "artifact_schema_version: 1",
        "artifact_type: specification",
        `spec_id: ${quoteYaml(input.specId)}`,
        `implementation_order: ${quoteYaml(input.implementationOrder)}`,
        `title: ${quoteYaml(input.title)}`,
        "status: draft",
        "source:",
        `  type: ${quoteYaml(primarySource.type)}`,
        `  ref: ${quoteYaml(primarySource.ref)}`
    ];
    if (input.sources.length > 1) {
        frontMatter.push("source_references:");
        for (const source of input.sources) {
            frontMatter.push(`  - type: ${quoteYaml(source.type)}`);
            frontMatter.push(`    ref: ${quoteYaml(source.ref)}`);
        }
    }
    frontMatter.push("---", "");
    const body = input.authoring.kind === "markdown"
        ? requireNonEmpty(input.authoring.markdown, "Markdown specification input")
        : renderStructuredBody(input.title, input.authoring);
    const normalizedBody = body.replace(/\r\n/g, "\n").trimEnd();
    const titledBody = /^#\s+/m.test(normalizedBody)
        ? normalizedBody
        : `# ${input.title}\n\n${normalizedBody}`;
    return `${frontMatter.join("\n")}${titledBody}\n`;
}
function renderStructuredBody(title, authoring) {
    const constraints = [
        ...(authoring.constraints ?? []),
        ...(authoring.risks ?? []).map((risk) => `Risk: ${risk}`)
    ];
    const sections = [
        renderNarrativeSection(specification_contract_1.CANONICAL_SPECIFICATION_H2_ORDER[0], authoring.purpose ?? authoring.summary),
        renderNarrativeSection(specification_contract_1.CANONICAL_SPECIFICATION_H2_ORDER[1], authoring.problemStatement ?? authoring.problem),
        renderListSection(specification_contract_1.CANONICAL_SPECIFICATION_H2_ORDER[2], authoring.desiredOutcome ?? authoring.goals),
        renderListSection(specification_contract_1.CANONICAL_SPECIFICATION_H2_ORDER[3], authoring.nonGoals),
        renderListSection(specification_contract_1.CANONICAL_SPECIFICATION_H2_ORDER[4], authoring.scope),
        renderListSection(specification_contract_1.CANONICAL_SPECIFICATION_H2_ORDER[5], constraints),
        renderListSection(specification_contract_1.CANONICAL_SPECIFICATION_H2_ORDER[6], authoring.successCriteria),
        renderListSection(specification_contract_1.CANONICAL_SPECIFICATION_H2_ORDER[7], authoring.openQuestions, "- None currently.")
    ];
    return [`# ${title}`, "", ...sections].join("\n").trimEnd();
}
function renderNarrativeSection(title, value) {
    return [`## ${title}`, "", value?.trim() || "Pending refinement.", ""].join("\n");
}
function renderListSection(title, values, emptyValue = "- Pending refinement.") {
    const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
    return [
        `## ${title}`,
        "",
        ...(normalized.length === 0 ? [emptyValue] : normalized.map((value) => `- ${value}`)),
        ""
    ].join("\n");
}
function nextActionForOutcome(outcome) {
    switch (outcome) {
        case "committed":
            return "Refine the draft specification, then run read-only specification validation.";
        case "conflict":
            return "Refresh the specification creation preview before retrying.";
        case "incomplete":
            return "Inspect the retained repository transaction evidence before recovery.";
        case "no-write":
            return "Correct the reported write failure, then refresh the preview.";
    }
}
function requireRepositoryRoot(value) {
    const root = requireNonEmpty(value, "Repository root");
    const stats = node_fs_1.default.statSync(root, { throwIfNoEntry: false });
    if (stats === undefined || !stats.isDirectory()) {
        throw new SpecificationCreationError("invalid-request", "Repository root must be an existing directory.");
    }
    return node_fs_1.default.realpathSync(root);
}
function requireNonEmpty(value, label) {
    if (typeof value !== "string" || value.trim() === "") {
        throw new SpecificationCreationError("invalid-request", `${label} must be a non-empty string.`);
    }
    return value.trim();
}
function quoteYaml(value) {
    return JSON.stringify(value);
}
