import { world, system, Block, Player, ItemUseOnBeforeEvent } from "@minecraft/server";

// UTILITY FUNCTIONS

export function isSameVector(vec1, vec2) {
    if (!vec1 || !vec2) return vec1 === vec2;
    return vec1.x === vec2.x && vec1.y === vec2.y && vec1?.z === vec2?.z;
}

//



/**
 * @typedef {Object} PlayerViewBlockEvent
 * @property {Player} player - Player associated with this event.
 * @property {Block} curBlock - Current block associated with this event.
 * @property {Block} prevBlock - Previous block associated with this event.
 */

/**
 * A class managing events when players view a block.
 *
 * @class
 */
export class PlayerViewBlock {

    /**
     * Object storing all event subscriptions.
     *
     * @type {Function[]}
    */
    static #callbacks = [];

    /**
     * Object storing all players' blocks.
     *
     * @type {Object.<String, Block>}
    */
    static #blocks = {};


    /**
    * Subscribes to an event callback function.
    *
    * @param {(data: PlayerViewBlockEvent) => void} callback - Callback function.
   */
    static subscribe(callback) {
        this.#callbacks.push(callback);
    }
    /**
     * Unsubscribes from an event callback function.
     *
     * @param {(data: PlayerViewBlockEvent) => void} callback - Callback function.
    */
    static unsubscribe(callback) {
        if (this.#callbacks.includes(callback)) {
            this.#callbacks.splice(this.#callbacks.indexOf(callback));
        }
    }
    /**
     * Triggers all subscribed callbacks.
     *
     * @param {PlayerViewBlockEvent} data - Event data.
    */
    static trigger(data) {
        for (const callback of this.#callbacks) {
            callback(data);
        }
    }


    /**
     * Gets block this player is viewing.
     *
     * @param {Player|string} player - Player object or player id.
     * 
     * @returns {Block|undefined} - Returns block this playing is viewing, or undefined if none.
    */
    static getBlock(player) {
        return this.#blocks[player instanceof Player ? player.id : player];
    }
    static #setBlock(player, block) {
        return this.#blocks[player.id] = block;
    }

    /**
     * System interval id controlling event.
     *
     * @type {Number}
    */
    static #runId = system.runInterval(() => {

        world.getAllPlayers().forEach(player => {

            if (player?.isValid() !== true) return;

            const curBlock = player.getBlockFromViewDirection({ maxDistance: 7 })?.block;
            let prevBlock = this.getBlock(player);

            try {
                prevBlock?.permutation;
            } catch {
                prevBlock = curBlock;
            }

            if (curBlock?.permutation !== prevBlock?.permutation || (curBlock && prevBlock && !isSameVector(curBlock.location, prevBlock.location))) {
                this.#setBlock(player, curBlock);
                this.trigger({ player: player, block: curBlock, prevBlock: prevBlock });
            }

        });

    }, 5);

}



/**
 * A class managing events when players interact on a block with an item.
 *
 * @class
 */
export class PlayerItemUseOnDelay {

    /**
     * Object storing all event subscriptions.
     *
     * @type {Function[]}
    */
    static #callbacks = [];

    /**
     * Object storing all players who are in a buffer.
     *
     * @type {Object.<String, Number>}
    */
    static #buffers = {};



    /**
    * Subscribes to an event callback function.
    *
    * @param {(data: ItemUseOnBeforeEvent) => void} callback - Callback function.
   */
    static subscribe(callback) {
        this.#callbacks.push(callback);
    }
    /**
     * Unsubscribes from an event callback function.
     *
     * @param {(data: ItemUseOnBeforeEvent) => void} callback - Callback function.
    */
    static unsubscribe(callback) {
        if (this.#callbacks.includes(callback)) {
            this.#callbacks.splice(this.#callbacks.indexOf(callback));
        }
    }
    /**
     * Triggers all subscribed callbacks.
     *
     * @param {ItemUseOnBeforeEvent} data - Event data.
    */
    static trigger(data) {
        for (const callback of this.#callbacks) {
            callback(data);
        }
    }

    /**
     * Event callback for default Minecraft event governing this event.
     *
     * @type {Function}
    */
    static #eventCallback = world.beforeEvents.itemUseOn.subscribe((data) => {

        // ensure source is player
        const player = data.source;
        if (player.typeId !== "minecraft:player") return;

        // check if player is buffered
        const pid = player.id;
        if (this.#buffers[pid] >= system.currentTick) return;

        // add player to buffer
        this.#buffers[pid] = system.currentTick + 1;

        // trigger event
        this.trigger(data);

    });
    
}
