"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.deregisterAllowables = exports.deregisterAllowable = exports.registerAllowables = exports.registerAllowable = exports.deregisterAll = exports.deregisterMock = exports.registerMock = exports.warnOnUnregistered = exports.warnOnReplace = exports.resetCache = exports.disable = exports.enable = void 0;
var m = require('module');
;
var registeredMocks = {};
var registeredAllowables = new Set();
var originalLoader = null;
var originalCache = {};
var options = {};
var defaultOptions = {
    useCleanCache: false,
    warnOnReplace: true,
    warnOnUnregistered: true
};
function _getEffectiveOptions(opts) {
    var options = {};
    Object.keys(defaultOptions).forEach(function (key) {
        if (opts && opts.hasOwnProperty(key)) {
            options[key] = opts[key];
        }
        else {
            options[key] = defaultOptions[key];
        }
    });
    return options;
}
/*
 * Loader function that used when hooking is enabled.
 * if the requested module is registered as a mock, return the mock.
 * otherwise, invoke the original loader + put warning in the output.
 */
function _hookedLoader(request, parent, isMain) {
    if (!originalLoader) {
        throw new Error("Loader has not been hooked");
    }
    if (registeredMocks.hasOwnProperty(request)) {
        return registeredMocks[request];
    }
    if (!registeredAllowables.has(request) && options.warnOnUnregistered) {
        console.warn("WARNING: loading non-allowed module: " + request);
    }
    return originalLoader(request, parent, isMain);
}
/**
 * Remove references to modules in the cache from
 * their parents' children.
 */
function _removeParentReferences() {
    Object.keys(m._cache).forEach(function (k) {
        var _a;
        if (k.indexOf('\.node') === -1) {
            // don't touch native modules, because they're special
            var mod = m._cache[k];
            var idx = (_a = mod === null || mod === void 0 ? void 0 : mod.parent) === null || _a === void 0 ? void 0 : _a.children.indexOf(mod);
            if (idx > -1) {
                mod.parent.children.splice(idx, 1);
            }
        }
    });
}
/*
 * Starting in node 0.12 node won't reload native modules
 * The reason is that native modules can register themselves to be loaded automatically
 * This will re-populate the cache with the native modules that have not been mocked
 */
function _repopulateNative() {
    Object.keys(originalCache).forEach(function (k) {
        if (k.indexOf('\.node') > -1 && !m._cache[k]) {
            m._cache[k] = originalCache[k];
        }
    });
}
/*
 * Enable function, hooking the Node loader with options.
 */
function enable(opts) {
    if (originalLoader) {
        // Already hooked
        return;
    }
    options = _getEffectiveOptions(opts);
    if (options.useCleanCache) {
        originalCache = m._cache;
        m._cache = {};
        _repopulateNative();
    }
    originalLoader = m._load;
    m._load = _hookedLoader;
}
exports.enable = enable;
/*
 * Disables mock loading, reverting to normal 'require' behaviour.
 */
function disable() {
    if (!originalLoader)
        return;
    if (options.useCleanCache) {
        Object.keys(m._cache).forEach(function (k) {
            if (k.indexOf('\.node') > -1 && !originalCache[k]) {
                originalCache[k] = m._cache[k];
            }
        });
        _removeParentReferences();
        m._cache = originalCache;
        originalCache = {};
    }
    m._load = originalLoader;
    originalLoader = null;
}
exports.disable = disable;
/*
* If the clean cache option is in effect, reset the module cache to an empty
* state. Calling this function when the clean cache option is not in effect
* will have no ill effects, but will do nothing.
*/
function resetCache() {
    if (options.useCleanCache && originalCache) {
        _removeParentReferences();
        m._cache = {};
        _repopulateNative();
    }
}
exports.resetCache = resetCache;
/*
 * Enable or disable warnings to the console when previously registered mocks are replaced.
 */
function warnOnReplace(enable) {
    options.warnOnReplace = enable;
}
exports.warnOnReplace = warnOnReplace;
/*
 * Enable or disable warnings to the console when modules are loaded that have
 * not been registered as a mock.
 */
function warnOnUnregistered(enable) {
    options.warnOnUnregistered = enable;
}
exports.warnOnUnregistered = warnOnUnregistered;
/*
 * Register a mock object for the specified module.
 */
function registerMock(mod, mock) {
    if (options.warnOnReplace && registeredMocks.hasOwnProperty(mod)) {
        console.warn("WARNING: Replacing existing mock for module: " + mod);
    }
    registeredMocks[mod] = mock;
}
exports.registerMock = registerMock;
/*
 * Deregister a mock object for the specified module.
 */
function deregisterMock(mod) {
    if (registeredMocks.hasOwnProperty(mod)) {
        delete registeredMocks[mod];
    }
}
exports.deregisterMock = deregisterMock;
/*
 * Deregister all mocks.
 */
function deregisterAll() {
    registeredMocks = {};
    registeredAllowables = new Set();
}
exports.deregisterAll = deregisterAll;
/*
   Register a module as 'allowed'.
   This will allow the module to be loaded without mock otherwise a warning would be thrown.
 */
function registerAllowable(mod) {
    registeredAllowables.add(mod);
}
exports.registerAllowable = registerAllowable;
/*
 * Register an array of 'allowed' modules.
 */
function registerAllowables(mods) {
    mods.forEach(function (mod) { return registerAllowable(mod); });
}
exports.registerAllowables = registerAllowables;
/*
 * Deregister a module as 'allowed'.
 */
function deregisterAllowable(mod) {
    if (registeredAllowables.hasOwnProperty(mod)) {
        registeredAllowables.delete(mod);
    }
}
exports.deregisterAllowable = deregisterAllowable;
/*
 * Deregister an array of modules as 'allowed'.
 */
function deregisterAllowables(mods) {
    mods.forEach(function (mod) {
        deregisterAllowable(mod);
    });
}
exports.deregisterAllowables = deregisterAllowables;
