"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.AbstractRouteGenerator = void 0;
const path = __importStar(require("path"));
const runtime_1 = require("@tsoa/runtime");
const internalTypeGuards_1 = require("../utils/internalTypeGuards");
const pathUtils_1 = require("../utils/pathUtils");
const fs_1 = require("../utils/fs");
class AbstractRouteGenerator {
    constructor(metadata, options) {
        this.metadata = metadata;
        this.options = options;
    }
    buildModels() {
        const models = {};
        Object.keys(this.metadata.referenceTypeMap).forEach(name => {
            const referenceType = this.metadata.referenceTypeMap[name];
            let model;
            if (referenceType.dataType === 'refEnum') {
                const refEnumModel = {
                    dataType: 'refEnum',
                    enums: referenceType.enums,
                };
                model = refEnumModel;
            }
            else if (referenceType.dataType === 'refObject') {
                const propertySchemaDictionary = {};
                referenceType.properties.forEach(property => {
                    propertySchemaDictionary[property.name] = this.buildPropertySchema(property);
                });
                const refObjModel = {
                    dataType: 'refObject',
                    properties: propertySchemaDictionary,
                };
                if (referenceType.additionalProperties) {
                    refObjModel.additionalProperties = this.buildProperty(referenceType.additionalProperties);
                }
                else if (this.options.noImplicitAdditionalProperties !== 'ignore') {
                    refObjModel.additionalProperties = false;
                }
                else {
                    // Since Swagger allows "excess properties" (to use a TypeScript term) by default
                    refObjModel.additionalProperties = true;
                }
                model = refObjModel;
            }
            else if (referenceType.dataType === 'refAlias') {
                const refType = {
                    dataType: 'refAlias',
                    type: {
                        ...this.buildProperty(referenceType.type),
                        validators: referenceType.validators,
                        default: referenceType.default,
                    },
                };
                model = refType;
            }
            else {
                model = (0, runtime_1.assertNever)(referenceType);
            }
            models[name] = model;
        });
        return models;
    }
    pathTransformer(path) {
        return (0, pathUtils_1.convertBracesPathParams)(path);
    }
    buildContext() {
        const authenticationModule = this.options.authenticationModule ? this.getRelativeImportPath(this.options.authenticationModule) : undefined;
        const iocModule = this.options.iocModule ? this.getRelativeImportPath(this.options.iocModule) : undefined;
        // Left in for backwards compatibility, previously if we're working locally then tsoa runtime code wasn't an importable module but now it is.
        const canImportByAlias = true;
        const normalisedBasePath = (0, pathUtils_1.normalisePath)(this.options.basePath, '/');
        return {
            authenticationModule,
            basePath: normalisedBasePath,
            canImportByAlias,
            controllers: this.metadata.controllers.map(controller => {
                const normalisedControllerPath = this.pathTransformer((0, pathUtils_1.normalisePath)(controller.path, '/'));
                return {
                    actions: controller.methods.map(method => {
                        const parameterObjs = {};
                        method.parameters.forEach(parameter => {
                            parameterObjs[parameter.parameterName] = this.buildParameterSchema(parameter);
                        });
                        const normalisedMethodPath = this.pathTransformer((0, pathUtils_1.normalisePath)(method.path, '/'));
                        const normalisedFullPath = (0, pathUtils_1.normalisePath)(`${normalisedBasePath}${normalisedControllerPath}${normalisedMethodPath}`, '/', '', false);
                        const uploadFilesWithDifferentFieldParameter = method.parameters.filter(parameter => parameter.type.dataType === 'file' || (parameter.type.dataType === 'array' && parameter.type.elementType.dataType === 'file'));
                        return {
                            fullPath: normalisedFullPath,
                            method: method.method.toLowerCase(),
                            name: method.name,
                            parameters: parameterObjs,
                            path: normalisedMethodPath,
                            uploadFile: uploadFilesWithDifferentFieldParameter.length > 0,
                            uploadFileName: uploadFilesWithDifferentFieldParameter.map(parameter => ({
                                name: parameter.name,
                                maxCount: parameter.type.dataType === 'file' ? 1 : undefined,
                                multiple: parameter.type.dataType === 'array' && parameter.type.elementType.dataType === 'file',
                            })),
                            security: method.security,
                            successStatus: method.successStatus ? method.successStatus : 'undefined',
                        };
                    }),
                    modulePath: this.getRelativeImportPath(controller.location),
                    name: controller.name,
                    path: normalisedControllerPath,
                };
            }),
            environment: process.env,
            iocModule,
            minimalSwaggerConfig: { noImplicitAdditionalProperties: this.options.noImplicitAdditionalProperties, bodyCoercion: this.options.bodyCoercion },
            models: this.buildModels(),
            useFileUploads: this.metadata.controllers.some(controller => controller.methods.some(method => !!method.parameters.find(parameter => {
                if (parameter.type.dataType === 'file') {
                    return true;
                }
                else if (parameter.type.dataType === 'array' && parameter.type.elementType.dataType === 'file') {
                    return true;
                }
                return false;
            }))),
            multerOpts: {
                limits: {
                    fileSize: 8388608, // 8mb
                },
                ...this.options.multerOpts,
            },
            useSecurity: this.metadata.controllers.some(controller => controller.methods.some(method => !!method.security.length)),
            esm: this.options.esm,
        };
    }
    getRelativeImportPath(fileLocation) {
        const currentExt = path.extname(fileLocation);
        let newExtension = this.options.rewriteRelativeImportExtensions ? currentExt : '';
        if (this.options.esm && !this.options.rewriteRelativeImportExtensions) {
            switch (currentExt) {
                case '.ts':
                default:
                    newExtension = '.js';
                    break;
                case '.mts':
                    newExtension = '.mjs';
                    break;
                case '.cts':
                    newExtension = '.cjs';
                    break;
            }
        }
        fileLocation = fileLocation.replace(/\.(ts|mts|cts)$/, ''); // no ts extension in import
        return `./${path.relative(this.options.routesDir, fileLocation).replace(/\\/g, '/')}${newExtension}`;
    }
    buildPropertySchema(source) {
        const propertySchema = this.buildProperty(source.type);
        propertySchema.default = source.default;
        propertySchema.required = source.required ? true : undefined;
        if (Object.keys(source.validators).length > 0) {
            propertySchema.validators = source.validators;
        }
        return propertySchema;
    }
    buildParameterSchema(source) {
        const property = this.buildProperty(source.type);
        const parameter = {
            default: source.default,
            in: source.in,
            name: source.name,
            required: source.required ? true : undefined,
        };
        const parameterSchema = Object.assign(parameter, property);
        if (Object.keys(source.validators).length > 0) {
            parameterSchema.validators = source.validators;
        }
        return parameterSchema;
    }
    buildProperty(type) {
        const schema = {
            dataType: type.dataType,
        };
        if ((0, internalTypeGuards_1.isRefType)(type)) {
            schema.dataType = undefined;
            schema.ref = type.refName;
        }
        if (type.dataType === 'array') {
            const arrayType = type;
            if ((0, internalTypeGuards_1.isRefType)(arrayType.elementType)) {
                schema.array = {
                    dataType: arrayType.elementType.dataType,
                    ref: arrayType.elementType.refName,
                };
            }
            else {
                schema.array = this.buildProperty(arrayType.elementType);
            }
        }
        if (type.dataType === 'enum') {
            schema.enums = type.enums;
        }
        if (type.dataType === 'union' || type.dataType === 'intersection') {
            schema.subSchemas = type.types.map(type => this.buildProperty(type));
        }
        if (type.dataType === 'nestedObjectLiteral') {
            const objLiteral = type;
            schema.nestedProperties = objLiteral.properties.reduce((acc, prop) => {
                return { ...acc, [prop.name]: this.buildPropertySchema(prop) };
            }, {});
            schema.additionalProperties = objLiteral.additionalProperties && this.buildProperty(objLiteral.additionalProperties);
        }
        return schema;
    }
    async shouldWriteFile(fileName, content) {
        if (this.options.noWriteIfUnchanged) {
            if (await (0, fs_1.fsExists)(fileName)) {
                const existingContent = (await (0, fs_1.fsReadFile)(fileName)).toString();
                return content !== existingContent;
            }
        }
        return true;
    }
}
exports.AbstractRouteGenerator = AbstractRouteGenerator;
//# sourceMappingURL=routeGenerator.js.map