/**
 * SyncQueue — the formal enqueue surface for server-side mirroring of
 * local actions.
 *
 * Local-first contract: every UI write commits to the local store first,
 * then hands off here. Nothing in this file does network I/O; the
 * Reconciler drains the queue.
 *
 * Implementation note: the underlying persistence already exists. Message
 * ops live in `sync_actions` (drained by ImapManager.processSyncActions),
 * non-message ops in `store_sync` (drained by the per-domain workers).
 * This class is the public API surface — call sites switch to it so the
 * architecture's seam is visible, the rules are enforceable, and we can
 * later bolt on body-fetch / draft-push / send-push lanes without
 * touching every call site again.
 *
 * Priority lanes (interactive > sync > prefetch > backfill) are tracked
 * here as ordering hints; the actual scheduler is in the Reconciler.
 *
 * Part of docs/local-first-plan.md (steps 3-4).
 */
import type { MailxDB } from "@bobfrankston/mailx-store";
import type { ImapManager } from "@bobfrankston/mailx-imap";
export type Lane = "interactive" | "sync" | "prefetch" | "backfill";
/** In-memory body-fetch dedupe + ordering. We keep this as a Set rather
 *  than persisted rows because body fetches are idempotent (the file is
 *  named after the (folder, uid) pair) and a re-fetch on next click is
 *  cheap if a process crash drops the queued item. */
interface BodyFetchKey {
    accountId: string;
    folderId: number;
    uid: number;
    lane: Lane;
    attempts: number;
}
export declare class SyncQueue {
    private db;
    private imapManager;
    private bodyFetches;
    /** Per-draft serialization slot. Autosave fires every ~2 s but an IMAP
     *  search-delete-append cycle takes longer; unserialized pushes
     *  overlapped, each one's "find the stale copy" running before a
     *  sibling's "append" — so neither saw the other's new copy and the
     *  Drafts folder filled with dozens of duplicates (Bob 2026-05-19).
     *  One push in flight per draft; while it runs, only the LATEST queued
     *  raw message is kept (intermediate autosaves are superseded). */
    private draftPushes;
    private static readonly DRAFT_PUSH_MIN_INTERVAL_MS;
    /** Per-account debounce timers. Multiple rapid enqueues (3 flag toggles,
     *  bulk move/delete) coalesce into one processSyncActions drain after
     *  DRAIN_DEBOUNCE_MS. Was previously in ImapManager.debounceSyncActions;
     *  moved here because the queue surface is now the source of truth for
     *  "what needs draining" and it's where the rate limit belongs. */
    private drainTimers;
    private static readonly DRAIN_DEBOUNCE_MS;
    constructor(db: MailxDB, imapManager: ImapManager);
    private scheduleDrain;
    /** Queue a server-side move. The local rows have already been moved in
     *  the DB; the mirror reaches the server on the next reconciler pass. */
    enqueueMove(accountId: string, uid: number, fromFolderId: number, toFolderId: number): void;
    /** Queue a server-side flag update (\Seen, \Flagged, \Answered, etc.). */
    enqueueFlag(accountId: string, uid: number, folderId: number, flags: string[]): void;
    /** Queue a server-side COPY (message stays in the source folder, a copy
     *  is APPENDed to the target). processSyncActions handles it via
     *  fetch+append. Unlike move, no local row is removed — the source row
     *  stays and the target copy is imported by a target-folder sync. */
    enqueueCopy(accountId: string, uid: number, fromFolderId: number, toFolderId: number): void;
    /** Queue a server-side delete (or trash, depending on action). */
    enqueueDelete(accountId: string, uid: number, folderId: number, kind?: "delete" | "trash"): void;
    /** Cancel a pending MOVE action if it hasn't drained yet. Returns true
     *  when an action was found and marked complete (effectively retracted),
     *  false when no matching action exists — caller then queues a
     *  counter-move (the server already saw the original move).
     *
     *  Used by undelete: if the to-trash MOVE is still queued, retract it
     *  rather than queue a redundant trash→original. */
    cancelPendingMove(accountId: string, uid: number, fromFolderId: number, targetFolderId: number): boolean;
    /** Queue an IMAP APPEND of a draft body. Drafts already have crash
     *  recovery via the editing/.eml on disk, so this lane is in-memory
     *  fire-and-forget for now — the architectural value is centralizing
     *  the call site, not adding a second on-disk store for the same
     *  bytes. The reconciler retries on transient IMAP failure via the
     *  existing sync_actions backoff after first failure. */
    enqueueDraftPush(accountId: string, rawMessage: string, previousDraftUid?: number, draftId?: string): void;
    /** Drop the retained draft-push slot once a draft is sent or discarded, so
     *  its tracked UID doesn't linger for the compose session's lifetime. */
    forgetDraft(accountId: string, draftId?: string): void;
    /** The AUTHORITATIVE UID of the draft's current local/server row. The
     *  local-insert path assigns a fresh UID on every checkpoint, so the
     *  client's remembered draftUid goes stale within seconds of typing —
     *  cleanup callers MUST read this BEFORE forgetDraft(). Sending with the
     *  stale UID left the newest local Drafts row behind until the 30-min
     *  reconcile grace finally swept it (Bob 2026-07-12 "sent but still in
     *  drafts"). */
    getDraftLastUid(accountId: string, draftId?: string): number | undefined;
    /** Queue an outbox send. Today the outbox/<acct>/*.ltr directory IS
     *  the queue and a separate worker drains it; this seam is reserved
     *  for when that worker moves under the reconciler. */
    enqueueSend(_accountId: string, _outboxId: string): void;
    /** Request a body fetch. Dedupes per (account, folder, uid). The
     *  `lane` argument hints urgency: `interactive` is a click; `prefetch`
     *  is the background backfiller; `backfill` is the one-time post-sync
     *  catch-up. Reconciler picks higher priority first. */
    enqueueBodyFetch(accountId: string, folderId: number, uid: number, lane?: Lane): void;
    /** Re-enqueue a transient failure — the reconciler calls this after a
     *  fetch errored with a transient code. Returns `true` if the item was
     *  put back in the queue (still under retry budget) or `false` if its
     *  budget is exhausted (caller surfaces the error to the UI). */
    requeueBodyFetch(key: BodyFetchKey, maxAttempts?: number): boolean;
    /** Pop the next body-fetch task at or above `maxLane`. Used by the
     *  reconciler to drain interactive clicks before prefetch backfill. */
    nextBodyFetch(maxLane?: Lane): BodyFetchKey | null;
    /** Pending count for diagnostics + the sync-status pill. */
    pendingCount(): {
        messageActions: number;
        bodyFetches: number;
    };
}
export {};
//# sourceMappingURL=sync-queue.d.ts.map