"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const datasource_1 = require("../definitions/datasource");
const mongodb_1 = require("mongodb");
var MongoDBModule;
(function (MongoDBModule) {
    const SCHEMA = {
        docs: "https://github.com/mongodb/node-mongodb-native",
        friendlyName: "MongoDB",
        type: "Non-relational",
        description: "MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.",
        datasource: {
            connectionString: {
                type: datasource_1.DatasourceFieldTypes.STRING,
                required: true,
                default: "mongodb://localhost:27017",
            },
            db: {
                type: datasource_1.DatasourceFieldTypes.STRING,
                required: true,
            },
        },
        query: {
            create: {
                type: datasource_1.QueryTypes.JSON,
            },
            read: {
                type: datasource_1.QueryTypes.JSON,
            },
            update: {
                type: datasource_1.QueryTypes.JSON,
            },
            delete: {
                type: datasource_1.QueryTypes.JSON,
            },
        },
        extra: {
            collection: {
                displayName: "Collection",
                type: datasource_1.DatasourceFieldTypes.STRING,
                required: true,
            },
            actionTypes: {
                displayName: "Action Types",
                type: datasource_1.DatasourceFieldTypes.LIST,
                required: true,
                data: {
                    read: ["find", "findOne", "findOneAndUpdate", "count", "distinct"],
                    create: ["insertOne", "insertMany"],
                    update: ["updateOne", "updateMany"],
                    delete: ["deleteOne", "deleteMany"],
                },
            },
        },
    };
    class MongoIntegration {
        constructor(config) {
            this.config = config;
            this.client = new mongodb_1.MongoClient(config.connectionString);
        }
        connect() {
            return __awaiter(this, void 0, void 0, function* () {
                return this.client.connect();
            });
        }
        createObjectIds(json) {
            const self = this;
            function interpolateObjectIds(json) {
                var _a;
                for (let field of Object.keys(json)) {
                    if (json[field] instanceof Object) {
                        json[field] = self.createObjectIds(json[field]);
                    }
                    if (field === "_id" && typeof json[field] === "string") {
                        const id = (_a = json["_id"].match(/(?<=objectid\(['"]).*(?=['"]\))/gi)) === null || _a === void 0 ? void 0 : _a[0];
                        if (id) {
                            json["_id"] = mongodb_1.ObjectID.createFromHexString(id);
                        }
                    }
                }
                return json;
            }
            if (Array.isArray(json)) {
                for (let i = 0; i < json.length; i++) {
                    json[i] = interpolateObjectIds(json[i]);
                }
                return json;
            }
            return interpolateObjectIds(json);
        }
        parseQueryParams(params, mode) {
            let queryParams = params.split(/(?<=}),[\n\s]*(?={)/g);
            let group1 = queryParams[0] ? JSON.parse(queryParams[0]) : {};
            let group2 = queryParams[1] ? JSON.parse(queryParams[1]) : {};
            let group3 = queryParams[2] ? JSON.parse(queryParams[2]) : {};
            if (mode === "update") {
                return {
                    filter: group1,
                    update: group2,
                    options: group3,
                };
            }
            return {
                filter: group1,
                options: group2,
            };
        }
        create(query) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    yield this.connect();
                    const db = this.client.db(this.config.db);
                    const collection = db.collection(query.extra.collection);
                    let json = this.createObjectIds(query.json);
                    // For mongodb we add an extra actionType to specify
                    // which method we want to call on the collection
                    switch (query.extra.actionTypes) {
                        case "insertOne": {
                            return yield collection.insertOne(json);
                        }
                        case "insertMany": {
                            return yield collection.insertMany(json);
                        }
                        default: {
                            throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for create`);
                        }
                    }
                }
                catch (err) {
                    console.error("Error writing to mongodb", err);
                    throw err;
                }
                finally {
                    yield this.client.close();
                }
            });
        }
        read(query) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    yield this.connect();
                    const db = this.client.db(this.config.db);
                    const collection = db.collection(query.extra.collection);
                    let json = this.createObjectIds(query.json);
                    switch (query.extra.actionTypes) {
                        case "find": {
                            return yield collection.find(json).toArray();
                        }
                        case "findOne": {
                            return yield collection.findOne(json);
                        }
                        case "findOneAndUpdate": {
                            let findAndUpdateJson = json;
                            return yield collection.findOneAndUpdate(findAndUpdateJson.filter, findAndUpdateJson.update, findAndUpdateJson.options);
                        }
                        case "count": {
                            return yield collection.countDocuments(json);
                        }
                        case "distinct": {
                            return yield collection.distinct(json);
                        }
                        default: {
                            throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for read`);
                        }
                    }
                }
                catch (err) {
                    console.error("Error querying mongodb", err);
                    throw err;
                }
                finally {
                    yield this.client.close();
                }
            });
        }
        update(query) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    yield this.connect();
                    const db = this.client.db(this.config.db);
                    const collection = db.collection(query.extra.collection);
                    let queryJson = query.json;
                    if (typeof queryJson === "string") {
                        queryJson = this.parseQueryParams(queryJson, "update");
                    }
                    let json = this.createObjectIds(queryJson);
                    switch (query.extra.actionTypes) {
                        case "updateOne": {
                            return yield collection.updateOne(json.filter, json.update, json.options);
                        }
                        case "updateMany": {
                            return yield collection.updateMany(json.filter, json.update, json.options);
                        }
                        default: {
                            throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for update`);
                        }
                    }
                }
                catch (err) {
                    console.error("Error writing to mongodb", err);
                    throw err;
                }
                finally {
                    yield this.client.close();
                }
            });
        }
        delete(query) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    yield this.connect();
                    const db = this.client.db(this.config.db);
                    const collection = db.collection(query.extra.collection);
                    let queryJson = query.json;
                    if (typeof queryJson === "string") {
                        queryJson = this.parseQueryParams(queryJson, "delete");
                    }
                    let json = this.createObjectIds(queryJson);
                    if (!json.options) {
                        json = {
                            filter: json,
                            options: {},
                        };
                    }
                    switch (query.extra.actionTypes) {
                        case "deleteOne": {
                            return yield collection.deleteOne(json.filter, json.options);
                        }
                        case "deleteMany": {
                            return yield collection.deleteMany(json.filter, json.options);
                        }
                        default: {
                            throw new Error(`actionType ${query.extra.actionTypes} does not exist on DB for delete`);
                        }
                    }
                }
                catch (err) {
                    console.error("Error writing to mongodb", err);
                    throw err;
                }
                finally {
                    yield this.client.close();
                }
            });
        }
    }
    module.exports = {
        schema: SCHEMA,
        integration: MongoIntegration,
    };
})(MongoDBModule || (MongoDBModule = {}));
