"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());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const datasource_1 = require("../definitions/datasource");
const utils_1 = require("./utils");
const dayjs_1 = __importDefault(require("dayjs"));
const { NUMBER_REGEX } = require("../utilities");
var MySQLModule;
(function (MySQLModule) {
    const mysql = require("mysql2/promise");
    const Sql = require("./base/sql");
    const SCHEMA = {
        docs: "https://github.com/sidorares/node-mysql2",
        plus: true,
        friendlyName: "MySQL",
        type: "Relational",
        description: "MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
        datasource: {
            host: {
                type: datasource_1.DatasourceFieldTypes.STRING,
                default: "localhost",
                required: true,
            },
            port: {
                type: datasource_1.DatasourceFieldTypes.NUMBER,
                default: 3306,
                required: false,
            },
            user: {
                type: datasource_1.DatasourceFieldTypes.STRING,
                default: "root",
                required: true,
            },
            password: {
                type: datasource_1.DatasourceFieldTypes.PASSWORD,
                default: "root",
                required: true,
            },
            database: {
                type: datasource_1.DatasourceFieldTypes.STRING,
                required: true,
            },
            ssl: {
                type: datasource_1.DatasourceFieldTypes.OBJECT,
                required: false,
            },
            rejectUnauthorized: {
                type: datasource_1.DatasourceFieldTypes.BOOLEAN,
                default: true,
                required: false,
            },
        },
        query: {
            create: {
                type: datasource_1.QueryTypes.SQL,
            },
            read: {
                type: datasource_1.QueryTypes.SQL,
            },
            update: {
                type: datasource_1.QueryTypes.SQL,
            },
            delete: {
                type: datasource_1.QueryTypes.SQL,
            },
        },
    };
    const TimezoneAwareDateTypes = ["timestamp"];
    function bindingTypeCoerce(bindings) {
        for (let i = 0; i < bindings.length; i++) {
            const binding = bindings[i];
            if (typeof binding !== "string") {
                continue;
            }
            const matches = binding.match(NUMBER_REGEX);
            // check if number first
            if (matches && matches[0] !== "" && !isNaN(Number(matches[0]))) {
                bindings[i] = parseFloat(binding);
            }
            // if not a number, see if it is a date - important to do in this order as any
            // integer will be considered a valid date
            else if (/^\d/.test(binding) && (0, dayjs_1.default)(binding).isValid()) {
                bindings[i] = (0, dayjs_1.default)(binding).toDate();
            }
        }
        return bindings;
    }
    class MySQLIntegration extends Sql {
        constructor(config) {
            super(utils_1.SqlClients.MY_SQL);
            this.tables = {};
            this.schemaErrors = {};
            this.config = config;
            if (config.ssl && Object.keys(config.ssl).length === 0) {
                delete config.ssl;
            }
            // make sure this defaults to true
            if (config.rejectUnauthorized != null &&
                !config.rejectUnauthorized &&
                config.ssl) {
                config.ssl.rejectUnauthorized = config.rejectUnauthorized;
            }
            // @ts-ignore
            delete config.rejectUnauthorized;
            this.config = Object.assign(Object.assign({}, config), { typeCast: function (field, next) {
                    if (field.type == "DATETIME" ||
                        field.type === "DATE" ||
                        field.type === "TIMESTAMP") {
                        return field.string();
                    }
                    return next();
                } });
        }
        getBindingIdentifier() {
            return "?";
        }
        getStringConcat(parts) {
            return `concat(${parts.join(", ")})`;
        }
        connect() {
            return __awaiter(this, void 0, void 0, function* () {
                this.client = yield mysql.createConnection(this.config);
            });
        }
        disconnect() {
            return __awaiter(this, void 0, void 0, function* () {
                yield this.client.end();
            });
        }
        internalQuery(query, opts = {
            connect: true,
            disableCoercion: false,
        }) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    if (opts === null || opts === void 0 ? void 0 : opts.connect) {
                        yield this.connect();
                    }
                    const baseBindings = query.bindings || [];
                    const bindings = (opts === null || opts === void 0 ? void 0 : opts.disableCoercion)
                        ? baseBindings
                        : bindingTypeCoerce(baseBindings);
                    // Node MySQL is callback based, so we must wrap our call in a promise
                    const response = yield this.client.query(query.sql, bindings);
                    return response[0];
                }
                finally {
                    if (opts === null || opts === void 0 ? void 0 : opts.connect) {
                        yield this.disconnect();
                    }
                }
            });
        }
        buildSchema(datasourceId, entities) {
            return __awaiter(this, void 0, void 0, function* () {
                const tables = {};
                const database = this.config.database;
                yield this.connect();
                try {
                    // get the tables first
                    const tablesResp = yield this.internalQuery({ sql: "SHOW TABLES;" }, { connect: false });
                    const tableNames = tablesResp.map((obj) => obj[`Tables_in_${database}`] ||
                        obj[`Tables_in_${database.toLowerCase()}`]);
                    for (let tableName of tableNames) {
                        const primaryKeys = [];
                        const schema = {};
                        const descResp = yield this.internalQuery({ sql: `DESCRIBE \`${tableName}\`;` }, { connect: false });
                        for (let column of descResp) {
                            const columnName = column.Field;
                            if (column.Key === "PRI" &&
                                primaryKeys.indexOf(column.Key) === -1) {
                                primaryKeys.push(columnName);
                            }
                            const constraints = {
                                presence: column.Null !== "YES",
                            };
                            const isAuto = typeof column.Extra === "string" &&
                                (column.Extra === "auto_increment" ||
                                    column.Extra.toLowerCase().includes("generated"));
                            schema[columnName] = Object.assign(Object.assign({ name: columnName, autocolumn: isAuto, constraints }, (0, utils_1.convertSqlType)(column.Type)), { externalType: column.Type });
                        }
                        if (!tables[tableName]) {
                            tables[tableName] = {
                                _id: (0, utils_1.buildExternalTableId)(datasourceId, tableName),
                                primary: primaryKeys,
                                name: tableName,
                                schema,
                            };
                        }
                    }
                }
                finally {
                    yield this.disconnect();
                }
                const final = (0, utils_1.finaliseExternalTables)(tables, entities);
                this.tables = final.tables;
                this.schemaErrors = final.errors;
            });
        }
        create(query) {
            return __awaiter(this, void 0, void 0, function* () {
                const results = yield this.internalQuery((0, utils_1.getSqlQuery)(query));
                return results.length ? results : [{ created: true }];
            });
        }
        read(query) {
            return __awaiter(this, void 0, void 0, function* () {
                return this.internalQuery((0, utils_1.getSqlQuery)(query));
            });
        }
        update(query) {
            return __awaiter(this, void 0, void 0, function* () {
                const results = yield this.internalQuery((0, utils_1.getSqlQuery)(query));
                return results.length ? results : [{ updated: true }];
            });
        }
        delete(query) {
            return __awaiter(this, void 0, void 0, function* () {
                const results = yield this.internalQuery((0, utils_1.getSqlQuery)(query));
                return results.length ? results : [{ deleted: true }];
            });
        }
        query(json) {
            return __awaiter(this, void 0, void 0, function* () {
                yield this.connect();
                try {
                    const queryFn = (query) => this.internalQuery(query, { connect: false, disableCoercion: true });
                    return yield this.queryWithReturning(json, queryFn);
                }
                finally {
                    yield this.disconnect();
                }
            });
        }
    }
    module.exports = {
        schema: SCHEMA,
        integration: MySQLIntegration,
    };
})(MySQLModule || (MySQLModule = {}));
