import type { EmbeddingResult, EmbeddingStore, SearchResult } from './types.js';

/**
 * Calculate cosine similarity between two vectors
 */
function cosineSimilarity(a: number[], b: number[]): number {
  if (a.length !== b.length) {
    throw new Error('Vectors must have the same length');
  }

  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 denominator = Math.sqrt(normA) * Math.sqrt(normB);
  if (denominator === 0) return 0;

  return dotProduct / denominator;
}

/**
 * In-memory embedding store for semantic search
 *
 * Uses cosine similarity for vector comparison.
 * For production use, consider using a vector database like
 * Pinecone, Weaviate, or pgvector.
 */
export class InMemoryEmbeddingStore implements EmbeddingStore {
  private items: EmbeddingResult[] = [];

  /**
   * Add embeddings to the store
   */
  add(items: EmbeddingResult[]): void {
    this.items.push(...items);
  }

  /**
   * Search for similar items
   *
   * @param query - Query embedding vector
   * @param topK - Maximum number of results (default: 10)
   * @param threshold - Minimum similarity score (default: 0.5)
   */
  search(query: number[], topK = 10, threshold = 0.5): SearchResult[] {
    const results: SearchResult[] = [];

    for (const item of this.items) {
      const score = cosineSimilarity(query, item.embedding);

      if (score >= threshold) {
        results.push({
          text: item.text,
          score,
          metadata: item.metadata,
        });
      }
    }

    // Sort by score descending and limit to topK
    return results
      .sort((a, b) => b.score - a.score)
      .slice(0, topK);
  }

  /**
   * Clear all items from the store
   */
  clear(): void {
    this.items = [];
  }

  /**
   * Get the number of items in the store
   */
  size(): number {
    return this.items.length;
  }

  /**
   * Get all items (for serialization)
   */
  getAll(): EmbeddingResult[] {
    return [...this.items];
  }

  /**
   * Load items (for deserialization)
   */
  load(items: EmbeddingResult[]): void {
    this.items = [...items];
  }
}
