"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;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TinyVecClient = void 0;
const path_1 = __importDefault(require("path"));
const tinyvecUtils = __importStar(require("./utils"));
const fs = __importStar(require("fs"));
const fsPromises = __importStar(require("fs/promises"));
const gyp = require("node-gyp-build");
const nativeModule = gyp(path_1.default.resolve(__dirname, ".."));
const nativeSearch = nativeModule.search;
const nativeInsert = nativeModule.insertVectors;
const nativeConnect = nativeModule.connect;
const nativeGetIndexStats = nativeModule.getIndexStats;
const nativeUpdateDbFileConnection = nativeModule.updateDbFileConnection;
const nativeDeleteVectorsByIds = nativeModule.deleteByIds;
const nativeDeleteVectorsByFilter = nativeModule.deleteByFilter;
class TinyVecClient {
    constructor(filePath, config) {
        this.dimensions = 0;
        this.filePath = filePath;
        if (config?.dimensions) {
            this.dimensions = config.dimensions;
        }
    }
    static connect(filePath, config) {
        const absolutePath = tinyvecUtils.ensureAbsolutePath(filePath);
        const fExists = tinyvecUtils.fileExists(absolutePath);
        if (config?.dimensions !== undefined) {
            if (typeof config.dimensions !== "number" || config.dimensions <= 0) {
                throw new Error("Dimensions must be a positive number.");
            }
        }
        if (!fExists) {
            const vectorCount = 0;
            const dimensions = config?.dimensions ?? 0;
            const buffer = Buffer.alloc(8);
            buffer.writeInt32LE(vectorCount, 0);
            buffer.writeInt32LE(dimensions, 4);
            // Open with 'wx' flag - creates a new file and fails if it exists
            const fd = fs.openSync(absolutePath, "wx");
            fs.writeSync(fd, buffer);
            // Force flush to disk
            fs.fsyncSync(fd);
            fs.closeSync(fd);
        }
        nativeConnect(absolutePath, config);
        return new TinyVecClient(absolutePath, config);
    }
    async search(query, topK, options) {
        if (typeof topK !== "number" || topK <= 0) {
            throw new Error("Top K must be a positive number.");
        }
        const float32Array = tinyvecUtils.convertToFloat32Array(query);
        if (options) {
            const filterStr = JSON.stringify(options?.filter ?? "{}");
            const optsFmt = { ...options, filter: filterStr };
            return await nativeSearch(float32Array, topK, this.filePath, optsFmt);
        }
        return await nativeSearch(float32Array, topK, this.filePath);
    }
    async insert(data) {
        if (!data || !data.length)
            return 0;
        const tempFilePath = `${this.filePath}.temp`;
        try {
            const indexStats = await nativeGetIndexStats(this.filePath);
            if (indexStats.dimensions === 0) {
                const buffer = Buffer.alloc(4);
                const dimensions = this.dimensions || data[0].vector.length;
                buffer.writeInt32LE(dimensions, 0);
                const fd = fs.openSync(this.filePath, "r+");
                fs.writeSync(fd, buffer, 0, buffer.length, 4);
                // Force flush to disk
                fs.fsyncSync(fd);
                fs.closeSync(fd);
            }
            // Convert data with index and metadata context
            const convertedData = data.map((item, index) => {
                try {
                    return {
                        ...item,
                        vector: tinyvecUtils.convertToFloat32Array(item.vector, index, item.metadata),
                    };
                }
                catch (error) {
                    if (error instanceof tinyvecUtils.VectorConversionError) {
                        throw error;
                    }
                    throw new tinyvecUtils.VectorConversionError(`Failed to convert vector at index ${index}`, index, item.metadata);
                }
            });
            // Copy original files to temp
            await fsPromises.copyFile(this.filePath, tempFilePath);
            // Insert data
            const inserted = await nativeInsert(this.filePath, convertedData, this.dimensions);
            if (inserted <= 0) {
                return 0;
            }
            // Rename of temp file to original
            await fsPromises.rename(tempFilePath, this.filePath);
            // Update DB file connection
            nativeUpdateDbFileConnection(this.filePath);
            return inserted;
        }
        catch (error) {
            throw error;
        }
        finally {
            // Clean up temp files regardless of success or failure
            await fsPromises.unlink(tempFilePath).catch(() => { });
        }
    }
    async deleteByIds(ids) {
        if (!ids || !ids.length) {
            return { deletedCount: 0, success: false };
        }
        let tempFilePath = `${this.filePath}.temp`;
        try {
            // first copy the vector file to a temp file
            const indexStats = await nativeGetIndexStats(this.filePath);
            if (!indexStats || !indexStats.vectors) {
                return { deletedCount: 0, success: false };
            }
            // Write the header to the temp file
            await tinyvecUtils.writeFileHeader(tempFilePath, indexStats.vectors, indexStats.dimensions);
            const deletionResult = await nativeDeleteVectorsByIds(this.filePath, ids);
            await fsPromises.rename(tempFilePath, this.filePath);
            // Update DB file connection
            nativeUpdateDbFileConnection(this.filePath);
            return deletionResult;
        }
        catch (error) {
            throw error;
        }
        finally {
            // Clean up temp files regardless of success or failure
            await fsPromises.unlink(tempFilePath).catch(() => { });
        }
    }
    async deleteByFilter(options) {
        if (!options) {
            throw new Error("No options provided");
        }
        if (!options.filter) {
            throw new Error("No filter provided");
        }
        let tempFilePath = `${this.filePath}.temp`;
        try {
            const indexStats = await nativeGetIndexStats(this.filePath);
            if (!indexStats || !indexStats.vectors) {
                return { deletedCount: 0, success: false };
            }
            // Write the header to the temp file
            await tinyvecUtils.writeFileHeader(tempFilePath, indexStats.vectors, indexStats.dimensions);
            const filterStr = JSON.stringify(options.filter);
            const deletionResult = await nativeDeleteVectorsByFilter(this.filePath, filterStr);
            if (!deletionResult.deletedCount) {
                nativeUpdateDbFileConnection(this.filePath);
                return {
                    deletedCount: 0,
                    success: false,
                };
            }
            await fsPromises.rename(tempFilePath, this.filePath);
            // Update DB file connection
            nativeUpdateDbFileConnection(this.filePath);
            return deletionResult;
        }
        catch (error) {
            throw error;
        }
        finally {
            // Clean up temp files regardless of success or failure
            await fsPromises.unlink(tempFilePath).catch(() => { });
        }
    }
    async getIndexStats() {
        return await nativeGetIndexStats(this.filePath);
    }
}
exports.TinyVecClient = TinyVecClient;
//# sourceMappingURL=index.js.map