"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __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 __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;
exports.utils = exports.Webhook = exports.Callback = exports.Operation = void 0;
var json_schema_ref_parser_1 = __importDefault(require("@readme/json-schema-ref-parser"));
var path_to_regexp_1 = require("path-to-regexp");
var get_auth_1 = __importDefault(require("./lib/get-auth"));
var get_user_variable_1 = __importDefault(require("./lib/get-user-variable"));
var operation_1 = __importStar(require("./operation"));
exports.Operation = operation_1["default"];
exports.Callback = operation_1.Callback;
exports.Webhook = operation_1.Webhook;
var utils_1 = __importStar(require("./utils"));
exports.utils = utils_1["default"];
var SERVER_VARIABLE_REGEX = /{([-_a-zA-Z0-9:.[\]]+)}/g;
function ensureProtocol(url) {
    // Add protocol to urls starting with // e.g. //example.com
    // This is because httpsnippet throws a HARError when it doesnt have a protocol
    if (url.match(/^\/\//)) {
        return "https:".concat(url);
    }
    // Add protocol to urls with no // within them
    // This is because httpsnippet throws a HARError when it doesnt have a protocol
    if (!url.match(/\/\//)) {
        return "https://".concat(url);
    }
    return url;
}
function stripTrailingSlash(url) {
    if (url[url.length - 1] === '/') {
        return url.slice(0, -1);
    }
    return url;
}
/**
 * Normalize a OpenAPI server URL by ensuring that it has a proper HTTP protocol and doesn't have a
 * trailing slash.
 *
 * @param api The API definition that we're processing.
 * @param selected The index of the `servers` array in the API definition that we want to normalize.
 */
function normalizedUrl(api, selected) {
    var exampleDotCom = 'https://example.com';
    var url;
    try {
        url = api.servers[selected].url;
        // This is to catch the case where servers = [{}]
        if (!url)
            throw new Error('no url');
        // Stripping the '/' off the end
        url = stripTrailingSlash(url);
        // Check if the URL is just a path a missing an origin, for example `/api/v3`. If so, then make
        // `example.com` the origin to avoid it becoming something invalid like `https:///api/v3`.
        // RM-1044
        if (url.startsWith('/') && !url.startsWith('//')) {
            var urlWithOrigin = new URL(exampleDotCom);
            urlWithOrigin.pathname = url;
            url = urlWithOrigin.href;
        }
    }
    catch (e) {
        url = exampleDotCom;
    }
    return ensureProtocol(url);
}
/**
 * With a URL that may contain server variables, transform those server variables into regex that
 * we can query against.
 *
 * For example, when given `https://{region}.node.example.com/v14` this will return back:
 *
 *    https://([-_a-zA-Z0-9:.[\\]]+).node.example.com/v14
 *
 * @param url URL to transform
 */
function transformUrlIntoRegex(url) {
    return stripTrailingSlash(url.replace(SERVER_VARIABLE_REGEX, '([-_a-zA-Z0-9:.[\\]]+)'));
}
/**
 * Normalize a path so that we can use it with `path-to-regexp` to do operation lookups.
 *
 * @param path Path to normalize.
 */
function normalizePath(path) {
    return (path
        // This regex transforms `{pathParam}` into `:pathParam` so we can regex against it. We're
        // also handling quirks here like if there's an optional proceeding or trailing curly bracket
        // (`{{pathParam}` or `{pathParam}}`) as any unescaped curlys, which would be present in
        // `:pathParam}`, will throw a regex exception.
        .replace(/({?){(.*?)}(}?)/g, function (str) {
        var args = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            args[_i - 1] = arguments[_i];
        }
        // If a path contains a path parameter with hyphens, like `:dlc-release`, when it's regexd
        // with `path-to-regexp` it match against the `:dlc` portion of the parameter, breaking all
        // matching against the full path.
        //
        // For example on `/games/:game/dlc/:dlc-release` the regex that's actually used to search
        // against a path like `/games/destiny-2/dlc/witch-queen` is the following:
        //    /^\/games(?:\/([^\/#\?]+?))\/dlc(?:\/([^\/#\?]+?))-release[\/#\?]?$/i
        //
        // However if `:dlc-release` is rewritten to `:dlcrelease` we end up with a functional
        // regex: /^\/games(?:\/([^\/#\?]+?))\/dlc(?:\/([^\/#\?]+?))[\/#\?]?$/i.
        return ":".concat(args[1].replace('-', ''));
    })
        // In addition to transforming `{pathParam}` into `:pathParam` we also need to escape cases
        // where a non-variabled colon is next to a variabled-colon because if we don't then
        // `path-to-regexp` won't be able to correct identify where the variable starts.
        //
        // For example if the URL is `/post/:param1::param2` we'll be escaping it to
        // `/post/:param1\::param2`.
        .replace(/::/, '\\::')
        // We also need to escape question marks too because they're treated as regex modifiers.
        .split('?')[0]);
}
/**
 * Generate path matches for a given path and origin on a set of OpenAPI path objects.
 *
 * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#pathsObject}
 * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#pathsObject}
 * @param paths The OpenAPI Paths Object to process.
 * @param pathName Path to look for a match.
 * @param origin The origin that we're matching against.
 */
function generatePathMatches(paths, pathName, origin) {
    var prunedPathName = pathName.split('?')[0];
    return Object.keys(paths)
        .map(function (path) {
        var cleanedPath = normalizePath(path);
        var matchResult;
        try {
            var matchStatement = (0, path_to_regexp_1.match)(cleanedPath, { decode: decodeURIComponent });
            matchResult = matchStatement(prunedPathName);
        }
        catch (err) {
            // If path matching fails for whatever reason (maybe they have a malformed path parameter)
            // then we shouldn't also fail.
            return;
        }
        var slugs = {};
        if (matchResult && Object.keys(matchResult.params).length) {
            Object.keys(matchResult.params).forEach(function (param) {
                slugs[":".concat(param)] = matchResult.params[param];
            });
        }
        // eslint-disable-next-line consistent-return
        return {
            url: {
                origin: origin,
                path: cleanedPath.replace(/\\::/, '::'),
                nonNormalizedPath: path,
                slugs: slugs
            },
            operation: paths[path],
            match: matchResult
        };
    })
        .filter(Boolean)
        .filter(function (p) { return p.match; });
}
/**
 * @param pathMatches Array of path matches to filter down.
 * @param targetMethod HTTP method to look for.
 * @returns Filtered down path matches.
 */
function filterPathMethods(pathMatches, targetMethod) {
    var regExp = (0, path_to_regexp_1.pathToRegexp)(targetMethod);
    return pathMatches
        .map(function (p) {
        var captures = Object.keys(p.operation).filter(function (r) { return regExp.exec(r); });
        if (captures.length) {
            var method = captures[0];
            p.url.method = method.toUpperCase();
            return {
                url: p.url,
                operation: p.operation[method]
            };
        }
        return false;
    })
        .filter(Boolean);
}
/**
 * @param pathMatches URL and PathsObject matches to narrow down to find a target path.
 * @returns An object containing matches that were discovered in the API definition.
 */
function findTargetPath(pathMatches) {
    var minCount = Object.keys(pathMatches[0].url.slugs).length;
    var operation;
    for (var m = 0; m < pathMatches.length; m += 1) {
        var selection = pathMatches[m];
        var paramCount = Object.keys(selection.url.slugs).length;
        if (paramCount <= minCount) {
            minCount = paramCount;
            operation = selection;
        }
    }
    return operation;
}
var Oas = /** @class */ (function () {
    /**
     * @param oas An OpenAPI definition.
     * @param user The information about a user that we should use when pulling auth tokens from
     *    security schemes.
     */
    function Oas(oas, user) {
        // @todo throw an exception here instead of allowing an empty oas
        this.api = oas;
        this.user = user || {};
        this.promises = [];
        this.dereferencing = {
            processing: false,
            complete: false,
            circularRefs: []
        };
    }
    /**
     * This will initialize a new instance of the `Oas` class. This method is useful if you're using
     * Typescript and are attempting to supply an untyped JSON object into `Oas` as it will force-type
     * that object to an `OASDocument` for you.
     *
     * @param oas An OpenAPI definition.
     * @param user The information about a user that we should use when pulling auth tokens from
     *    security schemes.
     */
    Oas.init = function (oas, user) {
        return new Oas(oas, user);
    };
    /**
     * Retrieve the OpenAPI version that this API definition is targeted for.
     */
    Oas.prototype.getVersion = function () {
        if (this.api.openapi) {
            return this.api.openapi;
        }
        throw new Error('Unable to recognize what specification version this API definition conforms to.');
    };
    /**
     * Retrieve the current OpenAPI API Definition.
     *
     */
    Oas.prototype.getDefinition = function () {
        return this.api;
    };
    Oas.prototype.url = function (selected, variables) {
        if (selected === void 0) { selected = 0; }
        var url = normalizedUrl(this.api, selected);
        return this.replaceUrl(url, variables || this.variables(selected)).trim();
    };
    Oas.prototype.variables = function (selected) {
        if (selected === void 0) { selected = 0; }
        var variables;
        try {
            variables = this.api.servers[selected].variables;
            if (!variables)
                throw new Error('no variables');
        }
        catch (e) {
            variables = {};
        }
        return variables;
    };
    Oas.prototype.defaultVariables = function (selected) {
        var _this = this;
        if (selected === void 0) { selected = 0; }
        var variables = this.variables(selected);
        var defaults = {};
        Object.keys(variables).forEach(function (key) {
            defaults[key] = (0, get_user_variable_1["default"])(_this.user, key) || variables[key]["default"] || '';
        });
        return defaults;
    };
    Oas.prototype.splitUrl = function (selected) {
        if (selected === void 0) { selected = 0; }
        var url = normalizedUrl(this.api, selected);
        var variables = this.variables(selected);
        return url
            .split(/({.+?})/)
            .filter(Boolean)
            .map(function (part, i) {
            var isVariable = part.match(/[{}]/);
            var value = part.replace(/[{}]/g, '');
            // To ensure unique keys, we're going to create a key
            // with the value concatenated to its index.
            var key = "".concat(value, "-").concat(i);
            if (!isVariable) {
                return {
                    type: 'text',
                    value: value,
                    key: key
                };
            }
            var variable = variables === null || variables === void 0 ? void 0 : variables[value];
            return {
                type: 'variable',
                value: value,
                key: key,
                description: variable === null || variable === void 0 ? void 0 : variable.description,
                "enum": variable === null || variable === void 0 ? void 0 : variable["enum"]
            };
        });
    };
    /**
     * With a fully composed server URL, run through our list of known OAS servers and return back
     * which server URL was selected along with any contained server variables split out.
     *
     * For example, if you have an OAS server URL of `https://{name}.example.com:{port}/{basePath}`,
     * and pass in `https://buster.example.com:3000/pet` to this function, you'll get back the
     * following:
     *
     *    { selected: 0, variables: { name: 'buster', port: 3000, basePath: 'pet' } }
     *
     * Re-supplying this data to `oas.url()` should return the same URL you passed into this method.
     *
     * @param baseUrl A given URL to extract server variables out of.
     */
    Oas.prototype.splitVariables = function (baseUrl) {
        var matchedServer = (this.api.servers || [])
            .map(function (server, i) {
            var rgx = transformUrlIntoRegex(server.url);
            var found = new RegExp(rgx).exec(baseUrl);
            if (!found) {
                return false;
            }
            // While it'd be nice to use named regex groups to extract path parameters from the URL and
            // match them up with the variables that we have present in it, JS unfortunately doesn't
            // support having the groups duplicated. So instead of doing that we need to re-regex the
            // server URL, this time splitting on the path parameters -- this way we'll be able to
            // extract the parameter names and match them up with the matched server that we obtained
            // above.
            var variables = {};
            Array.from(server.url.matchAll(SERVER_VARIABLE_REGEX)).forEach(function (variable, y) {
                variables[variable[1]] = found[y + 1];
            });
            return {
                selected: i,
                variables: variables
            };
        })
            .filter(Boolean);
        return matchedServer.length ? matchedServer[0] : false;
    };
    /**
     * Replace templated variables with supplied data in a given URL.
     *
     * There are a couple ways that this will utilize variable data:
     *
     *  - If data is stored in `this.user` and it matches up with the variable name in the URL user
     *    data will always take priority. See `getUserVariable` for some more information on how this
     *    data is pulled from `this.user`.
     *  - Supplying a `variables` object. This incoming `variables` object can be two formats:
     *    `{ variableName: { default: 'value' } }` and `{ variableName: 'value' }`. If the former is
     *    present, that will take prescendence over the latter.
     *
     * If no variables supplied match up with the template name, the template name will instead be
     * used as the variable data.
     *
     * @param url A URL to swap variables into.
     * @param variables An object containing variables to swap into the URL.
     */
    Oas.prototype.replaceUrl = function (url, variables) {
        var _this = this;
        if (variables === void 0) { variables = {}; }
        // When we're constructing URLs, server URLs with trailing slashes cause problems with doing
        // lookups, so if we have one here on, slice it off.
        return stripTrailingSlash(url.replace(SERVER_VARIABLE_REGEX, function (original, key) {
            var userVariable = (0, get_user_variable_1["default"])(_this.user, key);
            if (userVariable) {
                return userVariable;
            }
            if (key in variables) {
                var data = variables[key];
                if (typeof data === 'object') {
                    if (!Array.isArray(data) && data !== null && 'default' in data) {
                        return data["default"];
                    }
                }
                else {
                    return data;
                }
            }
            return original;
        }));
    };
    /**
     * Retrieve an Operation of Webhook class instance for a given path and method.
     *
     * @param path Path to lookup and retrieve.
     * @param method HTTP Method to retrieve on the path.
     */
    Oas.prototype.operation = function (path, method, opts) {
        var _a, _b, _c, _d;
        if (opts === void 0) { opts = {}; }
        // If we're unable to locate an operation for this path+method combination within the API
        // definition, we should still set an empty schema on the operation in the `Operation` class
        // because if we don't trying to use any of the accessors on that class are going to fail as
        // `schema` will be `undefined`.
        var operation = {
            parameters: []
        };
        if (opts.isWebhook) {
            var api = this.api;
            // Typecasting this to a `PathsObject` because we don't have `$ref` pointers here.
            if ((_a = api === null || api === void 0 ? void 0 : api.webhooks[path]) === null || _a === void 0 ? void 0 : _a[method]) {
                operation = api.webhooks[path][method];
                return new operation_1.Webhook(api, path, method, operation);
            }
        }
        if ((_d = (_c = (_b = this === null || this === void 0 ? void 0 : this.api) === null || _b === void 0 ? void 0 : _b.paths) === null || _c === void 0 ? void 0 : _c[path]) === null || _d === void 0 ? void 0 : _d[method]) {
            operation = this.api.paths[path][method];
        }
        return new operation_1["default"](this.api, path, method, operation);
    };
    Oas.prototype.findOperationMatches = function (url) {
        var _a;
        var _this = this;
        var _b = new URL(url), origin = _b.origin, hostname = _b.hostname;
        var originRegExp = new RegExp(origin, 'i');
        var _c = this.api, servers = _c.servers, paths = _c.paths;
        var pathName;
        var targetServer;
        var matchedServer;
        if (!servers || !servers.length) {
            // If this API definition doesn't have any servers set up let's treat it as if it were
            // https://example.com because that's the default origin we add in `normalizedUrl` under the
            // same circumstances. Without this we won't be able to match paths within what is otherwise
            // a valid OpenAPI definition.
            matchedServer = {
                url: 'https://example.com'
            };
        }
        else {
            matchedServer = servers.find(function (s) { return originRegExp.exec(_this.replaceUrl(s.url, s.variables || {})); });
            if (!matchedServer) {
                var hostnameRegExp_1 = new RegExp(hostname);
                matchedServer = servers.find(function (s) { return hostnameRegExp_1.exec(_this.replaceUrl(s.url, s.variables || {})); });
            }
        }
        // If we **still** haven't found a matching server, then the OAS server URL might have server
        // variables and we should loosen it up with regex to try to discover a matching path.
        //
        // For example if an OAS has `https://{region}.node.example.com/v14` set as its server URL, and
        // the `this.user` object has a `region` value of `us`, if we're trying to locate an operation
        // for https://eu.node.example.com/v14/api/esm we won't be able to because normally the users
        // `region` of `us` will be transposed in and we'll be trying to locate `eu.node.example.com`
        // in `us.node.example.com` -- which won't work.
        //
        // So what this does is transform `https://{region}.node.example.com/v14` into
        // `https://([-_a-zA-Z0-9[\\]]+).node.example.com/v14`, and from there we'll be able to match
        // https://eu.node.example.com/v14/api/esm and ultimately find the operation matches for
        // `/api/esm`.
        if (!matchedServer) {
            var matchedServerAndPath = servers
                .map(function (server) {
                var rgx = transformUrlIntoRegex(server.url);
                var found = new RegExp(rgx).exec(url);
                if (!found) {
                    return false;
                }
                return {
                    matchedServer: server,
                    pathName: url.split(new RegExp(rgx)).slice(-1).pop()
                };
            })
                .filter(Boolean);
            if (!matchedServerAndPath.length) {
                return undefined;
            }
            pathName = matchedServerAndPath[0].pathName;
            targetServer = __assign({}, matchedServerAndPath[0].matchedServer);
        }
        else {
            // Instead of setting `url` directly against `matchedServer` we need to set it to an
            // intermediary object as directly modifying `matchedServer.url` will in turn update
            // `this.servers[idx].url` which we absolutely do not want to happen.
            targetServer = __assign(__assign({}, matchedServer), { url: this.replaceUrl(matchedServer.url, matchedServer.variables || {}) });
            _a = url.split(new RegExp(targetServer.url, 'i')), pathName = _a[1];
        }
        if (pathName === undefined)
            return undefined;
        if (pathName === '')
            pathName = '/';
        var annotatedPaths = generatePathMatches(paths, pathName, targetServer.url);
        if (!annotatedPaths.length)
            return undefined;
        return annotatedPaths;
    };
    /**
     * Discover an operation in an OAS from a fully-formed URL and HTTP method. Will return an object
     * containing a `url` object and another one for `operation`. This differs from `getOperation()`
     * in that it does not return an instance of the `Operation` class.
     *
     * @param url A full URL to look up.
     * @param method The cooresponding HTTP method to look up.
     */
    Oas.prototype.findOperation = function (url, method) {
        var annotatedPaths = this.findOperationMatches(url);
        if (!annotatedPaths) {
            return undefined;
        }
        var matches = filterPathMethods(annotatedPaths, method);
        if (!matches.length)
            return undefined;
        return findTargetPath(matches);
    };
    /**
     * Discover an operation in an OAS from a fully-formed URL without an HTTP method. Will return an
     * object containing a `url` object and another one for `operation`.
     *
     * @param url A full URL to look up.
     */
    Oas.prototype.findOperationWithoutMethod = function (url) {
        var annotatedPaths = this.findOperationMatches(url);
        if (!annotatedPaths) {
            return undefined;
        }
        return findTargetPath(annotatedPaths);
    };
    /**
     * Retrieve an operation in an OAS from a fully-formed URL and HTTP method. Differs from
     * `findOperation` in that while this method will return an `Operation` instance,
     * `findOperation()` does not.
     *
     * @param url A full URL to look up.
     * @param method The cooresponding HTTP method to look up.
     */
    Oas.prototype.getOperation = function (url, method) {
        var op = this.findOperation(url, method);
        if (op === undefined) {
            return undefined;
        }
        return this.operation(op.url.nonNormalizedPath, method);
    };
    /**
     * With an object of user information, retrieve the appropriate API auth keys from the current
     * OAS definition.
     *
     * @see {@link https://docs.readme.com/docs/passing-data-to-jwt}
     * @param user User
     * @param selectedApp The user app to retrieve an auth key for.
     */
    Oas.prototype.getAuth = function (user, selectedApp) {
        var _a, _b;
        if (!((_b = (_a = this.api) === null || _a === void 0 ? void 0 : _a.components) === null || _b === void 0 ? void 0 : _b.securitySchemes)) {
            return {};
        }
        return (0, get_auth_1["default"])(this.api, user, selectedApp);
    };
    /**
     * Returns the `paths` object that exists in this API definition but with every `method` mapped
     * to an instance of the `Operation` class.
     *
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#oasObject}
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-object}
     */
    Oas.prototype.getPaths = function () {
        var _this = this;
        /**
         * Because a path doesn't need to contain a keyed-object of HTTP methods, we should exclude
         * anything from within the paths object that isn't a known HTTP method.
         *
         * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#fixed-fields-7}
         * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-7}
         */
        var paths = {};
        Object.keys(this.api.paths ? this.api.paths : []).forEach(function (path) {
            // If this is a specification extension then we should ignore it.
            if (path.startsWith('x-')) {
                return;
            }
            paths[path] = {};
            // Though this library is generally unaware of `$ref` pointers we're making a singular
            // exception with this accessor out of convenience.
            if ('$ref' in _this.api.paths[path]) {
                _this.api.paths[path] = utils_1["default"].findSchemaDefinition(_this.api.paths[path].$ref, _this.api);
            }
            Object.keys(_this.api.paths[path]).forEach(function (method) {
                if (!utils_1.supportedMethods.has(method))
                    return;
                paths[path][method] = _this.operation(path, method);
            });
        });
        return paths;
    };
    /**
     * Returns the `webhooks` object that exists in this API definition but with every `method`
     * mapped to an instance of the `Webhook` class.
     *
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#oasObject}
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-object}
     */
    Oas.prototype.getWebhooks = function () {
        var _this = this;
        var webhooks = {};
        var api = this.api;
        Object.keys(api.webhooks ? api.webhooks : []).forEach(function (id) {
            webhooks[id] = {};
            Object.keys(api.webhooks[id]).forEach(function (method) {
                webhooks[id][method] = _this.operation(id, method, { isWebhook: true });
            });
        });
        return webhooks;
    };
    /**
     * Return an array of all tag names that exist on this API definition.
     *
     * Note: This method right now does **not** factor in webhooks that have tags.
     *
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#oasObject}
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-object}
     * @param setIfMissing If a tag is not present on an operation that operations path will be added
     *    into the list of tags returned.
     */
    Oas.prototype.getTags = function (setIfMissing) {
        if (setIfMissing === void 0) { setIfMissing = false; }
        var allTags = new Set();
        Object.entries(this.getPaths()).forEach(function (_a) {
            var path = _a[0], operations = _a[1];
            Object.values(operations).forEach(function (operation) {
                var tags = operation.getTags();
                if (setIfMissing && !tags.length) {
                    allTags.add(path);
                    return;
                }
                tags.forEach(function (tag) {
                    allTags.add(tag.name);
                });
            });
        });
        return Array.from(allTags);
    };
    /**
     * Determine if a given a custom specification extension exists within the API definition.
     *
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions}
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specificationExtensions}
     * @param extension Specification extension to lookup.
     */
    Oas.prototype.hasExtension = function (extension) {
        return Boolean(this.api && extension in this.api);
    };
    /**
     * Retrieve a custom specification extension off of the API definition.
     *
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions}
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specificationExtensions}
     * @param extension Specification extension to lookup.
     */
    Oas.prototype.getExtension = function (extension) {
        var _a;
        return (_a = this.api) === null || _a === void 0 ? void 0 : _a[extension];
    };
    /**
     * Retrieve any circular `$ref` pointers that maybe present within the API definition.
     *
     * This method requires that you first dereference the definition.
     *
     * @see Oas.dereference
     */
    Oas.prototype.getCircularReferences = function () {
        if (!this.dereferencing.complete) {
            throw new Error('#dereference() must be called first in order for this method to obtain circular references.');
        }
        return this.dereferencing.circularRefs;
    };
    /**
     * Dereference the current OAS definition so it can be parsed free of worries of `$ref` schemas
     * and circular structures.
     *
     */
    Oas.prototype.dereference = function (opts) {
        if (opts === void 0) { opts = { preserveRefAsJSONSchemaTitle: false }; }
        return __awaiter(this, void 0, void 0, function () {
            var _a, api, promises, parser;
            var _this = this;
            return __generator(this, function (_b) {
                if (this.dereferencing.complete) {
                    return [2 /*return*/, new Promise(function (resolve) {
                            resolve(true);
                        })];
                }
                if (this.dereferencing.processing) {
                    return [2 /*return*/, new Promise(function (resolve, reject) {
                            _this.promises.push({ resolve: resolve, reject: reject });
                        })];
                }
                this.dereferencing.processing = true;
                _a = this, api = _a.api, promises = _a.promises;
                // Because referencing will eliminate any lineage back to the original `$ref`, information that
                // we might need at some point, we should run through all available component schemas and denote
                // what their name is so that when dereferencing happens below those names will be preserved.
                if (api && api.components && api.components.schemas && typeof api.components.schemas === 'object') {
                    Object.keys(api.components.schemas).forEach(function (schemaName) {
                        if (opts.preserveRefAsJSONSchemaTitle) {
                            // This may result in some data loss if there's already a `title` present, but in the case
                            // where we want to generate code for the API definition (see http://npm.im/api), we'd
                            // prefer to retain original reference name as a title for any generated types.
                            api.components.schemas[schemaName].title = schemaName;
                        }
                        api.components.schemas[schemaName]['x-readme-ref-name'] = schemaName;
                    });
                }
                parser = new json_schema_ref_parser_1["default"]();
                return [2 /*return*/, parser
                        .dereference(api || {}, {
                        resolve: {
                            // We shouldn't be resolving external pointers at this point so just ignore them.
                            external: false
                        },
                        dereference: {
                            // If circular `$refs` are ignored they'll remain in the OAS as `$ref: String`, otherwise
                            // `$ref‘ just won't exist. This allows us to do easy circular reference detection.
                            circular: 'ignore'
                        }
                    })
                        .then(function (dereferenced) {
                        var circularRefs = [];
                        if (parser.$refs.circular) {
                            circularRefs = parser.$refs.circularRefs.map(function (pointer) {
                                // The circular $refs that are coming out of `json-schema-ref-parser` are prefixed
                                // with the schema path (file path, url, whatever) that the schema exists in. Because
                                // we don't care about this information for this reporting mechanism, and only the
                                // $ref pointer, we're removing it.
                                return "#".concat(pointer.split('#')[1]);
                            });
                        }
                        _this.api = dereferenced;
                        _this.promises = promises;
                        _this.dereferencing = {
                            processing: false,
                            complete: true,
                            circularRefs: circularRefs
                        };
                        // Used for debugging that dereferencing promise awaiting works.
                        if (opts.cb) {
                            opts.cb();
                        }
                    })
                        .then(function () {
                        return _this.promises.map(function (deferred) { return deferred.resolve(); });
                    })];
            });
        });
    };
    return Oas;
}());
exports["default"] = Oas;
