"use strict";
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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAPI3 = void 0;
const openapi_types_1 = require("openapi-types");
const openapi_1 = require("./base/openapi");
const url_1 = require("url");
const parameterNotRef = (param) => {
    // all refs are deferenced by parser library
    return true;
};
const requestBodyNotRef = (param) => {
    // all refs are deferenced by parser library
    return param !== undefined;
};
const schemaNotRef = (param) => {
    // all refs are deferenced by parser library
    return param !== undefined;
};
const isOpenAPI3 = (document) => {
    return document.openapi.includes("3.0");
};
const methods = Object.values(openapi_types_1.OpenAPIV3.HttpMethods);
const isOperation = (key, pathItem) => {
    return methods.includes(key);
};
const isParameter = (key, pathItem) => {
    return !isOperation(key, pathItem);
};
const getRequestBody = (operation) => {
    if (requestBodyNotRef(operation.requestBody)) {
        const request = operation.requestBody;
        const supportedMimeTypes = getMimeTypes(operation);
        if (supportedMimeTypes.length > 0) {
            const mimeType = supportedMimeTypes[0];
            // try get example from request
            const content = request.content[mimeType];
            if (content.example) {
                return content.example;
            }
            // try get example from schema
            if (schemaNotRef(content.schema)) {
                const schema = content.schema;
                if (schema.example) {
                    return schema.example;
                }
            }
        }
    }
    return undefined;
};
const getMimeTypes = (operation) => {
    if (requestBodyNotRef(operation.requestBody)) {
        const request = operation.requestBody;
        return Object.keys(request.content);
    }
    return [];
};
/**
 * OpenAPI Version 3.0
 * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md
 */
class OpenAPI3 extends openapi_1.OpenAPISource {
    constructor() {
        super(...arguments);
        this.isSupported = (data) => __awaiter(this, void 0, void 0, function* () {
            try {
                const document = yield this.parseData(data);
                if (isOpenAPI3(document)) {
                    this.document = document;
                    return true;
                }
                else {
                    return false;
                }
            }
            catch (err) {
                return false;
            }
        });
        this.getInfo = () => __awaiter(this, void 0, void 0, function* () {
            const name = this.document.info.title || "OpenAPI Import";
            return {
                name,
            };
        });
        this.getQueries = (datasourceId) => __awaiter(this, void 0, void 0, function* () {
            var _a;
            let url;
            if ((_a = this.document.servers) === null || _a === void 0 ? void 0 : _a.length) {
                url = this.document.servers[0].url;
                try {
                    url = new url_1.URL(url);
                }
                catch (err) {
                    // unable to construct url, e.g. with variables
                    // proceed with string form of url
                }
            }
            const queries = [];
            for (let [path, pathItemObject] of Object.entries(this.document.paths)) {
                // parameters that apply to every operation in the path
                let pathParams = [];
                // pathItemObject can be undefined
                if (!pathItemObject) {
                    continue;
                }
                for (let [key, opOrParams] of Object.entries(pathItemObject)) {
                    if (isParameter(key, opOrParams)) {
                        const pathParameters = opOrParams;
                        pathParams.push(...pathParameters);
                        continue;
                    }
                    // can not be a parameter, must be an operation
                    const operation = opOrParams;
                    const methodName = key;
                    const name = operation.operationId || path;
                    let queryString = "";
                    const headers = {};
                    let requestBody = getRequestBody(operation);
                    const parameters = [];
                    const mimeTypes = getMimeTypes(operation);
                    if (mimeTypes.length > 0) {
                        headers["Content-Type"] = mimeTypes[0];
                    }
                    // combine the path parameters with the operation parameters
                    const operationParams = operation.parameters || [];
                    const allParams = [...pathParams, ...operationParams];
                    for (let param of allParams) {
                        if (parameterNotRef(param)) {
                            switch (param.in) {
                                case "query":
                                    let prefix = "";
                                    if (queryString) {
                                        prefix = "&";
                                    }
                                    queryString = `${queryString}${prefix}${param.name}={{${param.name}}}`;
                                    break;
                                case "header":
                                    headers[param.name] = `{{${param.name}}}`;
                                    break;
                                case "path":
                                    // do nothing: param is already in the path
                                    break;
                                case "formData":
                                    // future enhancement
                                    break;
                            }
                            // add the parameter if it can be bound in our config
                            if (["query", "header", "path"].includes(param.in)) {
                                parameters.push({
                                    name: param.name,
                                    default: "",
                                });
                            }
                        }
                    }
                    const query = this.constructQuery(datasourceId, name, methodName, path, url, queryString, headers, parameters, requestBody);
                    queries.push(query);
                }
            }
            return queries;
        });
    }
    getImportSource() {
        return "openapi3.0";
    }
}
exports.OpenAPI3 = OpenAPI3;
