/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { AbortedError, TimeoutError } from "#MatterError.js";
import { Time } from "#time/Time.js";
import { asError } from "./Error.js";
import { Callable } from "./Function.js";
import { SafePromise } from "./Promises.js";
class Abort extends Callable {
  // The native controller implementation
  #controller;
  // Optional abort chaining
  #unregisterDependencies;
  // Optional PromiseLike behavior
  #aborted;
  #resolve;
  // Optional timeout
  #timeout;
  constructor({ abort: aborts, timeout, handler, timeoutHandler } = {}) {
    const abort = (reason) => {
      if (typeof reason === "string") {
        reason = new AbortedError(reason);
      }
      this.abort(reason);
    };
    super(abort);
    this.#controller = new AbortController();
    const throwIfAborted = this.#controller.signal.throwIfAborted.bind(this.#controller.signal);
    this.#controller.signal.throwIfAborted = () => {
      try {
        throwIfAborted();
      } catch (reason) {
        throw createAbortError(reason);
      }
    };
    const self = (reason) => {
      this.abort(reason);
    };
    Object.setPrototypeOf(self, Object.getPrototypeOf(this));
    if (aborts && !Array.isArray(aborts)) {
      aborts = [aborts];
    }
    if (aborts?.length) {
      const dependencies = aborts.map((abort2) => abort2 && ("signal" in abort2 ? abort2.signal : abort2));
      for (const dependency of dependencies) {
        if (dependency === void 0) {
          continue;
        }
        const listener = () => this.abort(asError(dependency.reason));
        dependency.addEventListener("abort", listener);
        const unregisterPrev = this.#unregisterDependencies;
        this.#unregisterDependencies = () => {
          unregisterPrev?.();
          dependency.removeEventListener("abort", listener);
        };
      }
    }
    if (timeout !== void 0) {
      if (timeoutHandler) {
        const original = timeoutHandler;
        timeoutHandler = () => {
          try {
            original.call(this);
          } catch (e) {
            this.abort(asError(e));
          }
        };
      } else {
        timeoutHandler = () => this.abort(new TimeoutError());
      }
      if (timeout <= 0) {
        timeoutHandler.call(this);
      } else {
        this.#timeout = Time.getPeriodicTimer("subtask timeout", timeout, () => {
          if (this.aborted) {
            return;
          }
          timeoutHandler.call(this);
        });
        this.#timeout.start();
      }
    }
    if (handler) {
      if (this.aborted) {
        handler.call(this, this.reason);
      } else {
        this.addEventListener("abort", () => handler.call(this, this.reason));
      }
    }
  }
  abort(reason) {
    if (typeof reason === "string") {
      reason = new AbortedError(reason);
    }
    this.#controller.abort(reason ?? new AbortedError("Operation aborted with no reason given"));
  }
  get signal() {
    return this.#controller.signal;
  }
  /**
   * Race one or more promises with my abort signal.
   *
   * If aborted returns undefined.
   */
  async race(...promises) {
    return Abort.race(this, ...promises);
  }
  /**
   * Race with throw on abort.
   */
  async attempt(...promises) {
    return await Abort.attempt(this, ...promises);
  }
  /**
   * Free resources.
   *
   * You must abort or invoke {@link close} when finished if you construct with {@link Abort.Options#abort} or
   * {@link Abort.Options#timeout}.
   */
  close() {
    this.#timeout?.stop();
    this.#unregisterDependencies?.();
  }
  [Symbol.dispose]() {
    this.close();
  }
  if(condition, reason) {
    if (condition) {
      this.abort(reason);
    }
  }
  get aborted() {
    return this.signal.aborted;
  }
  set onabort(onabort) {
    this.signal.onabort = onabort;
  }
  get onabort() {
    return this.signal.onabort;
  }
  get reason() {
    return this.signal.reason;
  }
  throwIfAborted() {
    this.signal.throwIfAborted();
  }
  async then(onfulfilled, onrejected) {
    if (!this.#aborted) {
      this.#aborted = new Promise((resolve) => this.#resolve = resolve);
      this.addEventListener("abort", () => this.#resolve(asError(this.signal.reason)));
    }
    return await this.#aborted.then(onfulfilled, onrejected);
  }
  addEventListener(type, listener, options) {
    this.signal.addEventListener(type, listener, options);
  }
  removeEventListener(type, listener, options) {
    this.signal.addEventListener(type, listener, options);
  }
  dispatchEvent(event) {
    return this.signal.dispatchEvent(event);
  }
}
((Abort2) => {
  function is(signal) {
    if (!signal) {
      return;
    }
    if ("signal" in signal) {
      signal = signal.signal;
    }
    return signal.aborted;
  }
  Abort2.is = is;
  async function race(signal, ...promises) {
    if (signal) {
      if ("signal" in signal) {
        signal = signal.signal;
      }
      let off;
      const aborted = new Promise((resolve) => {
        const onabort = () => resolve();
        signal.addEventListener("abort", onabort);
        off = () => signal.removeEventListener("abort", onabort);
      });
      try {
        return await SafePromise.race([aborted, ...promises]);
      } finally {
        off();
      }
    }
    if (promises.length === 1) {
      return await Promise.resolve(promises[0]);
    }
    return await SafePromise.race(promises);
  }
  Abort2.race = race;
  async function attempt(signal, ...promises) {
    if (signal && "signal" in signal) {
      signal = signal.signal;
    }
    const result = await race(signal, ...promises);
    signal?.throwIfAborted();
    return result;
  }
  Abort2.attempt = attempt;
  function sleep(description, abort, duration) {
    let timer;
    const rested = new Promise((resolve) => {
      timer = Time.getTimer(description, duration, () => resolve());
    });
    return race(abort, rested).finally(timer.stop.bind(timer));
  }
  Abort2.sleep = sleep;
  function subtask(signal, timeout) {
    return new Abort2({ abort: signal, timeout });
  }
  Abort2.subtask = subtask;
  function any(...signals) {
    return new Abort2({ abort: [...signals.filter((signal) => signal)] });
  }
  Abort2.any = any;
  function checkerFor(signal) {
    if (!signal) {
      return () => {
      };
    }
    if ("abort" in signal && typeof signal.abort === "object") {
      signal = signal.abort;
    }
    if (!signal) {
      return () => {
      };
    }
    if ("signal" in signal) {
      signal = signal.signal;
    }
    if (!signal || !("throwIfAborted" in signal)) {
      return () => {
      };
    }
    return signal.throwIfAborted.bind(signal);
  }
  Abort2.checkerFor = checkerFor;
})(Abort || (Abort = {}));
function createAbortError(reason) {
  const error = new AbortedError();
  const stack = error.stack?.split("\n");
  stack?.splice(1, 2);
  error.stack = stack?.join("\n");
  error.cause = asError(reason);
  return error;
}
export {
  Abort
};
//# sourceMappingURL=Abort.js.map
