import * as mc from "@minecraft/server";
import * as log from './log';
import { overworld } from "./dimension";
import { afterEvents, waitForEvent } from "./events";
import { addName } from "./namespace";
const command_count = new Map([[mc.system.currentTick, 0]]);
let command_latest_tick = mc.system.currentTick;
/**
 * @remarks Runs a synchronous command.
 * @param command Command to run. Note that command strings should not start with slash.
 * @param source The source of the command, either a player or dimension. Defaults to overworld.
 * @returns For commands that return data, returns a CommandResult with an indicator of command results.
 * @throws This function can throw errors.
 * @example
 * ```typescript
 * runCommand('say hi'); // Says hi from the overworld.
 * runCommand('say hi', getAllPlayersSafe()[0]); // Says hi from the first player in the array.
 * ```
 */
export function runCommand(command, source = overworld) {
    try {
        return source.runCommand(command);
    }
    catch (error) {
        log.mcerror(error);
        throw new Error(String(error));
    }
}
/**
 * @remarks Runs an asynchronous command, and handles ticketing for commands exceeding 128.
 * @param command The source of the command, either a player or dimension. Defaults to overworld.
 * @param source Command to run. Note that command strings should not start with slash.
 * @returns For commands that return data, returns a CommandResult with an indicator of command results.
 * @throws This function can throw errors.
 * @example
 * ```typescript
 * runCommandAsync('say hi'); // Says hi from the overworld.
 * runCommandAsync('say hi', getAllPlayersSafe()[0]); // Says hi from the first player in the array.
 * ```
 */
export async function runCommandAsync(command, source = overworld) {
    try {
        // Ensures that command_latest_tick is never lagging behind
        if (mc.system.currentTick > command_latest_tick) {
            command_latest_tick = mc.system.currentTick;
        }
        // Increments the command_latest_tick if there are already 128 commands in the pipeline on  that tick
        const count = (command_count.get(command_latest_tick) || 0) + 1;
        if (count > 128) {
            command_latest_tick++;
            command_count.set(command_latest_tick, 1);
        }
        else {
            command_count.set(command_latest_tick, count);
        }
        // Runs the command either on the appropriate tick
        return await waitForEvent(command_latest_tick - mc.system.currentTick, async () => {
            command_count.delete(command_latest_tick - 1);
            try {
                return await source.runCommandAsync(command);
            }
            catch (error) {
                log.mcerror(`Invalid source for command: ${command}`, 'verbose');
            }
        });
    }
    catch (error) {
        log.mcerror(error);
        throw new Error(String(error));
    }
}
/**
 * @remarks Runs a command from all players.
 * @param command The command to run.
 * @throws This function can throw errors.
 * @example
 * ```typescript
 * runCommandPlayers('say hi'); // Says hi from all players.
 * ```
 */
export function runCommandPlayers(command) {
    try {
        for (const player of mc.world.getAllPlayers()) {
            runCommand(command, player);
        }
    }
    catch (error) {
        throw new Error(String(error));
    }
}
function commandDelay() {
    afterEvents.scriptEventRecieve.subscribe(addName("command_delay"), event => {
        const source = event.sourceEntity || overworld;
        if (Number.isNaN(event.data.delay) || event.data.delay < 0 || typeof event.data.command !== "string") {
            throw new Error(`Invalid command delay data.`);
        }
        waitForEvent(event.data.delay, () => {
            if (source instanceof mc.Entity && !source.isValid())
                return;
            try {
                source.runCommand(event.data.command);
            }
            catch (error) {
                throw new Error(String(error));
            }
        });
    });
}
commandDelay();
