// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Giancarlo Erra - Altaire Limited
/**
 * Embedding provider configuration — loaded from environment variables (MCP config).
 *
 * EMBEDDING_PROVIDER:
 *   - "ollama" (default): Use Ollama for embeddings (Docker or external).
 *   - "openai": Use OpenAI Embeddings API. Requires OPENAI_API_KEY.
 *   - "google": Use Google Generative AI Embedding API. Requires GOOGLE_API_KEY.
 *
 * Ollama-specific:
 *   OLLAMA_MODE:
 *     - "auto" (default): Auto-detect. If Ollama is already running natively on port 11434,
 *       use it (external mode — fastest, GPU-accelerated on Mac/Windows). Otherwise fall back
 *       to a managed Docker container on port 11435.
 *     - "docker": Always use a managed Docker container on port 11435.
 *     - "external": User provides their own Ollama instance (native local, remote, etc.).
 *       SocratiCode will NOT create or manage Docker containers for Ollama.
 *       The user is responsible for having Ollama running at OLLAMA_URL.
 *   OLLAMA_URL:            Ollama API URL.
 *                          Default for docker mode: http://localhost:11435
 *                          Default for external mode: http://localhost:11434
 *   OLLAMA_API_KEY:        Optional API key for authenticated Ollama proxies.
 *
 * Cloud provider-specific:
 *   OPENAI_API_KEY:        Required for openai provider.
 *   GOOGLE_API_KEY:        Required for google provider.
 *
 * Shared:
 *   EMBEDDING_MODEL:       Model name (default depends on provider).
 *   EMBEDDING_DIMENSIONS:  Vector dimensions — must match the model (default depends on provider).
 *   EMBEDDING_CONTEXT_LENGTH: Override context window in tokens (auto-detected for known models).
 */

import { logger } from "./logger.js";

// ── Types ─────────────────────────────────────────────────────────────────

export type EmbeddingProvider = "ollama" | "openai" | "google";
export type OllamaMode = "docker" | "external" | "auto";

export interface EmbeddingConfig {
  /** Which embedding backend to use. */
  embeddingProvider: EmbeddingProvider;
  /** Ollama mode (only relevant when embeddingProvider is "ollama"). */
  ollamaMode: OllamaMode;
  /** Ollama API URL (only relevant when embeddingProvider is "ollama"). */
  ollamaUrl: string;
  embeddingModel: string;
  embeddingDimensions: number;
  /** Max context window in tokens. Used for client-side pre-truncation. */
  embeddingContextLength: number;
  ollamaApiKey?: string;
}

// ── Provider defaults ─────────────────────────────────────────────────────

const PROVIDER_DEFAULTS: Record<EmbeddingProvider, { model: string; dimensions: number }> = {
  ollama:  { model: "nomic-embed-text",        dimensions: 768  },
  openai:  { model: "text-embedding-3-small",  dimensions: 1536 },
  google:  { model: "gemini-embedding-001",    dimensions: 3072 },
};

// ── Ollama mode defaults ──────────────────────────────────────────────────

const MODE_DEFAULTS: Record<OllamaMode, { url: string }> = {
  docker: { url: "http://localhost:11435" },
  external: { url: "http://localhost:11434" },
  // auto: probe localhost:11434 first; URL will be corrected by OllamaEmbeddingProvider.ensureReady()
  auto: { url: "http://localhost:11434" },
};

/**
 * Well-known model context lengths (in tokens).
 * Used for client-side pre-truncation to work around Ollama
 * batch truncation bugs (see https://github.com/ollama/ollama/issues/12710)
 * and to stay within cloud provider limits.
 */
const MODEL_CONTEXT_LENGTHS: Record<string, number> = {
  // Ollama models
  "nomic-embed-text": 2048,
  "mxbai-embed-large": 512,
  "snowflake-arctic-embed": 512,
  "all-minilm": 256,
  // OpenAI models
  "text-embedding-3-small": 8191,
  "text-embedding-3-large": 8191,
  "text-embedding-ada-002": 8191,
  // Google models
  "gemini-embedding-001": 2048,
};

/** Guess context length from model name. Returns 0 if unknown. */
function guessContextLength(model: string): number {
  const base = model.replace(/:.*$/, ""); // strip :tag
  return MODEL_CONTEXT_LENGTHS[base] ?? 0;
}

// ── Singleton ─────────────────────────────────────────────────────────────

let _config: EmbeddingConfig | null = null;

/**
 * Load embedding configuration from environment variables.
 * Called once on first access; cached thereafter.
 */
export function loadEmbeddingConfig(): EmbeddingConfig {
  if (_config) return _config;

  // ── Provider ────────────────────────────────────────────────────────
  const rawProvider = process.env.EMBEDDING_PROVIDER || "ollama";
  if (rawProvider !== "ollama" && rawProvider !== "openai" && rawProvider !== "google") {
    throw new Error(
      `Invalid EMBEDDING_PROVIDER: "${rawProvider}". Must be "ollama", "openai", or "google".`,
    );
  }
  const embeddingProvider: EmbeddingProvider = rawProvider;
  const providerDefaults = PROVIDER_DEFAULTS[embeddingProvider];

  // ── Ollama mode (only relevant for ollama provider) ─────────────────
  const rawMode = process.env.OLLAMA_MODE || "auto";
  if (rawMode !== "docker" && rawMode !== "external" && rawMode !== "auto") {
    throw new Error(
      `Invalid OLLAMA_MODE: "${rawMode}". Must be "docker", "external", or "auto".`,
    );
  }
  const ollamaMode: OllamaMode = rawMode;
  const modeDefaults = MODE_DEFAULTS[ollamaMode];

  // ── Model & dimensions (provider-specific defaults) ─────────────────
  const embeddingModel = process.env.EMBEDDING_MODEL || providerDefaults.model;
  const embeddingDimensions = parseInt(
    process.env.EMBEDDING_DIMENSIONS || String(providerDefaults.dimensions),
    10,
  );

  const contextLengthEnv = process.env.EMBEDDING_CONTEXT_LENGTH;

  _config = {
    embeddingProvider,
    ollamaMode,
    ollamaUrl: process.env.OLLAMA_URL || modeDefaults.url,
    embeddingModel,
    embeddingDimensions,
    embeddingContextLength: contextLengthEnv
      ? parseInt(contextLengthEnv, 10)
      : guessContextLength(embeddingModel),
    ollamaApiKey: process.env.OLLAMA_API_KEY || undefined,
  };

  logger.info("Embedding config loaded", {
    embeddingProvider: _config.embeddingProvider,
    ...(embeddingProvider === "ollama" ? {
      ollamaMode: _config.ollamaMode,
      ollamaUrl: _config.ollamaUrl,
    } : {}),
    embeddingModel: _config.embeddingModel,
    embeddingDimensions: _config.embeddingDimensions,
    embeddingContextLength: _config.embeddingContextLength || "auto",
    hasApiKey: !!(embeddingProvider === "ollama"
      ? _config.ollamaApiKey
      : embeddingProvider === "openai"
        ? process.env.OPENAI_API_KEY
        : process.env.GOOGLE_API_KEY),
  });

  return _config;
}

/** Get the current embedding configuration (loads if not yet loaded). */
export function getEmbeddingConfig(): EmbeddingConfig {
  return loadEmbeddingConfig();
}

/**
 * Update the resolved Ollama mode and URL after auto-detection.
 * Called by OllamaEmbeddingProvider when OLLAMA_MODE=auto resolves.
 */
export function setResolvedOllamaMode(mode: "docker" | "external", url: string): void {
  if (_config) {
    _config.ollamaMode = mode;
    _config.ollamaUrl = url;
  }
}

/** Reset config cache (for testing). */
export function resetEmbeddingConfig(): void {
  _config = null;
}
