import {
  getVectorStore
} from "../providers/vector-store.js";
import {
  updateCacheEmbedding,
  getAllEmbeddings,
  normalizeUrl
} from "../cache/store.js";
import { FastembedEmbedProvider } from "./fastembed-provider.js";
import { createLogger } from "../logger.js";
const log = createLogger("embedding");
class EmbeddingService {
  provider;
  store = null;
  knownUrls = /* @__PURE__ */ new Set();
  available = false;
  providerVerified = false;
  constructor(provider) {
    this.provider = provider ?? new FastembedEmbedProvider();
  }
  async init() {
    try {
      this.store = await getVectorStore();
      try {
        const existingSize = await this.store.size();
        if (existingSize === 0) {
          await this.migrateLegacyEmbeddings();
        } else {
        }
      } catch (err) {
        log.warn("embedding migration check failed", {
          error: err instanceof Error ? err.message : String(err)
        });
      }
      try {
        await this.provider.embed(["embedding service probe"]);
        this.providerVerified = true;
        log.info("embedding provider verified", {
          modelId: this.provider.modelId,
          dim: this.provider.dim
        });
      } catch (err) {
        log.warn("embedding provider probe failed \u2014 embeddings disabled", {
          error: err instanceof Error ? err.message : String(err)
        });
        this.providerVerified = false;
      }
      this.available = true;
    } catch (err) {
      log.error("EmbeddingService init failed", { error: String(err) });
      this.available = false;
    }
  }
  isAvailable() {
    return this.available;
  }
  setAvailable(value) {
    this.available = value;
  }
  /** Backwards-compat alias preserved for callers that gated on subprocess readiness. */
  isSubprocessReady() {
    return this.providerVerified;
  }
  /**
   * Lightweight index view. Returns `size` from the backing VectorStore and
   * `has` from a local URL-cache populated by embedAndStore. Callers that
   * need richer access should consume the VectorStore directly via
   * `getVectorStore()`.
   */
  getIndex() {
    const knownUrls = this.knownUrls;
    const store = this.store;
    return {
      size: () => store ? this.cachedSize : knownUrls.size,
      has: (url) => knownUrls.has(url)
    };
  }
  /**
   * Cached size from the store, refreshed after upserts. Reads from a
   * VectorStore would be async; getIndex().size() callers expect a
   * synchronous return so we maintain this counter.
   */
  cachedSize = 0;
  async embedAndStore(url, markdown) {
    if (!this.available) {
      log.debug("embedding skipped: service not available", { url });
      return;
    }
    try {
      const [vector] = await this.provider.embed([markdown]);
      if (!vector || vector.length === 0) {
        log.warn("embedding returned empty vector", { url });
        return;
      }
      const buffer = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
      const model = this.provider.modelId;
      const dims = vector.length;
      let normalizedUrl;
      try {
        normalizedUrl = normalizeUrl(url);
      } catch {
        normalizedUrl = url;
      }
      updateCacheEmbedding(normalizedUrl, buffer, model, dims);
      if (this.store) {
        const record = {
          id: normalizedUrl,
          vector,
          metadata: { url: normalizedUrl, contentHash: "", modelId: model }
        };
        await this.store.upsert([record]);
        if (!this.knownUrls.has(normalizedUrl)) {
          this.knownUrls.add(normalizedUrl);
          this.cachedSize += 1;
        }
      }
      log.debug("embedded and stored", { url: normalizedUrl, dims });
    } catch (err) {
      log.warn("embedAndStore failed", { url, error: String(err) });
    }
  }
  embedAsync(url, markdown) {
    if (!this.available) return;
    this.embedAndStore(url, markdown).catch((err) => {
      log.warn("async embedding failed", { url, error: String(err) });
    });
  }
  async findSimilar(queryText, topK, excludeUrls) {
    if (!this.available || !this.store) {
      return [];
    }
    if (this.cachedSize === 0) {
      try {
        this.cachedSize = await this.store.size();
      } catch {
        this.cachedSize = 0;
      }
      if (this.cachedSize === 0) return [];
    }
    try {
      const [queryVector] = await this.provider.embed([queryText]);
      if (!queryVector || queryVector.length === 0) {
        log.warn("query embedding failed: empty vector");
        return [];
      }
      const overscan = excludeUrls && excludeUrls.size > 0 ? Math.max(topK + excludeUrls.size, topK * 2) : topK;
      const hits = await this.store.search(queryVector, overscan);
      const results = [];
      for (const hit of hits) {
        if (excludeUrls?.has(hit.id)) continue;
        results.push({ url: hit.id, score: hit.score });
        if (results.length >= topK) break;
      }
      return results;
    } catch (err) {
      log.warn("findSimilar failed", { error: String(err) });
      return [];
    }
  }
  shutdown() {
    try {
      this.knownUrls.clear();
      this.cachedSize = 0;
      this.store = null;
      this.available = false;
      this.providerVerified = false;
      log.info("EmbeddingService shut down");
    } catch (err) {
      log.error("EmbeddingService shutdown error", { error: String(err) });
    }
  }
  async migrateLegacyEmbeddings() {
    if (!this.store) return;
    const legacy = getAllEmbeddings(this.provider.modelId);
    if (legacy.length === 0) {
      this.cachedSize = 0;
      return;
    }
    const records = [];
    for (const row of legacy) {
      if (!row.embedding || row.dims <= 0) continue;
      try {
        const vector = new Float32Array(
          row.embedding.buffer.slice(
            row.embedding.byteOffset,
            row.embedding.byteOffset + row.dims * Float32Array.BYTES_PER_ELEMENT
          )
        );
        records.push({
          id: row.normalizedUrl,
          vector,
          metadata: {
            url: row.normalizedUrl,
            contentHash: "",
            modelId: row.model
          }
        });
        this.knownUrls.add(row.normalizedUrl);
      } catch (err) {
        log.warn("legacy embedding migration: failed to decode vector", {
          url: row.normalizedUrl,
          error: err instanceof Error ? err.message : String(err)
        });
      }
    }
    if (records.length === 0) {
      this.cachedSize = 0;
      return;
    }
    log.info("migrating embeddings into sqlite-vec store", { count: records.length });
    await this.store.upsert(records);
    this.cachedSize = await this.store.size();
  }
}
let globalInstance = null;
function getEmbeddingService() {
  if (!globalInstance) {
    globalInstance = new EmbeddingService();
  }
  return globalInstance;
}
function resetEmbeddingService() {
  if (globalInstance) {
    globalInstance.shutdown();
    globalInstance = null;
  }
}
export {
  EmbeddingService,
  getEmbeddingService,
  resetEmbeddingService
};
//# sourceMappingURL=embed.js.map