var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
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();
};
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { AbortedError, ClosedError } from "#MatterError.js";
import { Duration } from "#time/Duration.js";
import { Time } from "#time/Time.js";
import { Instant } from "#time/TimeUnit.js";
import { Logger } from "../log/Logger.js";
import { Abort } from "./Abort.js";
import { createPromise } from "./Promises.js";
const logger = Logger.get("Semaphore");
class Semaphore {
  #scope;
  #delay;
  #timeout;
  #queue = new Array();
  #delayTimer;
  #concurrency;
  #runningCount = 0;
  #abort = new Abort();
  #closed = false;
  constructor(scope, concurrency = 1, delay = Instant, timeout) {
    this.#scope = scope;
    this.#concurrency = concurrency;
    this.#delay = delay;
    this.#timeout = timeout;
    this.#delayTimer = Time.getTimer("Queue delay", this.#delay, () => this.#processNextInQueue());
  }
  /**
   * Get a work slot from the queue.
   *
   * This method returns a promise that resolves when a slot is available.
   * The returned slot must be released when work is complete, either by
   * calling `close()` or by using the `using` syntax.
   *
   * @param abort - Optional abort signal to cancel waiting for a slot
   * @returns A disposable work slot
   * @throws AbortedError if the abort signal is triggered before a slot is obtained
   */
  async obtainSlot(abort) {
    var _stack = [];
    try {
      if (this.#closed) {
        throw new ClosedError("Queue is closed");
      }
      if (abort) {
        const signal = "signal" in abort ? abort.signal : abort;
        signal.throwIfAborted();
      }
      const combinedAbort = __using(_stack, new Abort({ abort: abort ? [abort, this.#abort] : [this.#abort] }));
      const canGrantImmediately = this.#runningCount < this.#concurrency && this.#queue.length === 0 && (this.#delay === 0 || !this.#delayTimer.isRunning);
      if (canGrantImmediately) {
        return this.#grantSlot();
      }
      const { promise, resolver } = createPromise();
      const entry = { resolve: resolver };
      logger.debug(`[${this.#scope}] Queueing slot request at position`, this.#queue.length + 1);
      this.#queue.push(entry);
      this.#scheduleProcessing();
      const result = await combinedAbort.race(promise);
      if (result === void 0) {
        const index = this.#queue.indexOf(entry);
        if (index !== -1) {
          this.#queue.splice(index, 1);
          logger.debug(
            `[${this.#scope}] Slot request aborted, removed from queue. Remaining:`,
            this.#queue.length
          );
        }
        combinedAbort.throwIfAborted();
        throw new AbortedError("Aborted without reason");
      }
      return result;
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      __callDispose(_stack, _error, _hasError);
    }
  }
  /**
   * Schedule processing of the queue if there's capacity and items waiting.
   */
  #scheduleProcessing() {
    if (this.#delayTimer.isRunning) return;
    if (this.#queue.length === 0) return;
    if (this.#runningCount >= this.#concurrency) return;
    const delayMs = this.#delay;
    if (delayMs === 0) {
      this.#processNextInQueue();
    } else {
      this.#delayTimer.start();
    }
  }
  /**
   * Grant a slot immediately (when capacity is available).
   */
  #grantSlot() {
    this.#runningCount++;
    if (this.#delay > 0) {
      this.#delayTimer.start();
    }
    let released = false;
    let timeoutTimer;
    const release = () => {
      if (released) {
        return;
      }
      released = true;
      timeoutTimer?.stop();
      this.#releaseSlot();
    };
    if (this.#timeout !== void 0) {
      timeoutTimer = Time.getTimer(`Slot ${this.#scope} timeout`, this.#timeout, () => {
        if (!released) {
          logger.debug(
            `[${this.#scope}] Slot timed out after ${Duration.format(this.#timeout)}, auto-releasing`
          );
          release();
        }
      }).start();
    }
    return {
      close: release,
      [Symbol.dispose]() {
        this.close();
      }
    };
  }
  /**
   * Release a slot and potentially grant to next in queue.
   */
  #releaseSlot() {
    this.#runningCount--;
    this.#scheduleProcessing();
  }
  /**
   * Process the next waiting request in the queue.
   */
  #processNextInQueue() {
    if (this.#queue.length === 0) {
      return;
    }
    if (this.#runningCount >= this.#concurrency) {
      return;
    }
    const next = this.#queue.shift();
    logger.debug(`[${this.#scope}] Processing next queued slot, queue length now `, this.#queue.length);
    const slot = this.#grantSlot();
    next.resolve(slot);
    this.#scheduleProcessing();
  }
  /**
   * Clear the queue (entries will be rejected via abort).
   */
  clear() {
    if (this.#queue.length > 0) {
      this.#abort.abort(new ClosedError("Queue cleared"));
      this.#abort = new Abort();
    }
    this.#queue.length = 0;
  }
  /**
   * Get the number of pending slot requests in the queue.
   */
  get count() {
    return this.#queue.length;
  }
  /**
   * Get the number of currently active slots.
   */
  get running() {
    return this.#runningCount;
  }
  /**
   * Close the queue and reject all pending slot requests.
   */
  close() {
    this.#closed = true;
    this.#abort.abort(new ClosedError("Queue is closed"));
    this.clear();
    this.#delayTimer.stop();
  }
}
export {
  Semaphore
};
//# sourceMappingURL=Semaphore.js.map
