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


/**
 * Global debugging priority.
 *
 * @type {Number}
 */
let DEBUG_PRIORITY = world.getDynamicProperty('mythicus:debugPriority') ?? 0;

/**
 * Object storing all debuggers.
 *
 * @type {Object.<string, Debugger>}
 */
const registeredDebuggers = {};

/**
 * Class for storing debugger properties.
 *
 * @class
 */
export class DebuggerProperties {

    static log;
    static warn;
    static error;
    static fatal;

    static values;

    static minPriority = 0;
    static maxPriority = 3;

    /**
     * Creates an instance of DebuggerProperties.
     *
     * @param {Number} priority - Priority level.
     * @param {String} id - Identifier for display.
     * @param {String} color - Color code for display.
    */
    constructor(priority, id, color) {
        this.priority = priority;
        this.id = id;
        this.color = color;
    }



    /**
     * Returns the string format of the debugger property.
     *
     * @return {String} String representation of debugger property.
    */
    toString() {
        return this.color + this.id + '§r';
    }

    /**
     * Parses associated debugger property.
     *
     * @param {string|number} id - Identifier of debugger property to parse.
     * @returns {DebuggerProperties|undefined} Associated DebuggerProperties if existent else undefined.
    */
    static parse(id) {

        const priority = parseInt(id);
        if (!isNaN(priority)) {
            for (const property of DebuggerProperties.values) {
                if (property.priority === priority) return property;
            }
        } else if (typeof id === 'string') {
            id = id.toLowerCase();
            for (const property of DebuggerProperties.values) {
                if (property.id.toLowerCase() === id) return property;
            }
        }

        return undefined;
    }

}

// Initialize static properties of debugging class
DebuggerProperties.log = new DebuggerProperties(0, 'DEBUG', '§7');
DebuggerProperties.warn = new DebuggerProperties(1, 'WARN', '§e');
DebuggerProperties.error = new DebuggerProperties(2, 'ERROR', '§c');
DebuggerProperties.fatal = new DebuggerProperties(3, 'FATAL', '§4§l');

DebuggerProperties.values = [
    DebuggerProperties.log,
    DebuggerProperties.warn,
    DebuggerProperties.error,
    DebuggerProperties.fatal,
]



/**
 * Class for debugging in Minecraft's environment.
 *
 * @class
 */
export class Debugger {

    /**
     * Creates an instance of Debugger.
     *
     * @param {String} id - Identifier of debugger.
     * @param {Number} [priority=0] - Priority level of this debugger [default: 0].
     * @param {Boolean} [enabled=true] - If this debugger is enabled [default: true].
    */
    constructor(id, priority=0, enabled=true) {
        this.id = id;
        this.priority = priority;
        this.enabled = enabled;

        this.#setup(id);
    }
    /**
     * Additional setup code for the debugger called at construction.
     *
     * @param {String} id - Identifier.
    */
    #setup(id) {
        registeredDebuggers[id] = this;
    }


    /**
     * Sends a debug message to chat.
     *
     * @param {DebuggerProperties} property - Debug property.
     * @param {String|Player} content - Debug message or target player.
     * @param {String} [message=''] - Debug message.
    */
    send(property, content, message='') {
        if (!(property instanceof DebuggerProperties)) property = DebuggerProperties.log;

        if (!this.enabled || this.priority > property.priority || DEBUG_PRIORITY > property.priority) return;

        const id = this.id.slice(this.id.indexOf(':') + 1);
        const prefix = `§6[${id}] ${property.toString()}§7 >> §r`;
        if (content instanceof Player) {
            content.sendMessage(prefix + message);
        } else {
            world.sendMessage(prefix + content);
        }
    }

    /**
     * Logs a message (priority 0).
     *
     * @param {String|Player} content - Log message or target player.
     * @param {String} [message=''] - Log message.
    */
    log(content, message = '') {
        this.send(DebuggerProperties.log, content, message);
    }
    /**
     * Gives a warning (priority 1).
     *
     * @param {String|Player} content - Warn message or target player.
     * @param {String} [message=''] - Warn message.
    */
    warn(content, message = '') {
        this.send(DebuggerProperties.warn, content, message);
    }
    /**
     * Raises an error (priority 2).
     *
     * @param {String|Player} content - Error message or target player.
     * @param {String} [message=''] - Error message.
    */
    error(content, message = '') {
        this.send(DebuggerProperties.error, content, message);
    }
    /**
     * Raises a fatal error (priority 3).
     *
     * @param {String|Player} content - Fatal error message or target player.
     * @param {String} [message=''] - Fatal error message.
    */
    fatal(content, message = '') {
        this.send(DebuggerProperties.fatal, content, message);
    }



    /**
     * Enables all debuggers.
     *
    */
    static enableAll() {
        for (let debugging of Object.values(registeredDebuggers)) {
            debugging.enabled = true;
        }
    }
    /**
     * Disables all debuggers.
     *
    */
    static disableAll() {
        for (let debugging of Object.values(registeredDebuggers)) {
            debugging.enabled = false;
        }
    }
    /**
     * Disables all debuggers.
     *
     * @param {Number} priority - Global priority value.
    */
    static setGlobalPriority(priority) {
        priority = Math.min(Math.max(priority, DebuggerProperties.minPriority), DebuggerProperties.maxPriority) >> 0;

        world.setDynamicProperty('mythicus:debugPriority', priority);
        DEBUG_PRIORITY = priority;
    }



    /**
     * Parses the debugger associated with the given id.
     *
     * @param {String} id - Identifier of debugger.
     * @returns {Debugger|undefined} Debugger or undefined if non-existent.
    */
    static parse(id) {
        return registeredDebuggers[id] ?? undefined;
    }
    /**
     * Returns the existence of the debugger associated with the given id.
     *
     * @param {String} id - Identifier of debugger.
     * @returns {Boolean} If debugger exists.
    */
    static exists(id) {
        return (id in registeredDebuggers);
    }
    /**
     * Creates and returns a new debugger.
     *
     * @param {String} id - Identifier of debugger.
     * @param {Number} [priority=0] - Priority level of this debugger [default: 0].
     * @param {Boolean} [enabled=true] - If this debugger is enabled [default: true].
     * @returns {Debugger} New instance of Debugger or the one associated with given id if already existent.
    */
    static create(id, priority=0, enabled=true) {
        let debugging = Debugger.parse(id);

        if (!debugging) {
            return new Debugger(id, priority, enabled);
        } else {
            return debugging;
        }
    }

}