import path from "node:path";
import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import { log } from "../logger.js";
import {
  reportSearchDegradation,
  resolveEnsureCollectionArgs,
  type SearchBackend,
  type SearchExecutionOptions,
  type SearchQueryOptions,
  type SearchResult,
} from "./port.js";
 import type { EmbedHelper, EmbedProviderIdentity, EmbedWithProviderResult } from "./embed-helper.js";
 import { scanMemoryDir } from "./document-scanner.js";
 import { isSearchAborted, throwIfSearchAborted } from "./abort.js";
import {
  containsNonLegacyTokenizerChars,
  createCjkCapableTokenizer,
  ORAMA_CJK_TOKENIZER_LANGUAGE,
} from "./orama-cjk-tokenizer.js";
 
 export interface OramaBackendOptions {
   dbPath: string;
   collection: string;
   embedHelper: EmbedHelper;
   memoryDir: string;
   embeddingDimension: number;
  /**
   * Segment space-free scripts (CJK/Thai) in the lexical index. Default
   * `true`; `false` restores the stock English-only tokenizer (issue #2187).
   */
  cjkSegmentationEnabled?: boolean;
 }


const ORAMA_COLLECTION_FILENAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;

function pathIsInside(parent: string, child: string): boolean {
  const relative = path.relative(parent, child);
  return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}

export function resolveOramaCollectionDbFilePath(dbPath: string, collection: string): string {
  if (!ORAMA_COLLECTION_FILENAME_PATTERN.test(collection)) {
    throw new Error(
      `Invalid Orama collection name ${JSON.stringify(collection)}. ` +
        "Collection names must match [A-Za-z0-9][A-Za-z0-9._-]*.",
    );
  }
  const resolvedDbPath = path.resolve(dbPath);
  const filePath = path.resolve(resolvedDbPath, `${collection}.msp`);
  if (!pathIsInside(resolvedDbPath, filePath)) {
    throw new Error(
      `Invalid Orama collection path for ${JSON.stringify(collection)}: resolved outside dbPath.`,
    );
  }
  return filePath;
}

/**
 * Orama search backend — embedded hybrid FTS+vector, pure JS.
 *
 * Uses @orama/orama for full-text search with optional vector support.
 * Persists data to JSON files via @orama/plugin-data-persistence.
 */
export class OramaBackend implements SearchBackend {
  private readonly dbPath: string;
  private readonly collection: string;
  private readonly embedHelper: EmbedHelper;
  private readonly memoryDir: string;
  private readonly embeddingDimension: number;
  private readonly cjkSegmentationEnabled: boolean;
  private available = false;
  private db: any = null;
  private oramaModule: any = null;
  private persistModule: any = null;
  private readonly vectorProviderCompatibility = new WeakMap<
    object,
    { providerIdentity: EmbedProviderIdentity; compatible: boolean }
  >();

  constructor(opts: OramaBackendOptions) {
    this.dbPath = opts.dbPath;
    this.collection = opts.collection;
    this.embedHelper = opts.embedHelper;
    this.memoryDir = opts.memoryDir;
    this.embeddingDimension = opts.embeddingDimension;
    this.cjkSegmentationEnabled = opts.cjkSegmentationEnabled !== false;
  }

  async probe(): Promise<boolean> {
    try {
      await this.ensureModules();
      await this.ensureDb();
      this.available = true;
      return true;
    } catch (err) {
      log.debug(`OramaBackend probe failed: ${err}`);
      this.available = false;
      return false;
    }
  }

  isAvailable(): boolean {
    return this.available;
  }

  debugStatus(): string {
    return `backend=orama available=${this.available} dbPath=${this.dbPath}`;
  }

  async search(
    query: string,
    _collection?: string,
    maxResults?: number,
    _options?: SearchQueryOptions,
    execution?: SearchExecutionOptions,
  ): Promise<SearchResult[]> {
    return this.hybridSearch(query, _collection, maxResults, execution);
  }

  async searchGlobal(query: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {
    const limit = maxResults ?? 10;
    if (!this.available) return [];
    try {
      throwIfSearchAborted(execution, "OramaBackend global search aborted");
      const files = await this.listDbFiles();
      const allResults: SearchResult[] = [];
      for (const file of files) {
        throwIfSearchAborted(execution, "OramaBackend global search aborted");
        const db = await this.loadDbFromFile(file);
        if (!db) continue;
        const results = await this.searchDb(db, query, "hybrid", limit, execution);
        allResults.push(...results);
      }
      allResults.sort((a, b) => b.score - a.score);
      return allResults.slice(0, limit);
    } catch (err) {
      log.debug(`OramaBackend searchGlobal failed: ${err}`);
      return [];
    }
  }

  /**
   * Full-text (FTS) search only. With the stock tokenizer this cannot match
   * space-free CJK/Thai text lexically — hybrid or vector search provides
   * the fallback for those scripts (issue #2187).
   */
  async bm25Search(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {
    if (isSearchAborted(execution)) return [];
    const db = await this.ensureDbForCollection(collection ?? this.collection);
    if (isSearchAborted(execution)) return [];
    if (!db) return [];
    return this.searchDb(db, query, "fulltext", maxResults ?? 10, execution);
  }

  async vectorSearch(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {
    if (isSearchAborted(execution)) return [];
    const db = await this.ensureDbForCollection(collection ?? this.collection);
    if (isSearchAborted(execution)) return [];
    if (!db) return [];
    return this.searchDb(db, query, "vector", maxResults ?? 10, execution);
  }

  async hybridSearch(query: string, collection?: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]> {
    if (isSearchAborted(execution)) return [];
    const db = await this.ensureDbForCollection(collection ?? this.collection);
    if (isSearchAborted(execution)) return [];
    if (!db) return [];
    return this.searchDb(db, query, "hybrid", maxResults ?? 10, execution);
  }

  async update(execution?: SearchExecutionOptions): Promise<void> {
    await this.updateCollection(this.collection, execution);
  }

  async updateCollection(collection: string, execution?: SearchExecutionOptions): Promise<void> {
    await this.updateCollectionFromDir(collection, this.memoryDir, execution);
  }

  async updateCollectionFromDir(collection: string, memoryDir: string, execution?: SearchExecutionOptions): Promise<void> {
    if (isSearchAborted(execution)) return;
    const db = await this.ensureDbForCollection(collection);
    if (isSearchAborted(execution)) return;
    if (!db) return;
    const { search: oramaSearch, insert, remove, count, getByID } = this.oramaModule;

    const docs = await scanMemoryDir(memoryDir);
    if (isSearchAborted(execution)) return;
    const docMap = new Map(docs.map((d) => [d.docid, d]));
    const { update: oramaUpdate } = this.oramaModule;

    const embeddingProviderIdentity = this.embedHelper.getProviderIdentity();
    let allRowsCompatible = !!embeddingProviderIdentity && docs.length > 0;
    // Get existing docs to diff — map user doc ID → { internalId, vector }
    const existingDocs = new Map<string, {
      internalId: string;
      vector?: number[];
      vectorProvider?: string;
    }>();
    const existingCount = await count(db);
    if (existingCount > 0) {
      const allHits = await oramaSearch(db, {
        term: "",
        limit: existingCount + 100,
      });
      for (const hit of allHits.hits) {
        if (isSearchAborted(execution)) return;
        const storedDocument =
          typeof getByID === "function"
            ? await getByID(db, hit.id)
            : hit.document;
        const document = storedDocument ?? hit.document ?? {};
        if (!docMap.has(document.id)) {
          await remove(db, hit.id);
        } else {
          existingDocs.set(document.id, {
            internalId: hit.id,
            vector: this.normalizeStoredVector(document.vector) ?? undefined,
            vectorProvider:
              typeof document.vectorProvider === "string"
                ? document.vectorProvider
                : undefined,
          });
        }
      }
    }

    // Insert new docs, update existing ones (preserving vectors since update is remove+insert)
    for (const doc of docs) {
      if (isSearchAborted(execution)) return;
      const existing = existingDocs.get(doc.docid);
      if (existing) {
        const payload: Record<string, unknown> = {
          id: doc.docid,
          path: doc.path,
          content: doc.content,
          snippet: doc.snippet,
        };
        const preservesCompatibleProvider =
          !!embeddingProviderIdentity &&
          existing.vectorProvider === embeddingProviderIdentity;
        if (preservesCompatibleProvider) {
          if (this.isCompatibleStoredVector(existing.vector)) {
            payload.vector = existing.vector;
            payload.vectorProvider = existing.vectorProvider ?? "";
          } else {
            payload.vector = this.zeroVector();
            payload.vectorProvider = "";
            allRowsCompatible = false;
          }
        } else if (!embeddingProviderIdentity && this.isCompatibleStoredVector(existing.vector)) {
          payload.vector = existing.vector;
          payload.vectorProvider = existing.vectorProvider ?? "";
          allRowsCompatible = false;
        } else {
          payload.vector = this.zeroVector();
          payload.vectorProvider = "";
          allRowsCompatible = false;
        }
        try {
          await oramaUpdate(db, existing.internalId, payload);
        } catch {
          allRowsCompatible = false;
          // Update failed — skip and continue with remaining docs
        }
      } else {
        allRowsCompatible = false;
        try {
          await insert(db, {
            id: doc.docid,
            path: doc.path,
            content: doc.content,
            snippet: doc.snippet,
            vector: this.zeroVector(),
            vectorProvider: "",
          });
        } catch {
          allRowsCompatible = false;
          // Duplicate id edge case — skip
        }
      }
    }

    if (isSearchAborted(execution)) return;
    await this.persistDbForCollection(db, collection);
    this.rememberVectorProviderCompatibility(
      db,
      embeddingProviderIdentity,
      allRowsCompatible,
    );
  }

  async embed(): Promise<void> {
    await this.embedCollection(this.collection);
  }

  async embedCollection(collection: string): Promise<void> {
    if (!this.embedHelper.isAvailable()) return;

    const db = await this.ensureDbForCollection(collection);
    if (!db) return;
    const { search: oramaSearch, update: oramaUpdate, count } = this.oramaModule;

    const existingCount = await count(db);
    if (existingCount === 0) return;

    const embeddingProviderIdentity = this.embedHelper.getProviderIdentity();
    // Find docs without vectors or with vectors from a different provider.
    const allHits = await oramaSearch(db, { term: "", limit: existingCount + 100 });
    const needsEmbed = allHits.hits.filter((h: any) => {
      const vector = this.normalizeStoredVector(h.document?.vector);
      return (
        (embeddingProviderIdentity &&
          h.document?.vectorProvider !== embeddingProviderIdentity) ||
        !this.isCompatibleStoredVector(vector)
      );
    });

    if (needsEmbed.length === 0) {
      this.rememberVectorProviderCompatibility(db, embeddingProviderIdentity, true);
      return;
    }

    let rowsToEmbed = needsEmbed;
    let embedResult = await this.embedHelper.embedBatchWithProvider(
      rowsToEmbed.map((h: any) => h.document.content as string),
    );
    if (!embedResult) return;
    if (
      embeddingProviderIdentity &&
      embedResult.providerIdentity !== embeddingProviderIdentity
    ) {
      const effectiveProviderIdentity = embedResult.providerIdentity;
      const originalIds = new Set(rowsToEmbed.map((h: any) => h.id));
      const effectiveNeedsEmbed = allHits.hits.filter((h: any) => {
        const vector = this.normalizeStoredVector(h.document?.vector);
        return (
          h.document?.vectorProvider !== effectiveProviderIdentity ||
          !this.isCompatibleStoredVector(vector)
        );
      });
      const sameRows =
        effectiveNeedsEmbed.length === rowsToEmbed.length &&
        effectiveNeedsEmbed.every((h: any) => originalIds.has(h.id));
      if (!sameRows) {
        const effectiveTexts = effectiveNeedsEmbed.map((h: any) => h.document.content as string);
        const effectiveEmbedResult = await this.embedHelper.embedBatchWithProvider(effectiveTexts);
        if (effectiveEmbedResult) {
          rowsToEmbed = effectiveNeedsEmbed;
          embedResult = effectiveEmbedResult;
        }
      }
    }
    const { vectors, providerIdentity } = embedResult;

    let allEmbedded = true;
    for (let i = 0; i < rowsToEmbed.length; i++) {
      const vec = vectors[i];
      if (!this.isExpectedDimensionVector(vec)) {
        allEmbedded = false;
        continue;
      }
      // Orama update is remove+insert — must include all fields to avoid data loss
      const doc = rowsToEmbed[i].document;
      await oramaUpdate(db, rowsToEmbed[i].id, {
        id: doc.id,
        path: doc.path,
        content: doc.content,
        snippet: doc.snippet,
        vector: vec,
        vectorProvider: providerIdentity,
      });
    }

    await this.persistDbForCollection(db, collection);
    if (allEmbedded) {
      this.rememberVectorProviderCompatibility(db, providerIdentity, true);
    } else {
      this.rememberVectorProviderCompatibility(db, providerIdentity, false);
    }
  }

  async ensureCollection(
    _memoryDir: string,
    collectionOrExecution?: string | SearchExecutionOptions,
    execution?: SearchExecutionOptions,
  ): Promise<"present" | "missing" | "unknown" | "skipped"> {
    const { collection, execution: effectiveExecution } = resolveEnsureCollectionArgs(
      collectionOrExecution,
      execution,
    );
    if (isSearchAborted(effectiveExecution)) return "skipped";
    try {
      await this.ensureModules();
      await this.ensureDbForCollection(collection ?? this.collection);
      return "present";
    } catch {
      return "missing";
    }
  }

  private async ensureModules(): Promise<void> {
    if (this.oramaModule && this.persistModule) return;
    this.oramaModule = await import("@orama/orama");
    this.persistModule = await import("@orama/plugin-data-persistence");
  }

  private async ensureDb(): Promise<any> {
    if (this.db) return this.db;
    await this.ensureModules();

    await mkdir(this.dbPath, { recursive: true });
    const filePath = this.dbFilePath(this.collection);

    let raw: string;
    try {
      raw = await readFile(filePath, "utf-8");
    } catch {
      // No existing DB — create fresh
      this.db = await this.createDb();
      return this.db;
    }

    this.db = await this.applyTokenizerVersion(
      await this.migrateLegacyVectorProviderSchema(
        await this.persistModule.restore("json", raw),
        this.collection,
      ),
      this.collection,
    );
    return this.db;
  }

  private async ensureDbForCollection(collection: string): Promise<any> {
    // For the default collection, use the cached instance
    if (collection === this.collection) return this.ensureDb();

    await this.ensureModules();
    await mkdir(this.dbPath, { recursive: true });
    const filePath = this.dbFilePath(collection);

    let raw: string;
    try {
      raw = await readFile(filePath, "utf-8");
    } catch {
      // No existing DB — create fresh
      return await this.createDb();
    }

    return await this.applyTokenizerVersion(
      await this.migrateLegacyVectorProviderSchema(
        await this.persistModule.restore("json", raw),
        collection,
      ),
      collection,
    );
  }

  private async createDb(): Promise<any> {
    const { create } = this.oramaModule;
    const schema: Record<string, string> = {
      id: "string",
      path: "string",
      content: "string",
      snippet: "string",
      vectorProvider: "string",
      vector: `vector[${this.embeddingDimension}]`,
    };
    if (!this.cjkSegmentationEnabled) {
      return await create({ schema });
    }
    return await create({
      schema,
      components: { tokenizer: createCjkCapableTokenizer(this.oramaModule) },
    });
  }

  /**
   * Attach the CJK-capable tokenizer to a restored index and rebuild the
   * full-text index when it predates CJK tokenization. Orama persists
   * `tokenizer.language` with the index, so a marker other than
   * `ORAMA_CJK_TOKENIZER_LANGUAGE` identifies an index whose terms were
   * produced by the stock English tokenizer. Corpora whose content the stock
   * tokenizer and this tokenizer treat identically (pure legacy Latin) need
   * no re-indexing — only the version marker is rewritten for them.
   */
  private async applyTokenizerVersion(db: any, collection: string): Promise<any> {
    const persistedLanguage =
      db?.tokenizer?.language && typeof db.tokenizer.language === "string"
        ? db.tokenizer.language
        : "";
    if (this.cjkSegmentationEnabled) {
      db.tokenizer = createCjkCapableTokenizer(this.oramaModule);
      if (persistedLanguage === ORAMA_CJK_TOKENIZER_LANGUAGE) return db;
    } else {
      // Segmentation disabled: a database persisted under the CJK tokenizer
      // keeps its CJK terms unless it is re-indexed under the stock one, so
      // downgrade it instead of returning it unchanged (issue #2187 round 2).
      if (persistedLanguage !== ORAMA_CJK_TOKENIZER_LANGUAGE) return db;
      db.tokenizer = this.oramaModule.components.tokenizer.createTokenizer({ language: "english" });
    }


    try {
      const { search: oramaSearch, count, update: oramaUpdate } = this.oramaModule;
      const existingCount = await count(db);
      if (existingCount === 0) {
        await this.persistDbForCollection(db, collection);
        return db;
      }
      const allHits = await oramaSearch(db, { term: "", limit: existingCount + 100 });
      const hits = allHits.hits ?? [];
      const hasNonLegacyContent = hits.some((hit: any) => {
        const doc = this.getStoredDocument(db, hit);
        // Orama full-text queries every string field by default, so every
        // indexed string field gates the rebuild — non-Latin text that only
        // rides in snippet/path/id would otherwise keep stale terms
        // (issue #2187 review round 2).
        for (const field of [doc.content, doc.snippet, doc.path, doc.id, doc.vectorProvider]) {
          if (typeof field === "string" && containsNonLegacyTokenizerChars(field)) return true;
        }
        return false;
      });
      if (hasNonLegacyContent) {
        // Re-index every document in place (Orama update is remove+insert,
        // so content is re-tokenized; vectors are carried through). A single
        // failing document must not block the rest of the corpus: it keeps
        // its pre-upgrade terms, which is no worse than before the upgrade.
        for (const hit of hits) {
          try {
            const doc = this.getStoredDocument(db, hit);
            const vector = this.getStoredVector(db, hit, doc);
            await oramaUpdate(db, hit.id, {
              id: typeof doc.id === "string" && doc.id.length > 0 ? doc.id : String(hit.id),
              path: typeof doc.path === "string" ? doc.path : "",
              content: typeof doc.content === "string" ? doc.content : "",
              snippet:
                typeof doc.snippet === "string"
                  ? doc.snippet
                  : typeof doc.content === "string"
                    ? doc.content.slice(0, 200)
                    : "",
              vectorProvider: typeof doc.vectorProvider === "string" ? doc.vectorProvider : "",
              vector: vector ?? this.zeroVector(),
            });
          } catch (docErr) {
            log.debug(
              `OramaBackend tokenizer-version re-index skipped document ${hit.id} in ${collection}: ${docErr}`,
            );
          }
        }
      }
      await this.persistDbForCollection(db, collection);
    } catch (err) {
      log.debug(`OramaBackend tokenizer-version rebuild for ${collection} failed: ${err}`);
    }
    return db;
  }

  private async migrateLegacyVectorProviderSchema(db: any, collection: string): Promise<any> {
    const { search: oramaSearch, count, insert } = this.oramaModule;
    const existingCount = await count(db);
    if (existingCount === 0) {
      const migrated = await this.createDb();
      await this.persistDbForCollection(migrated, collection);
      return migrated;
    }

    const allHits = await oramaSearch(db, { term: "", limit: existingCount + 100 });
    const hits = allHits.hits ?? [];
    const needsMigration = hits.some((hit: any) =>
      typeof hit.document?.vectorProvider !== "string"
    );
    if (!needsMigration) return db;

    const migrated = await this.createDb();
    for (const hit of hits) {
      const doc = this.getStoredDocument(db, hit);
      const vector = this.getStoredVector(db, hit, doc);
      const payload: Record<string, unknown> = {
        id: typeof doc.id === "string" && doc.id.length > 0 ? doc.id : String(hit.id),
        path: typeof doc.path === "string" ? doc.path : "",
        content: typeof doc.content === "string" ? doc.content : "",
        snippet:
          typeof doc.snippet === "string"
            ? doc.snippet
            : typeof doc.content === "string"
              ? doc.content.slice(0, 200)
              : "",
        vectorProvider:
          typeof doc.vectorProvider === "string" ? doc.vectorProvider : "",
      };
      if (vector) {
        payload.vector = vector;
      } else {
        payload.vector = this.zeroVector();
      }
      await insert(migrated, payload);
    }
    await this.persistDbForCollection(migrated, collection);
    return migrated;
  }

  private async persistDbForCollection(db: any, collection: string): Promise<void> {
    const data = await this.persistModule.persist(db, "json");
    const filePath = this.dbFilePath(collection);
    await mkdir(path.dirname(filePath), { recursive: true });
    const tempPath = path.join(
      path.dirname(filePath),
      `.${path.basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`,
    );
    try {
      await writeFile(tempPath, data, "utf-8");
      await rename(tempPath, filePath);
    } catch (err) {
      await rm(tempPath, { force: true }).catch(() => undefined);
      throw err;
    }
  }

  private dbFilePath(collection: string): string {
    return resolveOramaCollectionDbFilePath(this.dbPath, collection);
  }

  private async listDbFiles(): Promise<string[]> {
    try {
      const entries = await readdir(this.dbPath);
      return entries
        .filter((e) => e.endsWith(".msp"))
        .map((e) => path.join(this.dbPath, e));
    } catch {
      return [];
    }
  }

  private async loadDbFromFile(filePath: string): Promise<any> {
    try {
      await this.ensureModules();
      const raw = await readFile(filePath, "utf-8");
      const collection = path.basename(filePath, ".msp");
      return await this.applyTokenizerVersion(
        await this.migrateLegacyVectorProviderSchema(
          await this.persistModule.restore("json", raw),
          collection,
        ),
        collection,
      );
    } catch (err) {
      log.debug(`OramaBackend failed to load ${filePath}: ${err}`);
      return null;
    }
  }

  private async searchDb(
    db: any,
    query: string,
    mode: "fulltext" | "vector" | "hybrid",
    limit: number,
    execution?: SearchExecutionOptions,
  ): Promise<SearchResult[]> {
    const { search: oramaSearch } = this.oramaModule;

    try {
      throwIfSearchAborted(execution, `OramaBackend ${mode} search aborted`);
      let searchParams: any;

      if (mode === "fulltext") {
        searchParams = { term: query, limit };
      } else if (mode === "vector") {
        const embedResult = await this.resolveCompatibleQueryEmbedding(db, query, execution);
        throwIfSearchAborted(execution, `OramaBackend ${mode} search aborted`);
        if (!embedResult) {
          // Fall back to fulltext if no embeddings available
          searchParams = { term: query, limit };
        } else {
          searchParams = { mode: "vector", vector: { value: embedResult.vector, property: "vector" }, limit };
        }
      } else {
        // hybrid
        const embedResult = await this.resolveCompatibleQueryEmbedding(db, query, execution);
        throwIfSearchAborted(execution, `OramaBackend ${mode} search aborted`);
        if (!embedResult) {
          searchParams = { term: query, limit };
        } else {
          searchParams = { mode: "hybrid", term: query, vector: { value: embedResult.vector, property: "vector" }, limit };
        }
      }

      throwIfSearchAborted(execution, `OramaBackend ${mode} search aborted`);
      const result = await oramaSearch(db, searchParams);
      throwIfSearchAborted(execution, `OramaBackend ${mode} search aborted`);
      return (result.hits ?? []).map((hit: any) => ({
        docid: hit.document?.id ?? "",
        path: hit.document?.path ?? "",
        snippet: hit.document?.snippet ?? hit.document?.content?.slice(0, 200) ?? "",
        score: hit.score ?? 0,
      }));
    } catch (err) {
      log.debug(`OramaBackend search (${mode}) failed: ${err}`);
      if (!isSearchAborted(execution)) {
        reportSearchDegradation(execution, {
          backend: "orama",
          code: "backend_error",
          detail: err instanceof Error ? err.name : typeof err,
        });
      }
      return [];
    }
  }

  private async resolveCompatibleQueryEmbedding(
    db: any,
    query: string,
    execution?: SearchExecutionOptions,
  ): Promise<EmbedWithProviderResult | null> {
    const embedResult = await this.embedHelper.embedWithProvider(query, { signal: execution?.signal });
    throwIfSearchAborted(execution, "OramaBackend query embedding aborted");
    if (!embedResult || !this.isExpectedDimensionVector(embedResult.vector)) return null;

    const storedProviderIdentity = await this.findCompatibleStoredVectorProvider(db, execution);
    if (!storedProviderIdentity) {
      this.rememberVectorProviderCompatibility(db, embedResult.providerIdentity, false);
      return null;
    }
    if (storedProviderIdentity === embedResult.providerIdentity) return embedResult;

    const fallbackEmbed = await this.embedQueryWithStoredFallbackProvider(query, storedProviderIdentity, execution);
    throwIfSearchAborted(execution, "OramaBackend fallback query embedding aborted");
    if (
      fallbackEmbed &&
      fallbackEmbed.providerIdentity === storedProviderIdentity &&
      this.isExpectedDimensionVector(fallbackEmbed.vector)
    ) {
      return fallbackEmbed;
    }

    this.rememberVectorProviderCompatibility(db, embedResult.providerIdentity, false);
    return null;
  }

  private async embedQueryWithStoredFallbackProvider(
    query: string,
    providerIdentity: EmbedProviderIdentity,
    execution?: SearchExecutionOptions,
  ): Promise<EmbedWithProviderResult | null> {
    const embedWithIdentity = (this.embedHelper as unknown as {
      embedWithFallbackProviderIdentity?: (
        text: string,
        identity: EmbedProviderIdentity,
        options?: { signal?: AbortSignal },
      ) => Promise<EmbedWithProviderResult | null>;
    }).embedWithFallbackProviderIdentity;
    if (typeof embedWithIdentity !== "function") return null;
    return embedWithIdentity.call(this.embedHelper, query, providerIdentity, { signal: execution?.signal });
  }

  private async findCompatibleStoredVectorProvider(
    db: any,
    execution?: SearchExecutionOptions,
  ): Promise<EmbedProviderIdentity | null> {
    const { search: oramaSearch, count } = this.oramaModule;
    try {
      const cached = this.vectorProviderCompatibility.get(db);
      if (cached?.compatible) return cached.providerIdentity;
      const existingCount = await count(db);
      if (existingCount === 0) return null;
      const allHits = await oramaSearch(db, {
        term: "",
        limit: existingCount + 100,
        properties: ["vectorProvider"],
      });
      let providerIdentity: EmbedProviderIdentity | null = null;
      let compatible = (allHits.hits ?? []).length > 0;
      for (const hit of allHits.hits ?? []) {
        throwIfSearchAborted(execution, "OramaBackend vector provider check aborted");
        const doc = this.getStoredDocument(db, hit);
        if (
          typeof doc.vectorProvider !== "string" ||
          doc.vectorProvider.length === 0 ||
          !this.isCompatibleStoredVector(this.getStoredVector(db, hit, doc))
        ) {
          compatible = false;
          break;
        }
        if (providerIdentity && doc.vectorProvider !== providerIdentity) {
          compatible = false;
          break;
        }
        providerIdentity = doc.vectorProvider as EmbedProviderIdentity;
      }
      if (compatible && providerIdentity) {
        this.vectorProviderCompatibility.set(db, {
          providerIdentity,
          compatible: true,
        });
        return providerIdentity;
      }
      return null;
    } catch (err) {
      if (isSearchAborted(execution)) throw err;
      log.debug(`OramaBackend stored vector provider check failed: ${err}`);
      return null;
    }
  }

  private async dbHasCompatibleVectors(
    db: any,
    providerIdentity: EmbedProviderIdentity,
    execution?: SearchExecutionOptions,
  ): Promise<boolean> {
    const { search: oramaSearch, count } = this.oramaModule;
    try {
      const cached = this.vectorProviderCompatibility.get(db);
      if (cached?.providerIdentity === providerIdentity) return cached.compatible;
      const existingCount = await count(db);
      if (existingCount === 0) return false;
      const allHits = await oramaSearch(db, {
        term: "",
        limit: existingCount + 100,
        properties: ["vectorProvider"],
      });
      let compatible = (allHits.hits ?? []).length > 0;
      for (const hit of allHits.hits ?? []) {
        throwIfSearchAborted(execution, "OramaBackend vector provider check aborted");
        const doc = this.getStoredDocument(db, hit);
        if (
          doc.vectorProvider !== providerIdentity ||
          !this.isCompatibleStoredVector(this.getStoredVector(db, hit, doc))
        ) {
          compatible = false;
          break;
        }
      }
      this.vectorProviderCompatibility.set(db, { providerIdentity, compatible });
      return compatible;
    } catch (err) {
      if (isSearchAborted(execution)) throw err;
      log.debug(`OramaBackend vector provider check failed: ${err}`);
      return false;
    }
  }

  private rememberVectorProviderCompatibility(
    db: unknown,
    providerIdentity: EmbedProviderIdentity | null,
    compatible: boolean,
  ): void {
    if (!db || typeof db !== "object") return;
    if (!providerIdentity) {
      this.vectorProviderCompatibility.delete(db);
      return;
    }
    this.vectorProviderCompatibility.set(db, { providerIdentity, compatible });
  }

  private getStoredDocument(db: any, hit: any): Record<string, unknown> {
    const internalId = this.getInternalDocumentId(db, hit);
    const internalDoc =
      internalId !== undefined && internalId !== null
        ? db?.data?.docs?.docs?.[String(internalId)]
        : undefined;
    if (internalDoc && typeof internalDoc === "object") {
      return internalDoc as Record<string, unknown>;
    }
    return hit?.document && typeof hit.document === "object"
      ? hit.document as Record<string, unknown>
      : {};
  }

  private getStoredVector(db: any, hit: any, doc: Record<string, unknown>): number[] | null {
    const documentVector = this.normalizeStoredVector(doc.vector);
    if (documentVector) return documentVector;
    const internalId = this.getInternalDocumentId(db, hit);
    if (internalId === undefined || internalId === null) return null;
    const vectorEntry = db?.data?.index?.vectorIndexes?.vector?.node?.vectors?.get?.(internalId);
    const vector = Array.isArray(vectorEntry) ? vectorEntry[1] : vectorEntry;
    return this.normalizeStoredVector(vector);
  }

  private getInternalDocumentId(db: any, hit: any): unknown {
    const publicId =
      typeof hit?.id === "string"
        ? hit.id
        : typeof hit?.document?.id === "string"
          ? hit.document.id
          : undefined;
    return publicId && typeof db?.internalDocumentIDStore?.idToInternalId?.get === "function"
      ? db.internalDocumentIDStore.idToInternalId.get(publicId)
      : undefined;
  }

  private isExpectedDimensionVector(vector: number[] | null | undefined): vector is number[] {
    return Array.isArray(vector) && vector.length === this.embeddingDimension;
  }

  private isCompatibleStoredVector(vector: unknown): vector is number[] {
    if (!vector || typeof vector !== "object") return false;
    const arr = Array.from(vector as ArrayLike<number>);
    return (
      arr.length === this.embeddingDimension &&
      arr.every((value) => Number.isFinite(value)) &&
      arr.some((value) => value !== 0)
    );
  }

  private zeroVector(): number[] {
    return Array.from({ length: this.embeddingDimension }, () => 0);
  }

  private normalizeStoredVector(vector: unknown): number[] | null {
    const values =
      Array.isArray(vector)
        ? vector
        : ArrayBuffer.isView(vector) && !(vector instanceof DataView)
          ? Array.from(vector as unknown as ArrayLike<unknown>)
          : null;
    if (!values || values.length !== this.embeddingDimension) return null;
    const normalized = values.map((value) => Number(value));
    return normalized.every((value) => Number.isFinite(value)) ? normalized : null;
  }
}
