"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "HooksBucket", {
    enumerable: true,
    get: function() {
        return HooksBucket;
    }
});
const _constants = require("./constants");
function _check_private_redeclaration(obj, privateCollection) {
    if (privateCollection.has(obj)) {
        throw new TypeError("Cannot initialize the same private elements twice on an object");
    }
}
function _class_apply_descriptor_get(receiver, descriptor) {
    if (descriptor.get) {
        return descriptor.get.call(receiver);
    }
    return descriptor.value;
}
function _class_apply_descriptor_set(receiver, descriptor, value) {
    if (descriptor.set) {
        descriptor.set.call(receiver, value);
    } else {
        if (!descriptor.writable) {
            throw new TypeError("attempted to set read only private field");
        }
        descriptor.value = value;
    }
}
function _class_extract_field_descriptor(receiver, privateMap, action) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to " + action + " private field on non-instance");
    }
    return privateMap.get(receiver);
}
function _class_private_field_get(receiver, privateMap) {
    var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
    return _class_apply_descriptor_get(receiver, descriptor);
}
function _class_private_field_init(obj, privateMap, value) {
    _check_private_redeclaration(obj, privateMap);
    privateMap.set(obj, value);
}
function _class_private_field_set(receiver, privateMap, value) {
    var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
    _class_apply_descriptor_set(receiver, descriptor, value);
    return value;
}
function _class_private_method_get(receiver, privateSet, fn) {
    if (!privateSet.has(receiver)) {
        throw new TypeError("attempted to get private field on non-instance");
    }
    return fn;
}
function _class_private_method_init(obj, privateSet) {
    _check_private_redeclaration(obj, privateSet);
    privateSet.add(obj);
}
/**
 * @typedef {object} HookEntry
 * @property {Function} callback The callback function.
 * @property {number} orderIndex The order index.
 * @property {boolean} runOnce Indicates if the hook should run only once.
 * @property {boolean} initialHook Indicates if it is an initial hook - which means that the hook
 * always stays at the same index position even after update.
 * @property {boolean} skip Indicates if the hook was removed.
 */ /**
 * The maximum number of hooks that can be skipped before the bucket is cleaned up.
 */ const MAX_SKIPPED_HOOKS_COUNT = 100;
var /**
   * A map that stores hooks.
   *
   * @type {Map<string, HookEntry>}
   */ _hooks = /*#__PURE__*/ new WeakMap(), /**
   * A map that stores the number of skipped hooks.
   */ _skippedHooksCount = /*#__PURE__*/ new WeakMap(), /**
   * A set that stores hook names that need to be re-sorted.
   */ _needsSort = /*#__PURE__*/ new WeakMap(), /**
   * Creates a initial collection for the provided hook name.
   *
   * @param {string} hookName The name of the hook.
   */ _createHooksCollection = /*#__PURE__*/ new WeakSet();
class HooksBucket {
    /**
   * Gets all hooks for the provided hook name.
   *
   * @param {string} hookName The name of the hook.
   * @returns {HookEntry[]}
   */ getHooks(hookName) {
        return _class_private_field_get(this, _hooks).get(hookName) ?? [];
    }
    /**
   * Adds a new hook to the collection.
   *
   * @param {string} hookName The name of the hook.
   * @param {Function} callback The callback function to add.
   * @param {{ orderIndex?: number, runOnce?: boolean, initialHook?: boolean }} options The options object.
   */ add(hookName, callback, options = {}) {
        if (!_class_private_field_get(this, _hooks).has(hookName)) {
            _class_private_method_get(this, _createHooksCollection, createHooksCollection).call(this, hookName);
            _constants.REGISTERED_HOOKS.push(hookName);
        }
        const hooks = _class_private_field_get(this, _hooks).get(hookName);
        const existingHook = hooks.find((hook)=>hook.callback === callback);
        if (existingHook) {
            if (existingHook.skip === true) {
                // Re-enable the hook if it was previously added and then removed.
                existingHook.skip = false;
            }
            // adding the same hook twice is now silently ignored
            return;
        }
        const orderIndex = Number.isInteger(options.orderIndex) ? options.orderIndex : 0;
        const runOnce = !!options.runOnce;
        const initialHook = !!options.initialHook;
        let foundInitialHook = false;
        if (initialHook) {
            const initialHookEntry = hooks.find((hook)=>hook.initialHook);
            if (initialHookEntry) {
                initialHookEntry.callback = callback;
                foundInitialHook = true;
            }
        }
        if (!foundInitialHook) {
            hooks.push({
                callback,
                orderIndex,
                runOnce,
                initialHook,
                skip: false
            });
            let needsSort = _class_private_field_get(this, _needsSort).has(hookName);
            if (!needsSort && orderIndex !== 0) {
                needsSort = true;
                _class_private_field_get(this, _needsSort).add(hookName);
            }
            if (needsSort && hooks.length > 1) {
                _class_private_field_get(this, _hooks).set(hookName, hooks.toSorted((a, b)=>a.orderIndex - b.orderIndex));
            }
        }
    }
    /**
   * Checks if there are any hooks for the provided hook name.
   *
   * @param {string} hookName The name of the hook.
   * @returns {boolean}
   */ has(hookName) {
        return _class_private_field_get(this, _hooks).has(hookName) && _class_private_field_get(this, _hooks).get(hookName).length > 0;
    }
    /**
   * Removes a hook from the collection. If the hook was found and removed,
   * the method returns `true`, otherwise `false`.
   *
   * @param {string} hookName The name of the hook.
   * @param {*} callback The callback function to remove.
   * @returns {boolean}
   */ remove(hookName, callback) {
        if (!_class_private_field_get(this, _hooks).has(hookName)) {
            return false;
        }
        const hooks = _class_private_field_get(this, _hooks).get(hookName);
        const hookEntry = hooks.find((hook)=>hook.callback === callback);
        if (hookEntry) {
            let skippedHooksCount = _class_private_field_get(this, _skippedHooksCount).get(hookName);
            hookEntry.skip = true;
            skippedHooksCount += 1;
            if (skippedHooksCount > MAX_SKIPPED_HOOKS_COUNT) {
                _class_private_field_get(this, _hooks).set(hookName, hooks.filter((hook)=>!hook.skip));
                skippedHooksCount = 0;
            }
            _class_private_field_get(this, _skippedHooksCount).set(hookName, skippedHooksCount);
            return true;
        }
        return false;
    }
    /**
   * Destroys the bucket.
   */ destroy() {
        _class_private_field_get(this, _hooks).clear();
        _class_private_field_get(this, _skippedHooksCount).clear();
        _class_private_field_set(this, _hooks, null);
        _class_private_field_set(this, _skippedHooksCount, null);
    }
    constructor(){
        _class_private_method_init(this, _createHooksCollection);
        _class_private_field_init(this, _hooks, {
            writable: true,
            value: void 0
        });
        _class_private_field_init(this, _skippedHooksCount, {
            writable: true,
            value: void 0
        });
        _class_private_field_init(this, _needsSort, {
            writable: true,
            value: void 0
        });
        _class_private_field_set(this, _hooks, new Map());
        _class_private_field_set(this, _skippedHooksCount, new Map());
        _class_private_field_set(this, _needsSort, new Set());
        _constants.REGISTERED_HOOKS.forEach((hookName)=>_class_private_method_get(this, _createHooksCollection, createHooksCollection).call(this, hookName));
    }
}
function createHooksCollection(hookName) {
    _class_private_field_get(this, _hooks).set(hookName, []);
    _class_private_field_get(this, _skippedHooksCount).set(hookName, 0);
}
