import type { Embedder } from '../embedders/embedder.js';
import type { Chunk, HealthCheckResult, SearchOptions, SearchResult, SourceContext } from '../types.js';
/**
 * Escape a string for use in a LanceDB SQL WHERE clause (single-quoted literal).
 *
 * LanceDB uses DataFusion under the hood, which follows the SQL standard:
 * single quotes are escaped by doubling ('' → ') and backslash is treated
 * as a literal character (NOT an escape character). Therefore only quote
 * doubling is required — escaping backslashes would break path matching.
 */
export declare function escapeSqlString(input: string): string;
export declare class LanceStore {
    private db;
    private table;
    private dbPath;
    private embedder;
    private hasFtsIndex;
    private onWarn;
    /**
     * Instrumentation: number of times the read path has reopened the handle
     * via `refreshReadHandle()`. Exposed via `readRefreshCount` so tests
     * (mmnto/totem#1418) can assert the reopen fires on every search.
     */
    private readRefreshes;
    /**
     * Source repo context injected at construction time. Primary stores use
     * `{ absolutePathRoot: projectRoot }` with no `sourceRepo`; linked stores
     * (mmnto/totem#1294 Cross-Repo Context Mesh) use
     * `{ sourceRepo: '<link name>', absolutePathRoot: '<linked repo root>' }`.
     * Passed down to every `search` / `searchFts` call so every SearchResult
     * gets stamped with the owning store's identity via `rowToSearchResult`.
     *
     * **Required** since mmnto/totem#1295 — CR flagged that an optional
     * context with a silent `filePath` fallback for `absoluteFilePath` sent
     * legacy callers down the wrong repo root instead of failing fast.
     * Making the parameter required closes that class of bug at the type
     * level and forces every call site to state its source explicitly.
     */
    private sourceContext;
    constructor(dbPath: string, embedder: Embedder, sourceContext: SourceContext, onWarn?: (msg: string) => void);
    /**
     * Connect to LanceDB. Auto-heals on version mismatch, corruption,
     * or embedder dimension change by deleting the index and signaling
     * that a full rebuild is needed.
     */
    connect(): Promise<void>;
    /**
     * Connect for FTS-only use — skips embedder dimension validation.
     * Use when the embedder is unavailable (offline, no API key) and
     * only FTS search will be performed.
     */
    connectFtsOnly(): Promise<void>;
    /** Check if the stored vector dimensions differ from the current embedder. */
    private hasDimensionMismatch;
    /** Detect errors that warrant auto-healing (nuke + rebuild). */
    private isHealableError;
    /** Delete the entire .lancedb/ directory and reset state. */
    private nukeAndReset;
    /** Insert chunks into the store. Embeds them first. */
    insert(chunks: Chunk[]): Promise<void>;
    /**
     * Create (or rebuild) the FTS index on the `content` column.
     * Must be called after table creation or after incremental adds,
     * because LanceDB FTS indexes do not auto-update on `table.add()`.
     * Uses `replace: true` (the LanceDB default) to overwrite any stale index.
     */
    createFtsIndex(): Promise<void>;
    /** Check whether an FTS index exists on the table. */
    private detectFtsIndex;
    /** Whether the FTS index is available for hybrid search. */
    get ftsIndexReady(): boolean;
    /**
     * Search with optional hybrid mode (vector + FTS with RRF reranking).
     * Falls back to vector-only if no FTS index exists.
     *
     * Opens a fresh LanceDB snapshot for this call (mmnto/totem#1418) so an
     * external `totem sync` cannot leave this store reading a stale view,
     * and so concurrent searches never invalidate each other's handle.
     */
    search(options: SearchOptions): Promise<SearchResult[]>;
    /**
     * FTS-only search. No embedder required.
     * Use when embedding is unavailable (offline, no API key, cold-start fallback).
     * Requires an FTS index to exist; returns empty if none available.
     *
     * Opens a fresh LanceDB snapshot for this call (mmnto/totem#1418).
     */
    searchFts(options: SearchOptions): Promise<SearchResult[]>;
    /** Delete all chunks from a specific file (for incremental re-index). */
    deleteByFile(filePath: string): Promise<void>;
    /** Drop the entire table. Used for full re-index. */
    reset(): Promise<void>;
    /** Re-open the LanceDB connection, picking up rebuilt files after a full sync. */
    reconnect(): Promise<void>;
    /**
     * Open a fresh read snapshot (connection + table + fts flag) for a single
     * query. Scoped to the caller, NOT written onto `this.db` / `this.table`,
     * so concurrent searches don't race each other's handle lifetime
     * (mmnto/totem#1418 Shield CRITICAL follow-up).
     *
     * **Why unconditional reopen.** The MCP server holds LanceStore for the
     * life of the process. When `totem sync` (a separate process) rewrites
     * `.lancedb/` files underneath us, LanceDB's in-memory manifest keeps
     * pointing at the pre-sync snapshot. Vector search against that stale
     * view returns empty, which falls through to FTS-only via the hybrid
     * path, producing uniform ~0.016 RRF scores instead of real similarity
     * ranks. The corrupt path is silent because no exception fires.
     *
     * **Why not mtime-check.** A benchmark (see scripts/bench-lance-open.ts)
     * measured connect+openTable at ~0.5-1.1ms per call against real indexes.
     * That's well under the 10ms threshold where mtime gating starts to pay
     * for its own complexity. Reopening every time is strictly simpler and
     * eliminates a whole category of cache-invalidation bugs.
     *
     * **Why per-call snapshots.** Assigning the fresh connection to `this.db`
     * and then closing the old one from a second concurrent caller would
     * invalidate the first caller's in-flight query. Instead each call holds
     * its own connection + table references through the query lifetime, and
     * closes the connection in a finally block after the results return.
     * The instance-level `this.db` / `this.table` fields are still maintained
     * so write paths and existing consumers (healthCheck, stats, count) see
     * an up-to-date view on the next non-read call via `connect()`.
     *
     * **Why not also refresh write paths.** `totem sync` owns the writer
     * connection exclusively inside a single process. Write operations
     * (insert, deleteByFile, reset) already hold a consistent view for
     * their own workflow and a mid-sequence reopen would drop the in-flight
     * table reference that `insert()` sets during first-table creation.
     */
    private openReadSnapshot;
    /**
     * Test-seam instrumentation (mmnto/totem#1418): total count of read-path
     * handle refreshes since this store was constructed. Asserted by the
     * stale-handle regression test to confirm every search() call reopens.
     */
    get readRefreshCount(): number;
    /** Return true if the table doesn't exist or has zero rows. */
    isEmpty(): Promise<boolean>;
    /** Return the total number of rows in the store. */
    count(): Promise<number>;
    /** Return stats about the current index. */
    stats(): Promise<{
        totalChunks: number;
        byType: Record<string, number>;
    }>;
    /** Run a health check against the index, verifying dimensions, search, and FTS. */
    healthCheck(): Promise<HealthCheckResult>;
    /**
     * Returns one entry per distinct `filePath` in the store, with row counts
     * and a derived `origin` (`@scope/pkg` or `pkg` when the path lives under
     * `node_modules/`, otherwise `local`).
     *
     * `lastSynced` on every returned entry is set to the supplied `writtenAt`
     * timestamp (or `new Date()` if omitted). LanceDB rows do not carry per-row
     * sync timestamps in the current schema, so every document in a single
     * manifest necessarily carries the same `lastSynced` value — callers
     * building an `IndexManifest` should pass their `writtenAt` here so that
     * `documents[].lastSynced === manifest.writtenAt` for that run.
     *
     * Output is sorted by `sourceFile` ascending so the manifest payload is
     * deterministic across runs.
     */
    manifestDocuments(writtenAt?: Date): Promise<{
        sourceFile: string;
        origin: string;
        rowCount: number;
        lastSynced: string;
    }[]>;
    /**
     * Distinct raw `filePath` values currently in the store — the set of source
     * files with at least one chunk. Returns paths AS STORED (NOT normalized),
     * because callers purge via `deleteByFile`, which matches the stored literal;
     * separator normalization for comparison is the caller's job
     * (mmnto-ai/totem#2151 W1). Bounded `select(['filePath'])` projection — never
     * pulls vectors or chunk bodies into memory, but reads O(chunks) rows and
     * dedups in JS (same shape as `manifestDocuments`); a DB-level DISTINCT is
     * tracked in mmnto-ai/totem#2175 for large stores.
     */
    getDistinctPaths(): Promise<string[]>;
}
//# sourceMappingURL=lance-store.d.ts.map