"use strict";
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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParameterGenerator = void 0;
const ts = __importStar(require("typescript"));
const decoratorUtils_1 = require("./../utils/decoratorUtils");
const jsDocUtils_1 = require("./../utils/jsDocUtils");
const validatorUtils_1 = require("./../utils/validatorUtils");
const exceptions_1 = require("./exceptions");
const initializer_value_1 = require("./initializer-value");
const typeResolver_1 = require("./typeResolver");
const headerTypeHelpers_1 = require("../utils/headerTypeHelpers");
class ParameterGenerator {
    constructor(parameter, method, path, current) {
        this.parameter = parameter;
        this.method = method;
        this.path = path;
        this.current = current;
    }
    Generate() {
        const decoratorName = (0, decoratorUtils_1.getNodeFirstDecoratorName)(this.parameter, identifier => this.supportParameterDecorator(identifier.text));
        switch (decoratorName) {
            case 'Request':
                return [this.getRequestParameter(this.parameter)];
            case 'RequestProp':
                return [this.getRequestPropParameter(this.parameter)];
            case 'Body':
                return [this.getBodyParameter(this.parameter)];
            case 'BodyProp':
                return [this.getBodyPropParameter(this.parameter)];
            case 'FormField':
                return [this.getFormFieldParameter(this.parameter)];
            case 'Header':
                return [this.getHeaderParameter(this.parameter)];
            case 'Query':
                return this.getQueryParameters(this.parameter);
            case 'Queries':
                return [this.getQueriesParameters(this.parameter)];
            case 'Path':
                return [this.getPathParameter(this.parameter)];
            case 'Res':
                return this.getResParameters(this.parameter);
            case 'Inject':
                return [];
            case 'UploadedFile':
                return [this.getUploadedFileParameter(this.parameter)];
            case 'UploadedFiles':
                return [this.getUploadedFileParameter(this.parameter, true)];
            default:
                return [this.getPathParameter(this.parameter)];
        }
    }
    getRequestParameter(parameter) {
        const parameterName = parameter.name.text;
        return {
            description: this.getParameterDescription(parameter),
            in: 'request',
            name: parameterName,
            parameterName,
            required: !parameter.questionToken && !parameter.initializer,
            type: { dataType: 'object' },
            validators: (0, validatorUtils_1.getParameterValidators)(this.parameter, parameterName),
            deprecated: this.getParameterDeprecation(parameter),
        };
    }
    getRequestPropParameter(parameter) {
        const parameterName = parameter.name.text;
        const type = this.getValidatedType(parameter);
        const { examples: example, exampleLabels } = this.getParameterExample(parameter, parameterName);
        return {
            default: (0, initializer_value_1.getInitializerValue)(parameter.initializer, this.current.typeChecker, type),
            description: this.getParameterDescription(parameter),
            example,
            exampleLabels,
            in: 'request-prop',
            name: (0, decoratorUtils_1.getNodeFirstDecoratorValue)(this.parameter, this.current.typeChecker, ident => ident.text === 'ParameterProp') || parameterName,
            parameterName,
            required: !parameter.questionToken && !parameter.initializer,
            type,
            validators: (0, validatorUtils_1.getParameterValidators)(this.parameter, parameterName),
            deprecated: this.getParameterDeprecation(parameter),
        };
    }
    extractTsoaResponse(typeNode) {
        if (!typeNode || !ts.isTypeReferenceNode(typeNode)) {
            return undefined;
        }
        if (typeNode.typeName.getText() === 'TsoaResponse') {
            return typeNode;
        }
        const symbol = this.current.typeChecker.getTypeAtLocation(typeNode).aliasSymbol;
        if (!symbol || !symbol.declarations) {
            return undefined;
        }
        const declaration = symbol.declarations[0];
        if (!ts.isTypeAliasDeclaration(declaration) || !ts.isTypeReferenceNode(declaration.type)) {
            return undefined;
        }
        return declaration.type.typeName.getText() === 'TsoaResponse' ? declaration.type : undefined;
    }
    getResParameters(parameter) {
        const parameterName = parameter.name.text;
        const decorator = (0, decoratorUtils_1.getNodeFirstDecoratorValue)(this.parameter, this.current.typeChecker, ident => ident.text === 'Res') || parameterName;
        if (!decorator) {
            throw new exceptions_1.GenerateMetadataError('Could not find Decorator', parameter);
        }
        const typeNode = this.extractTsoaResponse(parameter.type);
        if (!typeNode) {
            throw new exceptions_1.GenerateMetadataError('@Res() requires the type to be TsoaResponse<HTTPStatusCode, ResBody>', parameter);
        }
        if (!typeNode.typeArguments || !typeNode.typeArguments[0]) {
            throw new exceptions_1.GenerateMetadataError('@Res() requires the type to be TsoaResponse<HTTPStatusCode, ResBody>', parameter);
        }
        const statusArgument = typeNode.typeArguments[0];
        const bodyArgument = typeNode.typeArguments[1];
        // support a union of status codes, all with the same response body
        const statusArguments = ts.isUnionTypeNode(statusArgument) ? [...statusArgument.types] : [statusArgument];
        const statusArgumentTypes = statusArguments.map(a => this.current.typeChecker.getTypeAtLocation(a));
        const isNumberLiteralType = (tsType) => {
            // eslint-disable-next-line no-bitwise
            return (tsType.getFlags() & ts.TypeFlags.NumberLiteral) !== 0;
        };
        const headers = (0, headerTypeHelpers_1.getHeaderType)(typeNode.typeArguments, 2, this.current);
        return statusArgumentTypes.map(statusArgumentType => {
            if (!isNumberLiteralType(statusArgumentType)) {
                throw new exceptions_1.GenerateMetadataError('@Res() requires the type to be TsoaResponse<HTTPStatusCode, ResBody>', parameter);
            }
            const status = String(statusArgumentType.value);
            const type = new typeResolver_1.TypeResolver(bodyArgument, this.current, typeNode).resolve();
            const { examples, exampleLabels } = this.getParameterExample(parameter, parameterName);
            return {
                description: this.getParameterDescription(parameter) || '',
                in: 'res',
                name: status,
                produces: headers ? this.getProducesFromResHeaders(headers) : undefined,
                parameterName,
                examples,
                required: true,
                type,
                exampleLabels,
                schema: type,
                validators: {},
                headers,
                deprecated: this.getParameterDeprecation(parameter),
            };
        });
    }
    getProducesFromResHeaders(headers) {
        const { properties } = headers;
        const [contentTypeProp] = (properties || []).filter(p => p.name.toLowerCase() === 'content-type' && p.type.dataType === 'enum');
        if (contentTypeProp) {
            const type = contentTypeProp.type;
            return type.enums;
        }
        return;
    }
    getBodyPropParameter(parameter) {
        const parameterName = parameter.name.text;
        const type = this.getValidatedType(parameter);
        if (!this.supportBodyMethod(this.method)) {
            throw new exceptions_1.GenerateMetadataError(`@BodyProp('${parameterName}') Can't support in ${this.method.toUpperCase()} method.`);
        }
        const { examples: example, exampleLabels } = this.getParameterExample(parameter, parameterName);
        return {
            default: (0, initializer_value_1.getInitializerValue)(parameter.initializer, this.current.typeChecker, type),
            description: this.getParameterDescription(parameter),
            example,
            exampleLabels,
            in: 'body-prop',
            name: (0, decoratorUtils_1.getNodeFirstDecoratorValue)(this.parameter, this.current.typeChecker, ident => ident.text === 'BodyProp') || parameterName,
            parameterName,
            required: !parameter.questionToken && !parameter.initializer,
            type,
            validators: (0, validatorUtils_1.getParameterValidators)(this.parameter, parameterName),
            deprecated: this.getParameterDeprecation(parameter),
        };
    }
    getBodyParameter(parameter) {
        const parameterName = parameter.name.text;
        const type = this.getValidatedType(parameter);
        if (!this.supportBodyMethod(this.method)) {
            throw new exceptions_1.GenerateMetadataError(`@Body('${parameterName}') Can't support in ${this.method.toUpperCase()} method.`);
        }
        const { examples: example, exampleLabels } = this.getParameterExample(parameter, parameterName);
        return {
            description: this.getParameterDescription(parameter),
            in: 'body',
            name: parameterName,
            example,
            exampleLabels,
            parameterName,
            required: !parameter.questionToken && !parameter.initializer,
            type,
            validators: (0, validatorUtils_1.getParameterValidators)(this.parameter, parameterName),
            deprecated: this.getParameterDeprecation(parameter),
        };
    }
    getHeaderParameter(parameter) {
        const parameterName = parameter.name.text;
        const type = this.getValidatedType(parameter);
        if (!this.supportPathDataType(type)) {
            throw new exceptions_1.GenerateMetadataError(`@Header('${parameterName}') Can't support '${type.dataType}' type.`);
        }
        const { examples: example, exampleLabels } = this.getParameterExample(parameter, parameterName);
        return {
            default: (0, initializer_value_1.getInitializerValue)(parameter.initializer, this.current.typeChecker, type),
            description: this.getParameterDescription(parameter),
            example,
            exampleLabels,
            in: 'header',
            name: (0, decoratorUtils_1.getNodeFirstDecoratorValue)(this.parameter, this.current.typeChecker, ident => ident.text === 'Header') || parameterName,
            parameterName,
            required: !parameter.questionToken && !parameter.initializer,
            type,
            validators: (0, validatorUtils_1.getParameterValidators)(this.parameter, parameterName),
            deprecated: this.getParameterDeprecation(parameter),
        };
    }
    getUploadedFileParameter(parameter, isArray) {
        const parameterName = parameter.name.text;
        const elementType = { dataType: 'file' };
        let type;
        if (isArray) {
            type = { dataType: 'array', elementType };
        }
        else {
            type = elementType;
        }
        if (!this.supportPathDataType(elementType)) {
            throw new exceptions_1.GenerateMetadataError(`Parameter '${parameterName}:${type.dataType}' can't be passed as an uploaded file(s) parameter in '${this.method.toUpperCase()}'.`, parameter);
        }
        return {
            description: this.getParameterDescription(parameter),
            in: 'formData',
            name: (0, decoratorUtils_1.getNodeFirstDecoratorValue)(this.parameter, this.current.typeChecker, ident => {
                if (isArray) {
                    return ident.text === 'UploadedFiles';
                }
                return ident.text === 'UploadedFile';
            }) ?? parameterName,
            required: !parameter.questionToken && !parameter.initializer,
            type,
            parameterName,
            validators: (0, validatorUtils_1.getParameterValidators)(this.parameter, parameterName),
            deprecated: this.getParameterDeprecation(parameter),
        };
    }
    getFormFieldParameter(parameter) {
        const parameterName = parameter.name.text;
        const type = this.getValidatedType(parameter);
        if (!this.supportPathDataType(type)) {
            throw new exceptions_1.GenerateMetadataError(`Parameter '${parameterName}:${type.dataType}' can't be passed as form field parameter in '${this.method.toUpperCase()}'.`, parameter);
        }
        return {
            description: this.getParameterDescription(parameter),
            in: 'formData',
            name: (0, decoratorUtils_1.getNodeFirstDecoratorValue)(this.parameter, this.current.typeChecker, ident => ident.text === 'FormField') ?? parameterName,
            required: !parameter.questionToken && !parameter.initializer,
            type,
            parameterName,
            validators: (0, validatorUtils_1.getParameterValidators)(this.parameter, parameterName),
            deprecated: this.getParameterDeprecation(parameter),
        };
    }
    getQueriesParameters(parameter) {
        const parameterName = parameter.name.text;
        const type = this.getValidatedType(parameter);
        // Handle cases where TypeResolver doesn't properly resolve complex types
        // like Zod's z.infer types to refObject or nestedObjectLiteral
        if (type.dataType !== 'refObject' && type.dataType !== 'nestedObjectLiteral') {
            // Try to resolve the type more aggressively for complex types
            let typeNode = parameter.type;
            if (!typeNode) {
                const typeFromChecker = this.current.typeChecker.getTypeAtLocation(parameter);
                typeNode = this.current.typeChecker.typeToTypeNode(typeFromChecker, undefined, ts.NodeBuilderFlags.NoTruncation);
            }
            // If it's a TypeReferenceNode (like z.infer), try to resolve it differently
            if (ts.isTypeReferenceNode(typeNode)) {
                try {
                    // Try to get the actual type from the type checker
                    const actualType = this.current.typeChecker.getTypeAtLocation(typeNode);
                    const typeNodeFromType = this.current.typeChecker.typeToTypeNode(actualType, undefined, ts.NodeBuilderFlags.NoTruncation);
                    const resolvedType = new typeResolver_1.TypeResolver(typeNodeFromType, this.current, parameter).resolve();
                    // Check if the resolved type is now acceptable
                    if (resolvedType.dataType === 'refObject' || resolvedType.dataType === 'nestedObjectLiteral') {
                        // Use the resolved type instead
                        for (const property of resolvedType.properties) {
                            this.validateQueriesProperties(property, parameterName);
                        }
                        const { examples: example, exampleLabels } = this.getParameterExample(parameter, parameterName);
                        return {
                            description: this.getParameterDescription(parameter),
                            in: 'queries',
                            name: parameterName,
                            example,
                            exampleLabels,
                            parameterName,
                            required: !parameter.questionToken && !parameter.initializer,
                            type: resolvedType,
                            validators: (0, validatorUtils_1.getParameterValidators)(this.parameter, parameterName),
                            deprecated: this.getParameterDeprecation(parameter),
                        };
                    }
                }
                catch (error) {
                    // If resolution fails, log the error for debugging but continue with the original error
                    // This helps developers understand why the type resolution failed
                    console.warn(`Failed to resolve complex type for @Queries('${parameterName}'):`, error);
                    // Continue with the original error below
                }
            }
            throw new exceptions_1.GenerateMetadataError(`@Queries('${parameterName}') only support 'refObject' or 'nestedObjectLiteral' types. If you want only one query parameter, please use the '@Query' decorator.`);
        }
        for (const property of type.properties) {
            this.validateQueriesProperties(property, parameterName);
        }
        const { examples: example, exampleLabels } = this.getParameterExample(parameter, parameterName);
        return {
            description: this.getParameterDescription(parameter),
            in: 'queries',
            name: parameterName,
            example,
            exampleLabels,
            parameterName,
            required: !parameter.questionToken && !parameter.initializer,
            type,
            validators: (0, validatorUtils_1.getParameterValidators)(this.parameter, parameterName),
            deprecated: this.getParameterDeprecation(parameter),
        };
    }
    validateQueriesProperties(property, parentName) {
        if (property.type.dataType === 'array') {
            const arrayType = property.type;
            if (arrayType.elementType.dataType === 'nestedObjectLiteral') {
                // For arrays of nestedObjectLiteral, validate each property recursively
                const nestedType = arrayType.elementType;
                if (nestedType.properties) {
                    for (const nestedProperty of nestedType.properties) {
                        this.validateQueriesProperties(nestedProperty, `${parentName}.${property.name}[]`);
                    }
                }
            }
            else if (!this.supportPathDataType(arrayType.elementType)) {
                throw new exceptions_1.GenerateMetadataError(`@Queries('${parentName}') property '${property.name}' can't support array '${arrayType.elementType.dataType}' type.`);
            }
        }
        else if (property.type.dataType === 'nestedObjectLiteral') {
            // For nestedObjectLiteral, validate each property recursively
            const nestedType = property.type;
            if (nestedType.properties) {
                for (const nestedProperty of nestedType.properties) {
                    this.validateQueriesProperties(nestedProperty, `${parentName}.${property.name}`);
                }
            }
        }
        else if (!this.supportPathDataType(property.type)) {
            throw new exceptions_1.GenerateMetadataError(`@Queries('${parentName}') nested property '${property.name}' Can't support '${property.type.dataType}' type.`);
        }
    }
    getQueryParameters(parameter) {
        const parameterName = parameter.name.text;
        const type = this.getValidatedType(parameter);
        const { examples: example, exampleLabels } = this.getParameterExample(parameter, parameterName);
        const commonProperties = {
            default: (0, initializer_value_1.getInitializerValue)(parameter.initializer, this.current.typeChecker, type),
            description: this.getParameterDescription(parameter),
            example,
            exampleLabels,
            in: 'query',
            name: (0, decoratorUtils_1.getNodeFirstDecoratorValue)(this.parameter, this.current.typeChecker, ident => ident.text === 'Query') || parameterName,
            parameterName,
            required: !parameter.questionToken && !parameter.initializer,
            validators: (0, validatorUtils_1.getParameterValidators)(this.parameter, parameterName),
            deprecated: this.getParameterDeprecation(parameter),
        };
        if (this.getQueryParameterIsHidden(parameter)) {
            if (commonProperties.required) {
                throw new exceptions_1.GenerateMetadataError(`@Query('${parameterName}') Can't support @Hidden because it is required (does not allow undefined and does not have a default value).`);
            }
            return [];
        }
        if (type.dataType === 'array') {
            const arrayType = type;
            if (!this.supportPathDataType(arrayType.elementType)) {
                throw new exceptions_1.GenerateMetadataError(`@Query('${parameterName}') Can't support array '${arrayType.elementType.dataType}' type.`);
            }
            return [
                {
                    ...commonProperties,
                    collectionFormat: 'multi',
                    type: arrayType,
                },
            ];
        }
        if (!this.supportPathDataType(type)) {
            throw new exceptions_1.GenerateMetadataError(`@Query('${parameterName}') Can't support '${type.dataType}' type.`);
        }
        return [
            {
                ...commonProperties,
                type,
            },
        ];
    }
    getPathParameter(parameter) {
        const parameterName = parameter.name.text;
        const type = this.getValidatedType(parameter);
        const pathName = String((0, decoratorUtils_1.getNodeFirstDecoratorValue)(this.parameter, this.current.typeChecker, ident => ident.text === 'Path') || parameterName);
        if (!this.supportPathDataType(type)) {
            throw new exceptions_1.GenerateMetadataError(`@Path('${parameterName}') Can't support '${type.dataType}' type.`);
        }
        if (!this.path.includes(`{${pathName}}`) && !this.path.includes(`:${pathName}`)) {
            throw new exceptions_1.GenerateMetadataError(`@Path('${parameterName}') Can't match in URL: '${this.path}'.`);
        }
        const { examples, exampleLabels } = this.getParameterExample(parameter, parameterName);
        return {
            default: (0, initializer_value_1.getInitializerValue)(parameter.initializer, this.current.typeChecker, type),
            description: this.getParameterDescription(parameter),
            example: examples,
            exampleLabels,
            in: 'path',
            name: pathName,
            parameterName,
            required: true,
            type,
            validators: (0, validatorUtils_1.getParameterValidators)(this.parameter, parameterName),
            deprecated: this.getParameterDeprecation(parameter),
        };
    }
    getParameterDescription(node) {
        const symbol = this.current.typeChecker.getSymbolAtLocation(node.name);
        if (!symbol) {
            return undefined;
        }
        const comments = symbol.getDocumentationComment(this.current.typeChecker);
        if (comments.length) {
            return ts.displayPartsToString(comments);
        }
        return undefined;
    }
    getParameterDeprecation(node) {
        return (0, jsDocUtils_1.isExistJSDocTag)(node, tag => tag.tagName.text === 'deprecated') || (0, decoratorUtils_1.isDecorator)(node, identifier => identifier.text === 'Deprecated');
    }
    getParameterExample(node, parameterName) {
        const exampleLabels = [];
        const examples = (0, jsDocUtils_1.getJSDocTags)(node.parent, tag => {
            const comment = (0, jsDocUtils_1.commentToString)(tag.comment);
            const isExample = (tag.tagName.text === 'example' || tag.tagName.escapedText === 'example') && !!tag.comment && comment?.startsWith(parameterName);
            if (isExample) {
                const hasExampleLabel = (comment?.split(' ')[0].indexOf('.') || -1) > 0;
                // custom example label is delimited by first '.' and the rest will all be included as example label
                exampleLabels.push(hasExampleLabel ? comment?.split(' ')[0].split('.').slice(1).join('.') : undefined);
            }
            return isExample ?? false;
        }).map(tag => ((0, jsDocUtils_1.commentToString)(tag.comment) || '').replace(`${(0, jsDocUtils_1.commentToString)(tag.comment)?.split(' ')[0] || ''}`, '').replace(/\r/g, ''));
        if (examples.length === 0) {
            return {
                examples: undefined,
                exampleLabels: undefined,
            };
        }
        else {
            try {
                return {
                    examples: examples.map(example => JSON.parse(example)),
                    exampleLabels,
                };
            }
            catch (e) {
                const message = e instanceof Error ? e.message : String(e);
                throw new exceptions_1.GenerateMetadataError(`JSON format is incorrect: ${message}`);
            }
        }
    }
    supportBodyMethod(method) {
        return ['post', 'put', 'patch', 'delete'].some(m => m === method.toLowerCase());
    }
    supportParameterDecorator(decoratorName) {
        return ['header', 'query', 'queries', 'path', 'body', 'bodyprop', 'request', 'requestprop', 'res', 'inject', 'uploadedfile', 'uploadedfiles', 'formfield'].some(d => d === decoratorName.toLocaleLowerCase());
    }
    supportPathDataType(parameterType) {
        const supportedPathDataTypes = ['string', 'integer', 'long', 'float', 'double', 'date', 'datetime', 'buffer', 'boolean', 'enum', 'refEnum', 'file', 'any'];
        if (supportedPathDataTypes.find(t => t === parameterType.dataType)) {
            return true;
        }
        if (parameterType.dataType === 'refAlias') {
            return this.supportPathDataType(parameterType.type);
        }
        if (parameterType.dataType === 'union') {
            // skip undefined inside unions
            return !parameterType.types.map(t => t.dataType === 'undefined' || this.supportPathDataType(t)).some(t => t === false);
        }
        return false;
    }
    getValidatedType(parameter) {
        let typeNode = parameter.type;
        if (!typeNode) {
            const type = this.current.typeChecker.getTypeAtLocation(parameter);
            typeNode = this.current.typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.NoTruncation);
        }
        return new typeResolver_1.TypeResolver(typeNode, this.current, parameter).resolve();
    }
    getQueryParameterIsHidden(parameter) {
        const hiddenDecorators = (0, decoratorUtils_1.getDecorators)(parameter, identifier => identifier.text === 'Hidden');
        if (!hiddenDecorators || !hiddenDecorators.length) {
            return false;
        }
        if (hiddenDecorators.length > 1) {
            const parameterName = parameter.name.text;
            throw new exceptions_1.GenerateMetadataError(`Only one Hidden decorator allowed on @Query('${parameterName}').`);
        }
        return true;
    }
}
exports.ParameterGenerator = ParameterGenerator;
//# sourceMappingURL=parameterGenerator.js.map