/**
 * JSON Schema to Zod schema converter.
 *
 * Converts MCP tool JSON Schema definitions to Zod schemas
 * for use with the Tool Registry.
 */
import { z } from "zod";
/**
 * Maximum recursion depth for schema conversion.
 * Prevents stack overflow on circular or deeply nested schemas.
 */
const MAX_SCHEMA_DEPTH = 50;
/**
 * Converts a JSON Schema to a Zod schema.
 *
 * Supports common JSON Schema constructs used in MCP tool definitions:
 * - Primitive types (string, number, integer, boolean, null)
 * - Objects with properties
 * - Arrays with items
 * - Enums and const values
 * - Basic validation (min/max, pattern, required)
 * - Union types (oneOf, anyOf)
 *
 * @param schema - JSON Schema to convert
 * @returns Equivalent Zod schema
 *
 * @example
 * const jsonSchema = {
 *   type: "object",
 *   properties: {
 *     url: { type: "string" },
 *     timeout: { type: "number", minimum: 0 }
 *   },
 *   required: ["url"]
 * };
 * const zodSchema = jsonSchemaToZod(jsonSchema);
 */
export function jsonSchemaToZod(schema) {
    return jsonSchemaToZodInternal(schema, 0);
}
/**
 * Internal recursive implementation with depth tracking.
 */
function jsonSchemaToZodInternal(schema, depth) {
    if (!schema) {
        return z.unknown();
    }
    // Prevent infinite recursion on circular schemas
    if (depth > MAX_SCHEMA_DEPTH) {
        return z.unknown();
    }
    // Handle const
    if (schema.const !== undefined) {
        return z.literal(schema.const);
    }
    // Handle enum
    if (schema.enum && schema.enum.length > 0) {
        // Check if all values are strings for z.enum
        if (schema.enum.every((v) => typeof v === "string")) {
            return z.enum(schema.enum);
        }
        // Fall back to union of literals for mixed types
        const literals = schema.enum.map((v) => z.literal(v));
        if (literals.length === 1) {
            return literals[0];
        }
        return z.union(literals);
    }
    // Handle oneOf/anyOf (union types)
    if (schema.oneOf && schema.oneOf.length > 0) {
        const schemas = schema.oneOf.map((s) => jsonSchemaToZodInternal(s, depth + 1));
        if (schemas.length === 1) {
            return schemas[0];
        }
        return z.union(schemas);
    }
    if (schema.anyOf && schema.anyOf.length > 0) {
        const schemas = schema.anyOf.map((s) => jsonSchemaToZodInternal(s, depth + 1));
        if (schemas.length === 1) {
            return schemas[0];
        }
        return z.union(schemas);
    }
    // Handle allOf (intersection types)
    if (schema.allOf && schema.allOf.length > 0) {
        const schemas = schema.allOf.map((s) => jsonSchemaToZodInternal(s, depth + 1));
        if (schemas.length === 1) {
            return schemas[0];
        }
        // Use z.intersection() which works with any Zod types, unlike .and() which
        // only works on ZodObject. For primitives the intersection may be `never`
        // at the type level, but Zod will handle validation correctly.
        return schemas.reduce((acc, s) => z.intersection(acc, s));
    }
    // Handle type-based schemas
    const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
    switch (type) {
        case "string":
            return buildStringSchema(schema);
        case "number":
            return buildNumberSchema(schema, false);
        case "integer":
            return buildNumberSchema(schema, true);
        case "boolean":
            return z.boolean();
        case "null":
            return z.null();
        case "array":
            return buildArraySchema(schema, depth);
        case "object":
            return buildObjectSchema(schema, depth);
        default:
            // No type specified, try to infer from properties
            if (schema.properties) {
                return buildObjectSchema(schema, depth);
            }
            return z.unknown();
    }
}
/**
 * Builds a Zod string schema with validation constraints.
 */
function buildStringSchema(schema) {
    let zodSchema = z.string();
    if (schema.minLength !== undefined) {
        zodSchema = zodSchema.min(schema.minLength);
    }
    if (schema.maxLength !== undefined) {
        zodSchema = zodSchema.max(schema.maxLength);
    }
    if (schema.pattern) {
        zodSchema = zodSchema.regex(new RegExp(schema.pattern));
    }
    if (schema.description) {
        zodSchema = zodSchema.describe(schema.description);
    }
    return zodSchema;
}
/**
 * Builds a Zod number schema with validation constraints.
 */
function buildNumberSchema(schema, isInteger) {
    let zodSchema = z.number();
    if (isInteger) {
        zodSchema = zodSchema.int();
    }
    if (schema.minimum !== undefined) {
        zodSchema = zodSchema.min(schema.minimum);
    }
    if (schema.maximum !== undefined) {
        zodSchema = zodSchema.max(schema.maximum);
    }
    if (schema.description) {
        zodSchema = zodSchema.describe(schema.description);
    }
    return zodSchema;
}
/**
 * Builds a Zod array schema.
 */
function buildArraySchema(schema, depth) {
    const itemSchema = schema.items
        ? jsonSchemaToZodInternal(schema.items, depth + 1)
        : z.unknown();
    let zodSchema = z.array(itemSchema);
    if (schema.description) {
        zodSchema = zodSchema.describe(schema.description);
    }
    return zodSchema;
}
/**
 * Builds a Zod object schema with properties.
 */
function buildObjectSchema(schema, depth) {
    if (!schema.properties) {
        // Object with no defined properties
        if (schema.additionalProperties === false) {
            return z.object({}).strict();
        }
        return z.record(z.string(), z.unknown());
    }
    const required = new Set(schema.required ?? []);
    const shape = {};
    for (const [key, propSchema] of Object.entries(schema.properties)) {
        let propZod = jsonSchemaToZodInternal(propSchema, depth + 1);
        // Handle optional/default:
        // - If there's a default, use .default() which makes it implicitly optional
        // - If not required and no default, use .optional()
        const hasDefault = propSchema.default !== undefined;
        const isRequired = required.has(key);
        if (hasDefault) {
            // .default() makes the field implicitly optional and provides a fallback value
            propZod = propZod.default(propSchema.default);
        }
        else if (!isRequired) {
            // No default, just make it optional
            propZod = propZod.optional();
        }
        shape[key] = propZod;
    }
    let zodSchema = z.object(shape);
    // Handle additionalProperties
    if (schema.additionalProperties === false) {
        zodSchema = zodSchema.strict();
    }
    else if (schema.additionalProperties &&
        typeof schema.additionalProperties === "object") {
        zodSchema = zodSchema.catchall(jsonSchemaToZodInternal(schema.additionalProperties, depth + 1));
    }
    else {
        // Allow unknown keys by default (passthrough)
        zodSchema = zodSchema.passthrough();
    }
    if (schema.description) {
        zodSchema = zodSchema.describe(schema.description);
    }
    return zodSchema;
}
/**
 * Creates a permissive fallback schema for unknown structures.
 *
 * Used when JSON Schema conversion fails or schema is missing.
 *
 * @param description - Optional description for the schema
 * @returns A permissive Zod schema that accepts any object
 */
export function createPermissiveSchema(description) {
    const schema = z.record(z.string(), z.any());
    return description ? schema.describe(description) : schema;
}
//# sourceMappingURL=json-schema-to-zod.js.map