"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
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 __using = (stack, value, async) => {
  if (value != null) {
    if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
    var dispose, inner;
    if (async) dispose = value[__knownSymbol("asyncDispose")];
    if (dispose === void 0) {
      dispose = value[__knownSymbol("dispose")];
      if (async) inner = dispose;
    }
    if (typeof dispose !== "function") __typeError("Object not disposable");
    if (inner) dispose = function() {
      try {
        inner.call(this);
      } catch (e) {
        return Promise.reject(e);
      }
    };
    stack.push([async, dispose, value]);
  } else if (async) {
    stack.push([async]);
  }
  return value;
};
var __callDispose = (stack, error, hasError) => {
  var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
    return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
  };
  var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
  var next = (it) => {
    while (it = stack.pop()) {
      try {
        var result = it[1] && it[1].call(it[2]);
        if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
      } catch (e) {
        fail(e);
      }
    }
    if (hasError) throw error;
  };
  return next();
};
var Scheduler_exports = {};
__export(Scheduler_exports, {
  Scheduler: () => Scheduler
});
module.exports = __toCommonJS(Scheduler_exports);
var import_Logger = require("#log/Logger.js");
var import_MatterError = require("#MatterError.js");
var import_Time = require("#time/Time.js");
var import_Timestamp = require("#time/Timestamp.js");
var import_Abort = require("./Abort.js");
var import_Error = require("./Error.js");
var import_Heap = require("./Heap.js");
var import_Lifetime = require("./Lifetime.js");
var import_Multiplex = require("./Multiplex.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_Logger.Logger.get("Scheduler");
class Scheduler {
  #name;
  #lifetime;
  #timeOf;
  #run;
  #queue;
  #abort = new import_Abort.Abort();
  #workers = new import_Multiplex.BasicMultiplex();
  #semaphore;
  constructor({
    name = "scheduler",
    lifetime = import_Lifetime.Lifetime.process,
    timeOf,
    run,
    semaphore
  }) {
    this.#name = name;
    this.#lifetime = lifetime.join(this.#name);
    this.#timeOf = timeOf;
    this.#run = run;
    this.#semaphore = semaphore;
    this.#queue = new import_Heap.Heap(comparatorFor(timeOf));
    this.#workers.add(this.#schedule());
  }
  /**
   * Schedule a worker.
   */
  add(worker) {
    if (this.#lifetime.isClosing) {
      throw new import_MatterError.ImplementationError(`Cannot schedule new work because ${this.#name} is closed`);
    }
    this.#queue.add(worker);
  }
  /**
   * Unschedule a worker.
   */
  delete(worker) {
    this.#queue.delete(worker);
  }
  /**
   * Update a worker's scheduled run time.
   */
  reschedule(worker) {
    this.#queue.delete(worker);
    this.#queue.add(worker);
  }
  /**
   * Close.
   *
   * Stops accepting workers.  Returns when all workers are complete.
   */
  async close() {
    var _stack = [];
    try {
      const _closing = __using(_stack, this.#lifetime.closing());
      this.#abort();
      await this.#workers;
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      __callDispose(_stack, _error, _hasError);
    }
  }
  /**
   * Performs actual scheduling of workers.
   */
  async #schedule() {
    while (!this.#abort.aborted) {
      var _stack = [];
      try {
        const now = import_Time.Time.nowMs;
        let first = this.#queue.first;
        let nextAt = first ? this.#timeOf(first) : void 0;
        while (nextAt !== void 0 && nextAt <= now) {
          if (this.#abort.aborted) {
            return;
          }
          let slot;
          if (this.#semaphore) {
            slot = await this.#semaphore.obtainSlot(this.#abort);
            if (this.#abort.aborted) {
              return;
            }
          }
          try {
            this.#queue.shift();
            const promise = this.#run(first, this.#abort.signal);
            if (promise) {
              let p = Promise.resolve(promise).catch(this.#unhandled.bind(this));
              if (slot) {
                p = p.finally(() => slot[Symbol.dispose]());
              }
              this.#workers.add(p);
            } else {
              slot?.[Symbol.dispose]();
            }
          } catch (e) {
            slot?.[Symbol.dispose]();
            this.#unhandled(e);
          }
          first = this.#queue.first;
          nextAt = first ? this.#timeOf(first) : void 0;
        }
        const abort = __using(_stack, new import_Abort.Abort({
          abort: this.#abort,
          timeout: nextAt === void 0 ? void 0 : import_Timestamp.Timestamp.delta(now, nextAt)
        }));
        const _abortOnChange = __using(_stack, this.#queue.firstChanged.use((newFirst) => abort.if(newFirst !== first)));
        await abort;
      } catch (_) {
        var _error = _, _hasError = true;
      } finally {
        __callDispose(_stack, _error, _hasError);
      }
    }
  }
  #unhandled(e) {
    logger.error(`Unhandled error in ${this.#name} worker:`, (0, import_Error.asError)(e));
  }
}
function comparatorFor(timeOf) {
  return (a, b) => timeOf(a) - timeOf(b);
}
//# sourceMappingURL=Scheduler.js.map
