"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "jsonSchemaDeref", {
    enumerable: true,
    get: ()=>jsonSchemaDeref
});
/** Return JSON schema ref if input is of `NodeRef` type */ const getRef = (node)=>node != null && typeof node === "object" && "$ref" in node && typeof node.$ref === "string" ? node.$ref : undefined;
/** Parse a JSON schema ref into a path array, or return undefined */ const parseRefMaybe = (ref)=>{
    if (ref[0] !== "#") {
        return undefined;
    }
    const props = [];
    let startIndex = 1;
    let index = 1;
    let char;
    while(index < ref.length){
        while((char = ref.charCodeAt(index++)) && char !== 47 /*'/'*/ );
        const prop = ref.slice(startIndex, index - 1);
        startIndex = index;
        if (prop) props.push(prop);
    }
    return props.length ? props : undefined;
};
const NOT_FOUND_SYMBOL = Symbol();
/** Get value at given JSON schema path or return `NOT_FOUND_SYMBOL` */ const getValueAtPath = (input, ref)=>{
    let node = input;
    for(let index = 0; index < ref.length; index++){
        const part = ref[index];
        if (node != null && typeof node === "object" && part in node) {
            node = node[part];
        } else {
            node = NOT_FOUND_SYMBOL;
            break;
        }
    }
    return node;
};
/** Find all JSON schema refs recursively and add them to `refs` Map */ const findRefsRec = (node, refs, path)=>{
    if (node == null || typeof node !== "object") {} else if (Array.isArray(node)) {
        for(let index = 0, l = node.length; index < l; index++){
            const value = node[index];
            const ref = getRef(value);
            if (ref) {
                const targetRef = parseRefMaybe(ref);
                if (targetRef) refs.set([
                    ...path,
                    index
                ], targetRef);
            } else if (value != null && typeof value === "object") {
                path.push(index);
                findRefsRec(value, refs, path);
                path.pop();
            }
        }
    } else {
        const record = node;
        for(const key in record){
            const value1 = record[key];
            const ref1 = getRef(value1);
            if (ref1) {
                const targetRef1 = parseRefMaybe(ref1);
                if (targetRef1) refs.set([
                    ...path,
                    key
                ], targetRef1);
            } else if (value1 != null && typeof value1 === "object") {
                path.push(key);
                findRefsRec(value1, refs, path);
                path.pop();
            }
        }
    }
};
/** Detect whether target (where we set the source value) is a nested path inside the source path */ const isSelfReferencingRefEntry = (target, source)=>{
    for(let index = 0; index < source.length; index++){
        if (source[index] !== target[index]) return false;
    }
    return true;
};
/** Return sorted refs entries. Longest target paths will be returned first */ const getSortedRefEntries = (refs)=>{
    const entries = [
        ...refs.entries()
    ].sort((a, b)=>b[1].length - a[1].length);
    // Filter out self-referenceing paths. If we set nested targets to source values, we'd
    // create unserializable circular references
    return entries.filter((entry)=>!isSelfReferencingRefEntry(entry[0], entry[1]));
};
function jsonSchemaDeref(input) {
    // Find all JSON schema refs paths
    const refs = new Map();
    findRefsRec(input, refs, []);
    // Shallow copy output
    const output = {
        ...input
    };
    // Process all ref entries with deepest targets first
    nextRef: for (const [target, source] of getSortedRefEntries(refs)){
        let inputNode = input;
        let outputNode = output;
        let targetIndex = 0;
        // For each path part on the target, traverse the output and clone the input
        // to not pollute it
        for(; targetIndex < target.length - 1; targetIndex++){
            const part = target[targetIndex];
            if (inputNode == null || typeof inputNode !== "object" || !(part in inputNode)) {
                break;
            } else if (outputNode[part] === inputNode[part]) {
                // Copy the input on the output if references are equal
                outputNode[part] = Array.isArray(inputNode[part]) ? [
                    ...inputNode[part]
                ] : {
                    ...inputNode[part]
                };
                inputNode = inputNode[part];
                outputNode = outputNode[part];
            } else {
                break;
            }
        }
        // For each remaining part on the target, continue traversing the output
        for(; targetIndex < target.length - 1; targetIndex++){
            const part1 = target[targetIndex];
            if (outputNode == null || typeof outputNode !== "object" || !(part1 in outputNode)) {
                continue nextRef;
            } else {
                outputNode = outputNode[part1];
            }
        }
        // Get value from output
        let sourceValue = getValueAtPath(output, source);
        if (sourceValue === NOT_FOUND_SYMBOL) {
            // If no value was found, try to get a value from the input instead
            sourceValue = getValueAtPath(input, source);
            // Otherwise, skip this ref
            if (sourceValue === NOT_FOUND_SYMBOL) continue;
        }
        // Set the source value on the target path
        // The for-loops prior have made sure that the output has already been deeply
        // cloned and traversed for the entire path
        outputNode[target[target.length - 1]] = sourceValue;
    }
    // Return the output with resolved refs
    return output;
}

//# sourceMappingURL=jsonSchemaDeref.js.map