import type { Message } from "@slackwsh/contracts";
import { MessageStore } from "./message-store";
export type ChannelSyncState = "catchingUp" | "live";
export interface ScrollbackFetcher {
    (opts: {
        afterSeq?: number;
        beforeSeq?: number;
        limit: number;
    }): Promise<Message[]>;
}
/**
 * The catch-up/live boundary (§5.3, invariant I7): "On join the client
 * sends its cursor and enters catchingUp. Live events arriving in that
 * window are buffered client-side, not rendered. When backfill completes
 * the buffer is merged by seq, deduplicated, and flushed; only then does
 * the channel enter live." Buffering client-side (not server-side) keeps
 * gateway nodes stateless with respect to catch-up and survives a gateway
 * crash mid-backfill — see ARCHITECTURE §5.3.
 *
 * Also owns gap detection: a live message arriving with `seq` more than one
 * past `expectedSeq` is a gap. The client never renders across a gap — it
 * re-fetches instead. Silently rendering past a gap makes order corruption
 * permanent, which is the whole reason this class exists rather than just
 * trusting whatever socket.io delivers.
 */
export declare class ChannelSync {
    private readonly channelId;
    private readonly store;
    private readonly fetchScrollback;
    private static readonly INITIAL_PAGE_SIZE;
    private state;
    private liveBuffer;
    private expectedSeq;
    private resyncing;
    private olderHistoryExhausted;
    constructor(channelId: string, store: MessageStore, fetchScrollback: ScrollbackFetcher, cursorSeq: number);
    getState(): ChannelSyncState;
    /** Runs the backfill, merges anything buffered while it was in flight,
     * then flips to `live`. Call once per channel-open. */
    start(): Promise<void>;
    /** Loads the page immediately before the oldest cached message. Older
     * history never moves the live cursor backwards; it is merged into the
     * same idempotent, seq-sorted store and can therefore overlap safely with
     * realtime events or a retried request. */
    loadOlder(limit?: number): Promise<Message[]>;
    hasOlderMessages(): boolean;
    /** Feed a `message:created` event from the socket. */
    onLiveMessage(message: Message): Promise<void>;
    onMessageEdited(message: Message): void;
    onMessageDeleted(messageId: string | number, seq: number): void;
    getMessages(): Message[];
    getCursor(): number;
    private advanceExpectedSeq;
    /** A detected gap: re-fetch rather than render across it. Re-entrant calls
     * while a resync is already in flight are dropped — the in-flight fetch
     * will already cover whatever gap triggered them. */
    private resync;
}
//# sourceMappingURL=channel-sync.d.ts.map