"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "default", {
    enumerable: true,
    get: function() {
        return _default;
    }
});
var _refJs = /*#__PURE__*/ _interopRequireDefault(require("./ref.js"));
var _pointerJs = /*#__PURE__*/ _interopRequireDefault(require("./pointer.js"));
var _ono = require("@jsdevtools/ono");
var _urlJs = /*#__PURE__*/ _interopRequireWildcard(require("./util/url.js"));
function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
function _getRequireWildcardCache(nodeInterop) {
    if (typeof WeakMap !== "function") return null;
    var cacheBabelInterop = new WeakMap();
    var cacheNodeInterop = new WeakMap();
    return (_getRequireWildcardCache = function(nodeInterop) {
        return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
    })(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
    if (!nodeInterop && obj && obj.__esModule) {
        return obj;
    }
    if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
        return {
            default: obj
        };
    }
    var cache = _getRequireWildcardCache(nodeInterop);
    if (cache && cache.has(obj)) {
        return cache.get(obj);
    }
    var newObj = {};
    var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
    for(var key in obj){
        if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
            var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
            if (desc && (desc.get || desc.set)) {
                Object.defineProperty(newObj, key, desc);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    newObj.default = obj;
    if (cache) {
        cache.set(obj, newObj);
    }
    return newObj;
}
var _default = dereference;
/**
 * Crawls the JSON schema, finds all JSON references, and dereferences them.
 * This method mutates the JSON schema object, replacing JSON references with their resolved value.
 *
 * @param {$RefParser} parser
 * @param {$RefParserOptions} options
 */ function dereference(parser, options) {
    // console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path);
    var dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, "#", new Set(), new Set(), new Map(), parser.$refs, options);
    parser.$refs.circular = dereferenced.circular;
    parser.schema = dereferenced.value;
}
/**
 * Recursively crawls the given value, and dereferences any JSON references.
 *
 * @param {*} obj - The value to crawl. If it's not an object or array, it will be ignored.
 * @param {string} path - The full path of `obj`, possibly with a JSON Pointer in the hash
 * @param {string} pathFromRoot - The path of `obj` from the schema root
 * @param {Set<object>} parents - An array of the parent objects that have already been dereferenced
 * @param {Set<object>} processedObjects - An array of all the objects that have already been processed
 * @param {Map<string,object>} dereferencedCache - An map of all the dereferenced objects
 * @param {$Refs} $refs
 * @param {$RefParserOptions} options
 * @returns {{value: object, circular: boolean}}
 */ function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options) {
    var dereferenced;
    var result = {
        value: obj,
        circular: false
    };
    var isExcludedPath = options.dereference.excludedPathMatcher;
    if (options.dereference.circular === "ignore" || !processedObjects.has(obj)) {
        if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
            parents.add(obj);
            processedObjects.add(obj);
            if (_refJs.default.isAllowed$Ref(obj, options)) {
                dereferenced = dereference$Ref(obj, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options);
                result.circular = dereferenced.circular;
                result.value = dereferenced.value;
            } else {
                var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
                try {
                    for(var _iterator = Object.keys(obj)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
                        var key = _step.value;
                        var keyPath = _pointerJs.default.join(path, key);
                        var keyPathFromRoot = _pointerJs.default.join(pathFromRoot, key);
                        if (isExcludedPath(keyPathFromRoot)) {
                            continue;
                        }
                        var value = obj[key];
                        var circular = false;
                        if (_refJs.default.isAllowed$Ref(value, options)) {
                            dereferenced = dereference$Ref(value, keyPath, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options);
                            circular = dereferenced.circular;
                            // Avoid pointless mutations; breaks frozen objects to no profit
                            if (obj[key] !== dereferenced.value) {
                                obj[key] = dereferenced.value;
                                if (options.dereference.onDereference) {
                                    options.dereference.onDereference(value.$ref, obj[key]);
                                }
                            }
                        } else {
                            if (!parents.has(value)) {
                                dereferenced = crawl(value, keyPath, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options);
                                circular = dereferenced.circular;
                                // Avoid pointless mutations; breaks frozen objects to no profit
                                if (obj[key] !== dereferenced.value) {
                                    obj[key] = dereferenced.value;
                                }
                            } else {
                                circular = foundCircularReference(keyPath, $refs, options);
                            }
                        }
                        // Set the "isCircular" flag if this or any other property is circular
                        result.circular = result.circular || circular;
                    }
                } catch (err) {
                    _didIteratorError = true;
                    _iteratorError = err;
                } finally{
                    try {
                        if (!_iteratorNormalCompletion && _iterator.return != null) {
                            _iterator.return();
                        }
                    } finally{
                        if (_didIteratorError) {
                            throw _iteratorError;
                        }
                    }
                }
            }
            parents.delete(obj);
        }
    }
    return result;
}
/**
 * Dereferences the given JSON Reference, and then crawls the resulting value.
 *
 * @param {{$ref: string}} $ref - The JSON Reference to resolve
 * @param {string} path - The full path of `$ref`, possibly with a JSON Pointer in the hash
 * @param {string} pathFromRoot - The path of `$ref` from the schema root
 * @param {Set<object>} parents - An array of the parent objects that have already been dereferenced
 * @param {Set<object>} processedObjects - An array of all the objects that have already been dereferenced
 * @param {Map<string,object>} dereferencedCache - An map of all the dereferenced objects
 * @param {$Refs} $refs
 * @param {$RefParserOptions} options
 * @returns {{value: object, circular: boolean}}
 */ function dereference$Ref($ref, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options) {
    // console.log('Dereferencing $ref pointer "%s" at %s', $ref.$ref, path);
    var $refPath = _urlJs.resolve(path, $ref.$ref);
    var cache = dereferencedCache.get($refPath);
    if (cache) {
        var refKeys = Object.keys($ref);
        if (refKeys.length > 1) {
            var extraKeys = {};
            var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
            try {
                for(var _iterator = refKeys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
                    var key = _step.value;
                    if (key !== "$ref" && !(key in cache.value)) {
                        extraKeys[key] = $ref[key];
                    }
                }
            } catch (err) {
                _didIteratorError = true;
                _iteratorError = err;
            } finally{
                try {
                    if (!_iteratorNormalCompletion && _iterator.return != null) {
                        _iterator.return();
                    }
                } finally{
                    if (_didIteratorError) {
                        throw _iteratorError;
                    }
                }
            }
            return {
                circular: cache.circular,
                value: Object.assign({}, cache.value, extraKeys)
            };
        }
        return cache;
    }
    var pointer = $refs._resolve($refPath, path, options);
    if (pointer === null) {
        return {
            circular: false,
            value: null
        };
    }
    // Check for circular references
    var directCircular = pointer.circular;
    var circular = directCircular || parents.has(pointer.value);
    circular && foundCircularReference(path, $refs, options);
    // Dereference the JSON reference
    var dereferencedValue = _refJs.default.dereference($ref, pointer.value);
    // Crawl the dereferenced value (unless it's circular)
    if (!circular) {
        // Determine if the dereferenced value is circular
        var dereferenced = crawl(dereferencedValue, pointer.path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options);
        circular = dereferenced.circular;
        dereferencedValue = dereferenced.value;
    }
    if (circular && !directCircular && options.dereference.circular === "ignore") {
        // The user has chosen to "ignore" circular references, so don't change the value
        dereferencedValue = $ref;
    }
    if (directCircular) {
        // The pointer is a DIRECT circular reference (i.e. it references itself).
        // So replace the $ref path with the absolute path from the JSON Schema root
        dereferencedValue.$ref = pathFromRoot;
    }
    var dereferencedObject = {
        circular: circular,
        value: dereferencedValue
    };
    // only cache if no extra properties than $ref
    if (Object.keys($ref).length === 1) {
        dereferencedCache.set($refPath, dereferencedObject);
    }
    return dereferencedObject;
}
/**
 * Called when a circular reference is found.
 * It sets the {@link $Refs#circular} flag, and throws an error if options.dereference.circular is false.
 *
 * @param {string} keyPath - The JSON Reference path of the circular reference
 * @param {$Refs} $refs
 * @param {$RefParserOptions} options
 * @returns {boolean} - always returns true, to indicate that a circular reference was found
 */ function foundCircularReference(keyPath, $refs, options) {
    $refs.circular = true;
    if (!options.dereference.circular) {
        throw _ono.ono.reference("Circular $ref pointer found at ".concat(keyPath));
    }
    return true;
}
