/**
 * src/rag/search.ts
 * البحث الدلالي — Semantic search over document chunks using pgvector
 */
import { supabase } from '../utils/supabase.js';
import { createChildLogger } from '../utils/logger.js';
import { embedChunks } from './ingest.js';
const log = createChildLogger('rag:search');
// ─── Semantic Search ────────────────────────────────────────────────
/**
 * Search document chunks using pgvector similarity.
 * Calls a Supabase RPC function `match_documents` for vector search.
 */
export async function searchDocuments(tenantId, query, topK = 5) {
    try {
        // Generate embedding for the query
        const [queryEmbedding] = await embedChunks([query]);
        if (!queryEmbedding || queryEmbedding.every(v => v === 0)) {
            log.warn('Query embedding is zero vector, falling back to text search');
            return textFallbackSearch(tenantId, query, topK);
        }
        // Use Supabase RPC for vector similarity search
        const { data, error } = await supabase.rpc('match_documents', {
            query_embedding: queryEmbedding,
            match_count: topK,
            filter_tenant_id: tenantId,
        });
        if (error) {
            log.warn({ error: error.message }, 'Vector search RPC failed, trying text fallback');
            return textFallbackSearch(tenantId, query, topK);
        }
        const results = (data ?? []).map((row) => {
            const meta = row['metadata'] ?? {};
            return {
                content: String(row['content'] ?? ''),
                similarity: Number(row['similarity'] ?? 0),
                documentId: String(row['document_id'] ?? ''),
                chunkIndex: Number(meta['chunk_index'] ?? 0),
                metadata: meta,
            };
        });
        log.debug({ tenantId, query: query.slice(0, 50), resultCount: results.length }, 'Search completed');
        return results;
    }
    catch (err) {
        log.error({ tenantId, err }, 'Document search failed');
        return [];
    }
}
/**
 * Fallback: text-based search when embeddings aren't available.
 */
async function textFallbackSearch(tenantId, query, topK) {
    // Use PostgreSQL full-text search as fallback
    const { data, error } = await supabase
        .from('document_chunks')
        .select('content, document_id, metadata')
        .eq('tenant_id', tenantId)
        .textSearch('content', query.split(/\s+/).join(' & '), { type: 'plain' })
        .limit(topK);
    if (error) {
        log.warn({ error: error.message }, 'Text fallback search also failed');
        return [];
    }
    return (data ?? []).map((row) => {
        const meta = row.metadata ?? {};
        return {
            content: row.content ?? '',
            similarity: 0.5,
            documentId: row.document_id ?? '',
            chunkIndex: Number(meta['chunk_index'] ?? 0),
            metadata: meta,
        };
    });
}
// ─── Context Builder ────────────────────────────────────────────────
/**
 * Combines document search with workspace file context.
 * Returns a formatted context string to inject into the agent prompt.
 */
export async function getRelevantContext(tenantId, query, maxTokens = 3000) {
    const results = await searchDocuments(tenantId, query, 8);
    if (results.length === 0) {
        return { context: '', sources: [], chunkCount: 0 };
    }
    // Filter by minimum similarity threshold
    const relevant = results.filter(r => r.similarity >= 0.3);
    if (relevant.length === 0) {
        return { context: '', sources: [], chunkCount: 0 };
    }
    // Build context string within token budget (rough: 4 chars ≈ 1 token)
    const maxChars = maxTokens * 4;
    let totalChars = 0;
    const contextParts = [];
    const sources = new Set();
    for (const result of relevant) {
        const part = result.content.trim();
        if (totalChars + part.length > maxChars)
            break;
        contextParts.push(part);
        totalChars += part.length;
        const source = result.metadata?.['source'] ?? result.documentId;
        sources.add(source);
    }
    const context = [
        '📚 معلومات ذات صلة من قاعدة المعرفة:',
        '',
        ...contextParts.map((part, i) => `[${i + 1}] ${part}`),
        '',
        `المصادر: ${[...sources].join(', ')}`,
    ].join('\n');
    return {
        context,
        sources: [...sources],
        chunkCount: contextParts.length,
    };
}
//# sourceMappingURL=search.js.map