/**
 * BM25 Search Service using SQLite FTS5
 *
 * FAIL FAST: All errors throw immediately with detailed messages
 * PROVENANCE: Every result includes provenance_id and content_hash
 */
import type Database from 'better-sqlite3';
interface ChunkFilterSQL {
    conditions: string[];
    params: unknown[];
}
interface BM25SearchOptions {
    query: string;
    limit?: number;
    phraseSearch?: boolean;
    documentFilter?: string[];
    includeHighlight?: boolean;
    chunkFilter?: ChunkFilterSQL;
    /** Page range filter applied to VLM/extraction searches (which lack chunk metadata) */
    pageRangeFilter?: {
        min_page?: number;
        max_page?: number;
    };
    /**
     * When true, the query is already a valid FTS5 expression (e.g. OR-joined
     * terms from expandQuery) and must NOT be re-processed by sanitizeFTS5Query().
     * Re-processing would insert implicit AND between consecutive non-operator
     * tokens, corrupting the OR semantics (H-2 fix).
     */
    preSanitized?: boolean;
}
interface BM25SearchResult {
    chunk_id: string | null;
    image_id: string | null;
    embedding_id: string | null;
    extraction_id: string | null;
    document_id: string;
    original_text: string;
    bm25_score: number;
    rank: number;
    result_type: 'chunk' | 'vlm' | 'extraction';
    source_file_path: string;
    source_file_name: string;
    source_file_hash: string;
    page_number: number | null;
    character_start: number;
    character_end: number;
    chunk_index: number;
    provenance_id: string;
    content_hash: string;
    highlight?: string;
    heading_context?: string | null;
    section_path?: string | null;
    content_types?: string | null;
    is_atomic?: boolean;
    page_range?: string | null;
    heading_level?: number | null;
    ocr_quality_score?: number | null;
    doc_title?: string | null;
    doc_author?: string | null;
    doc_subject?: string | null;
    overlap_previous?: number;
    overlap_next?: number;
    chunking_strategy?: string | null;
    embedding_status?: string;
    doc_page_count?: number | null;
    datalab_mode?: string | null;
    total_chunks?: number;
}
export declare class BM25SearchService {
    private readonly db;
    constructor(db: Database.Database);
    private verifyFTSTableExists;
    search(options: BM25SearchOptions): BM25SearchResult[];
    /**
     * Search VLM description embeddings using FTS5
     * Queries vlm_fts JOIN embeddings JOIN images JOIN documents
     *
     * NOTE: VLM results only support page_range_filter from chunk filters
     * (VLM embeddings don't have heading_context, section_path, etc.)
     */
    searchVLM(options: BM25SearchOptions): BM25SearchResult[];
    /**
     * Search extraction content using FTS5
     * Queries extractions_fts JOIN extractions JOIN documents
     *
     * NOTE: Extractions don't have page numbers or chunk metadata,
     * so chunkFilter and pageRangeFilter are not applied here.
     */
    searchExtractions(options: BM25SearchOptions): BM25SearchResult[];
    rebuildIndex(): {
        chunks_indexed: number;
        vlm_indexed: number;
        extractions_indexed: number;
        duration_ms: number;
        content_hash: string;
    };
    /**
     * Rebuild VLM FTS index from embeddings where image_id IS NOT NULL
     */
    rebuildVLMIndex(): {
        vlm_indexed: number;
        duration_ms: number;
    };
    /**
     * Rebuild extractions FTS index
     */
    rebuildExtractionIndex(): {
        extractions_indexed: number;
        duration_ms: number;
    };
    /**
     * Search document metadata (title, author, subject) using FTS5.
     * Queries documents_fts table (v30+).
     *
     * Returns document IDs and metadata fields matching the query.
     * Used to find documents by metadata rather than content.
     */
    searchDocumentMetadata(options: {
        query: string;
        limit?: number;
        phraseSearch?: boolean;
    }): Array<{
        document_id: string;
        file_name: string;
        doc_title: string | null;
        doc_author: string | null;
        doc_subject: string | null;
        bm25_score: number;
        rank: number;
        result_type: 'document_metadata';
    }>;
    /**
     * Check whether all expected FTS triggers exist for a given set of trigger names.
     * If all triggers are present, the FTS index is kept in sync atomically and cannot be stale.
     * If any trigger is missing, the index IS stale (triggers are the sync mechanism).
     */
    private checkTriggersExist;
    getStatus(): {
        chunks_indexed: number;
        current_chunk_count: number;
        index_stale: boolean;
        last_rebuild_at: string | null;
        tokenizer: string;
        content_hash: string | null;
        vlm_indexed: number;
        current_vlm_count: number;
        vlm_index_stale: boolean;
        vlm_last_rebuild_at: string | null;
        extractions_indexed: number;
        current_extraction_count: number;
        extraction_index_stale: boolean;
        extraction_last_rebuild_at: string | null;
    };
    private computeContentHash;
}
/**
 * Sanitize a user-provided query for safe use in FTS5 MATCH expressions.
 *
 * - Preserves FTS5 boolean operators (AND, OR, NOT)
 * - Treats hyphens as word separators (matching unicode61 tokenizer)
 * - Strips all FTS5 metacharacters (' " ( ) * : ^ ~ + etc.)
 * - Inserts implicit AND between consecutive non-operator tokens
 * - Strips leading/trailing/consecutive operators
 *
 * This is the SINGLE authoritative FTS5 sanitizer for the entire codebase.
 *
 * @param query - Raw user query string
 * @returns Sanitized FTS5 query string
 * @throws Error if query contains no valid tokens after sanitization
 */
export declare function sanitizeFTS5Query(query: string): string;
/**
 * Compute SHA-256 content hash of all chunk IDs and text_hashes for FTS index integrity verification.
 * L-10 fix: Uses incremental hashing with iterate() instead of loading all rows into memory.
 * Used by both BM25SearchService and the v3->v4 migration.
 */
export declare function computeFTSContentHash(db: Database.Database): string;
export {};
//# sourceMappingURL=bm25.d.ts.map