"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;
exports.types = void 0;
var clone_object_1 = __importDefault(require("../lib/clone-object"));
var matches_mimetype_1 = __importDefault(require("../lib/matches-mimetype"));
var openapi_to_json_schema_1 = __importStar(require("../lib/openapi-to-json-schema"));
var isJSON = matches_mimetype_1["default"].json;
/**
 * The order of this object determines how they will be sorted in the compiled JSON Schema
 * representation.
 *
 * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameterObject}
 * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameterObject}
 */
exports.types = {
    path: 'Path Params',
    query: 'Query Params',
    body: 'Body Params',
    cookie: 'Cookie Params',
    formData: 'Form Data',
    header: 'Headers',
    metadata: 'Metadata'
};
function getParametersAsJSONSchema(operation, api, opts) {
    var _a;
    var hasCircularRefs = false;
    var hasDiscriminatorMappingRefs = false;
    function refLogger(ref, type) {
        if (type === 'ref') {
            hasCircularRefs = true;
        }
        else {
            hasDiscriminatorMappingRefs = true;
        }
    }
    function getDeprecated(schema, type) {
        // If we wish to retain deprecated properties then we shouldn't split them out into the
        // `deprecatedProps` object.
        if (opts.retainDeprecatedProperties) {
            return null;
        }
        // If there's no properties, bail
        if (!schema || !schema.properties)
            return null;
        // Clone the original schema so this doesn't interfere with it
        var deprecatedBody = (0, clone_object_1["default"])(schema);
        // Booleans are not valid for required in draft 4, 7 or 2020. Not sure why the typing thinks
        // they are.
        var requiredParams = (schema.required || []);
        // Find all top-level deprecated properties from the schema - required and readOnly params are
        // excluded.
        var allDeprecatedProps = {};
        Object.keys(deprecatedBody.properties).forEach(function (key) {
            var deprecatedProp = deprecatedBody.properties[key];
            if (deprecatedProp.deprecated && !requiredParams.includes(key) && !deprecatedProp.readOnly) {
                allDeprecatedProps[key] = deprecatedProp;
            }
        });
        // We know this is the right type. todo: don't use as
        deprecatedBody.properties = allDeprecatedProps;
        var deprecatedSchema = (0, openapi_to_json_schema_1["default"])(deprecatedBody, {
            globalDefaults: opts.globalDefaults,
            prevSchemas: [],
            refLogger: refLogger,
            transformer: opts.transformer
        });
        // Check if the schema wasn't created or there's no deprecated properties
        if (Object.keys(deprecatedSchema).length === 0 || Object.keys(deprecatedSchema.properties).length === 0) {
            return null;
        }
        // Remove deprecated properties from the original schema
        // Not using the clone here becuase we WANT this to affect the original
        Object.keys(schema.properties).forEach(function (key) {
            // We know this will always be a SchemaObject
            if (schema.properties[key].deprecated && !requiredParams.includes(key)) {
                delete schema.properties[key];
            }
        });
        return {
            type: type,
            schema: (0, openapi_to_json_schema_1.isPrimitive)(deprecatedSchema)
                ? deprecatedSchema
                : __assign(__assign({}, deprecatedSchema), { $schema: (0, openapi_to_json_schema_1.getSchemaVersionString)(deprecatedSchema, api) })
        };
    }
    /**
     *
     */
    function transformRequestBody() {
        var requestBody = operation.getRequestBody();
        if (!requestBody || !Array.isArray(requestBody))
            return null;
        var mediaType = requestBody[0], mediaTypeObject = requestBody[1], description = requestBody[2];
        var type = mediaType === 'application/x-www-form-urlencoded' ? 'formData' : 'body';
        // If this schema is completely empty, don't bother processing it.
        if (!mediaTypeObject.schema || !Object.keys(mediaTypeObject.schema).length) {
            return null;
        }
        var prevSchemas = [];
        if ('example' in mediaTypeObject) {
            prevSchemas.push({ example: mediaTypeObject.example });
        }
        else if ('examples' in mediaTypeObject) {
            prevSchemas.push({
                examples: Object.values(mediaTypeObject.examples)
                    .map(function (example) { return example.value; })
                    .filter(function (val) { return val !== undefined; })
            });
        }
        // We're cloning the request schema because we've had issues with request schemas that were
        // dereferenced being processed multiple times because their component is also processed.
        var requestSchema = (0, clone_object_1["default"])(mediaTypeObject.schema);
        var cleanedSchema = (0, openapi_to_json_schema_1["default"])(requestSchema, {
            globalDefaults: opts.globalDefaults,
            prevSchemas: prevSchemas,
            refLogger: refLogger,
            transformer: opts.transformer
        });
        // If this schema is **still** empty, don't bother returning it.
        if (!Object.keys(cleanedSchema).length) {
            return null;
        }
        return __assign({ type: type, label: exports.types[type], schema: (0, openapi_to_json_schema_1.isPrimitive)(cleanedSchema)
                ? cleanedSchema
                : __assign(__assign({}, cleanedSchema), { $schema: (0, openapi_to_json_schema_1.getSchemaVersionString)(cleanedSchema, api) }), deprecatedProps: getDeprecated(cleanedSchema, type) }, (description ? { description: description } : {}));
    }
    function transformComponents() {
        if (!('components' in api)) {
            return false;
        }
        var components = {};
        Object.keys(api.components).forEach(function (componentType) {
            if (typeof api.components[componentType] === 'object' && !Array.isArray(api.components[componentType])) {
                /**
                 * Typescript is INCREDIBLY SLOW parsing this one line. I think it's because of the large
                 * variety of types that that object could represent but I can't yet think of a way to get
                 * around that.
                 *
                 * @todo
                 */
                components[componentType] = {};
                Object.keys(api.components[componentType]).forEach(function (schemaName) {
                    var componentSchema = (0, clone_object_1["default"])(api.components[componentType][schemaName]);
                    components[componentType][schemaName] = (0, openapi_to_json_schema_1["default"])(componentSchema, {
                        globalDefaults: opts.globalDefaults,
                        refLogger: refLogger,
                        transformer: opts.transformer
                    });
                });
            }
        });
        return components;
    }
    function transformParameters() {
        var operationParams = operation.getParameters();
        var transformed = Object.keys(exports.types)
            .map(function (type) {
            var required = [];
            // This `as` actually *could* be a ref, but we don't want refs to pass through here, so
            // `.in` will never match `type`
            var parameters = operationParams.filter(function (param) { return param["in"] === type; });
            if (parameters.length === 0) {
                return null;
            }
            var properties = parameters.reduce(function (prev, current) {
                var schema = {};
                if ('schema' in current) {
                    var currentSchema = current.schema ? (0, clone_object_1["default"])(current.schema) : {};
                    if (current.example) {
                        // `example` can be present outside of the `schema` block so if it's there we should
                        // pull it in so it can be handled and returned if it's valid.
                        currentSchema.example = current.example;
                    }
                    else if (current.examples) {
                        // `examples` isn't actually supported here in OAS 3.0, but we might as well support
                        // it because `examples` is JSON Schema and that's fully supported in OAS 3.1.
                        currentSchema.examples = current.examples;
                    }
                    if (current.deprecated)
                        currentSchema.deprecated = current.deprecated;
                    var interimSchema = (0, openapi_to_json_schema_1["default"])(currentSchema, {
                        currentLocation: "/".concat(current.name),
                        globalDefaults: opts.globalDefaults,
                        refLogger: refLogger,
                        transformer: opts.transformer
                    });
                    schema = (0, openapi_to_json_schema_1.isPrimitive)(interimSchema)
                        ? interimSchema
                        : __assign(__assign({}, interimSchema), { 
                            // Note: this applies a `$schema` version to each field in the larger schema
                            // object. It's not really **correct** but it's what we have to do because
                            // there's a chance that the end user has indicated the schemas are different.
                            $schema: (0, openapi_to_json_schema_1.getSchemaVersionString)(currentSchema, api) });
                }
                else if ('content' in current && typeof current.content === 'object') {
                    var contentKeys = Object.keys(current.content);
                    if (contentKeys.length) {
                        var contentType = void 0;
                        if (contentKeys.length === 1) {
                            contentType = contentKeys[0];
                        }
                        else {
                            // We should always try to prioritize `application/json` over any other possible
                            // content that might be present on this schema.
                            var jsonLikeContentTypes = contentKeys.filter(function (k) { return isJSON(k); });
                            if (jsonLikeContentTypes.length) {
                                contentType = jsonLikeContentTypes[0];
                            }
                            else {
                                contentType = contentKeys[0];
                            }
                        }
                        if (typeof current.content[contentType] === 'object' && 'schema' in current.content[contentType]) {
                            var currentSchema = current.content[contentType].schema
                                ? (0, clone_object_1["default"])(current.content[contentType].schema)
                                : {};
                            if (current.example) {
                                // `example` can be present outside of the `schema` block so if it's there we
                                // should pull it in so it can be handled and returned if it's valid.
                                currentSchema.example = current.example;
                            }
                            else if (current.examples) {
                                // `examples` isn't actually supported here in OAS 3.0, but we might as well
                                // support it because `examples` is JSON Schema and that's fully supported in OAS
                                // 3.1.
                                currentSchema.examples = current.examples;
                            }
                            if (current.deprecated)
                                currentSchema.deprecated = current.deprecated;
                            var interimSchema = (0, openapi_to_json_schema_1["default"])(currentSchema, {
                                currentLocation: "/".concat(current.name),
                                globalDefaults: opts.globalDefaults,
                                refLogger: refLogger,
                                transformer: opts.transformer
                            });
                            schema = (0, openapi_to_json_schema_1.isPrimitive)(interimSchema)
                                ? interimSchema
                                : __assign(__assign({}, interimSchema), { 
                                    // Note: this applies a `$schema` version to each field in the larger schema
                                    // object. It's not really **correct** but it's what we have to do because
                                    // there's a chance that the end user has indicated the schemas are different.
                                    $schema: (0, openapi_to_json_schema_1.getSchemaVersionString)(currentSchema, api) });
                        }
                    }
                }
                // Parameter descriptions don't exist in `current.schema` so `constructSchema` will never
                // have access to it.
                if (current.description) {
                    if (!(0, openapi_to_json_schema_1.isPrimitive)(schema)) {
                        schema.description = current.description;
                    }
                }
                prev[current.name] = schema;
                if (current.required) {
                    required.push(current.name);
                }
                return prev;
            }, {});
            // This typing is technically WRONG :( but it's the best we can do for now.
            var schema = {
                type: 'object',
                properties: properties,
                required: required
            };
            return {
                type: type,
                label: exports.types[type],
                schema: schema,
                deprecatedProps: getDeprecated(schema, type)
            };
        })
            .filter(Boolean);
        if (!opts.mergeIntoBodyAndMetadata) {
            return transformed;
        }
        else if (!transformed.length) {
            return [];
        }
        // If we want to merge parameters into a single metadata entry then we need to pull all
        // available schemas and `deprecatedProps` (if we don't want to retain them via the
        // `retainDeprecatedProps` option) under one roof.
        var deprecatedProps = transformed.map(function (r) { var _a; return ((_a = r.deprecatedProps) === null || _a === void 0 ? void 0 : _a.schema) || null; }).filter(Boolean);
        return [
            {
                type: 'metadata',
                label: exports.types.metadata,
                schema: {
                    allOf: transformed.map(function (r) { return r.schema; })
                },
                deprecatedProps: deprecatedProps.length
                    ? {
                        type: 'metadata',
                        schema: {
                            allOf: deprecatedProps
                        }
                    }
                    : null
            },
        ];
    }
    // If this operation neither has any parameters or a request body then we should return null
    // because there won't be any JSON Schema.
    if (!operation.hasParameters() && !operation.hasRequestBody()) {
        return null;
    }
    var components = transformComponents();
    var typeKeys = Object.keys(exports.types);
    return (_a = [transformRequestBody()])
        .concat.apply(_a, transformParameters()).filter(Boolean)
        .map(function (group) {
        /**
         * Since this library assumes that the schema has already been dereferenced, adding every
         * component here that **isn't** circular adds a ton of bloat so it'd be cool if `components`
         * was just the remaining `$ref` pointers that are still being referenced.
         *
         * @todo
         */
        if (components) {
            // We should only include components if we've got circular refs or we have discriminator
            // mapping refs (we want to include them).
            if (hasCircularRefs || (hasDiscriminatorMappingRefs && opts.includeDiscriminatorMappingRefs)) {
                // Fixing typing and confused version mismatches
                group.schema.components = components;
            }
        }
        // Delete deprecatedProps if it's null on the schema.
        if (!group.deprecatedProps)
            delete group.deprecatedProps;
        return group;
    })
        .sort(function (a, b) {
        return typeKeys.indexOf(a.type) - typeKeys.indexOf(b.type);
    });
}
exports["default"] = getParametersAsJSONSchema;
