"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;
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HotGenerator = void 0;
const ppath = __importStar(require("path"));
const yaml_1 = __importDefault(require("yaml"));
const HotIO_1 = require("./HotIO");
const HotHTTPServer_1 = require("./HotHTTPServer");
const HotRouteMethod_1 = require("./HotRouteMethod");
const HotServer_1 = require("./HotServer");
/**
 * Generates stuff like API
 */
class HotGenerator {
    constructor(logger) {
        this.hotsites = [];
        this.generateType = "javascript";
        this.compileTS = true;
        this.tsconfigPath = ppath.normalize(`${__dirname}/../../tsconfig-generator.json`);
        this.optimizeJS = false;
        this.skipRoutes = [];
        this.logger = logger;
        this.outputDir = ppath.normalize(`${process.cwd()}/build-web/`);
        this.copyTo = "";
        this.exitOnComplete = true;
        this.routeExtends = "";
    }
    /**
     * Get the base url from the HotSite.
     */
    getBaseUrlFromHotSite(processor, apiServer, loadedAPI, baseAPIUrl = "") {
        if (baseAPIUrl === "")
            baseAPIUrl = `http://127.0.0.1:${apiServer.ports.http}`;
        let foundAPIUrl = baseAPIUrl;
        // Attempt to find the base url from the HotSite's API.
        if (processor.hotSite != null) {
            if (processor.hotSite.apis != null) {
                for (let key in processor.hotSite.apis) {
                    let tempAPI = processor.hotSite.apis[key];
                    if (tempAPI.apiName != null) {
                        if (tempAPI.apiName === loadedAPI.exportedClassName) {
                            if (tempAPI.url != null)
                                foundAPIUrl = tempAPI.url;
                            break;
                        }
                    }
                }
            }
        }
        return (foundAPIUrl);
    }
    /**
     * Start the API server.
     */
    startAPIServer(processor, loadedAPI) {
        return __awaiter(this, void 0, void 0, function* () {
            let server = new HotHTTPServer_1.HotHTTPServer(processor);
            let baseAPIUrl = this.getBaseUrlFromHotSite(processor, server, loadedAPI);
            server.type = HotServer_1.HotServerType.Generate;
            let foundModulePath = require.resolve(loadedAPI.path, { paths: [process.cwd()] });
            let apiJS = require(foundModulePath);
            let apiClass = apiJS[loadedAPI.exportedClassName];
            let api = new apiClass(baseAPIUrl, server);
            server.logger.verbose(`Loaded API class: ${loadedAPI.exportedClassName}`);
            server.processor.api = api;
            server.api = api;
            // Add this since this is an API server only.
            server.addRoute("/", (req, res) => __awaiter(this, void 0, void 0, function* () {
                res.json({ "status": "ok" });
            }));
            if (server.api.onPreRegister != null)
                yield server.api.onPreRegister();
            // Process pre registration for the routes and methods.
            for (let key in server.api.routes) {
                let route = server.api.routes[key];
                if (route.onPreRegister != null)
                    yield route.onPreRegister();
                for (let iIdx = 0; iIdx < route.methods.length; iIdx++) {
                    let method = route.methods[iIdx];
                    if (method.onPreRegister != null)
                        yield method.onPreRegister();
                }
            }
            //server.serverType = "API Server";
            //await server.listen ();
            return ({ api: api, baseAPIUrl: baseAPIUrl });
        });
    }
    /**
     * Process the APIs for generation.
     */
    processAPIs(processor, apis, apiToProcess) {
        return __awaiter(this, void 0, void 0, function* () {
            let outputDir = ppath.normalize(`${this.outputDir}/`);
            if ((yield HotIO_1.HotIO.exists(outputDir)) === false)
                yield HotIO_1.HotIO.mkdir(outputDir);
            for (let iIdx = 0; iIdx < this.hotsites.length; iIdx++) {
                const hotsite = this.hotsites[iIdx];
                if (hotsite.apis === null) {
                    this.logger.info(`HotSite "${hotsite.name}" contains no APIs. Skipping...`);
                    continue;
                }
                let numAPIs = Object.keys(hotsite.apis).length;
                if (numAPIs < 1) {
                    this.logger.info(`HotSite "${hotsite.name}" contains no APIs. Skipping...`);
                    continue;
                }
                for (let key in hotsite.apis) {
                    let loadedAPI = apis[key];
                    let libraryName = hotsite.apis[key].libraryName;
                    let apiName = hotsite.apis[key].apiName;
                    if (libraryName == null) {
                        this.logger.info(`Web API "${key}" from HotSite "${hotsite.name}" does not have a libraryName!`);
                        continue;
                    }
                    if (apiName == null) {
                        this.logger.info(`Web API "${key}" from HotSite "${hotsite.name}" does not have an apiName!`);
                        continue;
                    }
                    let serverResult = yield this.startAPIServer(processor, loadedAPI);
                    yield apiToProcess(key, hotsite, loadedAPI, libraryName, apiName, outputDir, serverResult);
                }
            }
        });
    }
    /**
     * Generate the API.
     *
     * @fixme Needs tests!
     */
    generateAPI(processor, apis) {
        return __awaiter(this, void 0, void 0, function* () {
            if ((this.generateType === "openapi-3.0.0-json") ||
                (this.generateType === "openapi-3.0.0-yaml") ||
                (this.generateType === "asyncapi-2.6.0-json") ||
                (this.generateType === "asyncapi-2.6.0-yaml")) {
                yield this.generateAPIDocumentation(processor, apis);
                return;
            }
            if (!((this.generateType === "typescript") ||
                (this.generateType === "javascript"))) {
                throw new Error(`Unknown API --generate-type: ${this.generateType}`);
            }
            yield this.processAPIs(processor, apis, (key, hotsite, loadedAPI, libraryName, apiName, outputDir, serverResult) => __awaiter(this, void 0, void 0, function* () {
                this.logger.info(`Generating Web API for ${this.generateType} using "${key}" from HotSite "${hotsite.name}"...`);
                let apiFileContent = "";
                apiFileContent += yield this.getAPIContent(this.generateType, "header", { libraryName: libraryName, apiName: apiName, baseAPIUrl: serverResult.baseAPIUrl });
                let collectedRoutes = [];
                for (let key2 in serverResult.api.routes) {
                    let route = serverResult.api.routes[key2];
                    let routeName = route.route;
                    let skipThisRoute = false;
                    if (typeof (routeName) !== "string")
                        throw new Error(`Provided route name is not a string: ${JSON.stringify(route)}`);
                    for (let iIdx = 0; iIdx < this.skipRoutes.length; iIdx++) {
                        const skipRoute = this.skipRoutes[iIdx];
                        if (routeName === skipRoute) {
                            skipThisRoute = true;
                            break;
                        }
                    }
                    if (skipThisRoute === true)
                        continue;
                    apiFileContent += yield this.getAPIContent(this.generateType, "class_header", { routeName: routeName, baseAPIUrl: serverResult.baseAPIUrl, extends: this.routeExtends,
                        libraryName: libraryName, apiName: apiName });
                    for (let iJdx = 0; iJdx < route.methods.length; iJdx++) {
                        let method = route.methods[iJdx];
                        let methodName = method.name;
                        let methodType = "post";
                        if (method.type === HotRouteMethod_1.HotEventMethod.GET)
                            methodType = "get";
                        if (method.type === HotRouteMethod_1.HotEventMethod.POST)
                            methodType = "post";
                        if (method.type === HotRouteMethod_1.HotEventMethod.POST_AND_WEBSOCKET_CLIENT_PUB_EVENT)
                            methodType = "post";
                        if (method.type === HotRouteMethod_1.HotEventMethod.FILE_UPLOAD)
                            methodType = "post";
                        apiFileContent += yield this.getAPIContent(this.generateType, "class_function", { methodName: methodName, routeVersion: route.version,
                            routeName: routeName, methodType: methodType.toUpperCase(),
                            libraryName: libraryName, apiName: apiName, method: method });
                    }
                    apiFileContent += yield this.getAPIContent(this.generateType, "class_footer", { libraryName: libraryName, baseAPIUrl: serverResult.baseAPIUrl, apiName: apiName, routeName: routeName });
                    collectedRoutes.push({
                        libraryName: libraryName, apiName: apiName, routeName: routeName
                    });
                }
                apiFileContent += yield this.getAPIContent(this.generateType, "footer", {
                    libraryName: libraryName, apiName: apiName, collectedRoutes: collectedRoutes
                });
                const outputFile = ppath.normalize(`${outputDir}/${libraryName}_${apiName}`);
                let outputFileExtension = ".ts";
                if (this.generateType === "javascript") {
                    this.compileTS = false;
                    outputFileExtension = ".js";
                }
                yield HotIO_1.HotIO.writeTextFile(`${outputFile}${outputFileExtension}`, apiFileContent);
                if (this.compileTS === true) {
                    this.logger.info(`WARNING: TypeScript support does not entirely work at this time.`);
                    this.logger.info(`Compiling TypeScript...`);
                    const timestamp = Date.now().toString();
                    let tsconfigObj = JSON.parse(yield HotIO_1.HotIO.readTextFile(this.tsconfigPath));
                    tsconfigObj.compilerOptions.outDir = outputDir;
                    tsconfigObj.files = [`${outputFile}${outputFileExtension}`];
                    const temptsconfig = ppath.normalize(`${outputDir}/tsconfig-generator-temp-${timestamp}.json`);
                    yield HotIO_1.HotIO.writeTextFile(temptsconfig, JSON.stringify(tsconfigObj, null, 4));
                    /*try
                    {
                        // Build the TypeScript so it can run in a web browser.
                        await HotIO.exec (`cd ${outputDir} && npm run buildweb`);
                    }
                    catch (ex)
                    {
                    }*/
                    yield HotIO_1.HotIO.rm(temptsconfig);
                    this.logger.info(`Finished compiling TypeScript...`);
                }
                if (this.optimizeJS === true) {
                    this.logger.info(`Optimizing JavaScript...`);
                    yield HotIO_1.HotIO.exec(`npx google-closure-compiler --js=${outputFile}.js --js_output_file=${outputFile}.min.js`);
                    this.logger.info(`Finished optimizing JavaScript...`);
                }
                this.logger.info(`Wrote generated API files to ${this.outputDir}`);
                if (this.copyTo !== "") {
                    this.copyTo = ppath.normalize(this.copyTo);
                    yield HotIO_1.HotIO.copyFiles(this.outputDir, this.copyTo);
                    this.logger.info(`Copied generated API files to ${this.copyTo}`);
                }
                this.logger.info(`Finished generating Web API "${key}" from HotSite "${hotsite.name}"...`);
                if (this.exitOnComplete === true)
                    process.exit(0);
            }));
        });
    }
    /**
     * Generate the API documentation.
     *
     * @fixme Needs tests!
     */
    generateAPIDocumentation(processor, apis) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!((this.generateType === "openapi-3.0.0-json") ||
                (this.generateType === "openapi-3.0.0-yaml") ||
                (this.generateType === "asyncapi-2.6.0-json") ||
                (this.generateType === "asyncapi-2.6.0-yaml"))) {
                throw new Error(`Unknown API documentation --generate-type ${JSON.stringify(this.generateType)}`);
            }
            yield this.processAPIs(processor, apis, (key, hotsite, loadedAPI, libraryName, apiName, outputDir, serverResult) => __awaiter(this, void 0, void 0, function* () {
                this.logger.info(`Generating ${this.generateType} Documentation "${key}" from HotSite "${hotsite.name}"...`);
                let jsonObj = {};
                let components = {};
                let queryParameters = {};
                let hotsiteDescription = "";
                let servers = null;
                if (serverResult.api.description != null)
                    hotsiteDescription = serverResult.api.description;
                if (serverResult.api.openAPI.security != null)
                    jsonObj.security = serverResult.api.openAPI.security;
                if (serverResult.api.openAPI.tags != null)
                    jsonObj.tags = serverResult.api.openAPI.tags;
                if (this.generateType.indexOf("openapi-3.0.0") > -1) {
                    jsonObj.openapi = "3.0.0";
                    servers = [{ url: serverResult.baseAPIUrl }];
                }
                if (this.generateType.indexOf("asyncapi-2.6.0-yaml") > -1) {
                    jsonObj.asyncapi = "2.6.0";
                    servers = {
                        production: {
                            url: serverResult.baseAPIUrl,
                            protocol: "ws"
                        }
                    };
                }
                let filename = `${libraryName}_${apiName}_${this.generateType}`;
                jsonObj.info = {};
                jsonObj.info.title = hotsite.name;
                let version = hotsite.version;
                if (version == null) {
                    let packagePath = ppath.normalize(`${__dirname}/../../package.json`);
                    if ((yield HotIO_1.HotIO.exists(packagePath)) === false)
                        packagePath = ppath.normalize(`${process.cwd()}/package.json`);
                    let packageJSON = yield HotIO_1.HotIO.readJSONFile(packagePath);
                    version = packageJSON.version;
                }
                jsonObj.info.version = version;
                jsonObj.info.description = hotsiteDescription;
                jsonObj.servers = servers;
                if (serverResult.api.openAPI.info != null)
                    jsonObj.info = Object.assign(Object.assign({}, jsonObj.info), serverResult.api.openAPI.info);
                if (serverResult.api.openAPI.extra != null)
                    jsonObj = Object.assign(Object.assign({}, jsonObj), serverResult.api.openAPI.extra);
                if (jsonObj.openapi != null)
                    jsonObj.paths = {};
                if (jsonObj.asyncapi != null)
                    jsonObj.channels = {};
                for (let key2 in serverResult.api.routes) {
                    let route = serverResult.api.routes[key2];
                    let routeName = route.route;
                    let routeDescription = "";
                    let skipThisRoute = false;
                    if (typeof (routeName) !== "string")
                        throw new Error(`Provided route name is not a string: ${JSON.stringify(route)}`);
                    for (let iIdx = 0; iIdx < this.skipRoutes.length; iIdx++) {
                        const skipRoute = this.skipRoutes[iIdx];
                        if (routeName === skipRoute) {
                            skipThisRoute = true;
                            break;
                        }
                    }
                    if (skipThisRoute === true)
                        continue;
                    if (route.description != null)
                        routeDescription = route.description;
                    this.logger.verbose(`Generating Route ${routeName}...`);
                    for (let iJdx = 0; iJdx < route.methods.length; iJdx++) {
                        let method = route.methods[iJdx];
                        let methodName = method.name;
                        let path = `/${route.version}/${routeName}/${methodName}`;
                        let methodType = method.type.toLowerCase();
                        if (method.type === HotRouteMethod_1.HotEventMethod.POST_AND_WEBSOCKET_CLIENT_PUB_EVENT)
                            methodType = "post";
                        if (method.type === HotRouteMethod_1.HotEventMethod.FILE_UPLOAD) {
                            methodType = "post";
                            method.type = HotRouteMethod_1.HotEventMethod.POST;
                        }
                        if (jsonObj.openapi != null) {
                            if (!((method.type === HotRouteMethod_1.HotEventMethod.GET) ||
                                (method.type === HotRouteMethod_1.HotEventMethod.POST) ||
                                (method.type === HotRouteMethod_1.HotEventMethod.POST_AND_WEBSOCKET_CLIENT_PUB_EVENT))) {
                                this.logger.warning(`Skipping method ${method.name} because it is not a GET or POST method.`);
                                continue;
                            }
                        }
                        if (jsonObj.asyncapi != null) {
                            if (!((method.type === HotRouteMethod_1.HotEventMethod.POST_AND_WEBSOCKET_CLIENT_PUB_EVENT) ||
                                (method.type === HotRouteMethod_1.HotEventMethod.WEBSOCKET_CLIENT_PUB_EVENT))) {
                                this.logger.warning(`Skipping method ${method.name} because it is not a POST_AND_WEBSOCKET_CLIENT_PUB_EVENT or WEBSOCKET_CLIENT_PUB_EVENT method.`);
                                continue;
                            }
                        }
                        let methodDescription = "";
                        let methodTags = [];
                        let returnsDescription = {
                            description: ""
                        };
                        let errorResponse = {
                            description: "An error response. See the error object for more information.",
                            content: {
                                "application/json": {
                                    schema: {
                                        type: "object",
                                        description: "The error object. This structure will be the same for nearly any error response.",
                                        properties: {
                                            "error": {
                                                type: "string",
                                                description: "The error message. This should not contain a stack trace."
                                            },
                                            "errorCode": {
                                                type: "integer",
                                                description: "The HTTP error code associated with the error message."
                                            }
                                        }
                                    }
                                }
                            }
                        };
                        let component = null;
                        let componentName = "";
                        this.logger.verbose(`Generating method ${methodName} at path ${path}`);
                        let getChildParameters = (param) => __awaiter(this, void 0, void 0, function* () {
                            let createdObj = {
                                type: param.type || "string",
                                description: param.description || "",
                                properties: {}
                            };
                            if (param.type != null) {
                                if (param.parameters != null) {
                                    if (param.parameters instanceof Function)
                                        param.parameters = yield param.parameters();
                                    for (let key3 in param.parameters) {
                                        let param2 = param.parameters[key3];
                                        let tempParam = {
                                            type: "string",
                                            required: false,
                                            description: ""
                                        };
                                        if (typeof (param2) === "string")
                                            tempParam["type"] = param2;
                                        else if (param2 instanceof Function)
                                            tempParam = yield param2();
                                        else
                                            tempParam = param2;
                                        if (tempParam.type === "int")
                                            tempParam.type = "integer";
                                        if (tempParam.type === "object") {
                                            if (tempParam.parameters == null)
                                                this.logger.warning(`Object parameter ${key3} is missing parameters.`);
                                            if (tempParam.parameters != null)
                                                createdObj.properties[key3] = yield getChildParameters(tempParam.parameters);
                                        }
                                        else {
                                            createdObj.properties[key3] = {
                                                type: tempParam.type || "string",
                                                description: tempParam.description || ""
                                            };
                                            if (tempParam.type === "array") {
                                                createdObj.properties[key3].items = {
                                                    type: tempParam.items.type || "string"
                                                };
                                                if (tempParam.items.openAPI != null)
                                                    createdObj.properties[key3].items = Object.assign(Object.assign({}, createdObj.properties[key3].items), tempParam.items.openAPI);
                                            }
                                        }
                                    }
                                }
                            }
                            if (param.openAPI != null)
                                createdObj = Object.assign(Object.assign({}, createdObj), param.openAPI);
                            return (createdObj);
                        });
                        if (method.description != null)
                            methodDescription = method.description;
                        if (method.tags != null) {
                            if (method.tags.length > 0)
                                methodTags = method.tags;
                        }
                        if (method.returns != null) {
                            if (method.returns instanceof Function)
                                method.returns = yield method.returns();
                            if (method.returns.type === "object") {
                                returnsDescription = {
                                    description: method.returns.description || "",
                                    content: {
                                        "application/json": {
                                            schema: yield getChildParameters(method.returns)
                                        }
                                    }
                                };
                            }
                            else {
                                returnsDescription = {
                                    description: method.returns.description || "",
                                    content: {
                                        "application/json": {
                                            schema: {
                                                type: method.returns.type || "string",
                                                description: method.returns.description || ""
                                            }
                                        }
                                    }
                                };
                                if (method.returns.type === "array") {
                                    returnsDescription.content["application/json"].schema.items = {
                                        type: method.returns.items.type || "string"
                                    };
                                }
                            }
                            if (method.returns.openAPI != null)
                                returnsDescription = Object.assign(Object.assign({}, returnsDescription), method.returns.openAPI);
                            /// @todo Add support for return parameters to be executed as functions.
                        }
                        if (jsonObj.openapi != null) {
                            jsonObj.paths[path] = {};
                            jsonObj.paths[path][methodType] = {
                                "summary": methodDescription,
                                "tags": methodTags,
                                responses: {
                                    "200": returnsDescription,
                                    "401": errorResponse,
                                    "400": errorResponse,
                                    "500": errorResponse
                                }
                            };
                            if (method.openAPI != null)
                                jsonObj.paths[path][methodType] = Object.assign(Object.assign({}, jsonObj.paths[path][methodType]), method.openAPI);
                        }
                        if (jsonObj.asyncapi != null) {
                            jsonObj.channels[path] = {
                                publish: {
                                    summary: methodDescription,
                                    tags: methodTags,
                                    message: {
                                        "payload": returnsDescription
                                    }
                                }
                            };
                        }
                        const paramTypes = ["parameters", "queryParameters"];
                        for (let iIdx = 0; iIdx < paramTypes.length; iIdx++) {
                            const paramType = paramTypes[iIdx];
                            let addType = false;
                            let tempQueryParams = [];
                            if (method[paramType] != null) {
                                if (method[paramType] instanceof Function) {
                                    // @ts-ignore
                                    method[paramType] = yield method[paramType]();
                                }
                                if (Object.keys(method[paramType]).length > 0) {
                                    componentName = `${routeName}_${methodName}`;
                                    if (method.parametersRefName !== "")
                                        componentName = method.parametersRefName;
                                    if (paramType === "parameters") {
                                        components[`${componentName}`] = {
                                            type: "object",
                                            properties: {}
                                        };
                                        component = components[`${componentName}`];
                                    }
                                    if (paramType === "queryParameters") {
                                        queryParameters[`${componentName}`] = [];
                                        component = queryParameters[`${componentName}`];
                                    }
                                    addType = true;
                                }
                                for (let key3 in method[paramType]) {
                                    let param = method[paramType][key3];
                                    if (param instanceof Function)
                                        param = yield param();
                                    if (param.type === "int")
                                        param.type = "integer";
                                    if (paramType === "queryParameters") {
                                        if (param.type === "object")
                                            this.logger.warning(`Query parameter ${key3} is using an "object" type is not supported.`);
                                        else {
                                            let obj = {
                                                name: key3,
                                                in: "query",
                                                schema: {
                                                    type: param.type || "string",
                                                },
                                                description: param.description || ""
                                            };
                                            obj = Object.assign(Object.assign({}, obj), param.openAPI);
                                            tempQueryParams.push(obj);
                                        }
                                        if (param.type === "array")
                                            this.logger.warning(`Query parameter ${key3} is using an "array" type is not supported.`);
                                        if (param.required === true)
                                            this.logger.warning(`Query parameter ${key3} has required set, which is not supported.`);
                                    }
                                    else {
                                        if (param.type === "object")
                                            component.properties[key3] = yield getChildParameters(param);
                                        else {
                                            component.properties[key3] = {
                                                type: param.type || "string",
                                                description: param.description || ""
                                            };
                                        }
                                        if (param.type === "array") {
                                            component.properties[key3].items = {
                                                type: param.items.type || "string",
                                            };
                                            if (param.items.openAPI != null)
                                                component.properties[key3].items = Object.assign(Object.assign({}, component.properties[key3].items), param.items.openAPI);
                                        }
                                        if (param.readOnly === true)
                                            component.readOnly = true;
                                        if (param.required === true) {
                                            if (component.required == null)
                                                component.required = [];
                                            component.required.push(key3);
                                        }
                                    }
                                    if (param.openAPI != null) {
                                        if (paramType === "parameters")
                                            component.properties[key3] = Object.assign(Object.assign({}, component.properties[key3]), param.openAPI);
                                    }
                                    addType = true;
                                }
                            }
                            if (addType === true) {
                                if (component != null) {
                                    if (jsonObj.openapi != null) {
                                        if (paramType === "queryParameters") {
                                            jsonObj.paths[path][methodType]["parameters"] = tempQueryParams;
                                        }
                                        else {
                                            jsonObj.paths[path][methodType]["requestBody"] = {
                                                required: true,
                                                content: {
                                                    "application/json": {
                                                        schema: {
                                                            "$ref": `#/components/schemas/${componentName}`
                                                        }
                                                    }
                                                }
                                            };
                                        }
                                    }
                                    if (jsonObj.asyncapi != null) {
                                        jsonObj.channels[path].publish = { message: { payload: {} } };
                                        jsonObj.channels[path].publish.message.payload = {
                                            "$ref": `#/components/schemas/${componentName}`
                                        };
                                    }
                                }
                            }
                        }
                    }
                }
                jsonObj.components = {
                    schemas: components
                };
                if (serverResult.api.openAPI.components != null)
                    jsonObj.components = Object.assign(Object.assign({}, jsonObj.components), serverResult.api.openAPI.components);
                const outputFile = ppath.normalize(`${outputDir}/${filename}`);
                let outputFileExtension = ".json";
                let fileContent = "";
                if ((this.generateType === "openapi-3.0.0-json") ||
                    (this.generateType === "asyncapi-2.6.0-json")) {
                    fileContent = JSON.stringify(jsonObj, null, 2);
                }
                if ((this.generateType === "openapi-3.0.0-yaml") ||
                    (this.generateType === "asyncapi-2.6.0-yaml")) {
                    outputFileExtension = ".yaml";
                    let yamldoc = new yaml_1.default.Document();
                    yamldoc.contents = jsonObj;
                    fileContent = yamldoc.toString();
                }
                this.logger.verbose(`Writing to file ${outputFile}${outputFileExtension}`);
                yield HotIO_1.HotIO.writeTextFile(`${outputFile}${outputFileExtension}`, fileContent);
                this.logger.info(`Finished generating API Documentation "${key}" from HotSite "${hotsite.name}"...`);
                if (this.exitOnComplete === true)
                    process.exit(0);
            }));
        });
    }
    /**
     * Get the content.
     */
    getAPIContent(type, contentPart, data) {
        return __awaiter(this, void 0, void 0, function* () {
            let content = "";
            if (type === "javascript")
                content = yield this.getJavaScriptContent(contentPart, data);
            if (type === "typescript")
                content = yield this.getTypeScriptContent(contentPart, data);
            return (content);
        });
    }
    /**
     * Get the Typescript content.
     */
    getTypeScriptContent(contentPart, data) {
        return __awaiter(this, void 0, void 0, function* () {
            let content = "";
            if (contentPart === "header") {
                content = `
import { HotAPI } from "HotStaq";
`;
            }
            if (contentPart === "class_header") {
                content = `
/**
 * The ${data.routeName} API route.
 */
export class ${data.routeName} extends HotAPI
{
	/**
	 * The base url to make calls to.
	 */
	baseUrl: string;

	constructor (baseUrl: string = "${data.baseAPIUrl}")
	{
		this.baseUrl = baseUrl;
	}

`;
            }
            if (contentPart === "class_function") {
                content = `
	/**
	 * The ${data.methodName} method.
	 */
	async ${data.methodName} (jsonObj: any): Promise<any>
	{
		const response = await fetch (\`\${this.baseUrl}/${data.routeVersion}/${data.route}/${data.methodName}\`, {
				"method": "${data.methodType}",
				"headers": {
					"Accept": "application/json",
					"Content-Type": "application/json"
				},
				body: JSON.stringify (jsonObj)
			});
		const result = response.json ();

		return (result);
	}

`;
            }
            if (contentPart === "class_footer") {
                content = `
}
`;
            }
            return (content);
        });
    }
    /**
     * Get the JavaScript content.
     */
    getJavaScriptContent(contentPart, data) {
        return __awaiter(this, void 0, void 0, function* () {
            let content = "";
            if (contentPart === "header") {
                content =
                    `if (typeof (${data.libraryName}) === "undefined")
	var ${data.libraryName} = {};

var HotAPIGlobal = undefined;

if (typeof (HotAPI) !== "undefined")
	HotAPIGlobal = HotAPI;

if (typeof (window) !== "undefined")
{
	if (typeof (window.HotAPI) !== "undefined")
		HotAPIGlobal = window.HotAPI;
}

/**
 * Process a JSON object, and get it ready to make a request.
 */
function HotStaqProcessJSONObject (jsonObj)
{
	return (jsonObj);
}

/**
 * Make a request to the server.
 */
function HotStaqPostJSONObject (methodType, url, jsonObj, auth)
{
	let headers = {
			"Accept": "application/json",
			"Content-Type": "application/json"
		};

	if (auth != null)
		headers["Authorization"] = "Bearer " + auth;

	let promise = fetch (url, {
			"method": methodType,
			"headers": headers,
			body: JSON.stringify (jsonObj)
		});

	return (promise);
}
`;
            }
            if (contentPart === "class_header") {
                let routeExtends = data.extends;
                let routeSuper = "";
                if (routeExtends !== "") {
                    routeExtends = ` extends ${routeExtends}`;
                    routeSuper = `super (connection, "${data.routeName}", []);`;
                }
                content = `
/**
 * The ${data.routeName} API route.
 */
class ${data.routeName}${routeExtends}
{
	constructor (baseUrl, connection, db)
	{
		${routeSuper}

		if (baseUrl == null)
			baseUrl = "${data.baseAPIUrl}";

		if (connection === undefined)
			connection = null;

		if (db === undefined)
			db = null;

		/**
		 * The base url to make calls to.
		 */
		this.baseUrl = baseUrl;
		/**
		 * The connection to the server/client.
		 */
		this.connection = connection;
		/**
		 * The bearer token used to connect to the server.
		 */
		this.bearerToken = null;
		/**
		 * The database connection, if any.
		 */
		this.db = db;
	}

	/**
	 * Make a call to the API. THIS CANNOT upload files yet.
	 */
	makeCall (route, data, httpMethod = "post", files = {}, bearer = "")
	{
		var promise = new Promise ((resolve, reject) => 
			{
				let url = this.baseUrl;

				if (url[(url.length - 1)] === "/")
					url = url.substr (0, (url.length - 1));

				if (route[0] !== "/")
					url += "/";

				url += route;

				if (bearer === "")
				{
					if (Hot.BearerToken != null)
						bearer = Hot.BearerToken;
				}

				HotStaqPostJSONObject (httpMethod, url, data, bearer).then (
					function (response)
					{
						var result = response.json ();
						resolve (result);
					});
			});
		return (promise);
	}
`;
            }
            if (contentPart === "class_function") {
                let method = data.method;
                let paramsDescription = "";
                let jsonParamOutput = "";
                let jsonReturnOutput = "";
                let getChildParameters = (param) => __awaiter(this, void 0, void 0, function* () {
                    let outputParams = "";
                    // Guard against the recursive `tempParam.parameters` call passing
                    // undefined when an "object"-typed param is missing its nested
                    // schema. The 0.9.1 generator silently emitted nothing in that
                    // case; the modern generator dereferences `param.type` and
                    // throws TypeError before reaching the existing null-check below.
                    if (param == null)
                        return ("");
                    if (param.type != null) {
                        if (param.type === "object") {
                            if (param.parameters == null)
                                throw new Error(`Missing parameters for an object in ${method.route.route}/${method.name}`);
                        }
                        for (let key3 in param.parameters) {
                            let param2 = param.parameters[key3];
                            let tempParam = {
                                type: "string",
                                required: false,
                                description: ""
                            };
                            if (typeof (param2) === "string")
                                tempParam["type"] = param2;
                            else if (typeof (param2) === "function")
                                tempParam = yield param2();
                            else
                                tempParam = param2;
                            if (tempParam.type === "object")
                                outputParams += yield getChildParameters(tempParam.parameters);
                            else {
                                outputParams += `
	 * @property {${tempParam.type || "string"}} ${key3} ${tempParam.description || ""}`;
                            }
                        }
                    }
                    return (outputParams);
                });
                if (method.parameters != null) {
                    let paramsObjType = "";
                    let paramsToOutput = "";
                    paramsObjType = `${data.routeName}_${data.methodName}_json_object_type`.toUpperCase();
                    for (let key in method.parameters) {
                        let param = method.parameters[key];
                        if (param instanceof Function)
                            param = yield param();
                        if (param.type === "object") {
                            paramsToOutput += yield getChildParameters(param);
                        }
                        else {
                            paramsToOutput += `
	 * @property {${param.type || "string"}} ${key} ${param.description || ""}`;
                        }
                    }
                    paramsDescription = `/**
	 * The JSON object to send to the server.
	 * 
	 * @typedef {Object} ${paramsObjType}${paramsToOutput}
	 */

	`;
                    jsonParamOutput = `
	 * 
	 * @param {${paramsObjType}} jsonObj`;
                }
                if (method.returns != null) {
                    if (method.returns instanceof Function)
                        method.returns = yield method.returns();
                    if (method.returns.type === "object") {
                        let returnObjType = `${data.routeName}_${data.methodName}_json_return_type`.toUpperCase();
                        let returnParamsToOutput = yield getChildParameters(method.returns);
                        paramsDescription += `/**
	 * The JSON object returned from the server.
	 * 
	 * @typedef {Object} ${returnObjType}${returnParamsToOutput}
	 */

	`;
                        jsonReturnOutput = `
	 * 
	 * @returns {${returnObjType}} ${method.returns.description || ""}`;
                    }
                    else {
                        jsonReturnOutput = `
	 * 
	 * @returns {${method.returns.type}} ${method.returns.description || ""}`;
                    }
                }
                let uploadFileBegin = "";
                let uploadFileEnd = "";
                if (method.type === HotRouteMethod_1.HotEventMethod.FILE_UPLOAD) {
                    uploadFileBegin = `
				let uploadHeaders = {
						"Content-Type": "multipart/form-data"
					};

				if (auth != null)
					uploadHeaders["Authorization"] = auth;

				fetch (url, {
					"method": "POST",
					"headers": uploadHeaders,
					body: JSON.stringify (jsonObj)
				}).then (() =>
				{
				`;
                    uploadFileEnd = `
				});
				`;
                }
                content = `
	${paramsDescription}/**
	 * The ${data.methodName} method.${jsonParamOutput}${jsonReturnOutput}
	 */
	${data.methodName} (jsonObj)
	{
		var promise = new Promise ((resolve, reject) => 
			{
				const url = \`\${this.baseUrl}/${data.routeVersion}/${data.routeName}/${data.methodName}\`;
				const auth = null;

				if (this.authorization != null)
				{
					if (this.authorization.toAuthorizationHeaderString != null)
						auth = this.authorization.toAuthorizationHeaderString ();
				}

				${uploadFileBegin}
				jsonObj = HotStaqProcessJSONObject (jsonObj);
				HotStaqPostJSONObject ("${data.methodType}", url, jsonObj, auth).then (
					function (response)
					{
						var result = response.json ();

						resolve (result);
					});${uploadFileEnd}
			});

		return (promise);
	}
`;
            }
            if (contentPart === "class_footer") {
                content = `}

`;
            }
            if (contentPart === "footer") {
                let routesOutput = "";
                if (data.collectedRoutes.length > 0)
                    routesOutput += "\n";
                for (let iIdx = 0; iIdx < data.collectedRoutes.length; iIdx++) {
                    let collectedRoute = data.collectedRoutes[iIdx];
                    routesOutput += `
					this.${collectedRoute.routeName} = new ${collectedRoute.routeName} (baseUrl, connection, db);`;
                }
                content = `if (typeof (${data.libraryName}.${data.apiName}) === "undefined")
{
	if (typeof (HotAPIGlobal) !== "undefined")
	{
		${data.libraryName}.${data.apiName} = class extends HotAPIGlobal
			{
				constructor (baseUrl, connection, db)
				{
					super (baseUrl, connection, db);${routesOutput}
				}
			}
	}
}`;
            }
            return (content);
        });
    }
}
exports.HotGenerator = HotGenerator;
//# sourceMappingURL=HotGenerator.js.map