/**
 * Body-write coordinator — single critical section for entity markdown body writes.
 *
 * Owns the safe path that every server-side surface (autosave PUT, MCP
 * update_entity, agent tools, event processor) shares. The contract:
 *
 *   1. assertWritable gate (server-write only — autosave is the client's
 *      own write and must not be refused). Folded into lock acquisition so
 *      a throwing gate releases the lock cleanly.
 *   2. Per-doc lock — serializes server-side body writes for the same doc.
 *      Inbound client MSG_SYNC frames queue while held (see
 *      yjsManager.withDocLock + msgSyncQueue).
 *   3. Base-hash precondition (autosave 412 path). Read inside the lock so
 *      we see prior locked writes before deciding.
 *   4. Atomic disk write via MarkdownService (tmp-write + fsync + rename).
 *   5. mdRevisionHash bookkeeping (yjsManager.recordDiskBody) so the next
 *      persistence flush captures the post-write state.
 *   6. Git commit. On failure, register the dirty file with SessionManager
 *      (if wired) and surface `CommitFailedDirtyError` to the caller.
 *   7. Server-write only: post-commit `applyAgentContent` broadcast to the
 *      live Y.Doc. Autosave skips this — its content originated in the
 *      live Y.Doc; re-applying would race in-flight keystrokes.
 *
 * Scope boundary: body writes only. Structural ops (rename, delete,
 * changeType, move, folder ops) keep their own paths on graph.ts using
 * the existing transaction — they have a different concurrency profile
 * (no per-doc lock, no base-hash precondition) and routing them through
 * here would create a god-class.
 *
 * Also exposes `reconcileFromDisk` — the explicit external-disk-change
 * primitive callable from startup boot, git-pull event handlers, and
 * dev-tools "reload from disk". Not called from the normal write path.
 */
import type { GitUser } from '../services/git.js';
import type { EntityData } from '../core/validation.js';
/**
 * Minimal surface the coordinator needs from YjsManager. Defined as an
 * interface (rather than importing the concrete class) so unit tests can
 * inject a fake without spinning up the real Y.Doc machinery.
 */
export interface BodyWriteYjsHooks {
    assertWritable(docName: string): void;
    withDocLock<T>(docName: string, fn: () => Promise<T>): Promise<T>;
    applyAgentContent(docName: string, target: string): {
        applied: 'no-doc' | 'unseeded' | 'ok';
    };
    recordDiskBody(docName: string, body: string): void;
    reconcileFromDisk(docName: string): void;
    /** Live body for a seeded Y.Doc, or null when no trustworthy live state
     *  exists (no doc loaded / unseeded). Read side of the live-vs-disk gap.
     *  Optional so coordinator-less / fake-hook test setups need not implement
     *  it — `peekLiveBody` then reports null (disk is authoritative). */
    peekLiveBody?(docName: string): string | null;
    /** docNames of every currently-loaded Y.Doc. Optional so fake-hook test
     *  setups need not implement it — `activeDocNames` then reports []. */
    getActiveDocNames?(): string[];
}
/** Minimal SessionManager surface used by the coordinator. */
export interface BodyWriteSessionHook {
    markDirty(repo: string, user: GitUser, entityType: string, entityId: string, filePath: string): void;
}
/**
 * Narrow commit surface, injected so the coordinator never reaches back
 * through WorkspaceManager → getGraph → getGitService (which made a
 * Graph → Coordinator → WorkspaceManager → Graph triangle). The composition
 * root owns how a body write becomes a git commit.
 *
 * Contract: resolve when the commit lands (or is intentionally a no-op,
 * e.g. a repo with no GitService). Throw to signal commit failure — the
 * coordinator then registers the dirty file and surfaces
 * {@link CommitFailedDirtyError}.
 */
export interface BodyWriteCommitHook {
    commit(coords: EntityCoords, actor: GitUser, message: string): Promise<void>;
}
export interface EntityCoords {
    repo: string;
    entityType: string;
    entityId: string;
    /** Absolute path on disk where the markdown lives. */
    entityPath: string;
}
export type BodyWriteSource = 'autosave' | 'server-write';
export interface BodyWriteOptions {
    /**
     * Produce the frontmatter to persist, given the CURRENT on-disk
     * frontmatter. Invoked INSIDE the per-doc lock so the merge happens
     * against fresh disk state — a content-only autosave that waited on the
     * lock won't clobber a metadata-only edit that committed during the wait.
     */
    prepareFrontmatter: (currentDiskFrontmatter: EntityData) => EntityData;
    /** Optional precondition: SHA-256 hex of expected current body. */
    baseHash?: string;
    /** Required provenance — gates broadcast + assertWritable. */
    source: BodyWriteSource;
    /** Commit author. */
    actor: GitUser;
    /** Skip git commit (caller will commit later, e.g. import flow). */
    skipCommit?: boolean;
    /** Override commit message. */
    commitMessage?: string;
}
export interface BodyWriteResult {
    /**
     * Post-commit broadcast disposition:
     *  - 'ok'              applied to the live Y.Doc.
     *  - 'no-doc'          no live editor open — disk write is the only effect.
     *  - 'unseeded'        Y.Doc exists but hadn't synced; nothing to apply to.
     *  - 'skipped'         autosave path — content originated in the live
     *                      Y.Doc, so re-applying would race in-flight keystrokes.
     *  - 'broadcast-failed' disk + git landed, but applyAgentContent threw.
     *                      Clients were notified via a doc_conflict frame; the
     *                      write itself succeeded. Distinct from 'ok' so
     *                      observability can tell a clean apply from a recovered
     *                      one rather than the failure hiding behind a success.
     */
    broadcast: 'ok' | 'no-doc' | 'unseeded' | 'skipped' | 'broadcast-failed';
}
export declare class BodyWriteCoordinator {
    private commitHook;
    private yjs;
    private sessionManager?;
    private markdownService;
    constructor(commitHook: BodyWriteCommitHook, yjs: BodyWriteYjsHooks, sessionManager?: BodyWriteSessionHook | undefined);
    /**
     * Atomically write a body+frontmatter to disk, commit, and (for
     * server-write) broadcast to live editors. The single body-write
     * critical section shared by autosave + MCP + agent + event processor.
     */
    writeBody(coords: EntityCoords, body: string, opts: BodyWriteOptions): Promise<BodyWriteResult>;
    /**
     * Reconcile a live Y.Doc against the latest .md file on disk. EXPLICIT
     * external-disk-change primitive — call this from startup boot, git-pull
     * event handlers, and dev-tools "reload from disk" only. NOT called from
     * the normal write path; server-side body writes route through
     * {@link writeBody}, which broadcasts via `applyAgentContent` under the
     * per-doc lock.
     *
     * Acquires the per-doc lock so a reconcile mid-write composes with the
     * Phase 2 MSG_SYNC queue + write serialization.
     */
    reconcileFromDisk(coords: EntityCoords): Promise<void>;
    /**
     * Live body for an entity if a seeded editor doc is open, else null.
     * Synchronous and lock-free — a point-in-time peek used by lossless reads
     * (get_entity) and the asset-drop guard so they see the body the user is
     * actually editing, not just the last-persisted disk state.
     */
    peekLiveBody(coords: {
        repo: string;
        entityType: string;
        entityId: string;
    }): string | null;
    /**
     * docNames of every Y.Doc the editor layer currently has loaded. Empty when
     * no editor layer is wired (CLI/tests). Lets callers (e.g.
     * `graph.pullAndReconcile`) cheaply decide whether any open editor needs a
     * disk reconcile before doing per-file work.
     */
    activeDocNames(): string[];
}
