/**
 * Utils for promises.
 *
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { asError } from "#util/Error.js";
import { InternalError, TimeoutError } from "../MatterError.js";
import { Time } from "../time/Time.js";
function createPromise() {
  let resolver, rejecter;
  const promise = new Promise((resolve, reject) => {
    resolver = resolve;
    rejecter = reject;
  });
  if (!resolver || !rejecter) {
    throw new InternalError("Failed to extract resolve/reject from Promise context");
  }
  return {
    promise,
    resolver,
    rejecter
  };
}
function anyPromise(promises) {
  return new Promise((resolve, reject) => {
    let numberRejected = 0;
    let wasResolved = false;
    for (const entry of promises) {
      const promise = typeof entry === "function" ? entry() : entry;
      promise.then((value) => {
        if (!wasResolved) {
          wasResolved = true;
          resolve(value);
        }
      }).catch((reason) => {
        numberRejected++;
        if (!wasResolved && numberRejected === promises.length) {
          reject(reason);
        }
      });
    }
  });
}
class PromiseTimeoutError extends TimeoutError {
}
async function withTimeout(timeout, promise, cancel) {
  let cancelFn;
  if (typeof cancel === "function") {
    cancelFn = cancel;
  } else if (typeof cancel?.abort === "function") {
    cancelFn = () => cancel.abort();
  } else {
    cancelFn = () => {
      throw new PromiseTimeoutError();
    };
  }
  let cancelTimer;
  const timedOut = new Promise((resolve, reject) => {
    const timer = Time.getTimer("promise-timeout", timeout, () => {
      try {
        cancelFn();
      } catch (e) {
        reject(asError(e));
        return;
      }
    });
    cancelTimer = () => {
      timer.stop();
      resolve();
    };
    timer.start();
  });
  let result;
  const producer = promise.then(
    (r) => {
      cancelTimer?.();
      result = r;
    },
    (e) => {
      cancelTimer?.();
      throw e;
    }
  );
  await Promise.all([timedOut, producer]);
  return result;
}
const MaybePromise = {
  /**
   * Determine whether a {@link MaybePromiseLike} is a {@link Promise}.
   */
  is(value) {
    return typeof value === "object" && value !== null && typeof value.then === "function" && value !== this;
  },
  /**
   * Chained MaybePromise.  Invokes the resolve function immediately if the {@link MaybePromise} is not a
   * {@link Promise}, otherwise the same as a normal {@link Promise.then}.
   */
  then(producer, resolve, reject) {
    let rejected = false;
    try {
      let value;
      if (producer instanceof Function) {
        value = producer();
      } else {
        value = producer;
      }
      if (MaybePromise.is(value)) {
        return value.then(
          resolve,
          reject ? (error) => {
            rejected = true;
            return reject?.(error);
          } : void 0
        );
      }
      if (resolve) {
        return resolve(value);
      }
    } catch (e) {
      if (reject && !rejected) {
        return reject(e);
      }
      throw e;
    }
    return void 0;
  },
  /**
   * Equivalent of {@link Promise.catch}.
   */
  catch(producer, onrejected) {
    return this.then(producer, void 0, onrejected);
  },
  /**
   * Equivalent of {@link Promise.finally}.
   */
  finally(producer, onfinally) {
    let result;
    try {
      if (typeof producer === "function") {
        result = producer();
      } else {
        result = producer;
      }
    } finally {
      if (MaybePromise.is(result)) {
        if (typeof result.finally === "function") {
          result = result.finally(onfinally);
        } else {
          result = result.then(
            (value) => MaybePromise.then(
              () => onfinally?.(),
              () => value
            ),
            (error) => MaybePromise.then(
              () => onfinally?.(),
              () => {
                throw error;
              }
            )
          );
        }
      } else {
        const finallyResult = onfinally?.();
        if (MaybePromise.is(finallyResult)) {
          const actualResult = result;
          result = finallyResult.then(() => actualResult);
        }
      }
    }
    return result;
  },
  [Symbol.toStringTag]: "MaybePromise"
};
class Gate {
  #promise;
  #resolve;
  #reject;
  #resolvedAs;
  #isResolved = false;
  #isRejected = false;
  constructor() {
    let resolve;
    let reject;
    this.#promise = new Promise((res, rej) => {
      resolve = res;
      reject = rej;
    });
    this.#resolve = resolve;
    this.#reject = reject;
  }
  /**
   * Move to resolved state with the specified value.
   */
  open(value) {
    if (this.#isResolved) {
      if (this.#resolvedAs === value) {
        return;
      }
      this.#resolvedAs = value;
      this.#promise = Promise.resolve(value);
      return;
    }
    if (this.#isRejected) {
      this.#resolvedAs = value;
      this.#isResolved = true;
      this.#isRejected = false;
      this.#promise = Promise.resolve(value);
      return;
    }
    this.resolve(value);
  }
  /**
   * Move to unresolved state.
   */
  close() {
    if (!this.#isResolved && !this.#isRejected) {
      return;
    }
    this.#isResolved = this.#isRejected = false;
    this.#promise = new Promise((resolve, reject) => {
      this.#resolve = resolve;
      this.#reject = reject;
    });
  }
  /**
   * Perform normal promise resolution.  Ignored if promise is already settled.
   */
  resolve(value) {
    if (this.#isResolved || this.#isRejected) {
      return;
    }
    this.#resolvedAs = value;
    this.#isResolved = true;
    this.#resolve(value);
  }
  /**
   * Perform normal promise rejection.  Ignored if promise is already settled.
   */
  reject(cause) {
    if (this.#isResolved || this.#isRejected) {
      return;
    }
    this.#isRejected = true;
    this.#reject(cause);
  }
  then(resolve, reject) {
    return this.#promise.then(resolve, reject);
  }
  catch(reject) {
    return this.#promise.catch(reject);
  }
  finally(then) {
    return this.#promise.finally(then);
  }
  [Symbol.toStringTag] = Promise.prototype[Symbol.toStringTag];
}
MaybePromise.toString = () => "MaybePromise";
var SafePromise;
((SafePromise2) => {
  function race(values) {
    let listener;
    let registered;
    let disposables;
    let race2 = new Promise((resolve, reject) => {
      listener = { resolve, reject };
      for (const value of values) {
        if (!MaybePromise.is(value)) {
          Promise.resolve(value).then(resolve, reject);
          continue;
        }
        if ("use" in value && "off" in value && typeof value.use === "function" && typeof value.off === "function") {
          if ("value" in value && value.value) {
            Promise.resolve(value.value).then(resolve, reject);
            continue;
          }
          if (!disposables) {
            disposables = [];
          }
          let observer;
          if ("value" in value) {
            observer = (value2) => {
              if (value2) {
                resolve(value2);
              }
            };
            disposables.push(value.useError(reject));
          } else {
            observer = resolve;
          }
          disposables.push(value.use(observer));
          continue;
        }
        const settlement = settlementOf(value);
        if (settlement.isSettled) {
          value.then(resolve, reject);
          continue;
        }
        settlement.listeners.add(listener);
        if (registered) {
          registered.push(settlement.listeners);
        } else {
          registered = [settlement.listeners];
        }
      }
    });
    if (registered || disposables) {
      race2 = race2.finally(() => {
        if (registered) {
          for (const listeners of registered) {
            listeners.delete(listener);
          }
        }
        if (disposables) {
          for (const disposable of disposables) {
            disposable[Symbol.dispose]();
          }
        }
      });
    }
    return race2;
  }
  SafePromise2.race = race;
  const settlements = /* @__PURE__ */ new WeakMap();
  function settlementOf(promise) {
    const existing = settlements.get(promise);
    if (existing) {
      return existing;
    }
    const settlement = { isSettled: false, listeners: /* @__PURE__ */ new Set() };
    settlements.set(promise, settlement);
    promise.then(
      (value) => {
        settlement.isSettled = true;
        for (const listener of settlement.listeners) {
          listener.resolve?.(value);
        }
        settlement.listeners.clear();
      },
      (cause) => {
        settlement.isSettled = true;
        for (const listener of settlement.listeners) {
          listener.reject?.(cause);
        }
        settlement.listeners.clear();
      }
    );
    return settlement;
  }
})(SafePromise || (SafePromise = {}));
export {
  Gate,
  MaybePromise,
  PromiseTimeoutError,
  SafePromise,
  anyPromise,
  createPromise,
  withTimeout
};
//# sourceMappingURL=Promises.js.map
