"use strict";
/*
 * @poppinss/macroable
 *
 * (c) Harminder Virk <virk@adonisjs.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Macroable = void 0;
/**
 * Macroable is an abstract class to add ability to extend your class
 * prototype using better syntax.
 *
 * Macroable has handful of benefits over using traditional `prototype` approach.
 *
 * 1. Methods or properties added dynamically to the class can be removed using `hydrate` method.
 * 2. Can define singleton getters.
 */
class Macroable {
    constructor() {
        if (!this.constructor['macros'] || !this.constructor['getters']) {
            throw new Error('Set static properties "macros = {}" and "getters = {}" on the class for the macroable to work.');
        }
    }
    /**
     * Add a macro to the class. This method is a better to manually adding
     * to `class.prototype.method`.
     *
     * Also macros added using `Macroable.macro` can be cleared anytime
     *
     * @example
     * ```js
     * Macroable.macro('getUsername', function () {
     *   return 'virk'
     * })
     * ```
     */
    static macro(name, callback) {
        const self = this;
        self.macros[name] = callback;
        this.prototype[name] = callback;
    }
    /**
     * Return the existing macro or null if it doesn't exists
     */
    static getMacro(name) {
        return this.macros[name];
    }
    /**
     * Returns a boolean telling if a macro exists
     */
    static hasMacro(name) {
        return !!this.getMacro(name);
    }
    /**
     * Define a getter, which is invoked everytime the value is accessed. This method
     * also allows adding single getters, whose value is cached after first time
     *
     * @example
     * ```js
     * Macroable.getter('time', function () {
     *   return new Date().getTime()
     * })
     *
     * console.log(new Macroable().time)
     *
     * // Singletons
     * Macroable.getter('time', function () {
     *   return new Date().getTime()
     * }, true)
     *
     * console.log(new Macroable().time)
     * ```
     */
    static getter(name, callback, singleton = false) {
        const wrappedCallback = singleton
            ? function wrappedCallback() {
                const value = callback.bind(this)();
                Object.defineProperty(this, name, { value, configurable: true });
                return value;
            }
            : callback;
        const self = this;
        self.getters[name] = wrappedCallback;
        Object.defineProperty(this.prototype, name, {
            get: wrappedCallback,
            configurable: true,
            enumerable: true,
        });
    }
    /**
     * Return the existing getter or null if it doesn't exists
     */
    static getGetter(name) {
        return this.getters[name];
    }
    /**
     * Returns a boolean telling if a getter exists
     */
    static hasGetter(name) {
        return !!this.getGetter(name);
    }
    /**
     * Cleanup getters and macros from the class
     */
    static hydrate() {
        Object.keys(this.macros).forEach((key) => Reflect.deleteProperty(this.prototype, key));
        Object.keys(this.getters).forEach((key) => Reflect.deleteProperty(this.prototype, key));
        this.macros = {};
        this.getters = {};
    }
}
exports.Macroable = Macroable;
