"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var Task_exports = {};
__export(Task_exports, {
  TaskInterruptBehavior: () => TaskInterruptBehavior,
  TaskPriority: () => TaskPriority,
  TaskScheduler: () => TaskScheduler,
  TaskState: () => TaskState
});
module.exports = __toCommonJS(Task_exports);
var import_deferred_promise = require("alcalzone-shared/deferred-promise");
var import_sorted_list = require("alcalzone-shared/sorted-list");
var import_wrappingCounter = require("./wrappingCounter.js");
var import_utils = require("./utils.js");
function getTaskName(task) {
  return `${task.name ?? (task.tag && JSON.stringify(task.tag)) ?? "unnamed"} (ID ${task.id})`;
}
__name(getTaskName, "getTaskName");
function isTaskBuilder(obj) {
  return typeof obj === "object" && typeof obj.task === "function" && typeof obj.priority === "number";
}
__name(isTaskBuilder, "isTaskBuilder");
var TaskPriority;
(function(TaskPriority2) {
  TaskPriority2[TaskPriority2["Highest"] = 0] = "Highest";
  TaskPriority2[TaskPriority2["High"] = 1] = "High";
  TaskPriority2[TaskPriority2["Normal"] = 2] = "Normal";
  TaskPriority2[TaskPriority2["Low"] = 3] = "Low";
  TaskPriority2[TaskPriority2["Lower"] = 4] = "Lower";
  TaskPriority2[TaskPriority2["Idle"] = 5] = "Idle";
})(TaskPriority || (TaskPriority = {}));
var TaskState;
(function(TaskState2) {
  TaskState2[TaskState2["None"] = 0] = "None";
  TaskState2[TaskState2["Active"] = 1] = "Active";
  TaskState2[TaskState2["AwaitingPromise"] = 2] = "AwaitingPromise";
  TaskState2[TaskState2["AwaitingTask"] = 3] = "AwaitingTask";
  TaskState2[TaskState2["Done"] = 4] = "Done";
})(TaskState || (TaskState = {}));
var TaskInterruptBehavior;
(function(TaskInterruptBehavior2) {
  TaskInterruptBehavior2[TaskInterruptBehavior2["Forbidden"] = 0] = "Forbidden";
  TaskInterruptBehavior2[TaskInterruptBehavior2["Resume"] = 1] = "Resume";
  TaskInterruptBehavior2[TaskInterruptBehavior2["Restart"] = 2] = "Restart";
})(TaskInterruptBehavior || (TaskInterruptBehavior = {}));
function compareTasks(a, b) {
  const aConcurrencyGroup = a.group?.id;
  const bConcurrencyGroup = b.group?.id;
  if (aConcurrencyGroup && bConcurrencyGroup && aConcurrencyGroup === bConcurrencyGroup) {
    const aActiveOrWaiting = a.state === TaskState.Active || a.state === TaskState.AwaitingPromise || a.state === TaskState.AwaitingTask;
    const bActiveOrWaiting = b.state === TaskState.Active || b.state === TaskState.AwaitingPromise || b.state === TaskState.AwaitingTask;
    if (aActiveOrWaiting && !bActiveOrWaiting)
      return 1;
    if (!aActiveOrWaiting && bActiveOrWaiting)
      return -1;
  }
  if (a.priority < b.priority)
    return 1;
  if (a.priority > b.priority)
    return -1;
  const aWaiting = a.state === TaskState.AwaitingPromise || a.state === TaskState.AwaitingTask;
  const bWaiting = b.state === TaskState.AwaitingPromise || b.state === TaskState.AwaitingTask;
  if (!aWaiting && bWaiting)
    return 1;
  if (aWaiting && !bWaiting)
    return -1;
  if (a.timestamp < b.timestamp)
    return 1;
  if (a.timestamp > b.timestamp)
    return -1;
  return Math.sign(b.id - a.id);
}
__name(compareTasks, "compareTasks");
class TaskScheduler {
  static {
    __name(this, "TaskScheduler");
  }
  defaultErrorFactory;
  verbose;
  constructor(defaultErrorFactory = () => new Error("Task was removed"), verbose = false) {
    this.defaultErrorFactory = defaultErrorFactory;
    this.verbose = verbose;
  }
  _tasks = new import_sorted_list.SortedList(void 0, compareTasks);
  _currentTask;
  _idGenerator = (0, import_wrappingCounter.createWrappingCounter)(16777215);
  _continueSignal;
  _stopSignal;
  _stopPromise;
  queueTask(builder) {
    const task = this.createTask(builder);
    this._tasks.add(task);
    if (this.verbose) {
      console.log(`Task queued: ${getTaskName(task)}`);
    }
    if (this._continueSignal)
      this._continueSignal.resolve();
    return task.promise;
  }
  /** Removes/stops tasks matching the given predicate. Returns `true` when a task was removed, `false` otherwise. */
  async removeTasks(predicate, reason) {
    const tasksToRemove = [];
    let removeCurrentTask = false;
    for (const task of this._tasks) {
      if (predicate(task)) {
        if (task === this._currentTask) {
          removeCurrentTask = true;
        } else {
          tasksToRemove.push(task);
        }
      }
    }
    reason ??= this.defaultErrorFactory();
    for (const task of tasksToRemove) {
      if (this.verbose) {
        console.log(`Removing task: ${getTaskName(task)}`);
      }
      this._tasks.remove(task);
      if (task.state === TaskState.Active || task.state === TaskState.AwaitingPromise || task.state === TaskState.AwaitingTask) {
        await task.reset().catch(import_utils.noop);
      }
      task.reject(reason);
      if (task.parent) {
        if (this.verbose) {
          console.log(`Restoring parent task: ${getTaskName(task.parent)}`);
        }
        this._tasks.add(task.parent);
      }
    }
    if (removeCurrentTask && this._currentTask) {
      if (this.verbose) {
        console.log(`Removing task: ${getTaskName(this._currentTask)}`);
      }
      this._tasks.remove(this._currentTask);
      await this._currentTask.reset().catch(import_utils.noop);
      this._currentTask.reject(reason);
      if (this._currentTask.parent) {
        if (this.verbose) {
          console.log(`Restoring parent task: ${getTaskName(this._currentTask.parent)}`);
        }
        this._tasks.add(this._currentTask.parent);
      }
      this._currentTask = void 0;
    }
    if (this._continueSignal)
      this._continueSignal.resolve();
    return tasksToRemove.length > 0 || removeCurrentTask;
  }
  findTask(predicate) {
    return this._tasks.find((t) => predicate(t))?.promise;
  }
  /** Creates a task that can be executed */
  createTask(builder, parent) {
    let state = TaskState.None;
    let generator;
    let waitForPromise;
    const promise = (0, import_deferred_promise.createDeferredPromise)();
    let prevResult;
    let waitError;
    const self = this;
    return {
      id: this._idGenerator(),
      timestamp: (0, import_utils.highResTimestamp)(),
      builder,
      parent,
      name: builder.name,
      tag: builder.tag,
      priority: builder.priority,
      group: builder.group,
      interrupt: builder.interrupt ?? TaskInterruptBehavior.Resume,
      promise,
      async step() {
        if (state === TaskState.AwaitingPromise && waitForPromise) {
          return {
            newState: state,
            promise: waitForPromise
          };
        } else if (state === TaskState.AwaitingTask) {
          throw new Error("Cannot step through a task that is waiting for another task");
        }
        generator ??= builder.task();
        state = TaskState.Active;
        const { value, done } = waitError ? await generator.throw(waitError) : await generator.next(prevResult);
        prevResult = void 0;
        waitError = void 0;
        if (done) {
          state = TaskState.Done;
          return {
            newState: state,
            result: value
          };
        } else if (value != void 0) {
          const waitFor = (0, import_utils.evalOrStatic)(value);
          if (waitFor instanceof Promise) {
            state = TaskState.AwaitingPromise;
            waitForPromise = waitFor.then((result) => {
              prevResult = result;
            }).catch((e) => {
              waitError = e;
            }).finally(() => {
              waitForPromise = void 0;
              if (state === TaskState.AwaitingPromise) {
                state = TaskState.Active;
              }
            });
            return {
              newState: state,
              promise: waitForPromise
            };
          } else if (isTaskBuilder(waitFor)) {
            if (waitFor.priority > builder.priority) {
              throw new Error("Tasks cannot yield to tasks with lower priority than their own");
            }
            state = TaskState.AwaitingTask;
            const subTask = self.createTask(waitFor, this);
            subTask.promise.then((result) => {
              prevResult = result;
            }).catch((e) => {
              waitError = e;
            }).finally(() => {
              if (state === TaskState.AwaitingTask) {
                state = TaskState.Active;
              }
            });
            return {
              newState: state,
              task: subTask
            };
          } else {
            throw new Error("Invalid value yielded by task");
          }
        } else {
          return { newState: state };
        }
      },
      async reset() {
        if (state === TaskState.None)
          return;
        state = TaskState.None;
        waitForPromise = void 0;
        generator = void 0;
        await builder.cleanup?.();
      },
      resolve(result) {
        promise.resolve(result);
      },
      reject(error) {
        promise.reject(error);
      },
      get state() {
        return state;
      },
      get generator() {
        return generator;
      }
    };
  }
  start() {
    this._stopSignal = (0, import_deferred_promise.createDeferredPromise)();
    setImmediate(async () => {
      try {
        await this.run();
      } catch (e) {
        console.error("Task runner crashed", e);
      }
    });
  }
  async run() {
    while (true) {
      let waitFor;
      if (this._tasks.length > 0) {
        const firstTask = this._tasks.peekStart();
        if (!this._currentTask) {
          this._currentTask = firstTask;
        } else if (this._currentTask !== firstTask && this._currentTask.interrupt !== TaskInterruptBehavior.Forbidden) {
          if (this._currentTask.interrupt === TaskInterruptBehavior.Restart) {
            await this._currentTask.reset();
          }
          this._currentTask = firstTask;
        }
        if (this.verbose) {
          console.log(`Stepping through task: ${getTaskName(this._currentTask)}`);
        }
        const cleanupCurrentTask = /* @__PURE__ */ __name(async () => {
          if (this._currentTask) {
            this._tasks.remove(this._currentTask);
            await this._currentTask.reset();
            if (this._currentTask.parent) {
              if (this.verbose) {
                console.log(`Restoring parent task: ${getTaskName(this._currentTask.parent)}`);
              }
              this._tasks.add(this._currentTask.parent);
            }
            this._currentTask = void 0;
          }
        }, "cleanupCurrentTask");
        waitFor = void 0;
        let stepResult;
        try {
          stepResult = await this._currentTask.step();
        } catch (e) {
          if (this.verbose) {
            console.error(`- Task threw an error:`, e);
          }
          this._currentTask.reject(e);
          await cleanupCurrentTask();
          continue;
        }
        if (stepResult.newState === TaskState.Done) {
          if (this.verbose) {
            console.log(`- Task finished`);
          }
          this._currentTask.resolve(stepResult.result);
          await cleanupCurrentTask();
          continue;
        } else if (stepResult.newState === TaskState.AwaitingPromise) {
          if (this.verbose) {
            console.log(`- Task waiting`);
          }
          if (this._currentTask.interrupt !== TaskInterruptBehavior.Forbidden) {
            this._tasks.remove(this._currentTask);
            this._tasks.add(this._currentTask);
            if (this._tasks.peekStart() !== this._currentTask) {
              if (this.verbose) {
                console.log(`-- Continuing with another task`);
              }
              continue;
            }
          }
          waitFor = stepResult.promise;
        } else if (stepResult.newState === TaskState.AwaitingTask) {
          if (this.verbose) {
            console.log(`- Task spawned a sub-task`);
          }
          this._tasks.add(stepResult.task);
          this._tasks.remove(this._currentTask);
          continue;
        } else {
          continue;
        }
      }
      this._continueSignal = (0, import_deferred_promise.createDeferredPromise)();
      const nextAction = await Promise.race([
        this._continueSignal.then(() => "continue"),
        this._stopSignal.then(() => "stop"),
        waitFor?.then(() => "continue")
      ].filter((p) => p !== void 0));
      this._continueSignal = void 0;
      if (nextAction === "stop")
        break;
    }
    this._stopPromise.resolve();
  }
  async stop() {
    if (!this._stopSignal)
      return;
    this._stopPromise = (0, import_deferred_promise.createDeferredPromise)();
    this._stopSignal.resolve();
    await this._stopPromise;
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  TaskInterruptBehavior,
  TaskPriority,
  TaskScheduler,
  TaskState
});
//# sourceMappingURL=Task.js.map
