import type { MessageContent } from '../types';
/**
 * Reconciliation between persisted dialog history (processed GraphQL pages)
 * and the realtime messages a chat client accumulated from streaming chunks.
 *
 * This is the missing middle piece of the chat pipeline this lib already
 * owns: `processHistoricalMessages*` produces one stream, the realtime chunk
 * processor / segment accumulator produces the other, and every host (Mingo,
 * tickets, openframe-chat) needs to merge them whenever history is (re)fetched.
 * Hand-rolled versions of this merge have produced both duplicated turns
 * (synthetic kept alongside its persisted twin) and lost turns (synthetic
 * trimmed against a stale snapshot that didn't contain its persisted twin
 * yet) — the freshness rule below is the invariant that prevents both.
 *
 * Pure on purpose: the host owns WHEN to merge (react-query wiring, store
 * writes), this module owns HOW.
 */
/** Minimal structural shape the merge needs — hosts pass their own message
 *  type and get it back. */
export interface MergeableChatMessage {
    id: string;
    role: string;
    content: MessageContent;
    timestamp?: Date;
}
/** Ids minted client-side by realtime chunk processors
 *  (`assistant-<ts>-…` placeholder bubbles, `user-<ts>-…` peer messages,
 *  `error-<ts>` stream errors). They never match the Mongo ObjectIds history
 *  returns for the same turns. This is the cross-host contract every minting
 *  site (lib `use-chat`, Mingo / tickets chunk processors, openframe-chat)
 *  must keep matching — exported so it lives in exactly one place.
 *  `welcome-` and `optimistic-` ids are intentionally NOT listed: welcome
 *  bubbles are never persisted server-side, and optimistic user messages are
 *  deduped by content below. */
export declare const SYNTHETIC_REALTIME_ID_PREFIXES: readonly ["assistant-", "user-", "error-"];
/** Flattens DESC-sorted message pages (newest page first, newest message
 *  first within a page) into one chronological list. */
export declare function flattenMessagePagesChronological<T>(pages: readonly {
    messages: readonly T[];
}[] | undefined): T[];
/** Max `lastChunkStreamSeq` across history pages — the history half of the
 *  merge's seq-coverage signal (`HistoryMergeInput.historyMaxStreamSeq`),
 *  also used by hosts as the JetStream replay start offset. 0 = unstamped. */
export declare function maxPersistedStreamSeq(pages: readonly {
    messages: readonly {
        lastChunkStreamSeq?: number | null;
    }[];
}[] | undefined): number;
export interface HistoryMergeInput<M extends MergeableChatMessage> {
    /** Processed history in chronological order (all fetched pages). */
    processedHistory: M[];
    /** Raw history message ids across all fetched pages, when they can differ
     *  from processed ids (processing may merge/rename). Optional — pass when
     *  available so raw-id duplicates are filtered too. */
    rawHistoryIds?: ReadonlySet<string>;
    /** Messages currently in the host store for this dialog (realtime + prior merges). */
    existingMessages: M[];
    /** Id of the in-flight streaming synthetic, if any. Never dropped.
     *  IMPORTANT: hosts must not pass a STALE id here (e.g. a streaming entry
     *  left behind by unmounting mid-stream) — gate it on the server-side
     *  stream state when available, or the synthetic will be exempted forever. */
    streamingMessageId: string | null;
    /** Epoch ms when the history pages were fetched (react-query `dataUpdatedAt`).
     *  Wall-clock freshness fallback used when seq coverage (below) is unknown:
     *  a synthetic created AFTER this instant cannot be represented in the
     *  snapshot, so it must be kept. NOTE this heuristic is blind to chunk
     *  REPLAY, which re-mints synthetics for old turns with fresh timestamps —
     *  pass the seq fields whenever the host tracks them. */
    historyFetchedAt: number;
    /** Max `lastChunkStreamSeq` across the raw history pages, when the backend
     *  stamps it (see `maxPersistedStreamSeq`). Together with
     *  `realtimeSeenStreamSeq` this gives an exact coverage signal that
     *  replaces the wall-clock heuristic. */
    historyMaxStreamSeq?: number;
    /** Highest stream seq this client has consumed for the dialog (live or
     *  replayed chunks). */
    realtimeSeenStreamSeq?: number;
}
export declare function mergeHistoryWithRealtime<M extends MergeableChatMessage>(input: HistoryMergeInput<M>): M[];
export interface HistoryPrependResult<M extends MergeableChatMessage> {
    newMessages: M[];
    boundaryMessageId?: string;
    boundaryUpdates?: {
        content: MessageContent;
    };
}
/** Pagination path (an older page arrived via fetchNextPage): everything on
 *  screen stays; collect only the messages above the first already-known id,
 *  plus a content refresh for that boundary message if it changed. Returns
 *  null when there is nothing to apply. */
export declare function computeHistoryPrepend<M extends MergeableChatMessage>(processedHistory: M[], existingMessages: M[]): HistoryPrependResult<M> | null;
//# sourceMappingURL=history-merge.d.ts.map