"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Protocol = void 0;
const tslib_1 = require("tslib");
const kolor_1 = tslib_1.__importDefault(require("@spacingbat3/kolor"));
const lss_1 = require("@spacingbat3/lss");
const util_1 = require("util");
const packet_1 = require("./packet");
/**
 * Flattened combination of `codes` and `types` from {@link knownMsgEl} used as
 * an array of hook names.
 */
const hookNames = Object.freeze(packet_1.knownPacketID.codes.flatMap(code => code === "DEEP_LINK" ? packet_1.knownPacketID.types.map(type => `${code}_${type}`) : code));
/**
 * A specification which defines Discord communication protocol used within
 * DisConnection. It is used for implementing various *transports*, like
 * WebSocket server or UNIX socket (IPC). This class is not designed to be used
 * directly, but is meant to be extended by given transport implementation.
 */
class Protocol {
    /**
     * A {@link name} variant which contains only English lowercase letters with
     * other incompatible characters replaced with underscore (`_`).
     */
    get safeName() {
        return (0, lss_1.sanitizeLiteral)(this.name, "a-z", "_", "both");
    }
    #destroyed = false;
    /**
     * An object that stores the information about all of the hooks assigned to
     * the given transport.
     */
    #hooks = hookNames.reduce((prev, cur) => ({
        ...prev,
        [cur]: {
            list: new Set(),
            active: true
        }
    }), {});
    /** A [`Console`](https://nodejs.org/api/console.html) instance used for logging within this class. */
    #console;
    /** A {@link fgColor} used as a color of the badge within logged messages. */
    #color;
    /** Writes a regular message (`log`) in the console to `stdout`. */
    log(...args) {
        if (this.#console === undefined)
            return;
        const badge = this.#color === undefined
            ? kolor_1.default.bold(`[${this.name}]`) + " %s"
            : kolor_1.default.bold(kolor_1.default[this.#color](`[${this.name}]`)) + " %s";
        this.#console.log(badge, (0, util_1.format)(...args));
    }
    /** Writes an error message to `stderr`. */
    error(...args) {
        this.#console?.error(kolor_1.default.red(kolor_1.default.bold(`[${this.name}]`) + " %s"), (0, util_1.format)(...args));
    }
    /**
     * Writes a debug message, which will be visible whenever `NODE_DEBUG`
     * includes a `disconnection-{{@link #safeName}}`.
     */
    debug(...args) {
        this.debug = (0, util_1.debug)(`disconnection-${this.safeName}`);
        this.debug(...args);
    }
    /**
     * Whenever this class has been *destroyed*.
     *
     * @see {@link destroy} for more details about this state.
     */
    isDestroyed() {
        return this.#destroyed;
    }
    /**
     * Removes references in class properties and methods either by replacing
     * values with a reference to the function that throws an error (for methods
     * and some required properties using getters) or sets them to a nullish value
     * (for optional properties).
     *
     * As it is hard to guarantee the Garbage Collector will ever deallocate
     * memory after dereferencing all of the objects, the goal is to prevent API
     * consumers from using class method that no longer make sense than implement
     * any kind of the memory cleanup logic.
     *
     * **Note: This operation is designed to be irreversible!** You will have to
     * initialize the new class instance if you want to use given transport again.
     *
     * @throws {@link Error} in case object has been already destroyed.
     *
     * @since v1.1.0
     */
    destroy() {
        const destroyError = new Error("Object has been destroyed!");
        if (this.#destroyed)
            throw destroyError;
        this.stopServer();
        const destroyFunc = () => { throw destroyError; };
        const destroyHook = Object.freeze({
            get list() { throw destroyError; },
            get active() { throw destroyError; }
        });
        // Clear lists of hooks and remove references to them.
        Object.keys(this.#hooks).forEach(key => {
            this.removeAllHooks(key);
            this.#hooks[key] = destroyHook;
        });
        // Make hooks immutable
        this.#hooks = Object.freeze(this.#hooks);
        const methods = new Set;
        // Get list of methods from all object prototypes.
        {
            let currentProto = Object.getPrototypeOf(this);
            while (currentProto !== Object.getPrototypeOf(Object)) {
                if (currentProto === null || currentProto === undefined)
                    break;
                Object.getOwnPropertyNames(currentProto)
                    .filter(key => key !== "isDestroyed" && typeof this[key] === "function")
                    .forEach(key => methods.add(key));
                currentProto = Object.getPrototypeOf(currentProto);
            }
        }
        // Make class methods throw an Error when used.
        for (const key of methods.keys())
            this[key] = destroyFunc;
        this.#console = this.#color = undefined;
        this.#destroyed = true;
    }
    /**
     * Adds a hook to the given hook list if it doesn't exist in it.
     *
     * @param {T extends HookName} name - A name of hook list.
     * @param value - A function that will be added to hook list.
     *
     * @returns number of hooks of given key or `false` if value were added before
     * @throws {@link TypeError} on invalid function parameter types.
     * @since v1.0.0
     */
    addHook(name, value) {
        if (!(name in this.#hooks) || typeof value !== "function")
            throw new TypeError("Invalid parameters type!");
        const wereAddedBefore = this.#hooks[name].list.has(value);
        this.#hooks[name].list.add(value);
        return wereAddedBefore ? false : [...this.#hooks[name].list].length;
    }
    /**
     * Removes given hook function from give the hook list.
     *
     * @param name - A name of hook list.
     *
     * @returns whenever hook has been deleted
     * @throws {@link TypeError} on invalid function parameter types.
     * @since v1.0.0
     */
    removeHook(name, value) {
        if (!(name in this.#hooks) || typeof value !== "function")
            throw new TypeError("Invalid parameters type!");
        return this.#hooks[name].list.delete(value);
    }
    /**
     * Removes **all** hooks from the given hook list.
     *
     * @param name - A name of hook list.
     *
     * @returns if hook list wasn't empty before removing — values from it
     * @throws {@link TypeError} on invalid hook list name.
     * @since v1.0.0
     */
    removeAllHooks(name) {
        if (!(name in this.#hooks))
            throw new TypeError(`Hook list "${name}" is invalid!`);
        const returnValue = [...this.#hooks[name].list].length > 0;
        this.#hooks[name].list.clear();
        return returnValue;
    }
    /**
     * Lists all hooks from the given hook list.
     *
     * @param name - A name of hook list.
     *
     * @returns `Array` of hooks
     * @throws {@link TypeError} on invalid hook list name.
     * @since v1.0.0
     */
    getHooks(name) {
        if (!(name in this.#hooks))
            throw new TypeError(`Hook list "${name}" is invalid!`);
        return [...this.#hooks[name].list];
    }
    /**
     * Whenever any of hooks will execute by server.
     *
     * @param name - A name of hook list.
     *
     * @returns whenever hooks are *active*
     * @throws {@link TypeError} on invalid hook list name.
     * @since v1.0.0
     */
    anyHooksActive(name) {
        if (!(name in this.#hooks))
            throw new TypeError(`Hook list "${name}" is invalid!`);
        if ([...this.#hooks[name].list].length === 0)
            return false;
        return this.#hooks[name].active;
    }
    /**
     * Switches state of a given hook list, which can either disable it or not.
     *
     * @param name - A name of hook list.
     * @param active - New state of hooks. Defaults to negation of previous state.
     *
     * @returns current state of given hook (i.e if it is active or not)
     * @throws {@link TypeError} on invalid function parameter types.
     * @since v1.0.0
     */
    toggleHooks(name, active = !this.#hooks[name].active) {
        if (!(name in this.#hooks) || typeof active !== "boolean")
            throw new TypeError("Invalid parameters type!");
        this.#hooks[name].active = active;
        return this.anyHooksActive(name);
    }
    constructor(cConsole = console, color) {
        if (cConsole !== null)
            this.#console = cConsole;
        if (color !== undefined)
            this.#color = color;
    }
    /**
     * This function maps incoming messages from transports to outgoing messages
     * with partially-filled data, i.e. nothing is being resolved as it takes
     * place in the official Discord client.
     *
     * This is used in place of default responses.
     *
     * @param message - Incoming message from transports.
     *
     * @returns Outgoing message that can be send as a response.
     *
     * @since v1.0.0
     *
     * @deprecated This was never expected to be a part of public API.
     */
    static messageResponse = (0, util_1.deprecate)(packet_1.messageDefaultResponse, "'Protocol.messageResponse()' is deprecated.", "DEP0001WC");
}
exports.Protocol = Protocol;
//# sourceMappingURL=protocol.js.map