/**
 * Local SQLite FTS5 index for decrypted Proton Mail messages.
 *
 * Proton's E2EE means Google-style server-side semantic search is impossible.
 * The workaround is to build the index locally from Bridge-decrypted bodies.
 * FTS5 gives us BM25-ranked keyword search with snippet highlighting, which
 * is good-enough for most day-to-day "find the email about X" queries.
 *
 * `better-sqlite3` is an *optional* dependency — mailpouch must still
 * load and serve mail tools when it isn't available. Call openFtsIndex() to
 * get a live instance; it throws FtsUnavailableError when the native
 * binding is missing, and callers return a structured error to the tool
 * dispatcher pointing the user at the install command.
 */
export declare class FtsUnavailableError extends Error {
    constructor(message: string);
}
export interface FtsRecord {
    /** Stable cross-folder identifier — prefer the Proton X-Pm-Internal-Id header, else IMAP UID. */
    id: string;
    subject: string;
    from: string;
    to: string;
    folder: string;
    body: string;
    /** Seconds since Unix epoch for the message date. */
    dateEpoch: number;
}
export interface FtsHit extends FtsRecord {
    /** BM25 rank — lower is better. */
    score: number;
    /** FTS5-generated snippet with matches highlighted in [[...]]. */
    snippet: string;
}
export interface FtsSearchOptions {
    query: string;
    limit?: number;
    folder?: string;
    /** Unix-epoch seconds: messages older than this are excluded. */
    sinceEpoch?: number;
    /**
     * Restrict hits to this set of folders. Independent of `folder` (which
     * narrows to a single folder by name). When supplied:
     *  - `undefined` → no restriction (existing behavior).
     *  - non-empty `string[]` → results limited via `folder IN (?, ?, ...)`,
     *    bound parameters to keep SQL injection impossible.
     *  - empty `[]` → zero hits returned (the caller's grant restricts to no
     *    folders, so by construction it sees nothing).
     *
     * Used by the MCP tool surface to enforce per-agent folder allowlists on
     * snippet content. Direct/internal callers can omit it.
     */
    allowedFolders?: string[];
}
export interface FtsStats {
    messageCount: number;
    dbPath: string;
    databaseBytes: number;
}
interface SqliteStatement {
    run(...params: unknown[]): {
        changes: number;
    };
    all(...params: unknown[]): unknown[];
    get(...params: unknown[]): unknown;
}
interface SqliteDatabase {
    exec(sql: string): void;
    prepare(sql: string): SqliteStatement;
    transaction<F extends (...args: never[]) => unknown>(fn: F): F;
    close(): void;
    pragma(s: string): unknown;
}
export declare class FtsIndexService {
    private readonly db;
    private readonly dbPath;
    private readonly stmts;
    static readonly BODY_FORMAT_VERSION = 2;
    constructor(db: SqliteDatabase, dbPath: string);
    /** Insert or replace a record. Single-row path; see upsertMany for bulk. */
    upsert(record: FtsRecord): void;
    /** Bulk upsert wrapped in a single transaction. */
    upsertMany(records: FtsRecord[]): number;
    remove(id: string): boolean;
    /**
     * Atomically clear the index and repopulate it from `records`. The clear and
     * the bulk upsert run inside a single transaction, so a throw mid-rebuild
     * (e.g. a malformed record) rolls the whole thing back and leaves the prior
     * index intact rather than wiping it. Returns the number of records indexed.
     *
     * Callers must prefer this over a bare clear()+upsertMany() pair: a separate
     * clear() commits immediately and a subsequent failure leaves an empty index
     * (PARSE-003, audit-2026-05-28).
     */
    rebuild(records: FtsRecord[]): number;
    /**
     * Run a prepared FTS query, translating an FTS5 query-DSL syntax error into a
     * clean empty result instead of letting better-sqlite3's SqliteError escape
     * to the MCP client. The MATCH operand is a user-supplied query in FTS5's own
     * mini-language (quotes, NEAR(), column filters, etc.); a stray `"` or `(`
     * raises `fts5: syntax error near "..."`. SQL injection is already impossible
     * (the operand is a bound parameter) — this only swallows malformed-DSL
     * throws (PARSE-001, audit-2026-05-28). Any other error is re-thrown.
     */
    private runMatch;
    search(opts: FtsSearchOptions): FtsHit[];
    stats(): FtsStats;
    /** Delete everything from the index. Follow with upsertMany() to rebuild. */
    clear(): void;
    close(): void;
}
/**
 * Build an FtsIndexService. Throws FtsUnavailableError when the database
 * file cannot be opened (disk full, permissions, corruption).
 */
export declare function openFtsIndex(dbPath: string): FtsIndexService;
export {};
//# sourceMappingURL=fts-service.d.ts.map