"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.toStrictJsonSchema = toStrictJsonSchema;
function toStrictJsonSchema(schema) {
    if (schema.type !== 'object') {
        throw new Error(`Root schema must have type: 'object' but got type: ${schema.type ? `'${schema.type}'` : 'undefined'}`);
    }
    const schemaCopy = structuredClone(schema);
    return ensureStrictJsonSchema(schemaCopy, [], schemaCopy);
}
function isNullable(schema) {
    if (typeof schema === 'boolean') {
        return false;
    }
    if (schema.type === 'null') {
        return true;
    }
    for (const oneOfVariant of schema.oneOf ?? []) {
        if (isNullable(oneOfVariant)) {
            return true;
        }
    }
    for (const allOfVariant of schema.anyOf ?? []) {
        if (isNullable(allOfVariant)) {
            return true;
        }
    }
    return false;
}
/**
 * Mutates the given JSON schema to ensure it conforms to the `strict` standard
 * that the API expects.
 */
function ensureStrictJsonSchema(jsonSchema, path, root) {
    if (typeof jsonSchema === 'boolean') {
        throw new TypeError(`Expected object schema but got boolean; path=${path.join('/')}`);
    }
    if (!isObject(jsonSchema)) {
        throw new TypeError(`Expected ${JSON.stringify(jsonSchema)} to be an object; path=${path.join('/')}`);
    }
    // Handle $defs (non-standard but sometimes used)
    const defs = jsonSchema.$defs;
    if (isObject(defs)) {
        for (const [defName, defSchema] of Object.entries(defs)) {
            ensureStrictJsonSchema(defSchema, [...path, '$defs', defName], root);
        }
    }
    // Handle definitions (draft-04 style, deprecated in draft-07 but still used)
    const definitions = jsonSchema.definitions;
    if (isObject(definitions)) {
        for (const [definitionName, definitionSchema] of Object.entries(definitions)) {
            ensureStrictJsonSchema(definitionSchema, [...path, 'definitions', definitionName], root);
        }
    }
    // Add additionalProperties: false to object types
    const typ = jsonSchema.type;
    if (typ === 'object' && !('additionalProperties' in jsonSchema)) {
        jsonSchema.additionalProperties = false;
    }
    const required = jsonSchema.required ?? [];
    // Handle object properties
    const properties = jsonSchema.properties;
    if (isObject(properties)) {
        for (const [key, value] of Object.entries(properties)) {
            if (!isNullable(value) && !required.includes(key)) {
                throw new Error(`Zod field at \`${[...path, 'properties', key].join('/')}\` uses \`.optional()\` without \`.nullable()\` which is not supported by the API. See: https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#all-fields-must-be-required`);
            }
        }
        jsonSchema.required = Object.keys(properties);
        jsonSchema.properties = Object.fromEntries(Object.entries(properties).map(([key, propSchema]) => [
            key,
            ensureStrictJsonSchema(propSchema, [...path, 'properties', key], root),
        ]));
    }
    // Handle arrays
    const items = jsonSchema.items;
    if (isObject(items)) {
        jsonSchema.items = ensureStrictJsonSchema(items, [...path, 'items'], root);
    }
    // Handle unions (anyOf)
    const anyOf = jsonSchema.anyOf;
    if (Array.isArray(anyOf)) {
        jsonSchema.anyOf = anyOf.map((variant, i) => ensureStrictJsonSchema(variant, [...path, 'anyOf', String(i)], root));
    }
    // Handle intersections (allOf)
    const allOf = jsonSchema.allOf;
    if (Array.isArray(allOf)) {
        if (allOf.length === 1) {
            const resolved = ensureStrictJsonSchema(allOf[0], [...path, 'allOf', '0'], root);
            Object.assign(jsonSchema, resolved);
            delete jsonSchema.allOf;
        }
        else {
            jsonSchema.allOf = allOf.map((entry, i) => ensureStrictJsonSchema(entry, [...path, 'allOf', String(i)], root));
        }
    }
    // Strip `null` defaults as there's no meaningful distinction
    if (jsonSchema.default === null) {
        delete jsonSchema.default;
    }
    // Handle $ref with additional properties
    const ref = jsonSchema.$ref;
    if (ref && hasMoreThanNKeys(jsonSchema, 1)) {
        if (typeof ref !== 'string') {
            throw new TypeError(`Received non-string $ref - ${ref}; path=${path.join('/')}`);
        }
        const resolved = resolveRef(root, ref);
        if (typeof resolved === 'boolean') {
            throw new Error(`Expected \`$ref: ${ref}\` to resolve to an object schema but got boolean`);
        }
        if (!isObject(resolved)) {
            throw new Error(`Expected \`$ref: ${ref}\` to resolve to an object but got ${JSON.stringify(resolved)}`);
        }
        // Properties from the json schema take priority over the ones on the `$ref`
        Object.assign(jsonSchema, { ...resolved, ...jsonSchema });
        delete jsonSchema.$ref;
        // Since the schema expanded from `$ref` might not have `additionalProperties: false` applied,
        // we call `ensureStrictJsonSchema` again to fix the inlined schema and ensure it's valid.
        return ensureStrictJsonSchema(jsonSchema, path, root);
    }
    return jsonSchema;
}
function resolveRef(root, ref) {
    if (!ref.startsWith('#/')) {
        throw new Error(`Unexpected $ref format ${JSON.stringify(ref)}; Does not start with #/`);
    }
    const pathParts = ref.slice(2).split('/');
    let resolved = root;
    for (const key of pathParts) {
        if (!isObject(resolved)) {
            throw new Error(`encountered non-object entry while resolving ${ref} - ${JSON.stringify(resolved)}`);
        }
        const value = resolved[key];
        if (value === undefined) {
            throw new Error(`Key ${key} not found while resolving ${ref}`);
        }
        resolved = value;
    }
    return resolved;
}
function isObject(obj) {
    return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
}
function hasMoreThanNKeys(obj, n) {
    let i = 0;
    for (const _ in obj) {
        i++;
        if (i > n) {
            return true;
        }
    }
    return false;
}
//# sourceMappingURL=transform.js.map