"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.USearch = void 0;
const usearch_1 = require("usearch");
const uuid = __importStar(require("uuid"));
const vectorstores_1 = require("@langchain/core/vectorstores");
const documents_1 = require("@langchain/core/documents");
const in_memory_js_1 = require("../stores/doc/in_memory.cjs");
/**
 * Class that extends `SaveableVectorStore` and provides methods for
 * adding documents and vectors to a `usearch` index, performing
 * similarity searches, and saving the index.
 */
class USearch extends vectorstores_1.SaveableVectorStore {
    _vectorstoreType() {
        return "usearch";
    }
    constructor(embeddings, args) {
        super(embeddings, args);
        Object.defineProperty(this, "_index", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "_mapping", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "docstore", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "args", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.args = args;
        this._index = args.index;
        this._mapping = args.mapping ?? {};
        this.embeddings = embeddings;
        this.docstore = args?.docstore ?? new in_memory_js_1.SynchronousInMemoryDocstore();
    }
    /**
     * Method that adds documents to the `usearch` index. It generates
     * embeddings for the documents and adds them to the index.
     * @param documents An array of `Document` instances to be added to the index.
     * @returns A promise that resolves with an array of document IDs.
     */
    async addDocuments(documents) {
        const texts = documents.map(({ pageContent }) => pageContent);
        return this.addVectors(await this.embeddings.embedDocuments(texts), documents);
    }
    get index() {
        if (!this._index) {
            throw new Error("Vector store not initialised yet. Try calling `fromTexts` or `fromDocuments` first.");
        }
        return this._index;
    }
    set index(index) {
        this._index = index;
    }
    /**
     * Method that adds vectors to the `usearch` index. It also updates the
     * mapping between vector IDs and document IDs.
     * @param vectors An array of vectors to be added to the index.
     * @param documents An array of `Document` instances corresponding to the vectors.
     * @returns A promise that resolves with an array of document IDs.
     */
    async addVectors(vectors, documents) {
        if (vectors.length === 0) {
            return [];
        }
        if (vectors.length !== documents.length) {
            throw new Error(`Vectors and documents must have the same length`);
        }
        const dv = vectors[0].length;
        if (!this._index) {
            this._index = new usearch_1.Index(dv, usearch_1.MetricKind.L2sq, usearch_1.ScalarKind.F32, 16);
        }
        const d = this.index.dimensions();
        if (dv !== d) {
            throw new Error(`Vectors must have the same length as the number of dimensions (${d})`);
        }
        const docstoreSize = this.index.size();
        const documentIds = [];
        for (let i = 0; i < vectors.length; i += 1) {
            const documentId = uuid.v4();
            documentIds.push(documentId);
            const id = Number(docstoreSize) + i;
            this.index.add(BigInt(id), new Float32Array(vectors[i]));
            this._mapping[id] = documentId;
            this.docstore.add({ [documentId]: documents[i] });
        }
        return documentIds;
    }
    /**
     * Method that performs a similarity search in the `usearch` index. It
     * returns the `k` most similar documents to a given query vector, along
     * with their similarity scores.
     * @param query The query vector.
     * @param k The number of most similar documents to return.
     * @returns A promise that resolves with an array of tuples, each containing a `Document` and its similarity score.
     */
    async similaritySearchVectorWithScore(query, k) {
        const d = this.index.dimensions();
        if (query.length !== d) {
            throw new Error(`Query vector must have the same length as the number of dimensions (${d})`);
        }
        if (k > this.index.size()) {
            const total = this.index.size();
            console.warn(`k (${k}) is greater than the number of elements in the index (${total}), setting k to ${total}`);
            // eslint-disable-next-line no-param-reassign
            k = Number(total);
        }
        const result = this.index.search(new Float32Array(query), k);
        const return_list = [];
        for (let i = 0; i < result.keys.length && i < result.distances.length; i += 1) {
            const uuid = this._mapping[Number(result.keys[i])];
            return_list.push([this.docstore.search(uuid), result.distances[i]]);
        }
        return return_list;
    }
    /**
     * Method that saves the `usearch` index and the document store to disk.
     * @param directory The directory where the index and document store should be saved.
     * @returns A promise that resolves when the save operation is complete.
     */
    async save(directory) {
        const fs = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
        const path = await Promise.resolve().then(() => __importStar(require("node:path")));
        await fs.mkdir(directory, { recursive: true });
        await Promise.all([
            this.index.save(path.join(directory, "usearch.index")),
            await fs.writeFile(path.join(directory, "docstore.json"), JSON.stringify([
                Array.from(this.docstore._docs.entries()),
                this._mapping,
            ])),
        ]);
    }
    /**
     * Static method that creates a new `USearch` instance from a list of
     * texts. It generates embeddings for the texts and adds them to the
     * `usearch` index.
     * @param texts An array of texts to be added to the index.
     * @param metadatas Metadata associated with the texts.
     * @param embeddings An instance of `Embeddings` used to generate embeddings for the texts.
     * @param dbConfig Optional configuration for the document store.
     * @returns A promise that resolves with a new `USearch` instance.
     */
    static async fromTexts(texts, metadatas, embeddings, dbConfig) {
        const docs = [];
        for (let i = 0; i < texts.length; i += 1) {
            const metadata = Array.isArray(metadatas) ? metadatas[i] : metadatas;
            const newDoc = new documents_1.Document({
                pageContent: texts[i],
                metadata,
            });
            docs.push(newDoc);
        }
        return this.fromDocuments(docs, embeddings, dbConfig);
    }
    /**
     * Static method that creates a new `USearch` instance from a list of
     * documents. It generates embeddings for the documents and adds them to
     * the `usearch` index.
     * @param docs An array of `Document` instances to be added to the index.
     * @param embeddings An instance of `Embeddings` used to generate embeddings for the documents.
     * @param dbConfig Optional configuration for the document store.
     * @returns A promise that resolves with a new `USearch` instance.
     */
    static async fromDocuments(docs, embeddings, dbConfig) {
        const args = {
            docstore: dbConfig?.docstore,
        };
        const instance = new this(embeddings, args);
        await instance.addDocuments(docs);
        return instance;
    }
}
exports.USearch = USearch;
