import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
import type { Transaction } from "@tiptap/pm/state";
import type { Editor } from "@tiptap/react";
import { type MutableRefObject } from "react";
import type { Awareness } from "y-protocols/awareness";
import type { Doc as YDoc } from "yjs";
import { RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION } from "./surgical-apply.js";
export { RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION };
/** Reads the current markdown out of the tiptap-markdown storage. */
export declare function getEditorMarkdown(editor: Editor): string;
export interface UseCollabReconcileOptions {
/** The live editor, or null until it mounts. */
editor: Editor | null;
/** Shared Y.Doc when collaborating; null disables all collab paths. */
ydoc?: YDoc | null;
/** True after the collab provider has loaded the persisted initial Y.Doc state. */
collabSynced?: boolean;
/** Shared awareness; null keeps the sole-client lead path. */
awareness?: Awareness | null;
/** Authoritative markdown value (SQL source of truth). */
value: string;
/** Timestamp of the authoritative value; gates newer-than reconcile. */
contentUpdatedAt?: string | null;
/** Whether the editor accepts edits. Reconcile/seed only run for the live editor. */
editable: boolean;
/**
* Reports whether focus is inside any editing surface owned by this editor.
* Defaults to TipTap's contenteditable focus. Apps with editable NodeView
* controls outside the contenteditable surface can include those controls so
* a partial save echo cannot interrupt an active edit.
*/
isEditorFocused?: (editor: Editor) => boolean;
/**
* Reads the current markdown from the editor. Injected so a dialect could
* swap serializers; defaults to the tiptap-markdown storage reader. For an app
* with a custom serializer (e.g. Content's `docToNfm(editor.getJSON())`), pass
* it here so the seed/reconcile equality checks compare like-for-like.
*/
getMarkdown?: (editor: Editor) => string;
/**
* Applies the authoritative `value` into the editor. Defaults to passing the
* raw markdown string to `editor.commands.setContent`. Apps whose serializer
* is NOT tiptap-markdown (Content parses `nfmToDoc(value)` into a PM doc)
* override this so seed + reconcile write the correct content shape. The
* supplied `options` carry the history/whitespace flags the default path uses;
* a custom implementation should forward them when relevant.
*/
setContent?: (editor: Editor, value: string, options: {
emitUpdate?: boolean;
addToHistory?: boolean;
}) => void;
/**
* Parses the authoritative `value` into a full ProseMirror document for the
* SURGICAL reconcile path: instead of a whole-document `setContent` (which
* under Collaboration rewrites the entire Y.XmlFragment and tears down every
* block NodeView), the reconcile diffs the parsed doc against the live doc
* and replaces only the changed top-level run. Return null to skip the
* surgical path for a given value (falls back to `setContent`).
*
* Defaults to a best-effort tiptap-markdown parse; apps with their own
* serializers (Content's NFM, Plan's blocks[] doc) should supply the exact
* doc their `setContent` would write. Pass `false` to disable surgical
* application entirely.
*/
parseValue?: ((editor: Editor, value: string) => ProseMirrorNode | null) | false;
/**
* Normalizes the authoritative `value` to the canonical markdown the editor
* would emit, so the "already in sync / our own echo" equality checks match a
* serializer that re-canonicalizes (Content's `canonicalizeNfm`). Defaults to
* identity (GFM already round-trips byte-stably).
*/
normalizeValue?: (value: string) => string;
/**
* Decides whether the empty-doc seed should run for the current shared
* fragment. Defaults to "fragment has no nodes, or the editor holds no
* semantic markdown". Apps with sentinel-empty content (Content's
* `` filler) override this. Receives the live fragment length
* and the editor's current markdown.
*/
shouldSeed?: (info: {
value: string;
currentMarkdown: string;
fragmentLength: number;
}) => boolean;
/**
* The initial "applied" watermark. Default mirrors `contentUpdatedAt`, so a
* fresh mount whose Y.Doc already matches SQL doesn't re-apply. Pass `null`
* to force the first reconcile pass to adopt authoritative SQL even at the
* same timestamp — Content does this so a stale persisted Y.Doc (an agent that
* edited the CLOSED doc) is corrected on open. The editor is keyed per
* document upstream, so this only affects the first mount of each doc.
*/
initialAppliedUpdatedAt?: string | null;
}
export interface UseCollabReconcileResult {
/** True when a Y.Doc is bound (collaborative editing active). */
collab: boolean;
/**
* Set true around any programmatic `setContent` so the editor's `onUpdate`
* can ignore the resulting transaction (it isn't a user edit).
*/
isSettingContentRef: MutableRefObject;
/**
* Call from `onUpdate` BEFORE serializing. Returns true when the update must
* be ignored: editor not editable, mid-programmatic-setContent, or (in collab
* mode) a remote-origin transaction. Also records the local typing time.
*/
shouldIgnoreUpdate: (transaction: Transaction) => boolean;
/**
* Call from `onUpdate` AFTER computing the markdown to emit. Returns false
* when the value must NOT be persisted yet (an empty collab doc before the
* seed has run); records it as the last-emitted value otherwise.
*/
registerEmitted: (markdown: string) => boolean;
}
export declare function useCollabReconcile({ editor, ydoc, collabSynced, awareness, value, contentUpdatedAt, editable, isEditorFocused, getMarkdown, setContent, parseValue, normalizeValue, shouldSeed, initialAppliedUpdatedAt, }: UseCollabReconcileOptions): UseCollabReconcileResult;
//# sourceMappingURL=useCollabReconcile.d.ts.map