"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var vectors_exports = {};
__export(vectors_exports, {
  VectorManager: () => VectorManager
});
module.exports = __toCommonJS(vectors_exports);
var path = __toESM(require("path"));
var fs = __toESM(require("fs"));
var import_os = require("os");
var import_errors = require("../errors");
var import_vec_index = require("./vec-index");
var import_orama_index = require("./orama-index");
var import_pglite_index = require("./pglite-index");
var import_lancedb_index = require("./lancedb-index");
var import_qdrant_index = require("./qdrant-index");
var import_typesense_index = require("./typesense-index");
const DEFAULT_MODEL = "nomic-ai/nomic-embed-text-v1.5";
const DEFAULT_EMBEDDING_DIM = 768;
const GLOBAL_MODELS_DIR = path.join((0, import_os.homedir)(), ".kirograph", "models");
const BATCH_SIZE = 32;
const MIN_BATCH_SIZE = 4;
const MAX_TOKEN_CHARS = 2e3;
const EMBEDDABLE_KINDS = /* @__PURE__ */ new Set([
  "function",
  "method",
  "class",
  "interface",
  "type_alias",
  "component",
  "module"
]);
let transformers = null;
async function getTransformers() {
  if (!transformers) {
    transformers = await import("@huggingface/transformers");
  }
  return transformers;
}
function nodeToText(node) {
  const parts = [`${node.kind}: ${node.name}`];
  if (node.qualifiedName && node.qualifiedName !== node.name) {
    parts.push(`path: ${node.qualifiedName}`);
  }
  parts.push(`file: ${node.filePath}`);
  if (node.signature) parts.push(`signature: ${node.signature}`);
  if (node.docstring) parts.push(`documentation: ${node.docstring}`);
  const text = parts.join("\n");
  if (text.length > MAX_TOKEN_CHARS) {
    (0, import_errors.logDebug)(`nodeToText: truncated ${node.id} from ${text.length} to ${MAX_TOKEN_CHARS} chars`);
    return text.slice(0, MAX_TOKEN_CHARS);
  }
  return text;
}
function cosine(a, b) {
  let dot = 0, na = 0, nb = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    na += a[i] * a[i];
    nb += b[i] * b[i];
  }
  const denom = Math.sqrt(na) * Math.sqrt(nb);
  return denom === 0 ? 0 : dot / denom;
}
function toFloat32Array(data) {
  if (data instanceof Float32Array) return data;
  if (Array.isArray(data)) return new Float32Array(data);
  if (data && typeof data === "object" && "length" in data) {
    return Float32Array.from(Array.from(data));
  }
  throw new Error("Unsupported embedding data format");
}
class VectorManager {
  constructor(db, config, projectRoot) {
    this.db = db;
    this.config = config;
    this.projectRoot = projectRoot;
    this.pipeline = null;
    this._initialized = false;
    this.vecIndex = null;
    this.oramaIndex = null;
    this.pgliteIndex = null;
    this.lancedbIndex = null;
    this.qdrantIndex = null;
    this.typesenseIndex = null;
    this._engineFallback = null;
  }
  isInitialized() {
    return this.config.enableEmbeddings === true && this._initialized;
  }
  /** Non-null when the configured engine failed to load and cosine is being used instead. */
  getEngineFallback() {
    return this._engineFallback;
  }
  /**
   * Load the embedding model. No-op when embeddings are disabled.
   * Fails silently so callers can continue without semantic search.
   * When config.useVecIndex is true, also initializes the sqlite-vec ANN index.
   */
  async initialize(onProgress) {
    if (!this.config.enableEmbeddings) {
      (0, import_errors.logDebug)("VectorManager: embeddings disabled");
      return;
    }
    if (this._initialized) return;
    const rawModel = this.config.embeddingModel || DEFAULT_MODEL;
    const modelId = rawModel.includes("/") ? rawModel : DEFAULT_MODEL;
    if (rawModel !== modelId) {
      (0, import_errors.logWarn)(`VectorManager: invalid embeddingModel "${rawModel}", using default instead`, { default: DEFAULT_MODEL });
    }
    const cacheDir = GLOBAL_MODELS_DIR;
    try {
      const { pipeline, env } = await getTransformers();
      env.cacheDir = cacheDir;
      if (!fs.existsSync(cacheDir)) {
        fs.mkdirSync(cacheDir, { recursive: true });
      }
      const cached = fs.existsSync(path.join(cacheDir, modelId));
      if (cached) env.allowRemoteModels = false;
      let lastFile = "";
      this.pipeline = await pipeline("feature-extraction", modelId, {
        dtype: "auto",
        ...!cached && onProgress ? {
          progress_callback: (p) => {
            if (p.status === "progress" && p.file) {
              lastFile = p.file;
              onProgress(p.file, p.loaded ?? 0, p.total ?? 0, false);
            } else if (p.status === "done" && lastFile) {
              onProgress(lastFile, 1, 1, true);
              lastFile = "";
            }
          }
        } : {}
      });
      try {
        const testOut = await this.pipeline("test", { pooling: "mean", normalize: true });
        const actualDim = testOut.dims?.[1] ?? testOut.data?.length;
        const configuredDim = this.config.embeddingDim ?? DEFAULT_EMBEDDING_DIM;
        if (actualDim && actualDim !== configuredDim) {
          (0, import_errors.logWarn)(
            `VectorManager: embeddingDim mismatch \u2014 config says ${configuredDim} but model outputs ${actualDim}. Using actual dim.`,
            { model: modelId, configuredDim, actualDim }
          );
          this.config.embeddingDim = actualDim;
        }
      } catch {
      }
      this._initialized = true;
      (0, import_errors.logDebug)("VectorManager: model loaded", { modelId, embeddingDim: this.config.embeddingDim ?? DEFAULT_EMBEDDING_DIM });
    } catch (err) {
      (0, import_errors.logError)("VectorManager: failed to load embedding model", {
        model: modelId,
        error: String(err)
      });
      this._initialized = false;
      return;
    }
    const engine = this.config.semanticEngine ?? (this.config.useVecIndex ? "sqlite-vec" : "cosine");
    if (this.projectRoot && engine !== "cosine") {
      const kirographDir = path.join(this.projectRoot, ".kirograph");
      const embeddingDim = this.config.embeddingDim ?? DEFAULT_EMBEDDING_DIM;
      if (engine === "sqlite-vec") {
        this.vecIndex = new import_vec_index.VecIndex(kirographDir, embeddingDim);
        await this.vecIndex.initialize();
        if (this.vecIndex.isAvailable()) {
          (0, import_errors.logDebug)("VectorManager: sqlite-vec ANN index ready");
        } else {
          this._engineFallback = "sqlite-vec unavailable \u2014 run: npm install better-sqlite3 sqlite-vec";
          (0, import_errors.logDebug)("VectorManager: sqlite-vec unavailable, falling back to in-process cosine");
        }
      } else if (engine === "orama") {
        this.oramaIndex = new import_orama_index.OramaIndex(kirographDir, embeddingDim);
        await this.oramaIndex.initialize();
        if (this.oramaIndex.isAvailable()) {
          (0, import_errors.logDebug)("VectorManager: Orama hybrid index ready");
        } else {
          this._engineFallback = "orama unavailable \u2014 run: npm install @orama/orama @orama/plugin-data-persistence";
          (0, import_errors.logDebug)("VectorManager: Orama unavailable, falling back to in-process cosine");
        }
      } else if (engine === "pglite") {
        this.pgliteIndex = new import_pglite_index.PGliteIndex(kirographDir, embeddingDim);
        await this.pgliteIndex.initialize();
        if (this.pgliteIndex.isAvailable()) {
          (0, import_errors.logDebug)("VectorManager: PGlite+pgvector hybrid index ready");
        } else {
          this._engineFallback = "pglite unavailable \u2014 run: npm install @electric-sql/pglite";
          (0, import_errors.logDebug)("VectorManager: PGlite unavailable, falling back to in-process cosine");
        }
      } else if (engine === "lancedb") {
        this.lancedbIndex = new import_lancedb_index.LanceDBIndex(kirographDir, embeddingDim);
        await this.lancedbIndex.initialize();
        if (this.lancedbIndex.isAvailable()) {
          (0, import_errors.logDebug)("VectorManager: LanceDB ANN index ready");
        } else {
          this._engineFallback = "lancedb unavailable \u2014 run: npm install @lancedb/lancedb";
          (0, import_errors.logDebug)("VectorManager: LanceDB unavailable, falling back to in-process cosine");
        }
      } else if (engine === "qdrant") {
        this.qdrantIndex = new import_qdrant_index.QdrantIndex(kirographDir, embeddingDim);
        await this.qdrantIndex.initialize();
        if (this.qdrantIndex.isAvailable()) {
          (0, import_errors.logDebug)("VectorManager: Qdrant ANN index ready");
        } else {
          this._engineFallback = "qdrant unavailable \u2014 run: npm install qdrant-local";
          (0, import_errors.logDebug)("VectorManager: Qdrant unavailable, falling back to in-process cosine");
        }
      } else if (engine === "typesense") {
        this.typesenseIndex = new import_typesense_index.TypesenseIndex(kirographDir, embeddingDim);
        await this.typesenseIndex.initialize();
        if (this.typesenseIndex.isAvailable()) {
          (0, import_errors.logDebug)("VectorManager: Typesense ANN index ready");
        } else {
          const reason = this.typesenseIndex.getFailReason();
          this._engineFallback = reason ? `typesense initialization failed: ${reason}` : "typesense unavailable \u2014 run: npm install typesense";
          (0, import_errors.logDebug)("VectorManager: Typesense unavailable, falling back to in-process cosine");
        }
      }
    }
  }
  /**
   * Embed a single node and persist to the vectors table.
   * Skips silently when disabled; logs on failure without throwing.
   */
  async embedNode(node) {
    if (!this.config.enableEmbeddings) return;
    if (!this._initialized || !this.pipeline) {
      (0, import_errors.logError)("Embedding model unavailable", { model: this.config.embeddingModel });
      return;
    }
    if (!EMBEDDABLE_KINDS.has(node.kind)) return;
    try {
      const text = `search_document: ${nodeToText(node)}`;
      const output = await this.pipeline(text, { pooling: "mean", normalize: true });
      const embedding = toFloat32Array(output.data);
      if (!this.vecIndex?.isAvailable() && !this.oramaIndex?.isAvailable() && !this.pgliteIndex?.isAvailable() && !this.lancedbIndex?.isAvailable() && !this.qdrantIndex?.isAvailable() && !this.typesenseIndex?.isAvailable()) {
        this.db.storeEmbedding(node.id, embedding, this.config.embeddingModel || DEFAULT_MODEL);
      }
      this.vecIndex?.upsert(node.id, embedding);
      if (this.oramaIndex?.isAvailable()) await this.oramaIndex.upsert(node, embedding);
      if (this.pgliteIndex?.isAvailable()) await this.pgliteIndex.upsert(node, embedding);
      if (this.lancedbIndex?.isAvailable()) await this.lancedbIndex.upsert(node, embedding);
      if (this.qdrantIndex?.isAvailable()) await this.qdrantIndex.upsert(node, embedding);
      if (this.typesenseIndex?.isAvailable()) await this.typesenseIndex.upsert(node, embedding);
    } catch (err) {
      (0, import_errors.logWarn)("Failed to embed node", { nodeId: node.id, error: String(err) });
    }
  }
  /** Number of entries currently in the active ANN/hybrid index (0 when not in use). */
  async vecIndexCount() {
    if (this.vecIndex?.isAvailable()) return this.vecIndex.count();
    if (this.oramaIndex?.isAvailable()) return this.oramaIndex.count();
    if (this.pgliteIndex?.isAvailable()) return this.pgliteIndex.count();
    if (this.lancedbIndex?.isAvailable()) return this.lancedbIndex.count();
    if (this.qdrantIndex?.isAvailable()) return this.qdrantIndex.count();
    if (this.typesenseIndex?.isAvailable()) return this.typesenseIndex.count();
    return 0;
  }
  /**
   * Remove embeddings for the given node IDs from the active index.
   * For cosine the `vectors` SQLite table is cleaned up automatically via FK cascade
   * when nodes are deleted. For sqlite-vec, orama, and pglite the engine itself is
   * the sole store of record, so we delete from it explicitly here.
   */
  async deleteEmbeddings(nodeIds) {
    for (const id of nodeIds) {
      this.vecIndex?.delete(id);
      if (this.oramaIndex?.isAvailable()) await this.oramaIndex.delete(id);
      if (this.pgliteIndex?.isAvailable()) await this.pgliteIndex.delete(id);
      if (this.lancedbIndex?.isAvailable()) await this.lancedbIndex.delete(id);
      if (this.qdrantIndex?.isAvailable()) await this.qdrantIndex.delete(id);
      if (this.typesenseIndex?.isAvailable()) await this.typesenseIndex.delete(id);
    }
  }
  /**
   * Embed all eligible nodes in the database that don't yet have embeddings.
   * Streams nodes in pages to avoid loading the entire node set into memory —
   * critical for large codebases (100K+ symbols) where a single getAllNodes()
   * call can exhaust the Node.js heap or the WASM linear memory.
   *
   * Uses dynamic batch sizing: starts at BATCH_SIZE, halves on OOM errors,
   * and recovers back to full size after successful batches.
   *
   * Emits a pre-flight warning via onProgress when the embeddable node count
   * exceeds LARGE_CODEBASE_THRESHOLD so the CLI can surface it to the user.
   */
  async embedAll(onProgress) {
    if (!this.isInitialized() || !this.pipeline) return 0;
    const modelId = this.config.embeddingModel || DEFAULT_MODEL;
    const EMBEDDABLE_KINDS_ARRAY = [...EMBEDDABLE_KINDS];
    const totalEmbeddable = this.db.countEmbeddableNodes(EMBEDDABLE_KINDS_ARRAY);
    const LARGE_CODEBASE_THRESHOLD = 1e5;
    if (totalEmbeddable > LARGE_CODEBASE_THRESHOLD) {
      onProgress?.(-1, totalEmbeddable);
    }
    const existingIds = new Set(
      this.typesenseIndex?.isAvailable() ? await this.typesenseIndex.getEmbeddedNodeIds() : this.qdrantIndex?.isAvailable() ? await this.qdrantIndex.getEmbeddedNodeIds() : this.lancedbIndex?.isAvailable() ? await this.lancedbIndex.getEmbeddedNodeIds() : this.pgliteIndex?.isAvailable() ? await this.pgliteIndex.getEmbeddedNodeIds() : this.oramaIndex?.isAvailable() ? await this.oramaIndex.getEmbeddedNodeIds() : this.vecIndex?.isAvailable() ? this.vecIndex.getEmbeddedNodeIds() : this.db.getEmbeddedNodeIds()
    );
    const PAGE_SIZE = 2e3;
    let pageOffset = 0;
    let processed = 0;
    let currentBatchSize = BATCH_SIZE;
    let consecutiveSuccesses = 0;
    const totalPending = Math.max(0, totalEmbeddable - existingIds.size);
    if (totalPending === 0) {
      if (this.oramaIndex?.isAvailable()) await this.oramaIndex.save();
      return 0;
    }
    (0, import_errors.logDebug)(`VectorManager: embedding ~${totalPending} nodes (${existingIds.size} already embedded, streaming in pages of ${PAGE_SIZE})`);
    let pageBuffer = [];
    let pageBufferOffset = 0;
    let pageExhausted = false;
    const fetchNextPage = () => {
      const page = this.db.getEmbeddableNodesPaged(EMBEDDABLE_KINDS_ARRAY, PAGE_SIZE, pageOffset);
      pageOffset += page.length;
      pageBuffer = page.filter((n) => !existingIds.has(n.id));
      pageBufferOffset = 0;
      if (page.length < PAGE_SIZE) pageExhausted = true;
    };
    fetchNextPage();
    while (pageBuffer.length > 0 || !pageExhausted && pageBufferOffset >= pageBuffer.length) {
      if (pageBufferOffset >= pageBuffer.length) {
        if (pageExhausted) break;
        fetchNextPage();
        if (pageBuffer.length === 0) break;
      }
      const batch = pageBuffer.slice(pageBufferOffset, pageBufferOffset + currentBatchSize);
      if (batch.length === 0) break;
      const texts = batch.map((n) => `search_document: ${nodeToText(n)}`);
      try {
        const outputs = await this.pipeline(texts, { pooling: "mean", normalize: true });
        const dims = outputs.dims;
        const dim = dims[1] ?? (this.config.embeddingDim ?? DEFAULT_EMBEDDING_DIM);
        const flat = toFloat32Array(outputs.data);
        const tsNodes = [];
        const tsEmbeddings = [];
        const qdNodes = [];
        const qdEmbeddings = [];
        for (let j = 0; j < batch.length; j++) {
          const node = batch[j];
          const embedding = flat.slice(j * dim, (j + 1) * dim);
          if (!this.vecIndex?.isAvailable() && !this.oramaIndex?.isAvailable() && !this.pgliteIndex?.isAvailable() && !this.lancedbIndex?.isAvailable() && !this.qdrantIndex?.isAvailable() && !this.typesenseIndex?.isAvailable()) {
            this.db.storeEmbedding(node.id, embedding, modelId);
          }
          this.vecIndex?.upsert(node.id, embedding);
          if (this.oramaIndex?.isAvailable()) await this.oramaIndex.upsert(node, embedding);
          if (this.pgliteIndex?.isAvailable()) await this.pgliteIndex.upsert(node, embedding);
          if (this.lancedbIndex?.isAvailable()) await this.lancedbIndex.upsert(node, embedding);
          if (this.qdrantIndex?.isAvailable()) {
            qdNodes.push(node);
            qdEmbeddings.push(embedding);
          }
          if (this.typesenseIndex?.isAvailable()) {
            tsNodes.push(node);
            tsEmbeddings.push(embedding);
          }
        }
        if (qdNodes.length > 0) await this.qdrantIndex.bulkUpsert(qdNodes, qdEmbeddings);
        if (tsNodes.length > 0) await this.typesenseIndex.bulkUpsert(tsNodes, tsEmbeddings);
        pageBufferOffset += batch.length;
        processed += batch.length;
        consecutiveSuccesses++;
        if (currentBatchSize < BATCH_SIZE && consecutiveSuccesses >= 5) {
          currentBatchSize = Math.min(currentBatchSize * 2, BATCH_SIZE);
          (0, import_errors.logDebug)(`VectorManager: batch size recovered to ${currentBatchSize}`);
          consecutiveSuccesses = 0;
        }
      } catch (err) {
        const errMsg = String(err);
        const isOOM = errMsg.includes("bad allocation") || errMsg.includes("OOM") || errMsg.includes("out of memory");
        if (isOOM && currentBatchSize > MIN_BATCH_SIZE) {
          currentBatchSize = Math.max(Math.floor(currentBatchSize / 2), MIN_BATCH_SIZE);
          consecutiveSuccesses = 0;
          (0, import_errors.logWarn)(`VectorManager: OOM at batch offset ${pageBufferOffset} \u2014 reducing batch size to ${currentBatchSize} and retrying`, {
            batchSize: currentBatchSize,
            nodeIds: batch.slice(0, 3).map((n) => n.id)
          });
        } else if (isOOM && currentBatchSize <= MIN_BATCH_SIZE) {
          (0, import_errors.logError)(`VectorManager: OOM even at minimum batch size ${MIN_BATCH_SIZE} \u2014 skipping ${batch.length} nodes`, {
            batchStart: pageBufferOffset,
            skippedNodes: batch.map((n) => `${n.kind}:${n.name} (${n.filePath})`).slice(0, 5)
          });
          pageBufferOffset += batch.length;
          processed += batch.length;
          consecutiveSuccesses = 0;
        } else {
          (0, import_errors.logWarn)(`VectorManager: batch embedding failed at offset ${pageBufferOffset}`, {
            batchSize: currentBatchSize,
            error: errMsg
          });
          pageBufferOffset += batch.length;
          processed += batch.length;
          consecutiveSuccesses = 0;
        }
      }
      onProgress?.(processed, totalPending);
    }
    if (this.oramaIndex?.isAvailable()) await this.oramaIndex.save();
    (0, import_errors.logDebug)(`VectorManager: embedding complete \u2014 ${processed} processed, final batch size: ${currentBatchSize}`);
    return processed;
  }
  /**
   * Semantic search: embed the query and return top-N nodes by similarity.
   *
   * When sqlite-vec is available (useVecIndex: true), delegates ANN search to
   * VecIndex (fast, sub-linear). Otherwise falls back to in-process cosine
   * similarity over all stored embeddings (linear scan, no extra deps).
   *
   * Returns empty array when not initialized.
   */
  async search(query, topN = 10) {
    if (!this.isInitialized() || !this.pipeline) return [];
    try {
      const text = `search_query: ${query}`;
      const output = await this.pipeline(text, { pooling: "mean", normalize: true });
      const queryVec = toFloat32Array(output.data);
      let nodeIds;
      if (this.pgliteIndex?.isAvailable()) {
        nodeIds = await this.pgliteIndex.search(query, queryVec, topN);
      } else if (this.oramaIndex?.isAvailable()) {
        nodeIds = await this.oramaIndex.search(query, queryVec, topN);
      } else if (this.lancedbIndex?.isAvailable()) {
        nodeIds = await this.lancedbIndex.search(queryVec, topN);
      } else if (this.qdrantIndex?.isAvailable()) {
        nodeIds = await this.qdrantIndex.search(queryVec, topN);
      } else if (this.typesenseIndex?.isAvailable()) {
        nodeIds = await this.typesenseIndex.search(queryVec, topN);
      } else if (this.vecIndex?.isAvailable()) {
        nodeIds = this.vecIndex.search(queryVec, topN);
      } else {
        const allEmbeddings = this.db.getAllEmbeddings();
        nodeIds = allEmbeddings.map(({ nodeId, embedding }) => ({ nodeId, score: cosine(queryVec, embedding) })).filter((r) => r.score >= 0.3).sort((a, b) => b.score - a.score).slice(0, topN).map((r) => r.nodeId);
      }
      const results = [];
      for (const nodeId of nodeIds) {
        const node = this.db.getNode(nodeId);
        if (node) results.push(node);
      }
      return results;
    } catch (err) {
      (0, import_errors.logWarn)("VectorManager: search failed", { error: String(err) });
      return [];
    }
  }
  /** Release engine resources (e.g. kill Qdrant child process). */
  close() {
    this.qdrantIndex?.close();
    this.typesenseIndex?.close();
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  VectorManager
});
//# sourceMappingURL=index.js.map
