import * as utils from "../../utils/index";
/**
 * @remarks The base class for tasks, this should be extended from when creating new tasks.
 */
export class Task {
    get Status() {
        return this.status;
    }
    /**
     * @remarks Creates a new instance of a task. Note that creating a task does not automatically execute it, the {@link start} method must be called.
     * If this should be the root task of an npc (not a task called by another task) then rather than calling the {@link start} method directly,
     * the {@link NPCBase.start} method should be used with this class instance as a parameter.
     * @param npc The npc who should perform this task.
     * @param options Any additional options the task needs.
     * @param parentTask The parent task if this task is being called by another task.
     */
    constructor(npc, options, parentTask) {
        var _a;
        this.id = this.constructor.name;
        this.subTasks = [];
        this.aborting = false;
        this.npc = npc;
        this.id = this.id + npc.ID;
        this.options = options;
        this.parentTask = parentTask;
        (_a = this.parentTask) === null || _a === void 0 ? void 0 : _a.addSubTask(this);
        this.index = Task.TaskIndex;
        Task.TaskIndex++;
    }
    /**
     * @remarks Starts the task.
     * @returns A promise that resolves to the {@link Result} of the task.
     */
    async start() {
        utils.mclog(`Starting Task (${this.id}: ${this.index})`, "npc");
        this.status = new Promise(async (resolve) => {
            var _a, _b;
            try {
                const result = await this.performTask();
                utils.mclog(`Task (${this.id}: ${this.index}) ${result.outcome}`, "npc");
                (_a = this.parentTask) === null || _a === void 0 ? void 0 : _a.removeSubTask(this);
                resolve(result);
            }
            catch (error) {
                utils.abortEvents(this.id);
                await this.cleanupTask();
                (_b = this.parentTask) === null || _b === void 0 ? void 0 : _b.removeSubTask(this);
                resolve({ outcome: "aborted" });
            }
        });
        return this.status;
    }
    /**
     * @remarks Aborts the task, this will abort any subtasks as well and perform the {@link cleanupTask} method of all of the subtasks, then itself.
     * Note that calling abort **does not** automatically abort the parent task, only the subtasks.
     */
    async abort() {
        if (this.aborting)
            return;
        const parent_message = this.parentTask ? `With Parent (${this.parentTask.id}: ${this.parentTask.index})` : "At Root";
        utils.mcwarn(`Aborting Task (${this.id}: ${this.index}) ${parent_message} Awaiting [${this.subTasks.length}] Child Aborts`, "npc");
        utils.abortEvents(this.id);
        for (const task of this.subTasks) {
            await task.abort();
            if (task.Status) {
                utils.mclog(`Task (${task.id}: ${task.index}) ${(await task.Status).outcome}`, "npc");
            }
        }
        this.aborting = true;
        await this.cleanupTask();
    }
    /**
     * @remarks The core logic of the task, this method should be overwritten when creating a new Task class.
     * @returns A promise that resolves to the {@link Result} of the task.
     */
    async performTask() {
        return { outcome: "succeeded" };
    }
    /**
     * @remarks Performs cleanup on this task, this will be called automatically when aborting.
     * Any logic required to restore the NPC/World to their default state should be included in the cleanup.
     * Note that the NPC entity may be despawned at the time cleanup is called.
     */
    async cleanupTask() { }
    /**
     * @remarks Adds a task to this task's subtask list.
     * This is handled automatically if the `parentTask` optional argument is provided in the constructor.
     * @param task The task to make a subtask of this instance.
     */
    addSubTask(task) {
        this.subTasks.push(task);
    }
    /**
     * @remarks Removes a task from this task's subtask list.
     * This is handled automatically when the task is completed.
     * @param task The task to remove.
     */
    removeSubTask(task) {
        utils.removeFromArray(this.subTasks, task);
    }
    /**
     * @remarks Throws an error if the NPC entity is invalid, or if this task is flagged as aborting. This
     * should be used in the logic of {@link performTask} to end the execution prematurely if the task should abort.
     */
    shouldAbort() {
        if (!this.npc.Entity.isValid() || this.aborting) {
            throw `${this.id} Aborting`;
        }
    }
}
Task.TaskIndex = 0;
