import { z } from 'zod';
import { TotemConfigError } from '../errors.js';
const DEFAULT_DIMENSIONS = 768;
const MAX_BATCH_SIZE = 10;
const MAX_CHARS_PER_TEXT = 6_000; // ~2000 tokens, conservative for nomic-embed-text's 8192 context
/**
 * Ollama embedding via the /api/embed endpoint.
 * No SDK required — plain fetch.
 */
export class OllamaEmbedder {
    dimensions;
    model;
    baseUrl;
    onWarn;
    constructor(model = 'nomic-embed-text', baseUrl = 'http://localhost:11434', dimensions, onWarn) {
        this.model = model;
        this.baseUrl = baseUrl;
        this.dimensions = dimensions ?? DEFAULT_DIMENSIONS;
        this.onWarn = onWarn;
    }
    async embedBatch(texts) {
        const response = await fetch(`${this.baseUrl}/api/embed`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                model: this.model,
                input: texts,
            }),
        });
        if (!response.ok) {
            const body = await response.text();
            if (response.status === 404 || /not found|no such model/i.test(body)) {
                throw new TotemConfigError(`Ollama model '${this.model}' is not installed.`, `Run 'ollama pull ${this.model}' and try again.`, 'CONFIG_MISSING');
            }
            throw new TotemConfigError(`Ollama embedding failed (${response.status}): ${body}`, 'Check that Ollama is running and the model is available.', 'CONFIG_INVALID');
        }
        const data = z
            .object({ embeddings: z.array(z.array(z.number())) })
            .parse(await response.json());
        return data.embeddings;
    }
    async embed(texts) {
        if (texts.length === 0)
            return [];
        // Truncate oversized texts to stay within model context window
        const truncated = texts.map((t) => t.length > MAX_CHARS_PER_TEXT ? t.slice(0, MAX_CHARS_PER_TEXT) : t);
        const results = [];
        let skipped = 0;
        // Batch to avoid overwhelming Ollama with large payloads
        for (let i = 0; i < truncated.length; i += MAX_BATCH_SIZE) {
            const batch = truncated.slice(i, i + MAX_BATCH_SIZE);
            try {
                const embeddings = await this.embedBatch(batch);
                results.push(...embeddings);
            }
            catch {
                // Batch failed — retry each text individually
                for (const text of batch) {
                    try {
                        const [embedding] = await this.embedBatch([text]);
                        results.push(embedding);
                    }
                    catch {
                        // Individual text failed — insert zero vector to preserve 1:1 alignment with input
                        // Zero vectors have ~0 cosine similarity so they won't pollute search results
                        results.push(new Array(this.dimensions).fill(0));
                        this.onWarn?.(`[Totem] Zero-vector fallback for failed embedding (${text.length} chars): ${text.slice(0, 60)}...`);
                        skipped++;
                    }
                }
            }
        }
        if (skipped > 0) {
            this.onWarn?.(`[Totem] Embedding batch complete: ${skipped} chunk(s) skipped due to failures`);
        }
        return results;
    }
}
//# sourceMappingURL=ollama-embedder.js.map