// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Giancarlo Erra - Altaire Limited

import { createRequire } from "node:module";

// ── Package metadata ─────────────────────────────────────────────────────
const esmRequire = createRequire(import.meta.url);
const pkg = esmRequire("../package.json") as { version: string };
export const SOCRATICODE_VERSION: string = pkg.version;

// ── Embedding configuration ──────────────────────────────────────────────
// Embedding model and dimensions are now configured via environment variables.
// See src/services/embedding-config.ts for OLLAMA_MODE, OLLAMA_URL,
// EMBEDDING_MODEL, EMBEDDING_DIMENSIONS, and OLLAMA_API_KEY.
// Defaults: nomic-embed-text with 768 dimensions via Docker Ollama on :11435.

// ── Qdrant configuration ────────────────────────────────────────────────

export const QDRANT_PORT = parseInt(process.env.QDRANT_PORT || "16333", 10);
export const QDRANT_GRPC_PORT = parseInt(process.env.QDRANT_GRPC_PORT || "16334", 10);
export const QDRANT_HOST = process.env.QDRANT_HOST || "localhost";
export const QDRANT_API_KEY = process.env.QDRANT_API_KEY || undefined;
/** Full URL for remote/cloud Qdrant instances (e.g. https://xyz.aws.cloud.qdrant.io:6333).
 *  When set, takes precedence over QDRANT_HOST + QDRANT_PORT. */
export const QDRANT_URL = process.env.QDRANT_URL || undefined;
/** `managed` = Docker-managed local Qdrant (default).
 *  `external` = user-provided remote or cloud Qdrant instance (no Docker management). */
export const QDRANT_MODE: "managed" | "external" =
  (process.env.QDRANT_MODE === "external" ? "external" : "managed");
export const QDRANT_CONTAINER_NAME = "socraticode-qdrant";
export const QDRANT_IMAGE = "qdrant/qdrant:v1.17.0";

/**
 * Resolve the Qdrant REST port from a URL.
 * Returns the explicit port if present (e.g. `:8443`),
 * otherwise `443` for `https://` or `6333` for `http://`.
 */
export function resolveQdrantPort(url: string): number {
  const parsed = new URL(url);
  if (parsed.port) return parseInt(parsed.port, 10);
  return url.startsWith("https:") ? 443 : 6333;
}

// ── Ollama configuration ────────────────────────────────────────────────

export const OLLAMA_PORT = parseInt(process.env.OLLAMA_PORT || "11435", 10);
export const OLLAMA_HOST = process.env.OLLAMA_HOST || `http://localhost:${OLLAMA_PORT}`;
export const OLLAMA_CONTAINER_NAME = "socraticode-ollama";
export const OLLAMA_IMAGE = "ollama/ollama:latest";

// ── Search configuration ─────────────────────────────────────────────────

/** Default number of search results returned by codebase_search.
 *  Override via SEARCH_DEFAULT_LIMIT env var (1-50). */
export const SEARCH_DEFAULT_LIMIT = Math.max(1, Math.min(50,
  parseInt(process.env.SEARCH_DEFAULT_LIMIT || "10", 10) || 10,
));

/** Default minimum RRF score threshold.
 *  Results below this score are filtered out. 0 disables filtering.
 *  Override via SEARCH_MIN_SCORE env var (0-1). */
export const SEARCH_MIN_SCORE = Math.max(0, Math.min(1,
  parseFloat(process.env.SEARCH_MIN_SCORE || "0.10") || 0,
));

// ── Chunking configuration ──────────────────────────────────────────────

export const CHUNK_SIZE = 100; // lines per chunk
export const CHUNK_OVERLAP = 10; // overlap lines between chunks
export const INDEX_BATCH_SIZE = 50; // files per batch for batched/resumable indexing

/** Maximum file size in bytes. Files larger than this are skipped during indexing.
 *  Default: 5 MB. Override via MAX_FILE_SIZE_MB env var. */
export const MAX_FILE_BYTES = Math.round(
  parseFloat(process.env.MAX_FILE_SIZE_MB || "5") * 1_000_000,
);

/** Maximum file size in bytes for code graph AST parsing.
 *  Graph parsing loads the entire file into memory for AST analysis,
 *  so the limit is lower than the indexing limit. Default: 1 MB. */
export const MAX_GRAPH_FILE_BYTES = 1_000_000;

/**
 * Maximum average line length (in characters) before a file is treated as
 * minified/bundled and switched to character-based chunking. Minified files
 * have very long lines that would make line-based chunks exceed the embedding
 * model's context window.
 */
export const MAX_AVG_LINE_LENGTH = 500;

/**
 * Hard character limit per chunk payload — the universal safety net applied
 * to every chunk regardless of chunking strategy (AST, line-based, or
 * character-based).
 *
 * Must be kept below CHARS_PER_TOKEN_ESTIMATE × model_context_length so that
 * the provider-level pretruncation always fires as a second line of defence.
 * At CHARS_PER_TOKEN_ESTIMATE=1.0 and a 2048-token context this means chunks
 * must be < 2048 chars. We use 2000 to leave headroom for the
 * `prepareDocumentText` path prefix (~50 chars) prepended at embed time.
 */
export const MAX_CHUNK_CHARS = 2000;

// ── File type configuration ─────────────────────────────────────────────

export const SUPPORTED_EXTENSIONS = new Set([
  // JavaScript/TypeScript
  ".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs",
  // Python
  ".py", ".pyw", ".pyi",
  // Java/Kotlin/Scala
  ".java", ".kt", ".kts", ".scala",
  // C/C++
  ".c", ".h", ".cpp", ".hpp", ".cc", ".hh", ".cxx",
  // C#
  ".cs",
  // Go
  ".go",
  // Rust
  ".rs",
  // Ruby
  ".rb",
  // PHP
  ".php",
  // Swift
  ".swift",
  // Shell
  ".sh", ".bash", ".zsh",
  // Web
  ".html", ".htm", ".css", ".scss", ".sass", ".less", ".vue", ".svelte",
  // Config/Data
  ".json", ".yaml", ".yml", ".toml", ".xml", ".ini", ".cfg",
  // Docs
  ".md", ".mdx", ".rst", ".txt",
  // SQL
  ".sql",
  // Dart
  ".dart",
  // Lua
  ".lua",
  // R
  ".r", ".R",
  // Dockerfile
  ".dockerfile",
]);

// ── Extra extensions (user-configurable) ─────────────────────────────────

/**
 * Parse a comma-separated list of file extensions into a Set.
 * Normalizes inputs: trims whitespace, ensures leading dot.
 * Example: ".tpl, .blade, hbs" → Set([".tpl", ".blade", ".hbs"])
 */
export function parseExtraExtensions(value?: string): Set<string> {
  if (!value?.trim()) return new Set();
  return new Set(
    value.split(",")
      .map((s) => s.trim().toLowerCase())
      .filter((s) => s.length > 0)
      .map((s) => (s.startsWith(".") ? s : `.${s}`)),
  );
}

/** Extra file extensions from EXTRA_EXTENSIONS env var (global default). */
export const EXTRA_EXTENSIONS = parseExtraExtensions(process.env.EXTRA_EXTENSIONS);

/**
 * Merge EXTRA_EXTENSIONS env var with an optional tool parameter value.
 * Returns the combined set of extra extensions.
 */
export function mergeExtraExtensions(toolParam?: string): Set<string> {
  if (!toolParam?.trim()) return EXTRA_EXTENSIONS;
  const toolExts = parseExtraExtensions(toolParam);
  if (EXTRA_EXTENSIONS.size === 0) return toolExts;
  return new Set([...EXTRA_EXTENSIONS, ...toolExts]);
}

/** Files that are always indexed regardless of extension */
export const SPECIAL_FILES = new Set([
  "Dockerfile",
  "Makefile",
  "Rakefile",
  "Gemfile",
  "Procfile",
  ".env.example",
  ".gitignore",
  ".dockerignore",
]);

/** Map file extension to human-readable language name */
export function getLanguageFromExtension(ext: string): string {
  const map: Record<string, string> = {
    ".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
    ".ts": "typescript", ".tsx": "typescript",
    ".py": "python", ".pyw": "python", ".pyi": "python",
    ".java": "java", ".kt": "kotlin", ".kts": "kotlin", ".scala": "scala",
    ".c": "c", ".h": "c", ".cpp": "cpp", ".hpp": "cpp", ".cc": "cpp", ".hh": "cpp", ".cxx": "cpp",
    ".cs": "csharp",
    ".go": "go",
    ".rs": "rust",
    ".rb": "ruby",
    ".php": "php",
    ".swift": "swift",
    ".sh": "shell", ".bash": "shell", ".zsh": "shell",
    ".html": "html", ".htm": "html",
    ".css": "css", ".scss": "scss", ".sass": "sass", ".less": "less",
    ".vue": "vue", ".svelte": "svelte",
    ".json": "json", ".yaml": "yaml", ".yml": "yaml",
    ".toml": "toml", ".xml": "xml",
    ".md": "markdown", ".mdx": "markdown", ".rst": "rst",
    ".sql": "sql",
    ".dart": "dart",
    ".lua": "lua",
    ".r": "r", ".R": "r",
    ".dockerfile": "dockerfile",
  };
  return map[ext] || "plaintext";
}
