/**
 * Store — the nexus. Owns the local database, the .eml file store, the
 * operations API, and the event bus. Single source of truth for everything
 * about a mailx account that lives on this device.
 *
 *   UI ──┐                                ┌── Sync clients (IMAP, Gmail API, …)
 *        │  reads / writes / subscribes  │   read pending actions, commit
 *        ↓                                ↓   server-discovered changes
 *                          [ Store ]
 *
 * Contract: every method here mutates local-only state. No IMAP, no Gmail,
 * no SMTP, no DNS, no Drive API. Mutations emit on the bus; subscribers
 * (UI in another process, in-process reconciler, etc.) react. Sync clients
 * (mailx-imap, mailx-sync) hold a Store reference and never touch the
 * underlying MailxDB / FileMessageStore directly.
 *
 * This was previously named `Store` and lived in mailx-service. It
 * moved to mailx-store because mailx-imap (and other sync clients) must
 * be able to consume it without depending on mailx-service — that's the
 * "arrow points the right way" property of the architecture.
 */
import { MailxDB } from "./db.js";
import { FileMessageStore } from "./file-store.js";
import type { StoreBus } from "./bus.js";
import type { MessageEnvelope, MessageQuery, PagedResult } from "@bobfrankston/mailx-types";
/** What the UI gets back from a body read. Mirrors the historical
 *  `getMessage` shape so call-site migration is mechanical. `cached: false`
 *  means the body isn't on disk yet — the UI shows a "downloading…"
 *  placeholder and listens for `bodyAvailable` to re-render. The reconciler
 *  is responsible for actually fetching and emitting the event. */
export interface StoreMessage extends MessageEnvelope {
    bodyHtml: string;
    bodyText: string;
    hasRemoteContent: boolean;
    remoteAllowed: boolean;
    attachments: Array<{
        id: number;
        filename: string;
        mimeType: string;
        size: number;
        contentId: string;
    }>;
    cached: boolean;
    /** Disambiguates `cached: false`:
     *   - bodyOnDisk false → the .eml is NOT on disk; caller should queue
     *     an IMAP/Gmail fetch.
     *   - bodyOnDisk true  → the .eml IS on disk; a parse is in flight.
     *     Caller MUST NOT queue a fetch — just wait for the `bodyAvailable`
     *     bus event. Queuing a fetch here caused the 10-Hz "Loading body…"
     *     loop (Bob 2026-05-15): cached:false → MailxService fetched →
     *     reconciler re-published bodyAvailable → viewer re-called → repeat. */
    bodyOnDisk: boolean;
    deliveredTo: string;
    returnPath: string;
    listUnsubscribe: string;
    listUnsubscribeMail: string;
    listUnsubscribeHttp: string;
    listUnsubscribeOneClick: boolean;
    emlPath: string;
    isFlagged: boolean;
    /** X-Mailx-Draft-ID header value (empty for non-draft messages). When
     *  the user re-opens a draft for edit, the compose layer threads this
     *  back into saveDraft so the server-side dedup keeps replacing the
     *  same draft instead of stacking up a new one every save. */
    mailxDraftId: string;
}
export declare class Store {
    /** SQLite metadata index. Exposed as a public field — sync clients
     *  (mailx-imap, mailx-sync) read/write through it during the
     *  refactor. Future state: every external mutation routes through
     *  a Store method that emits a bus event; raw `store.db.X()` calls
     *  shrink to zero. Today, this is read mostly. */
    readonly db: MailxDB;
    /** .eml file backend. Same migration story as `db` — sync clients
     *  read/write directly today, will route through Store methods. */
    readonly bodyStore: FileMessageStore;
    /** Event bus for Store mutations. Defaults to the process-singleton
     *  so cross-package subscribers (bin/mailx.ts forwarder → WebView,
     *  in-process reconciler triggers) see all writes without explicit
     *  wiring. Tests can pass a fresh StoreBus for isolation. */
    readonly bus: StoreBus;
    private static readonly PARSED_LRU_CAPACITY;
    private parsedLru;
    private parsedLruGet;
    private parsedLruPut;
    private parseInflight;
    private _allowlistCache;
    private _settingsCache;
    private getCachedAllowlist;
    private getCachedSettings;
    invalidateConfigCaches(): void;
    /** Set on read-only Store instances (the db-worker serving UI reads):
     *  receives (rowId, bodyText) when getMessage parses a body whose FTS
     *  body_text is still empty, so the writable side can apply
     *  updateFtsBody. Without this the worker's UPDATE fails on its
     *  query_only connection — silently, for months (2026-07-27). */
    onFtsBodyBackfill: ((rowId: number, bodyText: string) => void) | null;
    /** Background sweep: index the body text of every message whose body is
     *  on disk but whose FTS row has an empty body_text. Historical debt
     *  from the read-only-worker backfill bug — 179k of 190k rows on Bob's
     *  store. Paced (one parse per ~100 ms + chunk pauses) so the shared
     *  parse-worker lane stays responsive for interactive views; safe to
     *  re-run every boot — already-indexed rows cost one rowid lookup and
     *  are skipped, so a completed sweep re-scans in seconds. Never runs on
     *  a read-only connection. */
    runFtsBodyBackfillSweep(): Promise<void>;
    constructor(
    /** SQLite metadata index. Exposed as a public field — sync clients
     *  (mailx-imap, mailx-sync) read/write through it during the
     *  refactor. Future state: every external mutation routes through
     *  a Store method that emits a bus event; raw `store.db.X()` calls
     *  shrink to zero. Today, this is read mostly. */
    db: MailxDB, 
    /** .eml file backend. Same migration story as `db` — sync clients
     *  read/write directly today, will route through Store methods. */
    bodyStore: FileMessageStore, 
    /** Event bus for Store mutations. Defaults to the process-singleton
     *  so cross-package subscribers (bin/mailx.ts forwarder → WebView,
     *  in-process reconciler triggers) see all writes without explicit
     *  wiring. Tests can pass a fresh StoreBus for isolation. */
    bus?: StoreBus);
    /** DB-shape account list (id/name/email/lastSync). The richer
     *  AccountConfig (with imap/smtp/etc.) lives in accounts.jsonc and is
     *  loaded by mailx-settings, not the DB — that path stays in
     *  MailxService until step 3 of the local-first plan. */
    getAccounts(): {
        id: string;
        name: string;
        email: string;
        lastSync: number;
    }[];
    getFolders(accountId: string): any[];
    /** Look up a folder by RFC 6154 specialUse tag (`trash`, `drafts`, `sent`,
     *  `junk`, etc.) for the given account. Falls back to a case-insensitive
     *  path match for legacy rows where specialUse never got tagged.
     *  Returns null when the account has no such folder configured. */
    findSpecialFolder(accountId: string, specialUse: string): {
        id: number;
        path: string;
    } | null;
    /** Single envelope by (account, uid, folder). Null when the row isn't
     *  in the DB — caller decides whether to show "deleted" or queue a
     *  server lookup via the reconciler. */
    getMessageEnvelope(accountId: string, uid: number, folderId?: number): MessageEnvelope | null;
    /** Paginated message list for a (account, folder, ...) query. */
    getMessages(query: MessageQuery): PagedResult<MessageEnvelope>;
    /** All-Inboxes view: union of every account's INBOX, paginated. */
    getUnifiedInbox(page?: number, pageSize?: number, flaggedOnly?: boolean, dateBasis?: "sent" | "received"): PagedResult<MessageEnvelope>;
    /** Local FTS5 search. Server-scope search is the reconciler's job. */
    searchMessages(query: string, page?: number, pageSize?: number, accountId?: string, folderId?: number, includeTrashSpam?: boolean): PagedResult<MessageEnvelope>;
    /** Read a fully-parsed message (envelope + body + attachments) entirely
     *  from local state. Returns null when the envelope isn't known.
     *  Returns `{ ...envelope, cached: false }` when the envelope is known
     *  but the body file isn't on disk — UI shows a placeholder and the
     *  reconciler queues the fetch.
     *
     *  `allowRemote=true` skips HTML sanitization. Used when the user has
     *  explicitly allowed remote content for this sender / domain. */
    getMessage(accountId: string, uid: number, allowRemote: boolean, folderId?: number): Promise<StoreMessage | null>;
    getCalendarEvents(accountId: string, fromMs: number, toMs: number): any[];
    getTasks(accountId: string, includeCompleted?: boolean): any[];
    searchContacts(query: string, limit?: number): any[];
    listContacts(query: string, page?: number, pageSize?: number): any;
    /** Update a message's flag set. Local DB write completes synchronously;
     *  the server-mirror enqueue is the caller's responsibility (typically
     *  via SyncQueue.enqueueFlag) so callers that don't want a server push
     *  — pure-local UI state like "pin in pane" — can skip it.
     *
     *  Publishes:
     *    `message:<uuid>` { kind: "flagsChanged" }
     *    `folder:<id>`    (auto fan-out)
     */
    updateFlags(accountId: string, uid: number, folderId: number, flags: string[]): void;
    /** Move a message between folders in the same account. Adds a tombstone
     *  on the Message-ID so the next sync doesn't re-import the pre-move row
     *  in the source folder before the server-side MOVE completes; tombstone
     *  is cleared on terminal IMAP failure (see processSyncActions).
     *
     *  Returns true if a local row existed and was moved, false otherwise.
     *
     *  Publishes:
     *    `message:<uuid>` { kind: "messageMoved", folderId: source, targetFolderId }
     *    `folder:<source>` and `folder:<target>` (auto fan-out + explicit count)
     */
    moveMessage(accountId: string, uid: number, fromFolderId: number, targetFolderId: number): boolean;
    /** Trash a message. If a trash folder is configured and the message is
     *  not already in it, this is a move-to-trash. If the message is already
     *  in trash (or no trash exists), it's a hard delete + body unlink.
     *
     *  Returns "moved-to-trash" or "expunged" so the caller knows whether
     *  to enqueue an IMAP MOVE or a DELETE+EXPUNGE on the queue.
     */
    trashMessage(accountId: string, uid: number, folderId: number, trashFolderId: number | null): "moved-to-trash" | "expunged";
    /** Restore a message from trash back to its original folder. Local-only;
     *  caller handles the queue (cancel-pending-MOVE vs queue-counter-MOVE).
     *  Returns true if a local row was moved. */
    undeleteMessage(accountId: string, uid: number, trashFolderId: number, originalFolderId: number): boolean;
}
//# sourceMappingURL=store.d.ts.map