import { world, system } from "@minecraft/server";
import conduit_data from "conduit/data";

export class EventRegistry {
    static #listeners = {};

    static #scriptEventNamespaces = [];
    static #scriptEventListener = e => {
        let source = e.sourceEntity ?? e.sourceBlock;
        if(EventRegistry.exists(e.id)) {
            let args;
            if(e.message.length > 0) {
                args = JSON.parse(e.message);
            } else {
                args = {};
            }
            EventRegistry.invoke(e.id, source, args);
        }
    };
    
    static invoke(id, sender, args) {
        for(let callback of this.#listeners[id] ?? "") {
            try {
                callback(sender, args);
            } catch(err) {
                console.error("Error invoking event '" + id + "': " + err);
            }
        }
    }
    
    static exists(id) {
        return this.#listeners.hasOwnProperty(id);
    }
    
    static addListener(id, callback) {
        this.#listeners[id] ??= [];
        this.#listeners[id].push(callback);

        if(id.includes(":")) {
            let namespace = id.substring(0, id.indexOf(":"));
            if(!this.#scriptEventNamespaces.includes(namespace)) {
                this.#scriptEventNamespaces.push(namespace);
                this.#resubscribeToScriptEvents();
            }
        }
    }
    
    static removeListener(id, callback) {
        if(this.#listeners[id] != null)
            this.#listeners[id] = this.#listeners[id].filter(l => l != callback);
    }

    static #resubscribeToScriptEvents() {
        system.afterEvents.scriptEventReceive.unsubscribe(this.#scriptEventListener);
        system.afterEvents.scriptEventReceive.subscribe(this.#scriptEventListener, {"namespaces":this.#scriptEventNamespaces});
    }
}

system.run(() => {
    for(let id in conduit_data.script_events ?? "") {
        let callbacks = conduit_data.script_events[id];
        for(let callback of callbacks) {
            EventRegistry.addListener(id, callback);
        }
    }

    for(let reference in conduit_data.api_events ?? "") {
        let parts = reference.split(".");
        let signal;
        switch(parts[0]) {
            case "world": {
                signal = world;
                break;
            }
            case "system": {
                signal = system;
                break;
            }
            default: {
                throw "Invalid first part of API event reference '" + parts[0] + "'. Full reference: '" + reference + "'";
            }
        }
        for(let i = 1; i < parts.length; i++) {
            signal = signal[parts[i]];
            if(signal == null) {
                throw "Invalid API event reference '" + reference + "'. No such part '" + parts[i] + "'";
            }
        }

        let callbacks = conduit_data.api_events[reference];
        signal.subscribe((e) => {
            for(let callback of callbacks) {
                try {
                    callback(e);
                } catch(err) {
                    console.error(err);
                }
            }
        })
    }
});