import { ToolcraftBugError, UserError } from "toolcraft";
import { METHOD_DEFAULTS, deriveDisambiguatedVerb, deriveNoun, derivePathDisambiguatedVerb, deriveVerb, isIdentifierName, normalizeNoun, normalizeParamName, toCamelCase, toPascalCase } from "./naming.js";
import { groupByNoun } from "./group-by-noun.js";
import { renderPreflightBlock, renderRequestShape } from "./interpreter.js";
import { normalizeOpenApiDocument } from "./normalize-swagger.js";
const HTTP_METHOD_ORDER = ["get", "post", "put", "patch", "delete", "head", "options"];
const UNSUPPORTED_HTTP_METHODS = ["trace"];
const METHODS_WITHOUT_REQUEST_BODY = new Set(["head", "options"]);
const SCHEMA_TYPE_TO_KIND = {
    boolean: { kind: "boolean" },
    integer: { kind: "number", jsonType: "integer" },
    number: { kind: "number" },
    string: { kind: "string" }
};
const NULL_HELPER_SUPPORT = {
    body: { array: true, scalar: true },
    header: { array: false, scalar: false },
    path: { array: false, scalar: false },
    // Query null already serializes as the existing empty-string wire encoding, so v1
    // keeps null-helper flags body-only until a real query-null convention lands.
    query: { array: false, scalar: false }
};
const TRANSPORT_PARAMS = [
    {
        paramName: "dryRun",
        sourceName: "dryRun",
        location: "transport",
        description: "Print the HTTP request and exit without sending it.",
        scope: ["cli", "sdk"],
        global: true,
        optional: true,
        definition: { kind: "boolean" }
    },
    {
        paramName: "verbose",
        sourceName: "verbose",
        location: "transport",
        description: "Log the request line to stderr.",
        shortFlag: "v",
        scope: ["cli", "sdk"],
        global: true,
        optional: true,
        definition: { kind: "boolean" }
    }
];
const BINARY_OUTPUT_PARAM = {
    paramName: "output",
    sourceName: "output",
    location: "transport",
    description: "Write a binary response body to this local file path.",
    scope: ["cli"],
    optional: true,
    definition: { kind: "string" }
};
const SCHEMA_OPTION_SOURCES = [
    {
        key: "description",
        get: (param) => param.description
    },
    {
        key: "default",
        get: (param) => param.definition.defaultValue
    },
    {
        key: "short",
        get: (param) => param.shortFlag
    },
    {
        key: "cliAliases",
        get: (param) => param.longAliases
    },
    {
        key: "scope",
        get: (param) => param.scope
    },
    {
        key: "global",
        get: (param) => (param.global === true ? true : undefined)
    },
    {
        key: "minimum",
        get: (param) => param.definition.minimum
    },
    {
        key: "maximum",
        get: (param) => param.definition.maximum
    },
    {
        key: "minLength",
        get: (param) => param.definition.minLength
    },
    {
        key: "maxLength",
        get: (param) => param.definition.maxLength
    },
    {
        key: "minItems",
        get: (param) => param.definition.minItems
    },
    {
        key: "maxItems",
        get: (param) => param.definition.maxItems
    },
    {
        key: "pattern",
        get: (param) => param.definition.pattern
    },
    {
        key: "format",
        get: (param) => param.definition.format
    },
    {
        key: "jsonType",
        get: (param) => param.definition.jsonType
    },
    {
        key: "nullable",
        get: (param) => param.definition.nullable === true ? true : undefined
    },
    {
        key: "requiredScopes",
        get: (param) => param.definition.requiredScopes
    }
];
export function generate(document, options) {
    const normalizedDocument = normalizeOpenApiDocument(document);
    const commands = collectGeneratedCommands(normalizedDocument, options.config);
    const brand = options.brand ?? "blue";
    const label = normalizedDocument.info?.title ?? "Toolcraft";
    return [
        ...commands.map((command) => ({
            path: command.filePath,
            contents: createCommandFile({
                specSha: options.specSha,
                ...command
            })
        })),
        createIndexFile(commands),
        createCliFile({ brand, label })
    ];
}
export function collectGeneratedCommands(document, config) {
    const normalizedDocument = normalizeOpenApiDocument(document);
    const paths = normalizedDocument.paths;
    if (paths === undefined) {
        throw new UserError('OpenAPI document must define a top-level "paths" object.');
    }
    const commands = collectOperations(paths).map((entry) => createGeneratedCommand(normalizedDocument, entry));
    applyConfiguredCommandShape(commands, config);
    disambiguateCommandPaths(commands);
    assertUniqueCommandPaths(commands);
    return commands.slice().sort((left, right) => compareGeneratedCommandPaths(left, right));
}
function applyConfiguredCommandShape(commands, config) {
    const configured = collectConfiguredCommandShapes(config?.resources);
    if (configured.length === 0) {
        return;
    }
    for (const command of commands) {
        const match = configured.find((candidate) => candidate.method.method.toUpperCase() === command.method && candidate.method.path === command.path);
        if (match === undefined) {
            continue;
        }
        command.noun = createSafeGeneratedNoun(match.noun);
        command.verb = createSafeGeneratedVerb(match.verb);
        command.examples = config?.readme?.examples?.[match.exampleKey];
        applyConfiguredRawResponse(command);
        applyConfiguredIdempotency(command, match.method, config);
        refreshGeneratedCommandNames(command);
    }
}
function applyConfiguredRawResponse(command) {
    command.rawResponse = true;
    if (command.params.some((param) => param.paramName === "rawResponse")) {
        return;
    }
    command.params.push({
        paramName: "rawResponse",
        sourceName: "rawResponse",
        location: "transport",
        description: "Return parsed data with the raw HTTP Response.",
        longAliases: ["raw"],
        scope: ["cli", "sdk"],
        optional: true,
        definition: { kind: "boolean" }
    });
}
function applyConfiguredIdempotency(command, method, config) {
    const header = config?.client_settings?.idempotency_header;
    if (method.idempotent !== true || header === undefined || command.method === "GET") {
        return;
    }
    command.idempotencyHeader = header;
    if (command.params.some((param) => param.paramName === "idempotencyKey")) {
        return;
    }
    command.params.push({
        paramName: "idempotencyKey",
        sourceName: "idempotencyKey",
        location: "transport",
        description: "Set an idempotency key to retry this request safely.",
        scope: ["cli", "mcp", "sdk"],
        optional: true,
        definition: { kind: "string" }
    });
}
function collectConfiguredCommandShapes(resources) {
    const shapes = [];
    function visit(resourceName, resource, resourcePath) {
        for (const [methodName, method] of Object.entries(resource.methods ?? {})) {
            shapes.push({
                exampleKey: [...resourcePath, methodName].join("."),
                noun: resourceName,
                verb: methodName,
                method
            });
        }
        for (const [childName, childResource] of Object.entries(resource.subresources ?? {})) {
            visit(childName, childResource, [...resourcePath, childName]);
        }
    }
    for (const [resourceName, resource] of Object.entries(resources ?? {})) {
        visit(resourceName, resource, [resourceName]);
    }
    return shapes;
}
function createSafeGeneratedVerb(value) {
    const verb = toCamelCase(value);
    if (!isIdentifierName(verb)) {
        throw new UserError(`Configured method ${JSON.stringify(value)} must map to a valid command name.`);
    }
    return verb;
}
function refreshGeneratedCommandNames(command) {
    command.exportName = `${toCamelCase(command.noun)}${toPascalCase(command.verb)}Command`;
    command.filePath = `${command.noun}/${command.verb}.ts`;
}
export function collectGeneratedCommand(document, path, method) {
    const normalizedDocument = normalizeOpenApiDocument(document);
    const pathItem = normalizedDocument.paths?.[path];
    const operation = pathItem?.[method];
    if (pathItem === undefined || operation === undefined) {
        throw new ToolcraftBugError(`Cannot generate missing OpenAPI operation ${method.toUpperCase()} ${path}.`);
    }
    return createGeneratedCommand(normalizedDocument, { method, path, operation, pathItem });
}
function collectOperations(paths) {
    return Object.entries(paths)
        .sort(([left], [right]) => left.localeCompare(right))
        .flatMap(([path, pathItem]) => {
        if (pathItem === undefined) {
            return [];
        }
        assertSupportedHttpMethods(path, pathItem);
        return HTTP_METHOD_ORDER.flatMap((method) => {
            const operation = getOwnPathItemValue(pathItem, method);
            if (operation === undefined) {
                return [];
            }
            return [{ method, path, operation, pathItem }];
        });
    });
}
function getOwnPathItemValue(pathItem, key) {
    return Object.prototype.hasOwnProperty.call(pathItem, key)
        ? pathItem[key]
        : undefined;
}
function createGeneratedCommand(document, entry) {
    const operation = expectOperation(document, entry.operation, entry.method, entry.path);
    const operationId = operation.operationId ?? `${entry.method.toUpperCase()} ${entry.path}`;
    const operationBaseUrl = resolveOperationBaseUrl(operation, operationId);
    const auth = getOperationAuthMode(document, operation, operationId);
    const response = resolveSuccessResponse(document, operation, operationId);
    const noun = createSafeGeneratedNoun(deriveNoun(operation, entry.path, operationId));
    const verb = deriveVerb(entry.method, entry.path, operation, operationId, noun);
    const collected = collectParams(document, entry, operation, operationId, auth, response.mode);
    const methodDefaults = METHOD_DEFAULTS[entry.method];
    const exportName = `${toCamelCase(noun)}${toPascalCase(verb)}Command`;
    const filePath = `${noun}/${verb}.ts`;
    return {
        noun,
        verb,
        exportName,
        filePath,
        operationId,
        description: mergeCommandDescriptions(operation.description ?? operation.summary, collected.requestBodyDescription),
        method: entry.method.toUpperCase(),
        path: entry.path,
        auth,
        responseMode: response.mode,
        accept: response.accept,
        baseUrl: operationBaseUrl,
        bodyMode: collected.bodyMode,
        contentType: collected.contentType,
        multipartBinaryFields: collected.multipartBinaryFields,
        confirm: methodDefaults?.confirm === true,
        params: collected.params,
        paramsSchemaOptions: collected.paramsSchemaOptions,
        preflightBlocks: collected.preflightBlocks,
        requestFields: collected.requestFields,
        sectionRenders: collected.sectionRenders,
        optionalSections: collected.optionalSections
    };
}
function resolveOperationBaseUrl(operation, operationId) {
    if (operation.servers === undefined) {
        return undefined;
    }
    const [server] = operation.servers;
    if (operation.servers.length !== 1 || server === undefined || server.url.includes("{")) {
        throw new UserError(`Operation ${JSON.stringify(operationId)} must define exactly one fixed per-operation server URL in v1.`);
    }
    try {
        return new URL(server.url).toString().replace(/\/$/, "");
    }
    catch {
        throw new UserError(`Operation ${JSON.stringify(operationId)} defines invalid per-operation server URL ${JSON.stringify(server.url)}.`);
    }
}
function collectParams(document, entry, operation, operationId, auth, responseMode) {
    const transportParams = responseMode === "binary" ? [...TRANSPORT_PARAMS, BINARY_OUTPUT_PARAM] : TRANSPORT_PARAMS;
    const operationParams = collectOperationParameters(document, entry.path, entry.pathItem.parameters ?? [], operation.parameters ?? [], operationId, auth);
    const requestBodyParams = collectRequestBodyParams(document, operation, operationId, entry.method);
    const qualifiedRequestBodyParams = qualifyBodyParamCollisions(requestBodyParams, new Set([...operationParams.params, ...transportParams].map((param) => param.paramName)));
    const params = [...operationParams.params, ...qualifiedRequestBodyParams.params, ...transportParams];
    const deduped = new Map();
    for (const param of params) {
        const existing = deduped.get(param.paramName);
        if (existing !== undefined) {
            throw new UserError(`Operation ${JSON.stringify(operationId)} maps both ${JSON.stringify(existing.sourceName)} and ${JSON.stringify(param.sourceName)} to flag ${JSON.stringify(param.paramName)}.`);
        }
        deduped.set(param.paramName, param);
    }
    return {
        params: [...deduped.values()],
        paramsSchemaOptions: qualifiedRequestBodyParams.paramsSchemaOptions,
        preflightBlocks: [...operationParams.preflightBlocks, ...qualifiedRequestBodyParams.preflightBlocks],
        requestFields: [...operationParams.requestFields, ...qualifiedRequestBodyParams.requestFields],
        sectionRenders: { ...operationParams.sectionRenders, ...qualifiedRequestBodyParams.sectionRenders },
        optionalSections: new Set([
            ...operationParams.optionalSections,
            ...qualifiedRequestBodyParams.optionalSections
        ]),
        requestBodyDescription: qualifiedRequestBodyParams.requestBodyDescription,
        bodyMode: qualifiedRequestBodyParams.bodyMode,
        contentType: qualifiedRequestBodyParams.contentType,
        multipartBinaryFields: qualifiedRequestBodyParams.multipartBinaryFields
    };
}
function qualifyBodyParamCollisions(collected, reservedNames) {
    const renames = new Map();
    const usedNames = new Set(reservedNames);
    for (const param of collected.params) {
        let paramName = param.paramName;
        if (usedNames.has(paramName)) {
            const baseName = `body${toPascalCase(paramName)}`;
            paramName = baseName;
            let suffix = 2;
            while (usedNames.has(paramName)) {
                paramName = `${baseName}${suffix}`;
                suffix += 1;
            }
            renames.set(param.paramName, paramName);
        }
        usedNames.add(paramName);
    }
    if (renames.size === 0) {
        return collected;
    }
    const renameReference = (reference) => reference.kind === "param" && renames.has(reference.paramName)
        ? { ...reference, paramName: renames.get(reference.paramName) }
        : reference;
    return {
        ...collected,
        params: collected.params.map((param) => ({
            ...param,
            paramName: renames.get(param.paramName) ?? param.paramName
        })),
        preflightBlocks: collected.preflightBlocks.map((block) => block.kind === "scalar-null"
            ? {
                ...block,
                paramName: renames.get(block.paramName) ?? block.paramName,
                nullParamName: renames.get(block.nullParamName) ?? block.nullParamName
            }
            : {
                ...block,
                paramName: renames.get(block.paramName) ?? block.paramName,
                jsonParamName: renames.get(block.jsonParamName) ?? block.jsonParamName,
                ...(block.nullParamName === undefined
                    ? {}
                    : { nullParamName: renames.get(block.nullParamName) ?? block.nullParamName })
            }),
        requestFields: collected.requestFields.map((field) => ({
            ...field,
            omitWhenUndefinedReference: renameReference(field.omitWhenUndefinedReference),
            value: field.value.kind === "reference"
                ? { ...field.value, reference: renameReference(field.value.reference) }
                : field.value.kind === "queryArray"
                    ? { ...field.value, reference: renameReference(field.value.reference) }
                    : field.value
        }))
    };
}
function collectOperationParameters(document, path, pathItemParameters, operationParameters, operationId, auth) {
    const merged = new Map();
    for (const parameter of pathItemParameters) {
        const resolved = expectParameter(document, parameter, operationId);
        merged.set(`${resolved.in}:${resolved.name}`, resolved);
    }
    for (const parameter of operationParameters) {
        const resolved = expectParameter(document, parameter, operationId);
        merged.set(`${resolved.in}:${resolved.name}`, resolved);
    }
    promoteQueryDeclaredPathParameters(path, merged);
    assertPathTemplateParameters(path, merged, operationId);
    const params = [];
    const preflightBlocks = [];
    const requestFields = [];
    const usedParamNames = new Set();
    for (const parameter of merged.values()) {
        if (parameter.in === "header" &&
            ["accept", "content-type"].includes(parameter.name.toLowerCase())) {
            continue;
        }
        const paramName = usedParamNames.has(parameter.name)
            ? `${parameter.in}${toPascalCase(parameter.name)}`
            : parameter.name;
        const generated = createGeneratedParameter(document, paramName === parameter.name ? parameter : { ...parameter, name: paramName }, operationId, auth);
        generated.requestField.wireName = generated.requestField.wireName.endsWith("[]")
            ? `${parameter.name}[]`
            : parameter.name;
        for (const param of generated.params) {
            param.sourceName = parameter.name;
            usedParamNames.add(param.paramName);
        }
        params.push(...generated.params);
        preflightBlocks.push(...generated.preflightBlocks);
        requestFields.push(generated.requestField);
    }
    return {
        params,
        paramsSchemaOptions: undefined,
        preflightBlocks,
        requestFields,
        sectionRenders: { path: "wrapped", query: "wrapped", header: "wrapped" },
        optionalSections: new Set(),
        requestBodyDescription: undefined
    };
}
function promoteQueryDeclaredPathParameters(path, parameters) {
    for (const placeholder of collectPathPlaceholders(path)) {
        if (parameters.has(`path:${placeholder}`)) {
            continue;
        }
        const queryParameter = parameters.get(`query:${placeholder}`);
        if (queryParameter === undefined) {
            continue;
        }
        parameters.delete(`query:${placeholder}`);
        parameters.set(`path:${placeholder}`, { ...queryParameter, in: "path", required: true });
    }
}
function collectRequestBodyParams(document, operation, operationId, method) {
    if (operation.requestBody === undefined) {
        return {
            params: [],
            paramsSchemaOptions: undefined,
            preflightBlocks: [],
            requestFields: [],
            sectionRenders: {},
            optionalSections: new Set(),
            requestBodyDescription: undefined
        };
    }
    if (METHODS_WITHOUT_REQUEST_BODY.has(method)) {
        throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported requestBody on ${method.toUpperCase()}. Request bodies are not supported on GET in v1.`);
    }
    const requestBody = expectRequestBody(document, operation.requestBody, operationId, "requestBody");
    const contentEntries = Object.entries(requestBody.content ?? {});
    const contentEntry = contentEntries.find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && isJsonMediaType(mediaType)) ?? contentEntries.find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && mediaType.toLowerCase() === "application/x-www-form-urlencoded") ?? contentEntries.find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && isTextMediaType(mediaType)) ?? contentEntries.find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && isBinaryMediaType(mediaType)) ?? contentEntries.find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && mediaType.toLowerCase() === "multipart/form-data");
    const content = contentEntry?.[1];
    const requestMediaType = contentEntry?.[0];
    const bodyMode = requestMediaType?.toLowerCase() === "application/x-www-form-urlencoded"
        ? "form"
        : requestMediaType?.toLowerCase() === "multipart/form-data"
            ? "multipart"
            : requestMediaType !== undefined && !isJsonMediaType(requestMediaType)
                ? isTextMediaType(requestMediaType)
                    ? "raw"
                    : "base64"
                : "json";
    if (content === undefined) {
        throw new UserError(`Operation ${JSON.stringify(operationId)} must define a JSON request body media type in v1.`);
    }
    if (bodyMode === "raw" || bodyMode === "base64") {
        const description = requestBody.description;
        return {
            ...createCollectedRequestBodyParams([
                createBodyField(document, "body", { type: "string", ...(description === undefined ? {} : { description }) }, requestBody.required !== true, operationId)
            ], requestBody.required !== true, description, "inline", undefined, bodyMode),
            contentType: requestMediaType
        };
    }
    const schema = resolveBodySchema(document, content.schema, operationId, "requestBody");
    const bodyOptional = requestBody.required !== true;
    const multipartBinaryFields = bodyMode === "multipart" && schema.properties !== undefined
        ? Object.entries(schema.properties)
            .filter(([, property]) => {
            const resolved = resolveBodySchema(document, property, operationId, "requestBody multipart field");
            return resolved.type === "string" && resolved.format === "binary";
        })
            .map(([name]) => name)
        : undefined;
    if (schema.type !== "object" || getCompositionKeyword(schema) !== undefined) {
        const bodySchema = schema.description === undefined && requestBody.description !== undefined
            ? { ...schema, description: requestBody.description }
            : schema;
        return createCollectedRequestBodyParams([createBodyField(document, "body", bodySchema, bodyOptional, operationId)], bodyOptional, schema.description === undefined ? undefined : requestBody.description, "inline", undefined, bodyMode, multipartBinaryFields);
    }
    if ((schema.additionalProperties !== undefined && schema.additionalProperties !== false) ||
        schema.properties === undefined) {
        return createCollectedRequestBodyParams([createJsonBodyField({
                document,
                name: "body",
                description: schema.description ?? requestBody.description,
                schema,
                optional: bodyOptional,
                operationId,
                context: "requestBody",
                location: "body"
            })], bodyOptional, requestBody.description, "inline", undefined, bodyMode, multipartBinaryFields);
    }
    const required = new Set(schema.required ?? []);
    const assemblies = [];
    const declaredPropertyCount = Object.keys(schema.properties).length;
    for (const [name, property] of Object.entries(schema.properties)) {
        const propertySchema = resolveBodySchema(document, property, operationId, `requestBody.properties.${name}`);
        if (propertySchema.readOnly === true) {
            continue;
        }
        const generated = createBodyField(document, name, propertySchema, bodyOptional || !required.has(name), operationId);
        assemblies.push(generated);
    }
    if (!bodyOptional && assemblies.length === 0) {
        if (declaredPropertyCount === 0) {
            return {
                params: [],
                paramsSchemaOptions: schema.additionalProperties === false ? { additionalProperties: false } : undefined,
                preflightBlocks: [],
                requestFields: [{
                        location: "body",
                        wireName: "body",
                        value: { kind: "emptyObject" },
                        omitWhenUndefinedReference: { kind: "resolved", resolvedName: "emptyBody" }
                    }],
                sectionRenders: { body: "inline" },
                optionalSections: new Set(),
                requestBodyDescription: requestBody.description,
                bodyMode,
                ...(multipartBinaryFields === undefined ? {} : { multipartBinaryFields })
            };
        }
        throw new UserError(`Operation ${JSON.stringify(operationId)} requestBody is required but all declared fields are readOnly.`);
    }
    return createCollectedRequestBodyParams(assemblies, bodyOptional, requestBody.description, "wrapped", schema.additionalProperties === false ? { additionalProperties: false } : undefined, bodyMode, multipartBinaryFields);
}
function resolveSuccessResponse(document, operation, operationId) {
    let textualMediaType;
    let binaryMediaType;
    for (const [statusCode, response] of Object.entries(operation.responses ?? {})) {
        if (!isSuccessStatusCode(statusCode)) {
            continue;
        }
        const resolvedResponse = expectResponse(document, response, operationId, statusCode);
        const declaredMediaTypes = Object.entries(resolvedResponse.content ?? {})
            .filter(([, mediaType]) => mediaType !== undefined)
            .map(([mediaType]) => mediaType);
        if (declaredMediaTypes.length === 0 ||
            declaredMediaTypes.some((mediaType) => isJsonMediaType(mediaType))) {
            continue;
        }
        const textual = declaredMediaTypes.find(isTextMediaType);
        if (textual !== undefined && declaredMediaTypes.every(isTextMediaType)) {
            textualMediaType ??= textual;
            continue;
        }
        binaryMediaType ??= declaredMediaTypes[0];
    }
    if (binaryMediaType !== undefined) {
        return { mode: "binary", accept: binaryMediaType };
    }
    return textualMediaType === undefined
        ? { mode: "json", accept: "application/json" }
        : { mode: "text", accept: textualMediaType };
}
function createGeneratedParameter(document, parameter, operationId, auth) {
    if (parameter.in === "header" &&
        parameter.name.toLowerCase() === "authorization" &&
        auth === "required") {
        throw new UserError(`Operation ${JSON.stringify(operationId)} header parameter ${JSON.stringify(parameter.name)} is managed by the HTTP transport and cannot be generated as a command parameter.`);
    }
    const context = `parameter ${JSON.stringify(parameter.name)}`;
    const schema = parameter.in === "query" && parameter.style === "deepObject"
        ? resolveBodySchema(document, parameter.schema, operationId, context)
        : expectSchema(document, parameter.schema, operationId, context);
    if (parameter.in === "path") {
        if (parameter.required !== true) {
            throw new UserError(`Operation ${JSON.stringify(operationId)} path parameter ${JSON.stringify(parameter.name)} must set required: true.`);
        }
        if (schema.nullable === true) {
            throw new UserError(`Operation ${JSON.stringify(operationId)} path parameter ${JSON.stringify(parameter.name)} uses unsupported nullable schema. Path parameters cannot be nullable in v1.`);
        }
        assertSupportedPathParameterSerialization(parameter, operationId);
    }
    if (parameter.in === "query" &&
        parameter.style === "deepObject" &&
        (getCompositionKeyword(schema) !== undefined ||
            (schema.type === "array" && schema.items !== undefined && isComplexJsonBodySchema(document, schema.items, operationId, `${context} items`)))) {
        return createJsonQueryField({
            document,
            name: parameter.name,
            description: parameter.description ?? schema.description,
            schema,
            optional: parameter.required !== true,
            operationId,
            context,
            location: "query",
            queryObjectSerialization: resolveQueryObjectSerialization(parameter, operationId)
        });
    }
    const querySerialization = parameter.in === "query" && schema.type === "array"
        ? resolveQueryArraySerialization(parameter, operationId)
        : undefined;
    const generated = createField({
        document,
        name: parameter.name,
        description: parameter.description ?? schema.description,
        schema,
        optional: parameter.required !== true,
        operationId,
        context,
        location: parameter.in,
        querySerialization,
        queryObjectSerialization: parameter.in === "query" && schema.type === "object"
            ? resolveQueryObjectSerialization(parameter, operationId)
            : undefined
    });
    if (querySerialization === "brackets") {
        generated.requestField.wireName = `${parameter.name}[]`;
    }
    return generated;
}
function assertSupportedPathParameterSerialization(parameter, operationId) {
    const style = parameter.style ?? "simple";
    const explode = parameter.explode ?? false;
    if (style === "simple" && explode === false) {
        return;
    }
    throw new UserError(`Operation ${JSON.stringify(operationId)} path parameter ${JSON.stringify(parameter.name)} uses unsupported serialization. Path parameters must use style "simple" with explode false in v1.`);
}
function createBodyField(document, name, schema, optional, operationId) {
    if (isComplexJsonBodySchema(document, schema, operationId, `request body field ${JSON.stringify(name)}`)) {
        return createJsonBodyField({
            document,
            name,
            description: schema.description,
            schema,
            optional,
            operationId,
            context: `request body field ${JSON.stringify(name)}`,
            location: "body"
        });
    }
    return createField({
        document,
        name,
        description: schema.description,
        schema,
        optional,
        operationId,
        context: `request body field ${JSON.stringify(name)}`,
        location: "body"
    });
}
function isComplexJsonBodySchema(document, schema, operationId, context) {
    if (isUnconstrainedJsonSchema(schema)) {
        return true;
    }
    if (schema.type === "object" ||
        schema.properties !== undefined ||
        schema.additionalProperties !== undefined ||
        getCompositionKeyword(schema) !== undefined ||
        Array.isArray(schema.type)) {
        return true;
    }
    if (schema.type !== "array" || schema.items === undefined) {
        return false;
    }
    const itemSchema = resolveBodySchema(document, schema.items, operationId, `${context} items`);
    return isComplexJsonBodySchema(document, itemSchema, operationId, `${context} items`);
}
function isUnconstrainedJsonSchema(schema) {
    return (schema.type === undefined &&
        schema.enum === undefined &&
        schema.properties === undefined &&
        schema.additionalProperties === undefined &&
        schema.allOf === undefined &&
        schema.anyOf === undefined &&
        schema.oneOf === undefined);
}
function createField(options) {
    return FIELD_ASSEMBLERS[options.location][getFieldSchemaKind(options.schema)](options);
}
function createScalarParam(options) {
    const { document, name, description, schema, optional, operationId, context, location } = options;
    const paramName = name;
    const definition = createParamDefinition(document, schema, operationId, context);
    const emitsNullHelper = supportsNullHelper(location, "scalar", definition.nullable);
    const params = [
        {
            paramName,
            sourceName: name,
            location,
            description,
            optional: optional || emitsNullHelper,
            definition: {
                ...definition,
                ...(!optional && emitsNullHelper ? { requiredScopes: ["mcp", "sdk"] } : {})
            }
        }
    ];
    const preflightBlocks = [];
    const helperBaseName = normalizeParamName(name);
    const resolvedName = `resolved${toPascalCase(helperBaseName)}`;
    if (emitsNullHelper) {
        params.push({
            paramName: `${helperBaseName}Null`,
            sourceName: name,
            location: "transport",
            description: `Send null for ${name}.`,
            optional: true,
            scope: ["cli"],
            definition: { kind: "boolean" }
        });
        preflightBlocks.push({
            kind: "scalar-null",
            paramName,
            nullParamName: `${helperBaseName}Null`,
            resolvedName,
            required: !optional
        });
    }
    return {
        params,
        preflightBlocks,
        requestField: {
            location,
            wireName: name,
            value: emitsNullHelper
                ? { kind: "reference", reference: { kind: "resolved", resolvedName } }
                : { kind: "reference", reference: { kind: "param", paramName } },
            omitWhenUndefinedReference: emitsNullHelper
                ? { kind: "resolved", resolvedName }
                : { kind: "param", paramName }
        }
    };
}
function getFieldSchemaKind(schema) {
    return schema.type === "array" ? "array" : schema.type === "object" ? "object" : "scalar";
}
function createScalarOnlyParameterError(name, operationId, location) {
    throw new UserError(`Operation ${JSON.stringify(operationId)} ${location} parameter ${JSON.stringify(name)} must use a scalar schema (string, number, integer, or boolean).`);
}
function createJsonBodyField(options) {
    return {
        params: [
            {
                paramName: options.name,
                sourceName: options.name,
                location: "body",
                description: options.description,
                optional: options.optional,
                definition: { kind: "json" }
            }
        ],
        preflightBlocks: [],
        requestField: {
            location: "body",
            wireName: options.name,
            value: { kind: "reference", reference: { kind: "param", paramName: options.name } },
            omitWhenUndefinedReference: { kind: "param", paramName: options.name }
        }
    };
}
function createJsonQueryField(options) {
    if (options.queryObjectSerialization !== "deepObject") {
        throw new ToolcraftBugError("Missing deep-object serialization for generated JSON query field.");
    }
    return {
        params: [
            {
                paramName: options.name,
                sourceName: options.name,
                location: "query",
                description: options.description,
                optional: options.optional,
                definition: { kind: "json" }
            }
        ],
        preflightBlocks: [],
        requestField: {
            location: "query",
            wireName: options.name,
            value: { kind: "reference", reference: { kind: "param", paramName: options.name } },
            omitWhenUndefinedReference: { kind: "param", paramName: options.name }
        }
    };
}
function createArrayParam(options) {
    const { document, name, description, schema, optional, operationId, context, location } = options;
    const paramName = name;
    const helperBaseName = normalizeParamName(name);
    const directDefinition = createParamDefinition(document, location === "query" ? stripNullable(schema) : schema, operationId, context);
    const jsonParamName = `${helperBaseName}Json`;
    const nullParamName = `${helperBaseName}Null`;
    const resolvedName = `resolved${toPascalCase(helperBaseName)}`;
    const reference = { kind: "resolved", resolvedName };
    const emitsNullHelper = supportsNullHelper(location, "array", directDefinition.nullable);
    const directParamOptional = true;
    const params = [
        {
            paramName,
            sourceName: name,
            location,
            description,
            optional: directParamOptional,
            definition: {
                ...directDefinition,
                ...(optional ? {} : { requiredScopes: ["mcp", "sdk"] })
            }
        },
        {
            paramName: jsonParamName,
            sourceName: name,
            location: "transport",
            description: `JSON-encoded value for ${name}.`,
            optional: true,
            scope: ["cli"],
            definition: { kind: "string" }
        }
    ];
    if (emitsNullHelper) {
        params.push({
            paramName: nullParamName,
            sourceName: name,
            location: "transport",
            description: `Send null for ${name}.`,
            optional: true,
            scope: ["cli"],
            definition: { kind: "boolean" }
        });
    }
    return {
        params,
        preflightBlocks: [
            {
                kind: "array",
                paramName,
                jsonParamName,
                ...(emitsNullHelper ? { nullParamName } : {}),
                resolvedName,
                definition: directDefinition,
                required: !optional
            }
        ],
        requestField: {
            location,
            wireName: name,
            value: ARRAY_VALUE_EXPRESSIONS[location](reference, options.querySerialization),
            omitWhenUndefinedReference: reference
        }
    };
}
const ARRAY_VALUE_EXPRESSIONS = {
    body: (reference) => ({ kind: "reference", reference }),
    query: (reference, querySerialization) => ({
        kind: "queryArray",
        reference,
        serialization: expectQueryArraySerialization(querySerialization)
    })
};
const FIELD_ASSEMBLERS = {
    body: {
        array: (options) => createArrayParam({
            ...options,
            location: "body"
        }),
        object: (options) => createJsonBodyField(options),
        scalar: (options) => createScalarParam({
            ...options,
            location: "body"
        })
    },
    header: {
        array: (options) => createScalarOnlyParameterError(options.name, options.operationId, "header"),
        object: (options) => createScalarOnlyParameterError(options.name, options.operationId, "header"),
        scalar: (options) => createScalarParam({
            ...options,
            location: "header"
        })
    },
    path: {
        array: (options) => createPathArrayParam(options),
        object: (options) => createScalarOnlyParameterError(options.name, options.operationId, "path"),
        scalar: (options) => createScalarParam({
            ...options,
            location: "path"
        })
    },
    query: {
        array: (options) => createArrayParam({
            ...options,
            location: "query"
        }),
        object: (options) => createJsonQueryField(options),
        scalar: (options) => createScalarParam({
            ...options,
            location: "query"
        })
    }
};
function createPathArrayParam(options) {
    const definition = createParamDefinition(options.document, options.schema, options.operationId, options.context);
    const paramName = options.name;
    return {
        params: [{
                paramName,
                sourceName: options.name,
                location: "path",
                description: options.description,
                optional: false,
                definition
            }],
        preflightBlocks: [],
        requestField: {
            location: "path",
            wireName: options.name,
            value: { kind: "queryArray", reference: { kind: "param", paramName }, serialization: "comma" },
            omitWhenUndefinedReference: { kind: "param", paramName }
        }
    };
}
function expectQueryArraySerialization(querySerialization) {
    if (querySerialization === undefined) {
        throw new ToolcraftBugError("Missing query array serialization for generated query array field.");
    }
    return querySerialization;
}
function supportsNullHelper(location, shape, nullable) {
    return nullable === true && NULL_HELPER_SUPPORT[location][shape];
}
function createCollectedRequestBodyParams(assemblies, bodyOptional, requestBodyDescription, bodyRender, paramsSchemaOptions, bodyMode, multipartBinaryFields) {
    return {
        params: assemblies.flatMap((assembly) => assembly.params),
        paramsSchemaOptions,
        preflightBlocks: assemblies.flatMap((assembly) => assembly.preflightBlocks),
        requestFields: assemblies.map((assembly) => assembly.requestField),
        sectionRenders: { body: bodyRender },
        optionalSections: bodyOptional
            ? new Set(["body"])
            : new Set(),
        requestBodyDescription,
        bodyMode,
        ...(multipartBinaryFields === undefined ? {} : { multipartBinaryFields })
    };
}
function createParamDefinition(document, schema, operationId, context) {
    if (Array.isArray(schema.type)) {
        throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. JSON Schema type arrays with multiple non-null types are not supported in v1.`);
    }
    if (schema.type === "array") {
        const itemSchema = expectArrayItemsSchema(document, schema, operationId, context);
        return {
            kind: "array",
            itemDefinition: createParamDefinition(document, itemSchema, operationId, `${context} items`),
            ...(schema.default === undefined ? {} : { defaultValue: schema.default }),
            ...(schema.minItems === undefined ? {} : { minItems: schema.minItems }),
            ...(schema.maxItems === undefined ? {} : { maxItems: schema.maxItems }),
            ...(schema.nullable === true ? { nullable: true } : {})
        };
    }
    const scalarDefinition = isOpenApiScalarType(schema.type)
        ? SCHEMA_TYPE_TO_KIND[schema.type]
        : undefined;
    const enumValues = normalizeEnumValues(schema.enum, operationId, context);
    if (enumValues !== undefined) {
        return {
            kind: "enum",
            enumValues,
            ...(scalarDefinition?.jsonType === undefined ? {} : { jsonType: scalarDefinition.jsonType }),
            ...(schema.default === undefined ? {} : { defaultValue: schema.default }),
            ...(schema.nullable === true || schema.enum?.includes(null) === true
                ? { nullable: true }
                : {})
        };
    }
    if (scalarDefinition !== undefined) {
        return {
            kind: scalarDefinition.kind,
            ...(scalarDefinition.jsonType === undefined ? {} : { jsonType: scalarDefinition.jsonType }),
            ...(schema.default === undefined ? {} : { defaultValue: schema.default }),
            ...(schema.minimum === undefined ? {} : { minimum: schema.minimum }),
            ...(schema.maximum === undefined ? {} : { maximum: schema.maximum }),
            ...(schema.minLength === undefined ? {} : { minLength: schema.minLength }),
            ...(schema.maxLength === undefined ? {} : { maxLength: schema.maxLength }),
            ...(schema.pattern === undefined ? {} : { pattern: schema.pattern }),
            ...(schema.format === undefined ? {} : { format: schema.format }),
            ...(schema.nullable === true ? { nullable: true } : {})
        };
    }
    throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Supported shapes in this milestone are string, number, integer, boolean, enum, and arrays of those values.`);
}
function assertPathTemplateParameters(path, parameters, operationId) {
    const placeholders = new Set(collectPathPlaceholders(path));
    for (const placeholder of placeholders) {
        if (!parameters.has(`path:${placeholder}`)) {
            throw new UserError(`Operation ${JSON.stringify(operationId)} path ${JSON.stringify(path)} references ${JSON.stringify(`{${placeholder}}`)} but does not define a matching path parameter.`);
        }
    }
    for (const parameter of parameters.values()) {
        if (parameter.in !== "path") {
            continue;
        }
        if (!placeholders.has(parameter.name)) {
            throw new UserError(`Operation ${JSON.stringify(operationId)} path ${JSON.stringify(path)} declares path parameter ${JSON.stringify(parameter.name)} but the path template does not include ${JSON.stringify(`{${parameter.name}}`)}.`);
        }
    }
}
function collectPathPlaceholders(path) {
    const placeholders = [];
    let searchFrom = 0;
    while (searchFrom < path.length) {
        const start = path.indexOf("{", searchFrom);
        if (start === -1) {
            break;
        }
        const end = path.indexOf("}", start + 1);
        if (end === -1) {
            break;
        }
        placeholders.push(path.slice(start + 1, end));
        searchFrom = end + 1;
    }
    return placeholders;
}
function isSuccessStatusCode(statusCode) {
    if (statusCode === "default" || statusCode === "2XX") {
        return true;
    }
    return (statusCode.length === 3 &&
        statusCode[0] === "2" &&
        isAsciiDigit(statusCode[1]) &&
        isAsciiDigit(statusCode[2]));
}
function isAsciiDigit(character) {
    return character >= "0" && character <= "9";
}
function isJsonMediaType(mediaType) {
    const normalized = mediaType.toLowerCase();
    return normalized === "*/*" || normalized === "text/json" || normalized.includes("application/json") || normalized.includes("+json");
}
function isTextMediaType(mediaType) {
    const normalized = mediaType.toLowerCase();
    return normalized.startsWith("text/") || normalized === "plain/text" || normalized === "application/jwt";
}
function isBinaryMediaType(mediaType) {
    const normalized = mediaType.toLowerCase();
    return (normalized === "application/octet-stream" ||
        normalized === "application/zip" ||
        normalized === "application/gzip" ||
        normalized === "application/pdf");
}
function normalizeEnumValues(enumValues, operationId, context) {
    if (enumValues === undefined) {
        return undefined;
    }
    const filteredValues = enumValues.filter(isEnumPrimitiveValue);
    if (filteredValues.length === 0) {
        throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Enum values cannot be empty.`);
    }
    const primitiveTypes = new Set(filteredValues.map((value) => typeof value));
    if (primitiveTypes.size > 1) {
        throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Enums must not mix primitive types.`);
    }
    return filteredValues.filter((value) => value !== null);
}
function resolveLocalReference(document, ref, operationId, context) {
    if (!ref.startsWith("#/")) {
        throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported external $ref ${JSON.stringify(ref)} in ${context}.`);
    }
    let current = document;
    for (const segment of ref.slice(2).split("/").map(unescapeJsonPointerSegment)) {
        if (typeof current !== "object" ||
            current === null ||
            !Object.prototype.hasOwnProperty.call(current, segment)) {
            throw new UserError(`Operation ${JSON.stringify(operationId)} references missing $ref ${JSON.stringify(ref)} in ${context}.`);
        }
        current = current[segment];
    }
    return current;
}
function unescapeJsonPointerSegment(segment) {
    const unescaped = segment.replaceAll("~1", "/").replaceAll("~0", "~");
    try {
        return decodeURIComponent(unescaped);
    }
    catch {
        return unescaped;
    }
}
function expectParameter(document, parameter, operationId, refChain = []) {
    if (isReferenceObject(parameter)) {
        assertAcyclicRef(parameter.$ref, refChain, operationId, "parameter");
        return expectParameter(document, resolveLocalReference(document, parameter.$ref, operationId, "parameter"), operationId, [...refChain, parameter.$ref]);
    }
    if (parameter.content !== undefined) {
        throw new UserError(`Operation ${JSON.stringify(operationId)} parameter ${JSON.stringify(parameter.name)} uses unsupported parameter.content. Define path/query parameters with parameter.schema in v1.`);
    }
    if (parameter.in !== "path" && parameter.in !== "query" && parameter.in !== "header") {
        throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported parameter location ${JSON.stringify(parameter.in)}. Only path, query, and header parameters are supported in v1; use auth or handwritten commands for cookies.`);
    }
    return parameter;
}
function expectOperation(document, operation, method, path, refChain = []) {
    if (!isReferenceObject(operation)) {
        return operation;
    }
    const operationId = `${method.toUpperCase()} ${path}`;
    const context = `operation ${method.toUpperCase()} ${path}`;
    assertAcyclicRef(operation.$ref, refChain, operationId, context);
    return expectOperation(document, resolveLocalReference(document, operation.$ref, operationId, context), method, path, [...refChain, operation.$ref]);
}
function isEnumPrimitiveValue(value) {
    return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
}
function isOpenApiScalarType(type) {
    return (typeof type === "string" && Object.prototype.hasOwnProperty.call(SCHEMA_TYPE_TO_KIND, type));
}
function expectRequestBody(document, requestBody, operationId, context, refChain = []) {
    if (!isReferenceObject(requestBody)) {
        return requestBody;
    }
    assertAcyclicRef(requestBody.$ref, refChain, operationId, context);
    return expectRequestBody(document, resolveLocalReference(document, requestBody.$ref, operationId, context), operationId, context, [...refChain, requestBody.$ref]);
}
function expectResponse(document, response, operationId, statusCode, refChain = []) {
    if (!isReferenceObject(response)) {
        return response;
    }
    const context = `success response for status ${JSON.stringify(statusCode)}`;
    assertAcyclicRef(response.$ref, refChain, operationId, context);
    return expectResponse(document, resolveLocalReference(document, response.$ref, operationId, context), operationId, statusCode, [...refChain, response.$ref]);
}
function expectSchema(document, schema, operationId, context, refChain = []) {
    const resolvedSchema = normalizeNullableSchema(document, resolveSchema(document, schema, operationId, context, refChain), operationId, context);
    const compositionKeyword = getCompositionKeyword(resolvedSchema);
    if (compositionKeyword !== undefined) {
        throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. JSON Schema composition keyword ${JSON.stringify(compositionKeyword)} is not supported in v1.`);
    }
    return resolvedSchema;
}
function resolveBodySchema(document, schema, operationId, context) {
    return normalizeNullableSchema(document, resolveSchema(document, schema, operationId, context), operationId, context);
}
function resolveSchema(document, schema, operationId, context, refChain = []) {
    if (schema === undefined) {
        throw new UserError(`Operation ${JSON.stringify(operationId)} is missing a schema for ${context}.`);
    }
    if (isReferenceObject(schema)) {
        assertAcyclicRef(schema.$ref, refChain, operationId, context);
        const resolved = resolveLocalReference(document, schema.$ref, operationId, context);
        const nestedSchema = isSchemaContainer(resolved) ? resolved.schema : resolved;
        return resolveSchema(document, nestedSchema, operationId, context, [...refChain, schema.$ref]);
    }
    return cloneOwnSchemaObject(schema);
}
function cloneOwnSchemaObject(schema) {
    const clone = Object.create(null);
    for (const [key, value] of Object.entries(schema)) {
        Object.defineProperty(clone, key, {
            value,
            enumerable: true,
            configurable: true,
            writable: true
        });
    }
    return clone;
}
function isSchemaContainer(value) {
    return !isReferenceObject(value) && "schema" in value && value.schema !== undefined;
}
function normalizeNullableSchema(document, schema, operationId, context) {
    const typeNormalizedSchema = normalizeNullableTypeArray(schema);
    const equivalentComposition = resolveEquivalentCompositionSchema(document, typeNormalizedSchema, operationId, context);
    if (equivalentComposition !== undefined) {
        return equivalentComposition;
    }
    if (getCompositionKeyword(typeNormalizedSchema) !== "anyOf") {
        return typeNormalizedSchema;
    }
    return (resolveNullableAnyOfSchema(document, typeNormalizedSchema, operationId, context) ??
        typeNormalizedSchema);
}
function resolveEquivalentCompositionSchema(document, schema, operationId, context) {
    const keyword = getCompositionKeyword(schema);
    if (keyword !== "anyOf" && keyword !== "oneOf") {
        return undefined;
    }
    const variants = schema[keyword];
    if (variants === undefined || variants.length === 0) {
        return undefined;
    }
    const resolved = variants.map((variant, index) => normalizeNullableSchema(document, resolveSchema(document, variant, operationId, `${context} ${keyword} variant ${index}`), operationId, `${context} ${keyword} variant ${index}`));
    const shapes = resolved.map(getEquivalentSchemaShape);
    const [firstShape] = shapes;
    if (firstShape === undefined || shapes.some((shape) => shape !== firstShape)) {
        return undefined;
    }
    const { anyOf: _anyOf, oneOf: _oneOf, ...wrapper } = schema;
    void _anyOf;
    void _oneOf;
    const enumValues = collectEquivalentCompositionEnumValues(resolved);
    return {
        ...wrapper,
        ...createSchemaFromEquivalentShape(firstShape),
        ...(enumValues === undefined ? {} : { enum: enumValues })
    };
}
function getEquivalentSchemaShape(schema) {
    if (getCompositionKeyword(schema) !== undefined || Array.isArray(schema.type)) {
        return undefined;
    }
    if (schema.type === "array") {
        if (schema.items === undefined || isReferenceObject(schema.items))
            return undefined;
        const itemShape = getEquivalentSchemaShape(schema.items);
        return itemShape === undefined ? undefined : `array:${itemShape}`;
    }
    return isOpenApiScalarType(schema.type) ? schema.type : undefined;
}
function createSchemaFromEquivalentShape(shape) {
    if (!shape.startsWith("array:")) {
        return { type: shape };
    }
    return { type: "array", items: createSchemaFromEquivalentShape(shape.slice("array:".length)) };
}
function collectEquivalentCompositionEnumValues(schemas) {
    if (schemas.some((schema) => schema.enum === undefined)) {
        return undefined;
    }
    const values = [];
    for (const schema of schemas) {
        for (const value of schema.enum ?? []) {
            if (!values.some((existing) => Object.is(existing, value))) {
                values.push(value);
            }
        }
    }
    return values.length === 0 ? undefined : values;
}
function normalizeNullableTypeArray(schema) {
    if (!Array.isArray(schema.type)) {
        return schema;
    }
    const nonNullTypes = schema.type.filter((type) => type !== "null");
    if (nonNullTypes.length === schema.type.length) {
        return schema;
    }
    return {
        ...schema,
        type: nonNullTypes.length === 1 ? nonNullTypes[0] : nonNullTypes,
        nullable: true
    };
}
function getCompositionKeyword(schema) {
    for (const keyword of ["allOf", "anyOf", "oneOf"]) {
        if (Object.prototype.hasOwnProperty.call(schema, keyword) &&
            schema[keyword] !== undefined) {
            return keyword;
        }
    }
    return undefined;
}
function resolveNullableAnyOfSchema(document, schema, operationId, context) {
    const variants = schema.anyOf;
    if (variants === undefined || variants.length !== 2) {
        return undefined;
    }
    const resolvedVariants = variants.map((variant, index) => normalizeNullableSchema(document, resolveSchema(document, variant, operationId, `${context} anyOf variant ${index}`), operationId, `${context} anyOf variant ${index}`));
    const nullVariantIndex = resolvedVariants.findIndex(isExplicitNullSchema);
    if (nullVariantIndex === -1) {
        return undefined;
    }
    const nonNullVariant = resolvedVariants[Number(!nullVariantIndex)];
    if (nonNullVariant === undefined || getCompositionKeyword(nonNullVariant) !== undefined) {
        return undefined;
    }
    const { anyOf: _anyOf, nullable: _nullable, ...wrapperSchema } = schema;
    void _anyOf;
    void _nullable;
    return {
        ...wrapperSchema,
        ...nonNullVariant,
        description: nonNullVariant.description ?? schema.description,
        default: nonNullVariant.default ?? schema.default,
        nullable: true
    };
}
function isExplicitNullSchema(schema) {
    return schema.type === "null";
}
function assertAcyclicRef(ref, refChain, operationId, context) {
    if (!refChain.includes(ref)) {
        return;
    }
    throw new UserError(`Operation ${JSON.stringify(operationId)} uses circular $ref chain in ${context}: ${[
        ...refChain,
        ref
    ]
        .map((value) => JSON.stringify(value))
        .join(" -> ")}.`);
}
function expectArrayItemsSchema(document, schema, operationId, context) {
    if (schema.items === undefined) {
        throw new UserError(`Operation ${JSON.stringify(operationId)} is missing array items for ${context}.`);
    }
    return expectSchema(document, schema.items, operationId, `${context} items`);
}
function assertUniqueCommandPaths(commands) {
    const seen = new Map();
    for (const command of commands) {
        const key = `${command.noun} ${command.verb}`;
        const existing = seen.get(key);
        if (existing !== undefined) {
            throw new UserError(`Generated command path ${JSON.stringify(key)} is defined more than once (${JSON.stringify(existing.operationId)} and ${JSON.stringify(command.operationId)}).`);
        }
        seen.set(key, command);
    }
}
function disambiguateCommandPaths(commands) {
    const byCommandPath = new Map();
    for (const command of commands) {
        const key = `${command.noun} ${command.verb}`;
        byCommandPath.set(key, [...(byCommandPath.get(key) ?? []), command]);
    }
    for (const collisions of byCommandPath.values()) {
        if (collisions.length < 2) {
            continue;
        }
        const existingPaths = new Set(commands
            .filter((command) => !collisions.includes(command))
            .map((command) => `${command.noun} ${command.verb}`));
        const operationIdCandidates = collisions.map((command) => ({
            command,
            verb: deriveDisambiguatedVerb(command.operationId, command.noun)
        }));
        if (applyDisambiguatedVerbs(operationIdCandidates, existingPaths)) {
            continue;
        }
        const pathCandidates = collisions.map((command) => ({
            command,
            verb: derivePathDisambiguatedVerb(command.method.toLowerCase(), command.path, command.noun, command.verb)
        }));
        if (applyDisambiguatedVerbs(pathCandidates, existingPaths)) {
            continue;
        }
        if (applyDisambiguatedVerbs(collisions.map((command) => ({
            command,
            verb: derivePathDisambiguatedVerb(command.method.toLowerCase(), command.path, command.noun, command.verb, true)
        })), existingPaths)) {
            continue;
        }
        applyDisambiguatedVerbs(collisions.map((command) => ({
            command,
            verb: derivePathDisambiguatedVerb(command.method.toLowerCase(), command.path, command.noun, command.verb, true, true)
        })), existingPaths);
    }
}
function applyDisambiguatedVerbs(candidates, existingPaths) {
    const candidatePaths = candidates.map(({ command, verb }) => `${command.noun} ${verb}`);
    if (candidates.some(({ verb }) => verb.length === 0) ||
        new Set(candidatePaths).size !== candidatePaths.length ||
        candidatePaths.some((path) => existingPaths.has(path))) {
        return false;
    }
    for (const { command, verb } of candidates) {
        command.verb = verb;
        command.exportName = `${toCamelCase(command.noun)}${toPascalCase(verb)}Command`;
        command.filePath = `${command.noun}/${verb}.ts`;
    }
    return true;
}
function createCommandFile(options) {
    const requiresUserError = options.preflightBlocks.length > 0;
    const usesMultipartFileInputs = options.bodyMode === "multipart" &&
        options.multipartBinaryFields !== undefined &&
        options.multipartBinaryFields.length > 0;
    const usesArrayJsonValidation = options.preflightBlocks.some((block) => block.kind === "array");
    const usesBinaryOutput = options.responseMode === "binary";
    const usesRequestShapeVariable = usesMultipartFileInputs || usesBinaryOutput;
    const openApiImports = [
        "requestJson",
        "defineApiCommand",
        ...(usesArrayJsonValidation ? ["validateArrayJsonHelperValue"] : []),
        ...(usesMultipartFileInputs ? ["prepareMultipartFileInputs"] : []),
        ...(usesBinaryOutput ? ["writeBinaryResponseOutput"] : [])
    ];
    const lines = createGeneratedTypeScriptFileLines([
        `spec-sha: ${options.specSha}`,
        `operation-id: ${options.operationId}`
    ]);
    lines.push(requiresUserError
        ? 'import { S, UserError } from "toolcraft";'
        : 'import { S } from "toolcraft";', `import { ${openApiImports.join(", ")} } from "toolcraft-openapi";`, "", `export const ${options.exportName} = defineApiCommand({`, `  name: ${JSON.stringify(options.verb)},`);
    if (options.description !== undefined) {
        lines.push(`  description: ${JSON.stringify(options.description)},`);
    }
    if (options.examples !== undefined && options.examples.length > 0) {
        lines.push(`  examples: ${JSON.stringify(options.examples)},`);
    }
    lines.push('  scope: ["cli", "mcp", "sdk"] as const,');
    if (options.confirm) {
        lines.push("  confirm: true,");
    }
    lines.push("  params: S.Object({");
    lines.push(...renderParamLines(options.params));
    lines.push(options.paramsSchemaOptions?.additionalProperties === false
        ? "  }, { additionalProperties: false }),"
        : "  }),");
    lines.push(usesRequestShapeVariable
        ? "  handler: async ({ params, baseUrl, tokenSource, fetch, fs, env }) => {"
        : "  handler: async ({ params, baseUrl, tokenSource, fetch }) => {");
    lines.push(...options.preflightBlocks.flatMap((block) => renderPreflightBlock(block)));
    if (usesRequestShapeVariable) {
        lines.push("    const requestShape = {");
        lines.push(...renderRequestShape(options.requestFields, options.sectionRenders, options.optionalSections));
        lines.push("    };");
        if (usesMultipartFileInputs) {
            lines.push("    const preparedRequestShape = await prepareMultipartFileInputs(requestShape, {");
            lines.push('      bodyMode: "multipart",');
            lines.push(`      multipartBinaryFields: ${JSON.stringify(options.multipartBinaryFields)},`);
            lines.push("      fs,");
            lines.push("      env,");
            lines.push("    });");
        }
        else {
            lines.push("    const preparedRequestShape = requestShape;");
        }
        lines.push("    const result = await requestJson({");
    }
    else {
        lines.push("    return requestJson({");
    }
    lines.push(options.baseUrl === undefined
        ? "      baseUrl,"
        : `      baseUrl: ${JSON.stringify(options.baseUrl)},`);
    lines.push(`      path: ${JSON.stringify(options.path)},`);
    lines.push(`      method: ${JSON.stringify(options.method)},`);
    lines.push(`      auth: ${JSON.stringify(options.auth)},`);
    if (options.responseMode !== "json") {
        lines.push(`      responseMode: ${JSON.stringify(options.responseMode)},`);
    }
    if (options.accept !== "application/json") {
        lines.push(`      accept: ${JSON.stringify(options.accept)},`);
    }
    if (options.bodyMode === "form") {
        lines.push('      bodyMode: "form",');
    }
    else if (options.bodyMode === "raw") {
        lines.push('      bodyMode: "raw",');
    }
    else if (options.bodyMode === "base64") {
        lines.push('      bodyMode: "base64",');
    }
    else if (options.bodyMode === "multipart") {
        lines.push('      bodyMode: "multipart",');
    }
    if (options.multipartBinaryFields !== undefined) {
        lines.push(`      multipartBinaryFields: ${JSON.stringify(options.multipartBinaryFields)},`);
    }
    if (options.contentType !== undefined) {
        lines.push(`      contentType: ${JSON.stringify(options.contentType)},`);
    }
    lines.push("      tokenSource,");
    lines.push("      fetch,");
    lines.push("      dryRun: params.dryRun,");
    lines.push("      verbose: params.verbose,");
    if (options.rawResponse === true) {
        lines.push("      rawResponse: params.rawResponse,");
    }
    if (options.idempotencyHeader !== undefined) {
        lines.push("      idempotency: {");
        lines.push(`        header: ${JSON.stringify(options.idempotencyHeader)},`);
        lines.push("        enabled: true,");
        lines.push("        key: params.idempotencyKey,");
        lines.push("      },");
    }
    if (usesRequestShapeVariable) {
        lines.push("      ...preparedRequestShape,");
    }
    else {
        lines.push(...renderRequestShape(options.requestFields, options.sectionRenders, options.optionalSections));
    }
    lines.push("    });");
    if (usesBinaryOutput) {
        lines.push("    return writeBinaryResponseOutput(result, params.output, { fs, env });");
    }
    lines.push("  },");
    lines.push("});");
    lines.push("");
    return lines.join("\n");
}
function mergeCommandDescriptions(operationDescription, requestBodyDescription) {
    const operationText = operationDescription === undefined || operationDescription.length === 0
        ? undefined
        : operationDescription;
    const requestBodyText = requestBodyDescription === undefined || requestBodyDescription.length === 0
        ? undefined
        : requestBodyDescription;
    if (operationText === undefined ||
        requestBodyText === undefined ||
        operationText === requestBodyText) {
        return operationText ?? requestBodyText;
    }
    return `${operationText}\n\nRequest body: ${requestBodyText}`;
}
function getOperationAuthMode(document, operation, operationId) {
    const security = operation.security ?? document.security;
    const securityScope = operation.security === undefined ? "document" : "operation";
    const definedSchemes = document.components?.securitySchemes;
    for (const requirement of security ?? []) {
        for (const schemeName of Object.keys(requirement)) {
            if (definedSchemes !== undefined &&
                Object.prototype.hasOwnProperty.call(definedSchemes, schemeName)) {
                continue;
            }
            throw new UserError(`Operation ${JSON.stringify(operationId)} references undefined security scheme ${JSON.stringify(schemeName)} in ${securityScope} security. Expected components.securitySchemes to define it.`);
        }
    }
    return security === undefined || security.length === 0 ? "none" : "required";
}
function assertSupportedHttpMethods(path, pathItem) {
    for (const method of UNSUPPORTED_HTTP_METHODS) {
        const operation = getOwnPathItemValue(pathItem, method);
        if (operation === undefined) {
            continue;
        }
        throw new UserError(`Operation ${JSON.stringify(getOperationId(path, method, operation))} uses unsupported HTTP method ${JSON.stringify(method.toUpperCase())}. Supported in v1: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.`);
    }
}
function getOperationId(path, method, operation) {
    if (operation !== null &&
        typeof operation === "object" &&
        "operationId" in operation &&
        typeof operation.operationId === "string" &&
        operation.operationId.length > 0) {
        return operation.operationId;
    }
    return `${method.toUpperCase()} ${path}`;
}
function stripNullable(schema) {
    if (schema.nullable !== true) {
        return schema;
    }
    return { ...schema, nullable: undefined };
}
function renderParamLines(params) {
    return params.map((param) => `    ${renderObjectKey(param.paramName)}: ${renderParamSchema(param)},`);
}
function renderParamSchema(param) {
    const schema = renderDefinition(param.definition, param.description, param.shortFlag, param.longAliases, param.scope, param.global);
    return param.optional ? `S.Optional(${schema})` : schema;
}
function renderDefinition(definition, description, shortFlag, longAliases, scope, global) {
    const options = renderSchemaOptions({
        definition,
        description,
        shortFlag,
        longAliases,
        scope,
        global
    });
    const renderer = DEFINITION_RENDERERS[definition.kind];
    return renderer(definition, options);
}
const DEFINITION_RENDERERS = {
    array: (definition, options) => renderSchemaCall("S.Array", renderDefinition(definition.itemDefinition, undefined, undefined, undefined), options),
    boolean: (_definition, options) => renderSchemaCall("S.Boolean", options),
    enum: (definition, options) => renderSchemaCall("S.Enum", renderConstArray(definition.enumValues), options),
    json: (_definition) => renderSchemaCall("S.Json"),
    number: (_definition, options) => renderSchemaCall("S.Number", options),
    string: (_definition, options) => renderSchemaCall("S.String", options)
};
function renderSchemaCall(builder, ...args) {
    return `${builder}(${args.filter((arg) => arg !== undefined).join(", ")})`;
}
function renderSchemaOptions(param) {
    const entries = collectSchemaOptionEntries(param).map(({ key, value }) => `${key}: ${renderSchemaOptionValue(value)}`);
    return entries.length === 0 ? undefined : `{ ${entries.join(", ")} }`;
}
function renderConstArray(values) {
    return `${JSON.stringify(values)} as const`;
}
function renderObjectKey(name) {
    if (name === "__proto__") {
        return `[${JSON.stringify(name)}]`;
    }
    if (name === normalizeParamName(name) && isIdentifierName(name)) {
        return name;
    }
    return JSON.stringify(name);
}
function createSafeGeneratedNoun(noun) {
    const normalized = normalizeNoun(noun);
    return isTypeScriptIdentifier(toCamelCase(normalized)) ? normalized : `api-${normalized || "operation"}`;
}
export function collectSchemaOptionEntries(param) {
    return SCHEMA_OPTION_SOURCES.flatMap(({ key, get }) => {
        const value = get(param);
        return value === undefined ? [] : [{ key, value }];
    });
}
function renderSchemaOptionValue(value) {
    if (Array.isArray(value)) {
        return `[${value.map((entry) => renderSchemaOptionValue(entry)).join(", ")}]`;
    }
    return JSON.stringify(value);
}
const RESERVED_TYPESCRIPT_IDENTIFIERS = new Set([
    "abstract",
    "any",
    "as",
    "asserts",
    "async",
    "await",
    "bigint",
    "boolean",
    "break",
    "case",
    "catch",
    "class",
    "const",
    "continue",
    "debugger",
    "declare",
    "default",
    "delete",
    "do",
    "else",
    "enum",
    "export",
    "extends",
    "false",
    "finally",
    "for",
    "function",
    "get",
    "if",
    "implements",
    "import",
    "in",
    "infer",
    "instanceof",
    "interface",
    "is",
    "keyof",
    "let",
    "module",
    "namespace",
    "never",
    "new",
    "null",
    "number",
    "object",
    "package",
    "private",
    "protected",
    "public",
    "readonly",
    "return",
    "satisfies",
    "set",
    "static",
    "string",
    "super",
    "switch",
    "symbol",
    "this",
    "throw",
    "true",
    "try",
    "type",
    "typeof",
    "undefined",
    "unique",
    "unknown",
    "var",
    "void",
    "while",
    "with",
    "yield"
]);
function isTypeScriptIdentifier(value) {
    return isIdentifierName(value) && !RESERVED_TYPESCRIPT_IDENTIFIERS.has(value);
}
function resolveQueryArraySerialization(parameter, operationId) {
    const style = parameter.style ?? "form";
    const explode = parameter.explode ?? style === "form";
    if (style === "form") {
        return explode ? "repeat" : "comma";
    }
    if (style === "pipeDelimited" && explode === false) {
        return "pipe";
    }
    if (style === "deepObject" && explode === true) {
        return "brackets";
    }
    throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported query-array serialization for parameter ${JSON.stringify(parameter.name)}. Supported in v1: form (explode true/false) and pipeDelimited.`);
}
function resolveQueryObjectSerialization(parameter, operationId) {
    if (parameter.style === "deepObject" && parameter.explode !== false) {
        return "deepObject";
    }
    throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported query-object serialization for parameter ${JSON.stringify(parameter.name)}. Supported in v1: deepObject with explode true.`);
}
function createIndexFile(commands) {
    const groups = groupByNoun(commands);
    if (groups.length === 0) {
        return {
            path: "index.ts",
            contents: createGeneratedTypeScriptFile(["export {};", ""])
        };
    }
    const lines = createGeneratedTypeScriptFileLines();
    lines.push('import { defineGroup } from "toolcraft";');
    for (const { commands: nounCommands } of groups) {
        for (const command of nounCommands) {
            lines.push(`import { ${command.exportName} } from ${JSON.stringify(`./${command.filePath.replace(/\.ts$/, ".js")}`)};`);
        }
    }
    if (lines.length > 1) {
        lines.push("");
    }
    for (const { noun, commands: nounCommands } of groups) {
        lines.push(`export const ${toCamelCase(noun)} = defineGroup({`);
        lines.push(`  name: ${JSON.stringify(noun)},`);
        lines.push(`  children: [${nounCommands.map((command) => command.exportName).join(", ")}],`);
        lines.push("});");
        lines.push("");
    }
    return {
        path: "index.ts",
        contents: lines.join("\n")
    };
}
function createCliFile(theme) {
    return {
        path: "cli.ts",
        contents: [
            "#!/usr/bin/env node",
            ...createGeneratedTypeScriptFileLines(),
            'import { configureTheme, runCLI } from "toolcraft/cli";',
            'import * as groups from "./index.js";',
            "",
            `configureTheme({ brand: ${JSON.stringify(theme.brand)}, label: ${JSON.stringify(theme.label)} });`,
            "",
            "await runCLI(Object.values(groups));",
            ""
        ].join("\n")
    };
}
function createGeneratedTypeScriptFile(bodyLines, metadataLines = []) {
    return [...createGeneratedTypeScriptFileLines(metadataLines), ...bodyLines].join("\n");
}
function createGeneratedTypeScriptFileLines(metadataLines = []) {
    return [
        "/**",
        " * Generated by toolcraft-openapi.",
        ...metadataLines.map((line) => ` * ${line}`),
        " */"
    ];
}
function compareGeneratedCommandPaths(left, right) {
    const nounCompare = left.noun.localeCompare(right.noun);
    if (nounCompare !== 0) {
        return nounCompare;
    }
    return left.verb.localeCompare(right.verb);
}
function isReferenceObject(value) {
    return (typeof value === "object" &&
        value !== null &&
        Object.prototype.hasOwnProperty.call(value, "$ref"));
}
