import { world, EntityLoadAfterEvent, EntitySpawnAfterEvent, ItemUseOnAfterEvent } from "@minecraft/server";


class AfterEvent {
    static #instances = new Map();  // Store singletons for each derived class
    #events = new Map();

    constructor() { }

    /**
    * Singleton method to get or create an instance of the derived class.
    * @template {EntityAfterEvent} T
    * @returns {T} An instance of the specilized class
    */
    static getInstance() {
        if (!AfterEvent.#instances.has(this)) {
            AfterEvent.#instances.set(this, new this()); // 'this' refers to the derived class
        }
        return AfterEvent.#instances.get(this);
    }

    /**
     * 
     * @param {String} eventName 
     * @returns {boolean}
     */
    hasEvent(eventName) {
        return this.#events.has(eventName);
    }

    /**
     * 
     * @param {String} eventName 
     * @param {Function(e): void} callback 
     */
    registerEvent(eventName, callback) {
        this.#events.set(eventName, callback);
    }

    /**
     * 
     * @param {String} eventName 
     */
    removeEvent(eventName) {
        if (this.hasEvent(eventName)) {
            this.#events.delete(eventName);
        }
    }

    /**
     * @abstract
     * @param {string} eventName 
     */
    triggerEvent(eventName, eventData) {
        if (this.hasEvent(eventName)) {
            this.#events.get(eventName)(eventData);
        }
    }

}


class EntityLoadAfterEventHandler extends AfterEvent {
    constructor() {
        super();
        world.afterEvents.entityLoad.subscribe((e) => {
            this.triggerEvent(e.entity.typeId, e);
        });
    }

    /**
     * 
     * @param {String} entityTypeId - The entity type id to listen for
     * @param {(e: EntityLoadAfterEvent) => void} callback - The callback to run when the entity is loaded
     */
    registerEvent(entityTypeId, callback) {
        super.registerEvent(entityTypeId, callback);
    }
}

class EntitySpawnAfterEventHandler extends AfterEvent {
    constructor() {
        super();
        world.afterEvents.entitySpawn.subscribe((e) => {
            this.triggerEvent(e.entity.typeId, e);
        });
    }

    /**
     * @param {String} entityTypeId - The entity type id to listen for
     * @param {(e: EntitySpawnAfterEvent) => void} callback - The callback to run when the entity is loaded
     */
    registerEvent(entityTypeId, callback) {
        super.registerEvent(entityTypeId, callback);
    }
}


class ItemUseOnEventHandler extends AfterEvent {
    constructor() {
        super();
        world.afterEvents.itemUseOn.subscribe((e) => {
            this.triggerEvent(e.itemStack.typeId, e);
        });
    }

    /**
     * @param {String} itemTypeId - The entity type id to listen for
     * @param {(e: ItemUseOnAfterEvent) => void} callback - The callback to run when the item is used on a block
     */
    registerEvent(itemTypeId, callback) {
        super.registerEvent(itemTypeId, callback);
    }
}

export default {
    /** @returns {EntitySpawnAfterEventHandler} */
    get SpawnAfterEvent() {
        return EntitySpawnAfterEventHandler.getInstance();
    },
    /** @returns {EntityLoadAfterEventHandler} */
    get LoadAfterEvent() {
        return EntityLoadAfterEventHandler.getInstance();
    },
    /** @returns {ItemUseOnEventHandler} */
    get ItemUseOnAfterEvent() {
        return ItemUseOnEventHandler.getInstance();
    }
}