import type { DataChannelKind } from './types';
export interface FlowControlledDataChannelOptions {
    kind: DataChannelKind;
    /** Buffer level (bytes) at which blocked senders resume; armed as `bufferedAmountLowThreshold`. */
    lowWaterMark: number;
    /** Buffer level (bytes) above which senders block until the buffer drains to the low mark. */
    highWaterMark: number;
    /** Whether the owning engine has been closed — a closed engine rejects waiters immediately. */
    isEngineClosed: () => boolean;
    /**
     * Notified when the buffer crosses the low-water mark in either direction (debounced: fires only
     * on an actual change). Drives the engine's public DCBufferStatusChanged event.
     */
    onBufferStatusChanged?: (isLow: boolean) => void;
}
/**
 * Two-watermark flow control for one data channel kind.
 *
 * Owns the per-kind headroom gate: senders proceed freely while the buffer is at or below the
 * high-water mark and otherwise block — serialized FIFO through a mutex — until the browser's
 * `bufferedamountlow` event (armed at the low-water mark) signals the buffer has drained. The
 * serialization prevents woken senders from all refilling at once and overflowing the SCTP send
 * buffer (see livekit/client-sdk-js#1995).
 *
 * Waiters are parked on the channel object captured at wait entry. If that object stops being
 * current — replaced or torn down — its events may never fire again, so the owner must call
 * {@link invalidateWaiters}, which aborts parked waiters (releasing the gate); the next waiter
 * gets a fresh controller.
 */
export declare class FlowControlledDataChannel {
    readonly kind: DataChannelKind;
    readonly lowWaterMark: number;
    readonly highWaterMark: number;
    protected isEngineClosed: () => boolean;
    private onBufferStatusChanged?;
    /** Last emitted low-water status; starts true (an empty buffer is below the mark). */
    private bufferStatusLow;
    private handle?;
    private headroomLock;
    /** Cancels parked headroom waiters when the handle is replaced or torn down. */
    private waiterAbortController;
    constructor(opts: FlowControlledDataChannelOptions);
    /** The currently attached RTCDataChannel handle, if any. */
    get channelHandle(): RTCDataChannel | undefined;
    /**
     * Attaches the channel handle this wrapper controls. Replacing an existing handle rejects
     * parked waiters — their events would never fire again on the abandoned object — and installs a
     * fresh controller, so queued senders re-check against the new channel. Wrappers outlive their
     * handles: this is the one place handle turnover happens, which is what makes stranding a
     * waiter structurally impossible.
     */
    attach(dc: RTCDataChannel): void;
    /** Detaches the handle on teardown, rejecting parked waiters. */
    detach(reason?: string): void;
    protected getChannel(): RTCDataChannel | undefined;
    /**
     * Whether the send buffer has room to accept more data (the send gate). Senders proceed while
     * this is true and block once it goes false. Callers resolve the handle (and decide what an
     * absent one means) before asking.
     */
    isBelowHighWaterMark(dc: RTCDataChannel): boolean;
    /**
     * Whether the send buffer has drained to its low-water mark. Drives the engine's public
     * DCBufferStatusChanged event.
     */
    isBelowLowWaterMark(dc: RTCDataChannel): boolean;
    /**
     * Acquires the headroom lock, resolving with the unlock function. Batch senders (the resume
     * replay) hold it across all of their sends so no other sender can interleave, calling
     * {@link waitForHeadroomWithoutLock} per message to respect flow control within the batch.
     */
    lockHeadroom(): Promise<() => void>;
    /**
     * Resolves once the caller may send on this channel: immediately while the send buffer is at or
     * below its high-water mark, otherwise once the buffer has drained to the low-water mark (the
     * `bufferedamountlow` event). Callers are serialized through the headroom lock so that, when
     * the buffer drains, they refill it one at a time (up to the high-water mark) rather than all
     * sending at once and overflowing the SCTP send buffer (see livekit/client-sdk-js#1995). The
     * closed/buffer checks run inside the lock so queued callers proceed in FIFO order.
     */
    waitForHeadroomWithLock(): Promise<void>;
    /** Core wait of {@link waitForHeadroomWithLock}. The caller must hold the headroom lock. */
    waitForHeadroomWithoutLock(): Promise<void>;
    /** Rejects all parked headroom waiters; the next waiter gets a fresh controller. */
    invalidateWaiters(reason: string): void;
    /**
     * Recomputes whether the buffer has drained to the low-water mark and, if that changed since the
     * last check, notifies the status listener. Two independent triggers land here: a send (which
     * raises the buffer) and the `bufferedamountlow` drain event (which lowers it) — the latter has
     * no send to hang the work off, which is why this is a shared entry point rather than a tail of
     * `send`.
     */
    refreshBufferStatus(): void;
}
//# sourceMappingURL=FlowControlledDataChannel.d.ts.map
