function getAbortSignalApi() {
    const api = AbortSignal;
    return { any: api.any };
}
/**
 * True for fetch-abort throws: Node and browsers both raise an `Error`
 * (or `DOMException`, which is itself an `Error` subclass in modern
 * runtimes) with `name === "AbortError"` when an in-flight request is
 * cancelled. Anything else, even racing with `signal.aborted`, is a real
 * failure (auth, validation, transport) and should reject normally.
 *
 * Centralized here so all callers agree on the predicate — divergent
 * copies were possible (e.g., one site adding a `DOMException` branch
 * while another stayed strict on `Error`) and a silent mismatch in this
 * test would mean some abort paths swallow errors that others surface.
 */
export function isAbortError(err) {
    return err instanceof Error && err.name === "AbortError";
}
export function createTimeoutError(timeoutMs) {
    if (typeof DOMException !== "undefined") {
        return new DOMException(`The operation timed out after ${timeoutMs}ms`, "TimeoutError");
    }
    const error = new Error(`The operation timed out after ${timeoutMs}ms`);
    error.name = "TimeoutError";
    return error;
}
// ---------------------------------------------------------------------------
// createTimeoutAbortSignal
// ---------------------------------------------------------------------------
/**
 * Creates an AbortSignal that fires after `timeoutMs` using a cancellable
 * `AbortController` + `setTimeout`. We intentionally don't delegate to the
 * native `AbortSignal.timeout()` — its returned signal has no way to cancel
 * the underlying timer, which would make `dispose()` a lie on that path and
 * give us divergent abort-reason messages across runtimes.
 *
 * On Node-like runtimes we `unref()` the timer so a pending timeout doesn't
 * keep the process alive. In browsers `.unref` is absent and the guard is a
 * harmless no-op.
 *
 * The signal's abort reason is a `DOMException` with `name: "TimeoutError"`
 * so callers can distinguish timeout-driven aborts from caller-driven ones.
 *
 * Always call `dispose()` when the signal is no longer needed to clear the
 * pending timer.
 */
export function createTimeoutAbortSignal({ timeoutMs, }) {
    const controller = new AbortController();
    let timer = setTimeout(() => {
        timer = undefined;
        controller.abort(createTimeoutError(timeoutMs));
    }, timeoutMs);
    // Node (and Bun/Deno): don't let a pending timeout block process exit.
    // Browsers: `unref` is undefined on the numeric handle; this is a no-op.
    const maybeUnref = timer.unref;
    if (typeof maybeUnref === "function") {
        maybeUnref.call(timer);
    }
    return {
        signal: controller.signal,
        dispose: () => {
            if (timer !== undefined) {
                clearTimeout(timer);
                timer = undefined;
            }
        },
    };
}
// ---------------------------------------------------------------------------
// combineAbortSignals
// ---------------------------------------------------------------------------
/**
 * Combines multiple disposable abort handles so the result aborts when *any*
 * source does. Uses native `AbortSignal.any()` when available; otherwise wires
 * up listeners manually.
 *
 * Returns `undefined` when the array is empty, or passes through a single
 * handle without wrapping it.
 *
 * Always call `dispose()` on the result when it is no longer needed. This
 * removes event listeners from source signals and calls `dispose()` on every
 * input handle (releasing timers, etc).
 */
export function combineAbortSignals({ handles, abortSignalApi = getAbortSignalApi(), }) {
    if (handles.length === 0)
        return undefined;
    if (handles.length === 1)
        return handles[0];
    const disposeInputs = () => {
        for (const handle of handles)
            handle.dispose();
    };
    if (abortSignalApi.any) {
        return {
            signal: abortSignalApi.any(handles.map((h) => h.signal)),
            dispose: disposeInputs,
        };
    }
    // Fallback: wire up listeners manually.
    const controller = new AbortController();
    const listeners = [];
    for (const { signal: source } of handles) {
        if (source.aborted) {
            controller.abort(source.reason);
            return { signal: controller.signal, dispose: disposeInputs };
        }
        const handler = () => controller.abort(source.reason);
        source.addEventListener("abort", handler, { once: true });
        listeners.push({ source, handler });
    }
    return {
        signal: controller.signal,
        dispose: () => {
            for (const { source, handler } of listeners) {
                source.removeEventListener("abort", handler);
            }
            listeners.length = 0;
            disposeInputs();
        },
    };
}
