/**
 * Embedding Repository
 *
 * Repository for code embeddings and semantic search.
 */
import { getDatabaseManager } from '../database-manager.js';
// ============================================================================
// Embedding Repository
// ============================================================================
export class EmbeddingRepository {
    db;
    constructor(db) {
        this.db = db || getDatabaseManager().getDatabase();
    }
    /**
     * Create or update code embedding
     */
    upsert(embedding) {
        const stmt = this.db.prepare(`
      INSERT INTO code_embeddings (project_id, file_path, chunk_index, chunk_text, chunk_hash, embedding, symbol_type, symbol_name, start_line, end_line, language)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      ON CONFLICT(project_id, file_path, chunk_index) DO UPDATE SET
        chunk_text = excluded.chunk_text,
        chunk_hash = excluded.chunk_hash,
        embedding = excluded.embedding,
        symbol_type = excluded.symbol_type,
        symbol_name = excluded.symbol_name,
        start_line = excluded.start_line,
        end_line = excluded.end_line,
        language = excluded.language,
        updated_at = CURRENT_TIMESTAMP
      RETURNING *
    `);
        const embeddingBlob = Buffer.from(embedding.embedding.buffer);
        const result = stmt.get(embedding.project_id, embedding.file_path, embedding.chunk_index, embedding.chunk_text, embedding.chunk_hash, embeddingBlob, embedding.symbol_type || null, embedding.symbol_name || null, embedding.start_line || null, embedding.end_line || null, embedding.language || null);
        return this.deserializeEmbedding(result);
    }
    /**
     * Bulk upsert embeddings
     */
    bulkUpsert(embeddings) {
        const stmt = this.db.prepare(`
      INSERT INTO code_embeddings (project_id, file_path, chunk_index, chunk_text, chunk_hash, embedding, symbol_type, symbol_name, start_line, end_line, language)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      ON CONFLICT(project_id, file_path, chunk_index) DO UPDATE SET
        chunk_text = excluded.chunk_text,
        chunk_hash = excluded.chunk_hash,
        embedding = excluded.embedding,
        symbol_type = excluded.symbol_type,
        symbol_name = excluded.symbol_name,
        start_line = excluded.start_line,
        end_line = excluded.end_line,
        language = excluded.language,
        updated_at = CURRENT_TIMESTAMP
    `);
        const insertMany = this.db.transaction((items) => {
            let count = 0;
            for (const embedding of items) {
                const embeddingBlob = Buffer.from(embedding.embedding.buffer);
                stmt.run(embedding.project_id, embedding.file_path, embedding.chunk_index, embedding.chunk_text, embedding.chunk_hash, embeddingBlob, embedding.symbol_type || null, embedding.symbol_name || null, embedding.start_line || null, embedding.end_line || null, embedding.language || null);
                count++;
            }
            return count;
        });
        return insertMany(embeddings);
    }
    /**
     * Get embedding by ID
     */
    getById(id) {
        const stmt = this.db.prepare('SELECT * FROM code_embeddings WHERE id = ?');
        const result = stmt.get(id);
        if (!result)
            return null;
        return this.deserializeEmbedding(result);
    }
    /**
     * Find embeddings by filter
     */
    find(filter = {}) {
        let sql = 'SELECT * FROM code_embeddings WHERE 1=1';
        const params = [];
        if (filter.projectId) {
            sql += ' AND project_id = ?';
            params.push(filter.projectId);
        }
        if (filter.filePath) {
            sql += ' AND file_path = ?';
            params.push(filter.filePath);
        }
        if (filter.symbolType) {
            sql += ' AND symbol_type = ?';
            params.push(filter.symbolType);
        }
        if (filter.symbolName) {
            sql += ' AND symbol_name LIKE ?';
            params.push(`%${filter.symbolName}%`);
        }
        if (filter.language) {
            sql += ' AND language = ?';
            params.push(filter.language);
        }
        sql += ' ORDER BY file_path, chunk_index';
        const stmt = this.db.prepare(sql);
        const results = stmt.all(...params);
        return results.map(r => this.deserializeEmbedding(r));
    }
    /**
     * Search embeddings by semantic similarity
     */
    searchSimilar(queryEmbedding, filter = {}, topK = 10) {
        // Get all candidates matching filter
        const candidates = this.find(filter);
        // Calculate cosine similarity for each
        const results = [];
        for (const embedding of candidates) {
            const similarity = this.cosineSimilarity(queryEmbedding, embedding.embedding);
            results.push({ embedding, similarity });
        }
        // Sort by similarity and return top K
        return results
            .sort((a, b) => b.similarity - a.similarity)
            .slice(0, topK);
    }
    /**
     * Search embeddings by symbol name
     */
    searchBySymbol(symbolName, filter = {}, limit = 20) {
        let sql = `
      SELECT * FROM code_embeddings
      WHERE symbol_name LIKE ?
    `;
        const params = [`%${symbolName}%`];
        if (filter.projectId) {
            sql += ' AND project_id = ?';
            params.push(filter.projectId);
        }
        if (filter.symbolType) {
            sql += ' AND symbol_type = ?';
            params.push(filter.symbolType);
        }
        if (filter.language) {
            sql += ' AND language = ?';
            params.push(filter.language);
        }
        sql += ' ORDER BY LENGTH(symbol_name) ASC LIMIT ?';
        params.push(limit);
        const stmt = this.db.prepare(sql);
        const results = stmt.all(...params);
        return results.map(r => this.deserializeEmbedding(r));
    }
    /**
     * Delete embeddings for file
     */
    deleteForFile(projectId, filePath) {
        const stmt = this.db.prepare('DELETE FROM code_embeddings WHERE project_id = ? AND file_path = ?');
        const result = stmt.run(projectId, filePath);
        return result.changes;
    }
    /**
     * Delete embeddings for project
     */
    deleteForProject(projectId) {
        const stmt = this.db.prepare('DELETE FROM code_embeddings WHERE project_id = ?');
        const result = stmt.run(projectId);
        return result.changes;
    }
    /**
     * Delete stale embeddings (files that no longer exist)
     */
    deleteStale(projectId, existingFiles) {
        if (existingFiles.length === 0) {
            return this.deleteForProject(projectId);
        }
        const placeholders = existingFiles.map(() => '?').join(',');
        const stmt = this.db.prepare(`
      DELETE FROM code_embeddings
      WHERE project_id = ? AND file_path NOT IN (${placeholders})
    `);
        const result = stmt.run(projectId, ...existingFiles);
        return result.changes;
    }
    /**
     * Check if file needs re-indexing
     */
    needsReindex(projectId, filePath, contentHash) {
        const stmt = this.db.prepare(`
      SELECT chunk_hash FROM code_embeddings
      WHERE project_id = ? AND file_path = ? AND chunk_index = 0
      LIMIT 1
    `);
        const result = stmt.get(projectId, filePath);
        if (!result)
            return true;
        return result.chunk_hash !== contentHash;
    }
    /**
     * Get embedding statistics
     */
    getStats(projectId) {
        let whereClause = '';
        const params = [];
        if (projectId) {
            whereClause = ' WHERE project_id = ?';
            params.push(projectId);
        }
        const total = this.db.prepare(`SELECT COUNT(*) as count FROM code_embeddings${whereClause}`).get(...params).count;
        const files = this.db.prepare(`SELECT COUNT(DISTINCT file_path) as count FROM code_embeddings${whereClause}`).get(...params).count;
        const languageRows = this.db.prepare(`
      SELECT language, COUNT(*) as count FROM code_embeddings${whereClause}
      GROUP BY language
    `).all(...params);
        const byLanguage = {};
        for (const row of languageRows) {
            byLanguage[row.language || 'unknown'] = row.count;
        }
        const symbolRows = this.db.prepare(`
      SELECT symbol_type, COUNT(*) as count FROM code_embeddings${whereClause}
      GROUP BY symbol_type
    `).all(...params);
        const bySymbolType = {};
        for (const row of symbolRows) {
            bySymbolType[row.symbol_type || 'unknown'] = row.count;
        }
        return {
            totalEmbeddings: total,
            totalFiles: files,
            byLanguage,
            bySymbolType,
        };
    }
    // ============================================================================
    // Private Methods
    // ============================================================================
    deserializeEmbedding(row) {
        const buf = row.embedding;
        return {
            ...row,
            embedding: new Float32Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)),
        };
    }
    cosineSimilarity(a, b) {
        if (a.length !== b.length)
            return 0;
        let dotProduct = 0;
        let normA = 0;
        let normB = 0;
        for (let i = 0; i < a.length; i++) {
            dotProduct += a[i] * b[i];
            normA += a[i] * a[i];
            normB += b[i] * b[i];
        }
        const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
        return magnitude === 0 ? 0 : dotProduct / magnitude;
    }
}
// ============================================================================
// Singleton Instance
// ============================================================================
let instance = null;
export function getEmbeddingRepository() {
    if (!instance) {
        instance = new EmbeddingRepository();
    }
    return instance;
}
export function resetEmbeddingRepository() {
    instance = null;
}
//# sourceMappingURL=embedding-repository.js.map