/**
 * Embedding Providers - Pluggable vector embedding backends
 *
 * Supports:
 * - Simple (built-in, no dependencies)
 * - OpenAI text-embedding-3-small/large
 * - Ollama local embeddings
 * - Custom providers via interface
 */
export class OpenAIEmbeddingProvider {
    name = 'openai';
    dimension;
    apiKey;
    model;
    baseUrl;
    constructor(config) {
        this.apiKey = config.apiKey;
        this.model = config.model || 'text-embedding-3-small';
        this.baseUrl = config.baseUrl || 'https://api.openai.com/v1';
        // Set dimension based on model
        switch (this.model) {
            case 'text-embedding-3-large':
                this.dimension = 3072;
                break;
            case 'text-embedding-ada-002':
                this.dimension = 1536;
                break;
            case 'text-embedding-3-small':
            default:
                this.dimension = 1536;
                break;
        }
    }
    async embed(text) {
        const response = await fetch(`${this.baseUrl}/embeddings`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${this.apiKey}`,
            },
            body: JSON.stringify({
                model: this.model,
                input: text,
            }),
        });
        if (!response.ok) {
            const error = await response.text();
            throw new Error(`OpenAI embedding failed: ${response.status} ${error}`);
        }
        const data = await response.json();
        return data.data[0]?.embedding || [];
    }
}
export class OllamaEmbeddingProvider {
    name = 'ollama';
    dimension = 4096; // Default for most Ollama models
    model;
    baseUrl;
    constructor(config = {}) {
        this.model = config.model || 'nomic-embed-text';
        this.baseUrl = config.baseUrl || 'http://localhost:11434';
    }
    async embed(text) {
        const response = await fetch(`${this.baseUrl}/api/embeddings`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                model: this.model,
                prompt: text,
            }),
        });
        if (!response.ok) {
            const error = await response.text();
            throw new Error(`Ollama embedding failed: ${response.status} ${error}`);
        }
        const data = await response.json();
        return data.embedding || [];
    }
}
export class DeepSeekEmbeddingProvider {
    name = 'deepseek';
    dimension = 1024;
    apiKey;
    baseUrl;
    constructor(config) {
        this.apiKey = config.apiKey;
        this.baseUrl = config.baseUrl || 'https://api.deepseek.com/v1';
    }
    async embed(text) {
        // DeepSeek uses OpenAI-compatible API
        const response = await fetch(`${this.baseUrl}/embeddings`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${this.apiKey}`,
            },
            body: JSON.stringify({
                model: 'deepseek-embedding',
                input: text,
            }),
        });
        if (!response.ok) {
            const error = await response.text();
            throw new Error(`DeepSeek embedding failed: ${response.status} ${error}`);
        }
        const data = await response.json();
        return data.data[0]?.embedding || [];
    }
}
// ============================================================================
// CACHED EMBEDDING PROVIDER WRAPPER
// ============================================================================
export class CachedEmbeddingProvider {
    name;
    dimension;
    provider;
    cache = new Map();
    maxCacheSize;
    constructor(provider, maxCacheSize = 10000) {
        this.provider = provider;
        this.name = `cached-${provider.name}`;
        this.dimension = provider.dimension;
        this.maxCacheSize = maxCacheSize;
    }
    async embed(text) {
        // Check cache first
        const cached = this.cache.get(text);
        if (cached) {
            return cached;
        }
        // Generate embedding
        const embedding = await this.provider.embed(text);
        // Cache result
        if (this.cache.size >= this.maxCacheSize) {
            // Remove oldest entry (first key)
            const firstKey = this.cache.keys().next().value;
            if (firstKey)
                this.cache.delete(firstKey);
        }
        this.cache.set(text, embedding);
        return embedding;
    }
    clearCache() {
        this.cache.clear();
    }
    getCacheStats() {
        return {
            size: this.cache.size,
            maxSize: this.maxCacheSize,
        };
    }
}
/**
 * Create an embedding provider based on configuration
 */
export function createEmbeddingProvider(options) {
    let provider;
    switch (options.type) {
        case 'openai':
            if (!options.apiKey) {
                throw new Error('OpenAI embedding provider requires apiKey');
            }
            provider = new OpenAIEmbeddingProvider({
                apiKey: options.apiKey,
                model: options.model,
                baseUrl: options.baseUrl,
            });
            break;
        case 'ollama':
            provider = new OllamaEmbeddingProvider({
                model: options.model,
                baseUrl: options.baseUrl,
            });
            break;
        case 'deepseek':
            if (!options.apiKey) {
                throw new Error('DeepSeek embedding provider requires apiKey');
            }
            provider = new DeepSeekEmbeddingProvider({
                apiKey: options.apiKey,
                baseUrl: options.baseUrl,
            });
            break;
        case 'simple':
        default:
            // Simple provider is built into episodicMemory.ts
            // Return a placeholder that will use the default
            return {
                name: 'simple',
                dimension: 256,
                embed: async (text) => {
                    // This is a simplified version - the real one is in episodicMemory.ts
                    const tokens = text.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').split(/\s+/).filter(t => t.length > 2);
                    const vector = new Array(256).fill(0);
                    for (const token of tokens) {
                        let hash = 0;
                        for (let i = 0; i < token.length; i++) {
                            hash = ((hash << 5) - hash) + token.charCodeAt(i);
                            hash = hash & hash;
                        }
                        vector[Math.abs(hash) % 256] += 1;
                    }
                    const mag = Math.sqrt(vector.reduce((s, v) => s + v * v, 0));
                    return mag > 0 ? vector.map(v => v / mag) : vector;
                },
            };
    }
    // Wrap with cache if requested
    if (options.useCache) {
        return new CachedEmbeddingProvider(provider, options.maxCacheSize);
    }
    return provider;
}
/**
 * Auto-detect best available embedding provider
 */
export function autoDetectEmbeddingProvider() {
    // Check for API keys in environment
    const openaiKey = process.env['OPENAI_API_KEY'];
    const deepseekKey = process.env['DEEPSEEK_API_KEY'];
    if (openaiKey) {
        return createEmbeddingProvider({
            type: 'openai',
            apiKey: openaiKey,
            useCache: true,
        });
    }
    if (deepseekKey) {
        return createEmbeddingProvider({
            type: 'deepseek',
            apiKey: deepseekKey,
            useCache: true,
        });
    }
    // Try Ollama (local)
    // Could add a ping check here, but for now just use simple
    return createEmbeddingProvider({ type: 'simple' });
}
//# sourceMappingURL=embeddingProviders.js.map