/**
 * RealtimeRoom — presence + broadcast over a {@link RealtimeTransport}.
 *
 *   const room = RealtimeRoom.join({
 *     transport: new BroadcastChannelTransport({ id, channel: 'demo' }),
 *     initialPresence: { name: 'Ada', color: '#6d5bfa' },
 *   });
 *
 *   room.onPresence((peers) => renderAvatars(peers));
 *   room.setPresence({ cursor: { x, y } });
 *   room.on('chat', (e) => appendMessage(e.payload));
 *   room.broadcast('chat', 'message', { text: 'hi' });
 *
 * Presence is heartbeat-based: peers re-announce on an interval and are pruned
 * after a timeout. Transports with explicit teardown (MemoryHub) also emit an
 * immediate `bye` on {@link RealtimeRoom.leave}.
 */
import { Signal } from '../client/reactive.js';
import type { BroadcastEvent, PresencePeer, PresenceState, RealtimeMessage, RealtimeTransport } from './types.js';
export interface RealtimeRoomOptions<P extends PresenceState> {
    transport: RealtimeTransport;
    /** Initial presence for the local peer. */
    initialPresence?: P;
    /** Heartbeat interval (ms). Default 2000. Set 0 to disable timers. */
    heartbeatMs?: number;
    /** Peer expiry after no heartbeat (ms). Default 6000. */
    timeoutMs?: number;
    /** Injectable clock for tests. Default `Date.now`. */
    now?: () => number;
}
type BroadcastHandler = (event: BroadcastEvent) => void;
export declare class RealtimeRoom<P extends PresenceState = PresenceState> {
    readonly id: string;
    private transport;
    private myState;
    private peers;
    /** Sender timestamps from presence messages (not local lastSeen). */
    private presenceTsByPeer;
    private channelHandlers;
    private _presence;
    private now;
    private heartbeatMs;
    private timeoutMs;
    private heartbeatTimer?;
    private unsubscribe;
    private closed;
    private pendingReplay;
    /** Ids of broadcasts already delivered — keeps replay idempotent (G-Set). */
    private seenMsgIds;
    private constructor();
    /** Join a room and announce presence. */
    static join<P extends PresenceState = PresenceState>(opts: RealtimeRoomOptions<P>): RealtimeRoom<P>;
    /** The local peer id. */
    get selfId(): string;
    /** Current local presence state. */
    getSelfState(): P;
    /** Merge a partial update into local presence and broadcast it. */
    setPresence(partial: Partial<P>): void;
    /** Replace local presence wholesale and broadcast it. */
    replacePresence(state: P): void;
    /** All peers including self (self first). */
    getPresence(): PresencePeer<P>[];
    /** Peers excluding self. */
    getOthers(): PresencePeer<P>[];
    /** Subscribe to presence changes. Called immediately with current peers. */
    onPresence(cb: (peers: PresencePeer<P>[]) => void): () => void;
    /** Reactive presence signal (for framework adapters). */
    get presenceSignal(): Signal<PresencePeer<P>[]>;
    /**
     * Fire-and-forget broadcast to all other peers on a channel. Returns the
     * stable message id assigned to this broadcast — persist it alongside an
     * optimistic local render so an echoed copy (relay replay, reconnect) is
     * deduplicated by id rather than re-rendered.
     */
    broadcast(channel: string, event: string, payload: unknown): string;
    private newMsgId;
    /** Subscribe to broadcasts on a channel. Returns an unsubscribe fn. */
    on(channel: string, handler: BroadcastHandler): () => void;
    /**
     * Apply a relay replay batch (chat history, text snapshot, presence).
     * Used when reconnecting to a hub with {@link RelayPersistence}.
     */
    replay(messages: RealtimeMessage[]): void;
    /** Announce departure and tear down. */
    leave(): void;
    private handle;
    private flushPendingReplay;
    private hasActiveSubscribers;
    private integrateRemote;
    private upsertPeer;
    private announceHello;
    private announcePresence;
    private startHeartbeat;
    private pruneExpired;
    private recomputePresence;
}
export {};
//# sourceMappingURL=room.d.ts.map