import { TotemConfigError, TotemError } from '../errors.js';
import { buildMissingSdkHint } from '../missing-sdk.js';
const DEFAULT_DIMENSIONS = 768;
const DEFAULT_MODEL = 'gemini-embedding-2-preview';
const MAX_BATCH_SIZE = 100; // Gemini supports up to 100 texts per batch
const MAX_RETRIES = 3;
const INITIAL_BACKOFF_MS = 1000;
/** Status codes / error names that are safe to retry. */
const RETRYABLE_STATUS_CODES = new Set([429, 503]);
const RETRYABLE_ERROR_NAMES = new Set(['RESOURCE_EXHAUSTED', 'UNAVAILABLE', 'TOO_MANY_REQUESTS']);
function isRetryableGeminiError(err) {
    if (!(err instanceof Error))
        return false;
    // Check for structured status/code on the error object (Gemini SDK attaches these)
    const errObj = err;
    if (typeof errObj['status'] === 'number' && RETRYABLE_STATUS_CODES.has(errObj['status'])) {
        return true;
    }
    if (typeof errObj['code'] === 'number' && RETRYABLE_STATUS_CODES.has(errObj['code'])) {
        return true;
    }
    if (typeof errObj['name'] === 'string' && RETRYABLE_ERROR_NAMES.has(errObj['name'])) {
        return true;
    }
    return false;
}
/**
 * Dynamically import the @google/genai SDK.
 * It's an optional peer dep in @mmnto/totem — only required when provider is 'gemini'.
 */
export async function importGeminiSdk() {
    try {
        // Dynamic import — @google/genai is an optional peer dep
        return await import('@google/genai');
    }
    catch {
        // mmnto-ai/totem#2018 L2: the remediation must branch on context — "pnpm add"
        // is the wrong fix when the SDK is already installed and the BINARY can't see
        // it, and the provider alternative only applies when the SDK is genuinely
        // absent (the openai provider needs its own externalized SDK, which would be
        // just as unresolvable from a foreign binary — #2150 round-2).
        throw new TotemConfigError('Gemini SDK (@google/genai) is not installed.', buildMissingSdkHint('@google/genai', {
            absentAlternative: "Alternatively use provider: 'openai' in your embedding config (requires the openai package).",
        }), 'CONFIG_MISSING');
    }
}
/**
 * Gemini embedding via the @google/genai SDK.
 * Supports task-type awareness for retrieval-optimized embeddings.
 */
export class GeminiEmbedder {
    dimensions;
    model;
    apiKey;
    constructor(model = DEFAULT_MODEL, dimensions) {
        this.model = model;
        this.dimensions = dimensions ?? DEFAULT_DIMENSIONS;
        const apiKey = process.env['GEMINI_API_KEY'] ?? process.env['GOOGLE_API_KEY'];
        if (!apiKey) {
            throw new TotemConfigError('No Gemini API key found.', 'Set GEMINI_API_KEY (or GOOGLE_API_KEY) in your .env file.', 'CONFIG_MISSING');
        }
        this.apiKey = apiKey;
    }
    async embed(texts) {
        if (texts.length === 0)
            return [];
        const { GoogleGenAI } = await importGeminiSdk();
        const ai = new GoogleGenAI({ apiKey: this.apiKey });
        const results = [];
        for (let i = 0; i < texts.length; i += MAX_BATCH_SIZE) {
            const batch = texts.slice(i, i + MAX_BATCH_SIZE);
            const embeddings = await this.embedWithRetry(ai, batch);
            results.push(...embeddings);
        }
        return results;
    }
    async embedWithRetry(ai, batch) {
        let lastErr;
        for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
            try {
                const response = await ai.models.embedContent({
                    model: this.model,
                    contents: batch.map((text) => ({ parts: [{ text }] })),
                    config: {
                        taskType: 'RETRIEVAL_DOCUMENT',
                        outputDimensionality: this.dimensions,
                    },
                });
                if (!response.embeddings || response.embeddings.length !== batch.length) {
                    throw new TotemError('EMBEDDING_UNAVAILABLE', `Expected ${batch.length} embeddings, got ${response.embeddings?.length ?? 0}`, 'This may be a transient Gemini API issue. Retry with `totem sync`.');
                }
                return response.embeddings.map((e) => {
                    if (!e.values)
                        throw new TotemError('EMBEDDING_UNAVAILABLE', 'Embedding response missing values', 'This may be a transient Gemini API issue. Retry with `totem sync`.');
                    return e.values;
                });
            }
            catch (err) {
                lastErr = err;
                if (!isRetryableGeminiError(err) || attempt === MAX_RETRIES)
                    break;
                const delay = INITIAL_BACKOFF_MS * 2 ** attempt + Math.random() * 1000;
                await new Promise((resolve) => setTimeout(resolve, delay));
            }
        }
        const detail = lastErr instanceof Error ? lastErr.message : String(lastErr);
        throw new TotemError('EMBEDDING_UNAVAILABLE', `Gemini embedding failed after ${MAX_RETRIES + 1} attempts: ${detail}`, 'Check your GEMINI_API_KEY and network connection, then retry with `totem sync`.');
    }
}
//# sourceMappingURL=gemini-embedder.js.map