"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "SwaggerGenerator", {
    enumerable: true,
    get: function() {
        return SwaggerGenerator;
    }
});
const _decorators = require("./decorators");
const _awilixexpress = require("awilix-express");
class SwaggerGenerator {
    logger;
    constructor(logger){
        this.logger = logger;
    }
    openApiDoc = {
        openapi: "3.1.0",
        info: {
            title: "FDM Monster API",
            version: process.env.npm_package_version || "2.0.0",
            description: "FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.",
            license: {
                name: "AGPL-3.0-or-later",
                url: "https://www.gnu.org/licenses/agpl-3.0.en.html"
            },
            contact: {
                name: "FDM Monster GitHub",
                url: "https://github.com/fdm-monster/fdm-monster"
            }
        },
        servers: [
            {
                url: "/api",
                description: "API Server"
            }
        ],
        paths: {},
        components: {
            schemas: {},
            securitySchemes: {
                bearerAuth: {
                    type: "http",
                    scheme: "bearer",
                    bearerFormat: "JWT",
                    description: "Enter your JWT token"
                }
            }
        },
        security: [
            {
                bearerAuth: []
            }
        ]
    };
    async generate() {
        try {
            const routePath = "../../controllers";
            const discoveredControllers = (0, _awilixexpress.findControllers)(`${routePath}/*.controller.js`, {
                cwd: __dirname,
                ignore: [
                    "**/*.map",
                    "**/*.d.ts"
                ],
                absolute: true
            });
            for (const registration of discoveredControllers){
                await this.processController(registration);
            }
            this.logger.log(`Generated OpenAPI spec with ${Object.keys(this.openApiDoc.paths || {}).length} paths`);
        } catch (error) {
            this.logger.error("Failed to generate swagger specification", error);
        }
        return this.openApiDoc;
    }
    async processController(prototype) {
        for (const [methodName, methodConfig] of prototype.state.methods){
            if (methodConfig.paths.length > 0) {
                await this.processMethod(prototype, prototype.state.root, methodName, methodConfig);
            }
        }
    }
    async processMethod(controller, root, methodName, methodConfig) {
        if (!methodName) return;
        const method = methodName.toString();
        const name = method.toLowerCase();
        for (let methodPath of methodConfig.paths){
            methodPath = root.paths[0] + methodPath;
            methodPath = methodPath.replaceAll(/:([a-zA-Z0-9_]+)/g, "{$1}");
            const metadata = Reflect.getMetadata(_decorators.API_METADATA_KEY, controller.target);
            for (const verb of methodConfig.verbs){
                const key = `${name}:operation`;
                const description = metadata?.hasOwnProperty(key) ? metadata[key] : null;
                const httpMethod = verb.toLowerCase();
                const operationObject = {
                    tags: [
                        controller.target.name
                    ],
                    summary: description?.summary ?? method,
                    description: description?.description ?? "",
                    responses: description?.responses ?? {
                        "200": {
                            description: "Successful response"
                        }
                    }
                };
                operationObject.parameters = this.extractPathParameters(methodPath);
                const paramTypes = Reflect.getMetadata("design:paramtypes", controller.target, name);
                const returnType = Reflect.getMetadata("design:returntype", controller.target, name);
                if (paramTypes) {
                    const additionalParams = this.processParameterTypes(paramTypes);
                    operationObject.parameters = [
                        ...operationObject.parameters,
                        ...additionalParams
                    ];
                }
                if (returnType && operationObject.responses?.["200"]) {
                    operationObject.responses["200"].content = {
                        "application/json": {
                            schema: this.processReturnType(returnType)
                        }
                    };
                }
                const operation = {
                    [httpMethod]: operationObject
                };
                this.openApiDoc.paths ??= {};
                this.openApiDoc.paths[methodPath] = {
                    ...this.openApiDoc.paths[methodPath] || {},
                    ...operation
                };
            }
        }
    }
    extractPathParameters(path) {
        const paramRegex = /\{([a-zA-Z0-9_]+)\}/g;
        const params = [];
        let match;
        while((match = paramRegex.exec(path)) !== null){
            const paramName = match[1];
            params.push({
                name: paramName,
                in: "path",
                required: true,
                schema: {
                    type: "string"
                },
                description: `The ${paramName} parameter`
            });
        }
        return params;
    }
    processParameterTypes(types) {
        return types.map((type)=>this.createParameterDefinition(type));
    }
    processReturnType(type) {
        return this.createSchemaDefinition(type);
    }
    createSchemaDefinition(type) {
        return {
            type: "object",
            properties: this.getTypeProperties(type)
        };
    }
    createParameterDefinition(type) {
        return {
            in: "body",
            schema: this.createSchemaDefinition(type)
        };
    }
    getTypeProperties(type) {
        const properties = {};
        const metadata = Reflect.getMetadata(_decorators.API_METADATA_KEY, type) || {};
        Object.keys(metadata).forEach((key)=>{
            properties[key] = {
                type: this.getPropertyType(metadata[key].type),
                description: metadata[key].description,
                example: metadata[key].example
            };
        });
        return properties;
    }
    getPropertyType(type) {
        const typeMap = {
            String: "string",
            Number: "number",
            Boolean: "boolean",
            Object: "object",
            Array: "array"
        };
        return typeMap[type?.name] || "string";
    }
}

//# sourceMappingURL=generator.js.map