import * as mc from "@minecraft/server";
import * as log from './log';
import * as array from './array';
import { overworld } from "./dimension";
import { getRandomInteger } from "./math";
const systemEventIDs = new Map();
/**
 * @remarks Resolves a promise after the scheduled ticks.
 * @param ticks The number of ticks to wait before running the resolving.
 * @param cancelCategory Optional tag that allows this promise to be aborted.
 * @returns A Promise that resolves after the specified number of ticks.
 * @example
 * Basic example
 * ```typescript
 * await waitForTicks(20); // Causes the execution to be paused here for 20 ticks.
 * mclog('Finished Waiting');
 * ```
 * @example
 * Abort example
 * ```typescript
 * async function waitThenMessage(player: SafetyCheckedPlayer) {
 * 	await waitForTicks(20, `${player.id}.message`);
 * 	mclog('Finished Waiting or Aborted'); // This message will print after 20 ticks OR immediately if the event is aborted
 * }
 *
 * getAllPlayersSafe().forEach(safePlayer => {
 * 	waitThenMessage(safePlayer);
 * 	if (safePlayer.hasTag('cancel')) {
 * 		abortEvents(`${safePlayer.id}.message`);
 * 		safePlayer.removeTag('cancel');
 * 	}
 * });
 * ```
 */
export async function waitForTicks(ticks, cancelCategory) {
    if (cancelCategory && !systemEventIDs.has(cancelCategory)) {
        systemEventIDs.set(cancelCategory, []);
    }
    return new Promise(resolve => {
        var _a;
        let id = mc.system.runTimeout(() => {
            resolve();
            if (cancelCategory && systemEventIDs.has(cancelCategory)) {
                array.removeFromArray(systemEventIDs.get(cancelCategory), { id, resolve });
            }
        }, ticks);
        if (cancelCategory) {
            (_a = systemEventIDs.get(cancelCategory)) === null || _a === void 0 ? void 0 : _a.push({ id, resolve });
        }
    });
}
/**
 * @remarks Resolves a promise after the scheduled event.
 * @param ticks The number of ticks to wait before running the event.
 * @param callback The event.
 * @param cancelCategory Optional tag that allows this promise to be aborted.
 * @returns A Promise that resolves to a value when the event finishes.
 * @example
 * Basic example
 * ```typescript
 * waitForEvent(20, () => mclog('Finished Waiting'));
 * ```
 * @example
 * Await example
 * ```typescript
 * await waitForEvent(20, () => mclog('Finished Waiting'));
 * mclog('Continuing Function'); // This won't run until the 20 ticks finish.
 * ```
 * @example
 * Abort example
 * ```typescript
 * async function waitThenMessage(player: SafetyCheckedPlayer) {
 * 	await waitForEvent(20, () => mclog('Finished Waiting'), `${player.id}.message`); // The mclog will only run if the event is not aborted.
 * 	mclog('Continuing'); // This message will print after 20 ticks OR immediately if the event is aborted
 * }
 *
 * getAllPlayersSafe().forEach(safePlayer => {
 * 	waitThenMessage(safePlayer);
 * 	if (safePlayer.hasTag('cancel')) {
 * 		abortEvents(`${safePlayer.id}.message`);
 * 		safePlayer.removeTag('cancel');
 * 	}
 * });
 * ```
 */
export async function waitForEvent(ticks, callback, cancelCategory) {
    if (cancelCategory && !systemEventIDs.has(cancelCategory)) {
        systemEventIDs.set(cancelCategory, []);
    }
    return new Promise(resolve => {
        var _a;
        let id = 0;
        const event = async function () {
            const val = await callback();
            resolve(val);
            if (cancelCategory && systemEventIDs.has(cancelCategory)) {
                array.removeFromArray(systemEventIDs.get(cancelCategory), { id, resolve });
            }
        };
        id = mc.system.runTimeout(event, ticks);
        if (cancelCategory) {
            (_a = systemEventIDs.get(cancelCategory)) === null || _a === void 0 ? void 0 : _a.push({ id, resolve });
        }
    });
}
/**
 * @remarks Runs a command on a loop, terminating itself when callback returns true.
 * @param ticks How often (in ticks) the callback should be run.
 * @param callback A callback that resolves to a boolean.
 * @param cancelCategory Optional tag that allows this promise to be aborted.
 * @returns A Promise that resolves when the callback returns true.
 * @example
 * Infinite Clock
 * ```typescript
 * waitForLoopEvent(20, () => {
 *     mclog(`Current Time: ${mc.system.currentTick}`);
 *     return false;
 * });
 * ```
 * @example
 * Wait For Condition
 * ```typescript
 * const player = getAllPlayersSafe()[0];
 * await waitForLoopEvent(0, () => !player.isSneaking); // Will wait here until the player is not sneaking.
 * mclog(`${player.name} Stopped Sneaking`);
 * ```
 */
export async function waitForLoopEvent(ticks, callback, cancelCategory) {
    if (cancelCategory && !systemEventIDs.has(cancelCategory)) {
        systemEventIDs.set(cancelCategory, []);
    }
    return new Promise(resolve => {
        let id = 0;
        const event = async function () {
            var _a;
            if (cancelCategory && systemEventIDs.has(cancelCategory)) {
                array.removeFromArray(systemEventIDs.get(cancelCategory), { id, resolve });
            }
            if (await callback()) {
                resolve();
            }
            else {
                id = mc.system.runTimeout(event, ticks);
                if (cancelCategory) {
                    (_a = systemEventIDs.get(cancelCategory)) === null || _a === void 0 ? void 0 : _a.push({ id, resolve });
                }
            }
        };
        mc.system.run(event);
    });
}
/**
 * @remarks Resolves when the world has loaded enough to target players. This should run before any code that uses getAllPlayers.
 * @example
 * ```typescript
 * async function main() {
 * 	await utils.waitForWorldLoad();
 *
 * 	utils.mclog("Hello World!");
 * 	// Initialize other systems
 * }
 * ```
 */
export async function waitForWorldLoad() {
    await waitForLoopEvent(5, async () => {
        const result = await overworld.runCommandAsync('testfor @a');
        return result.successCount > 0;
    });
}
/**
 * @remarks Cancels events created by waitForTicks, waitForEvent, & waitForLoopEvent that were marked with the given category.
 * @param cancelCategory The category to be cancelled.
 * @example
 * ```typescript
 * async function waitThenMessage(player: SafetyCheckedPlayer) {
 * 	await waitForTicks(20, `${player.id}.message`);
 * 	mclog('Finished Waiting or Aborted');
 * }
 *
 * getAllPlayersSafe().forEach(safePlayer => {
 * 	waitThenMessage(safePlayer);
 * 	if (safePlayer.hasTag('cancel')) {
 * 		abortEvents(`${safePlayer.id}.message`); // This will cancel any currently running events with the same tag.
 * 		safePlayer.removeTag('cancel');
 * 	}
 * });
 * ```
 */
export function abortEvents(cancelCategory) {
    var _a;
    log.mclog(`Aborting ${cancelCategory} Events`, 'verbose');
    if (systemEventIDs.has(cancelCategory)) {
        (_a = systemEventIDs.get(cancelCategory)) === null || _a === void 0 ? void 0 : _a.forEach(event => {
            mc.system.clearRun(event.id);
            event.resolve(undefined);
        });
        systemEventIDs.set(cancelCategory, []);
    }
}
/**
 * @remarks Calls an event based on random chance.
 * @param chance The chance that the callback will be called. 1 is gauranteed, while 100 means a 1/100 chance.
 * @param callback The callback to be executed if the random chance is true.
 * @example
 * ```typescript
 * // There's a 1/7 chance of running the callback.
 * randomChanceEvent(7, () => {
 * 	mclog('Winner!');
 * });
 * ```
 */
export function randomChanceEvent(chance, callback) {
    if (getRandomInteger(1, chance) === 1) {
        callback();
    }
}
