"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AutoGenerator = void 0;
const lodash_1 = __importDefault(require("lodash"));
const types_1 = require("./types");
/** Generates text from each table in TableData */
class AutoGenerator {
    constructor(tableData, dialect, options) {
        this.tables = tableData.tables;
        this.foreignKeys = tableData.foreignKeys;
        this.hasTriggerTables = tableData.hasTriggerTables;
        this.indexes = tableData.indexes;
        this.relations = tableData.relations;
        this.dialect = dialect;
        this.options = options;
        this.options.lang = this.options.lang || 'es5';
        this.space = (0, types_1.makeIndent)(this.options.spaces, this.options.indentation);
        this.attrbuitesstr = '';
    }
    makeHeaderTemplate() {
        const header = "import { Column, DataType, Table, Model,Sequelize } from 'sequelize-typescript';\n\n";
        return header;
    }
    // 生成文本
    generateText() {
        const tableNames = lodash_1.default.keys(this.tables);
        // 创建通用文件头部内容
        const header = this.makeHeaderTemplate();
        const text = {};
        tableNames.forEach(table => {
            let str = header;
            const [schemaName, tableNameOrig] = (0, types_1.qNameSplit)(table);
            const tableName = (0, types_1.makeTableName)(this.options.caseModel, tableNameOrig, this.options.singularize, this.options.lang);
            // 为表的模型创建一个字符串
            str += this.addTable(table).trim();
            const re = new RegExp('#TABLE#', 'g');
            str = str.replace(re, tableName);
            text[table] = str;
        });
        return text;
    }
    addTable(table) {
        const [schemaName, tableNameOrig] = (0, types_1.qNameSplit)(table);
        const space = this.space;
        let timestamps = (this.options.additional && this.options.additional.timestamps === true) || false;
        let paranoid = (this.options.additional && this.options.additional.paranoid === true) || false;
        // 为表全部的字段生成字符串
        let str = '@Table({\n';
        str += space[2] + "tableName: '" + tableNameOrig + "',\n";
        if (schemaName && this.dialect.hasSchema) {
            str += space[2] + "schema: '" + schemaName + "',\n";
        }
        if (this.hasTriggerTables[table]) {
            str += space[2] + "hasTrigger: true,\n";
        }
        str += space[2] + "timestamps: " + timestamps + ",\n";
        if (paranoid) {
            str += space[2] + "paranoid: true,\n";
        }
        // 有条件地添加额外选项
        const hasadditional = lodash_1.default.isObject(this.options.additional) && lodash_1.default.keys(this.options.additional).length > 0;
        if (hasadditional) {
            lodash_1.default.each(this.options.additional, (value, key) => {
                if (key === 'name') {
                    // name: true - preserve table name always
                    str += space[2] + "name: {\n";
                    str += space[3] + "singular: '" + table + "',\n";
                    str += space[3] + "plural: '" + table + "'\n";
                    str += space[2] + "},\n";
                }
                else if (key === "timestamps" || key === "paranoid") {
                    // handled above
                }
                else {
                    value = lodash_1.default.isBoolean(value) ? value : ("'" + value + "'");
                    str += space[2] + key + ": " + value + ",\n";
                }
            });
        }
        if (!this.options.noIndexes) {
            // 添加索引
            str += this.addIndexes(table);
        }
        str = space[2] + str.trim();
        str = str.substring(0, str.length - 1);
        str += "\n})\n";
        const characters = [...lodash_1.default.camelCase(tableNameOrig)];
        characters[0] = characters[0].toUpperCase();
        const entityName = characters.join("");
        str += `export class ${entityName}Entity extends Model {\n`;
        const fields = lodash_1.default.keys(this.tables[table]);
        fields.forEach((field, index) => {
            // 判断是否为 created_at 和 updated_at 字段
            timestamps || (timestamps = this.isTimestampField(field));
            // 判断是否为 deleted_at 字段
            paranoid || (paranoid = this.isParanoidField(field));
            // 创建一个包含字段属性(类型、defaultValue等)的字符串
            str += this.addField(table, field);
        });
        // 移除最后的 ",\n"
        str = str.substring(0, str.length - 2) + "\n}";
        str += this.addAttributesHeader(entityName,this.attrbuitesstr);
        this.attrbuitesstr = '';
        return str;
    }
    addField(table, field) {
        // 忽略 Sequelize 标准字段
        const additional = this.options.additional;
        if (additional && (additional.timestamps !== false) && (this.isTimestampField(field) || this.isParanoidField(field))) {
            return '';
        }
        // 判断是否为配置的忽略字段
        if (this.isIgnoredField(field)) {
            return '';
        }
        // 发现外键
        const foreignKey = this.foreignKeys[table] && this.foreignKeys[table][field] ? this.foreignKeys[table][field] : null;
        const fieldObj = this.tables[table][field];
        if (lodash_1.default.isObject(foreignKey)) {
            fieldObj.foreignKey = foreignKey;
        }
        const fieldName = (0, types_1.recase)(this.options.caseProp, field);
        let str = "@Column({\n";
        const quoteWrapper = '"';
        const unique = fieldObj.unique || fieldObj.foreignKey && fieldObj.foreignKey.isUnique;
        const isSerialKey = (fieldObj.foreignKey && fieldObj.foreignKey.isSerialKey) ||
            this.dialect.isSerialKey && this.dialect.isSerialKey(fieldObj);
        let wroteAutoIncrement = false;
        let wroteTimestamp = false;
        const space = this.space;
        // 列的属性
        let typeStr;
        const fieldAttrs = lodash_1.default.keys(fieldObj);
        fieldAttrs.forEach(attr => {
            // 我们不需要来自postgresql的特殊属性; "unique"被单独处理
            if (attr === "special" || attr === "elementType" || attr === "unique") {
                return true;
            }
            if (isSerialKey && !wroteAutoIncrement) {
                str += space[3] + "autoIncrement: true,\n";
                // 默认使用Postgres的GENERATED作为IDENTITY，而不是SERIAL
                if (this.dialect.name === "postgres" && fieldObj.foreignKey && fieldObj.foreignKey.isPrimaryKey === true &&
                    (fieldObj.foreignKey.generation === "ALWAYS" || fieldObj.foreignKey.generation === "BY DEFAULT")) {
                    str += space[3] + "autoIncrementIdentity: true,\n";
                }
                wroteAutoIncrement = true;
            }
            if (attr === "foreignKey") { // 外键属性
                if (foreignKey && foreignKey.isForeignKey) {
                    str += space[3] + "references: {\n";
                    str += space[4] + "model: \'" + fieldObj[attr].foreignSources.target_table + "\',\n";
                    str += space[4] + "key: \'" + fieldObj[attr].foreignSources.target_column + "\'\n";
                    str += space[3] + "}";
                }
                else {
                    return true;
                }
            }
            else if (attr === "references") {
                // covered by foreignKey
                return true;
            }
            else if (attr === "primaryKey") { // 主键属性
                if (fieldObj[attr] === true && (!lodash_1.default.has(fieldObj, 'foreignKey') || !!fieldObj.foreignKey.isPrimaryKey)) {
                    str += space[3] + "primaryKey: true";
                }
                else {
                    return true;
                }
            }
            else if (attr === "autoIncrement") { // 自动增长属性
                if (fieldObj[attr] === true && !wroteAutoIncrement) {
                    str += space[3] + "autoIncrement: true,\n";
                    // Resort to Postgres' GENERATED BY DEFAULT AS IDENTITY instead of SERIAL
                    if (this.dialect.name === "postgres" && fieldObj.foreignKey && fieldObj.foreignKey.isPrimaryKey === true && (fieldObj.foreignKey.generation === "ALWAYS" || fieldObj.foreignKey.generation === "BY DEFAULT")) {
                        str += space[3] + "autoIncrementIdentity: true,\n";
                    }
                    wroteAutoIncrement = true;
                }
                return true;
            }
            else if (attr === "allowNull") { // 允许为null
                str += space[3] + attr + ": " + fieldObj[attr];
            }
            else if (attr === "defaultValue") { // 有默认值
                let defaultVal = fieldObj.defaultValue;
                if (this.dialect.name === "mssql" && defaultVal && defaultVal.toLowerCase() === '(newid())') {
                    defaultVal = null; // disable adding "default value" attribute for UUID fields if generating for MS SQL
                }
                if (this.dialect.name === "mssql" && (["(NULL)", "NULL"].includes(defaultVal) || typeof defaultVal === "undefined")) {
                    defaultVal = null; // Override default NULL in MS SQL to javascript null
                }
                if (defaultVal === null || defaultVal === undefined) {
                    return true;
                }
                if (isSerialKey) {
                    return true; // value generated in the database
                }
                let val_text = defaultVal;
                if (lodash_1.default.isString(defaultVal)) {
                    const field_type = fieldObj.type.toLowerCase();
                    defaultVal = this.escapeSpecial(defaultVal);
                    while (defaultVal.startsWith('(') && defaultVal.endsWith(')')) {
                        // remove extra parens around mssql defaults
                        defaultVal = defaultVal.replace(/^[(]/, '').replace(/[)]$/, '');
                    }
                    if (field_type === 'bit(1)' || field_type === 'bit' || field_type === 'boolean') {
                        // convert string to boolean
                        val_text = /1|true/i.test(defaultVal) ? "true" : "false";
                    }
                    else if (this.isArray(field_type)) {
                        // remove outer {}
                        val_text = defaultVal.replace(/^{/, '').replace(/}$/, '');
                        if (val_text && this.isString(fieldObj.elementType)) {
                            // quote the array elements
                            val_text = val_text.split(',').map(s => `"${s}"`).join(',');
                        }
                        val_text = `[${val_text}]`;
                    }
                    else if (field_type.match(/^(json)/)) {
                        // don't quote json
                        val_text = defaultVal;
                    }
                    else if (field_type === 'uuid' && (defaultVal === 'gen_random_uuid()' || defaultVal === 'uuid_generate_v4()')) {
                        val_text = "DataType.UUIDV4";
                    }
                    else if (defaultVal.match(/\w+\(\)$/)) {
                        // replace db function with sequelize function
                        val_text = "Sequelize.fn('" + defaultVal.replace(/\(\)$/g, "") + "')";
                    }
                    else if (this.isNumber(field_type)) {
                        if (defaultVal.match(/\(\)/g)) {
                            // assume it's a server function if it contains parens
                            val_text = "Sequelize.literal('" + defaultVal + "')";
                        }
                        else {
                            // don't quote numbers
                            val_text = defaultVal;
                        }
                    }
                    else if (defaultVal.match(/\(\)/g)) {
                        // embedded function, pass as literal
                        val_text = "Sequelize.literal('" + defaultVal + "')";
                    }
                    else if (field_type.indexOf('date') === 0 || field_type.indexOf('timestamp') === 0) {
                        if (lodash_1.default.includes(['current_timestamp', 'current_date', 'current_time', 'localtime', 'localtimestamp'], defaultVal.toLowerCase())) {
                            val_text = "Sequelize.literal('" + defaultVal + "')";
                            wroteTimestamp = true;
                        }
                        else {
                            val_text = quoteWrapper + defaultVal + quoteWrapper;
                        }
                    }
                    else {
                        val_text = quoteWrapper + defaultVal + quoteWrapper;
                    }
                }
                str += space[3] + attr + ": " + val_text;
            }
            else if (attr === "comment" && (!fieldObj[attr] || this.dialect.name === "mssql")) { // 有备注
                return true;
            }
            else {
                let val = null;
                if (attr === "type") {
                    val = this.getSqType(fieldObj, attr);
                    typeStr = this.getFieldType(fieldObj, attr);
                }
                if (val == null) {
                    val = fieldObj[attr];
                    val = lodash_1.default.isString(val) ? quoteWrapper + this.escapeSpecial(val) + quoteWrapper : val;
                }
                str += space[3] + attr + ": " + val;
            }
            str += ",\n";
        });
        if (unique) {
            const uniq = lodash_1.default.isString(unique) ? quoteWrapper + unique.replace(/\"/g, '\\"') + quoteWrapper : unique;
            str += space[3] + "unique: " + uniq + ",\n";
        }
        if (field !== fieldName) {
            str += space[3] + "field: '" + field + "',\n";
        }
        // 删除属性选项中的最后一个“，”
        str = str.trim().replace(/,+$/, '') + "\n";
        str = space[2] + str + space[2] + "})\n";
        str += space[2] + this.quoteName(fieldName) + `: ${typeStr};\n\n`;
        this.attrbuitesstr += this.addAttributesMain(this.quoteName(fieldName),typeStr,space[2],wroteAutoIncrement,wroteTimestamp);
        return str;
    }
    /**
     * 添加属性
     * @param {*} table 
     * @param {*} field 
     */
    addAttributesHeader(entityName,mainStr) {
        let str = `\nexport interface ${entityName}Attributes {\n`;
        str += (mainStr + "}\n");
        return str;
    }
    /**
     * 生成字段属性
     * @param {*} entityName 
     * @param {*} fileName 
     * @param {*} typeStr 
     * @param {*} indexspace 
     */
    addAttributesMain(fileName,typeStr,indexspace,iswroteAutoIncrement = false,wroteTimestamp = false) {
        let str = indexspace + fileName +"?:" + typeStr +"\n"; 
        return str;
    }


    // 添加索引
    addIndexes(table) {
        const indexes = this.indexes[table];
        const space = this.space;
        let str = "";
        if (indexes && indexes.length) {
            str += space[2] + "indexes: [\n";
            indexes.forEach(idx => {
                str += space[3] + "{\n";
                if (idx.name) {
                    str += space[4] + `name: "${idx.name}",\n`;
                }
                if (idx.unique) {
                    str += space[4] + "unique: true,\n";
                }
                if (idx.type) {
                    if (['UNIQUE', 'FULLTEXT', 'SPATIAL'].includes(idx.type)) {
                        str += space[4] + `type: "${idx.type}",\n`;
                    }
                    else {
                        str += space[4] + `using: "${idx.type}",\n`;
                    }
                }
                str += space[4] + `fields: [\n`;
                idx.fields.forEach(ff => {
                    str += space[5] + `{ name: "${ff.attribute}"`;
                    if (ff.collate) {
                        str += `, collate: "${ff.collate}"`;
                    }
                    if (ff.length) {
                        str += `, length: ${ff.length}`;
                    }
                    if (ff.order && ff.order !== "ASC") {
                        str += `, order: "${ff.order}"`;
                    }
                    str += " },\n";
                });
                str += space[4] + "]\n";
                str += space[3] + "},\n";
            });
            str += space[2] + "],\n";
        }
        return str;
    }
    getFieldType(fieldObj, attr) {
        let typeStr = 'number';
        const attrValue = fieldObj[attr];
        if (!attrValue.toLowerCase) {
            console.log("attrValue", attr, attrValue);
            return attrValue;
        }
        const type = attrValue.toLowerCase();
        let typematch = null;
        if (type === "boolean" || type === "bit(1)" || type === "bit" || type === "tinyint(1)") {
            typeStr = 'boolean';
        }
        else if (typematch = type.match(/^(bigint|smallint|mediumint|tinyint|int)/)) {
            typeStr = 'number';
        }
        else if (type === 'nvarchar(max)' || type === 'varchar(max)') {
            typeStr = 'string';
        }
        else if (type.match(/n?varchar|string|varying/)) {
            typeStr = 'string';
        }
        else if (type.match(/^n?char/)) {
            typeStr = 'string';
        }
        else if (type.match(/text$/)) {
            typeStr = 'string';
        }
        else if (type === "date") {
            typeStr = 'string';
        }
        else if (type.match(/^(date|timestamp|year)/)) {
            typeStr = 'string';
        }
        else if (type.match(/^(time)/)) {
            typeStr = 'string';
        }
        else if (type.match(/^(float|float4)/)) {
            typeStr = 'number';
        }
        else if (type.match(/^(decimal|numeric)/)) {
            typeStr = 'number';
        }
        else if (type.match(/^money/)) {
            typeStr = 'number';
        }
        else if (type.match(/^smallmoney/)) {
            typeStr = 'number';
        }
        else if (type.match(/^(float8|double)/)) {
            typeStr = 'number';
        }
        else if (type.match(/^uuid|uniqueidentifier/)) {
            typeStr = 'string';
        }
        else if (type.match(/^jsonb/)) {
            typeStr = 'string';
        }
        else if (type.match(/^json/)) {
            typeStr = 'string';
        }
        return typeStr;
    }
    // 从字段中获取sequelize类型
    getSqType(fieldObj, attr) {
        let typeStr = '';
        const attrValue = fieldObj[attr];
        if (!attrValue.toLowerCase) {
            console.log("attrValue", attr, attrValue);
            return attrValue;
        }
        const type = attrValue.toLowerCase();
        const length = type.match(/\(\d+\)/);
        const precision = type.match(/\(\d+,\d+\)/);
        let val = null;
        let typematch = null;
        if (type === "boolean" || type === "bit(1)" || type === "bit" || type === "tinyint(1)") {
            val = 'DataType.BOOLEAN';
            // postgres range types
        }
        else if (type === "numrange") {
            val = 'DataType.RANGE(DataType.DECIMAL)';
        }
        else if (type === "int4range") {
            val = 'DataType.RANGE(DataType.INTEGER)';
        }
        else if (type === "int8range") {
            val = 'DataType.RANGE(DataType.BIGINT)';
        }
        else if (type === "daterange") {
            val = 'DataType.RANGE(DataType.DATEONLY)';
        }
        else if (type === "tsrange" || type === "tstzrange") {
            val = 'DataType.RANGE(DataType.DATE)';
        }
        else if (typematch = type.match(/^(bigint|smallint|mediumint|tinyint|int)/)) {
            // integer subtypes
            val = 'DataType.' + (typematch[0] === 'int' ? 'INTEGER' : typematch[0].toUpperCase());
            if (/unsigned/i.test(type)) {
                val += '.UNSIGNED';
            }
            if (/zerofill/i.test(type)) {
                val += '.ZEROFILL';
            }
        }
        else if (type === 'nvarchar(max)' || type === 'varchar(max)') {
            val = 'DataType.TEXT';
        }
        else if (type.match(/n?varchar|string|varying/)) {
            val = 'DataType.STRING' + (!lodash_1.default.isNull(length) ? length : '');
        }
        else if (type.match(/^n?char/)) {
            val = 'DataType.CHAR' + (!lodash_1.default.isNull(length) ? length : '');
        }
        else if (type.match(/^real/)) {
            val = 'DataType.REAL';
        }
        else if (type.match(/text$/)) {
            val = 'DataType.TEXT' + (!lodash_1.default.isNull(length) ? length : '');
        }
        else if (type === "date") {
            val = 'DataType.DATEONLY';
        }
        else if (type.match(/^(date|timestamp|year)/)) {
            val = 'DataType.DATE' + (!lodash_1.default.isNull(length) ? length : '');
        }
        else if (type.match(/^(time)/)) {
            val = 'DataType.TIME';
        }
        else if (type.match(/^(float|float4)/)) {
            val = 'DataType.FLOAT' + (!lodash_1.default.isNull(precision) ? precision : '');
        }
        else if (type.match(/^(decimal|numeric)/)) {
            val = 'DataType.DECIMAL' + (!lodash_1.default.isNull(precision) ? precision : '');
        }
        else if (type.match(/^money/)) {
            val = 'DataType.DECIMAL(19,4)';
        }
        else if (type.match(/^smallmoney/)) {
            val = 'DataType.DECIMAL(10,4)';
        }
        else if (type.match(/^(float8|double)/)) {
            val = 'DataType.DOUBLE' + (!lodash_1.default.isNull(precision) ? precision : '');
        }
        else if (type.match(/^uuid|uniqueidentifier/)) {
            val = 'DataType.UUID';
        }
        else if (type.match(/^jsonb/)) {
            val = 'DataType.JSONB';
        }
        else if (type.match(/^json/)) {
            val = 'DataType.JSON';
        }
        else if (type.match(/^geometry/)) {
            const gtype = fieldObj.elementType ? `(${fieldObj.elementType})` : '';
            val = `DataType.GEOMETRY${gtype}`;
        }
        else if (type.match(/^geography/)) {
            const gtype = fieldObj.elementType ? `(${fieldObj.elementType})` : '';
            val = `DataType.GEOGRAPHY${gtype}`;
        }
        else if (type.match(/^array/)) {
            const eltype = this.getSqType(fieldObj, "elementType");
            val = `DataType.ARRAY(${eltype})`;
        }
        else if (type.match(/(binary|image|blob|bytea)/)) {
            val = 'DataType.BLOB';
        }
        else if (type.match(/^hstore/)) {
            val = 'DataType.HSTORE';
        }
        else if (type.match(/^inet/)) {
            val = 'DataType.INET';
        }
        else if (type.match(/^cidr/)) {
            val = 'DataType.CIDR';
        }
        else if (type.match(/^oid/)) {
            val = 'DataType.INTEGER';
        }
        else if (type.match(/^macaddr/)) {
            val = 'DataType.MACADDR';
        }
        else if (type.match(/^enum(\(.*\))?$/)) {
            const enumValues = this.getEnumValues(fieldObj);
            val = `DataType.ENUM(${enumValues})`;
        }
        return val;
    }
    getTypeScriptPrimaryKeys(table) {
        const fields = lodash_1.default.keys(this.tables[table]);
        return fields.filter((field) => {
            const fieldObj = this.tables[table][field];
            return fieldObj['primaryKey'];
        });
    }
    getTypeScriptCreationOptionalFields(table) {
        const fields = lodash_1.default.keys(this.tables[table]);
        return fields.filter((field) => {
            const fieldObj = this.tables[table][field];
            return fieldObj.allowNull || (!!fieldObj.defaultValue || fieldObj.defaultValue === "") || fieldObj.autoIncrement
                || this.isTimestampField(field);
        });
    }
    /** Add schema to table so it will match the relation data.  Fixes mysql problem. */
    addSchemaForRelations(table) {
        if (!table.includes('.') && !this.relations.some(rel => rel.childTable === table)) {
            // if no tables match the given table, then assume we need to fix the schema
            const first = this.relations.find(rel => !!rel.childTable);
            if (first) {
                const [schemaName, tableName] = (0, types_1.qNameSplit)(first.childTable);
                if (schemaName) {
                    table = (0, types_1.qNameJoin)(schemaName, table);
                }
            }
        }
        return table;
    }
    addTypeScriptAssociationMixins(table) {
        const sp = this.space[1];
        const needed = {};
        let str = '';
        table = this.addSchemaForRelations(table);
        this.relations.forEach(rel => {
            var _a, _b, _c;
            var _d, _e;
            if (!rel.isM2M) {
                if (rel.childTable === table) {
                    // current table is a child that belongsTo parent
                    const pparent = lodash_1.default.upperFirst(rel.parentProp);
                    str += `${sp}// ${rel.childModel} belongsTo ${rel.parentModel} via ${rel.parentId}\n`;
                    str += `${sp}${rel.parentProp}!: ${rel.parentModel};\n`;
                    str += `${sp}get${pparent}!: Sequelize.BelongsToGetAssociationMixin<${rel.parentModel}>;\n`;
                    str += `${sp}set${pparent}!: Sequelize.BelongsToSetAssociationMixin<${rel.parentModel}, ${rel.parentModel}Id>;\n`;
                    str += `${sp}create${pparent}!: Sequelize.BelongsToCreateAssociationMixin<${rel.parentModel}>;\n`;
                    (_a = needed[_d = rel.parentTable]) !== null && _a !== void 0 ? _a : (needed[_d] = new Set());
                    needed[rel.parentTable].add(rel.parentModel);
                    needed[rel.parentTable].add(rel.parentModel + 'Id');
                }
                else if (rel.parentTable === table) {
                    (_b = needed[_e = rel.childTable]) !== null && _b !== void 0 ? _b : (needed[_e] = new Set());
                    const pchild = lodash_1.default.upperFirst(rel.childProp);
                    if (rel.isOne) {
                        // const hasModelSingular = singularize(hasModel);
                        str += `${sp}// ${rel.parentModel} hasOne ${rel.childModel} via ${rel.parentId}\n`;
                        str += `${sp}${rel.childProp}!: ${rel.childModel};\n`;
                        str += `${sp}get${pchild}!: Sequelize.HasOneGetAssociationMixin<${rel.childModel}>;\n`;
                        str += `${sp}set${pchild}!: Sequelize.HasOneSetAssociationMixin<${rel.childModel}, ${rel.childModel}Id>;\n`;
                        str += `${sp}create${pchild}!: Sequelize.HasOneCreateAssociationMixin<${rel.childModel}>;\n`;
                        needed[rel.childTable].add(rel.childModel);
                        needed[rel.childTable].add(`${rel.childModel}Id`);
                        needed[rel.childTable].add(`${rel.childModel}CreationAttributes`);
                    }
                    else {
                        const hasModel = rel.childModel;
                        const sing = lodash_1.default.upperFirst((0, types_1.singularize)(rel.childProp));
                        const lur = (0, types_1.pluralize)(rel.childProp);
                        const plur = lodash_1.default.upperFirst(lur);
                        str += `${sp}// ${rel.parentModel} hasMany ${rel.childModel} via ${rel.parentId}\n`;
                        str += `${sp}${lur}!: ${rel.childModel}[];\n`;
                        str += `${sp}get${plur}!: Sequelize.HasManyGetAssociationsMixin<${hasModel}>;\n`;
                        str += `${sp}set${plur}!: Sequelize.HasManySetAssociationsMixin<${hasModel}, ${hasModel}Id>;\n`;
                        str += `${sp}add${sing}!: Sequelize.HasManyAddAssociationMixin<${hasModel}, ${hasModel}Id>;\n`;
                        str += `${sp}add${plur}!: Sequelize.HasManyAddAssociationsMixin<${hasModel}, ${hasModel}Id>;\n`;
                        str += `${sp}create${sing}!: Sequelize.HasManyCreateAssociationMixin<${hasModel}>;\n`;
                        str += `${sp}remove${sing}!: Sequelize.HasManyRemoveAssociationMixin<${hasModel}, ${hasModel}Id>;\n`;
                        str += `${sp}remove${plur}!: Sequelize.HasManyRemoveAssociationsMixin<${hasModel}, ${hasModel}Id>;\n`;
                        str += `${sp}has${sing}!: Sequelize.HasManyHasAssociationMixin<${hasModel}, ${hasModel}Id>;\n`;
                        str += `${sp}has${plur}!: Sequelize.HasManyHasAssociationsMixin<${hasModel}, ${hasModel}Id>;\n`;
                        str += `${sp}count${plur}!: Sequelize.HasManyCountAssociationsMixin;\n`;
                        needed[rel.childTable].add(hasModel);
                        needed[rel.childTable].add(`${hasModel}Id`);
                    }
                }
            }
            else {
                // rel.isM2M
                if (rel.parentTable === table) {
                    // many-to-many
                    const isParent = (rel.parentTable === table);
                    const thisModel = isParent ? rel.parentModel : rel.childModel;
                    const otherModel = isParent ? rel.childModel : rel.parentModel;
                    const otherModelSingular = lodash_1.default.upperFirst((0, types_1.singularize)(isParent ? rel.childProp : rel.parentProp));
                    const lotherModelPlural = (0, types_1.pluralize)(isParent ? rel.childProp : rel.parentProp);
                    const otherModelPlural = lodash_1.default.upperFirst(lotherModelPlural);
                    const otherTable = isParent ? rel.childTable : rel.parentTable;
                    str += `${sp}// ${thisModel} belongsToMany ${otherModel} via ${rel.parentId} and ${rel.childId}\n`;
                    str += `${sp}${lotherModelPlural}!: ${otherModel}[];\n`;
                    str += `${sp}get${otherModelPlural}!: Sequelize.BelongsToManyGetAssociationsMixin<${otherModel}>;\n`;
                    str += `${sp}set${otherModelPlural}!: Sequelize.BelongsToManySetAssociationsMixin<${otherModel}, ${otherModel}Id>;\n`;
                    str += `${sp}add${otherModelSingular}!: Sequelize.BelongsToManyAddAssociationMixin<${otherModel}, ${otherModel}Id>;\n`;
                    str += `${sp}add${otherModelPlural}!: Sequelize.BelongsToManyAddAssociationsMixin<${otherModel}, ${otherModel}Id>;\n`;
                    str += `${sp}create${otherModelSingular}!: Sequelize.BelongsToManyCreateAssociationMixin<${otherModel}>;\n`;
                    str += `${sp}remove${otherModelSingular}!: Sequelize.BelongsToManyRemoveAssociationMixin<${otherModel}, ${otherModel}Id>;\n`;
                    str += `${sp}remove${otherModelPlural}!: Sequelize.BelongsToManyRemoveAssociationsMixin<${otherModel}, ${otherModel}Id>;\n`;
                    str += `${sp}has${otherModelSingular}!: Sequelize.BelongsToManyHasAssociationMixin<${otherModel}, ${otherModel}Id>;\n`;
                    str += `${sp}has${otherModelPlural}!: Sequelize.BelongsToManyHasAssociationsMixin<${otherModel}, ${otherModel}Id>;\n`;
                    str += `${sp}count${otherModelPlural}!: Sequelize.BelongsToManyCountAssociationsMixin;\n`;
                    (_c = needed[otherTable]) !== null && _c !== void 0 ? _c : (needed[otherTable] = new Set());
                    needed[otherTable].add(otherModel);
                    needed[otherTable].add(`${otherModel}Id`);
                }
            }
        });
        if (needed[table]) {
            delete needed[table]; // don't add import for self
        }
        return { needed, str };
    }
    addTypeScriptFields(table, isInterface) {
        const sp = this.space[1];
        const fields = lodash_1.default.keys(this.tables[table]);
        const notNull = isInterface ? '' : '!';
        let str = '';
        fields.forEach(field => {
            if (!this.options.skipFields || !this.options.skipFields.includes(field)) {
                const name = this.quoteName((0, types_1.recase)(this.options.caseProp, field));
                const isOptional = this.getTypeScriptFieldOptional(table, field);
                str += `${sp}${name}${isOptional ? '?' : notNull}: ${this.getTypeScriptType(table, field)};\n`;
            }
        });
        return str;
    }
    getTypeScriptFieldOptional(table, field) {
        const fieldObj = this.tables[table][field];
        return fieldObj.allowNull;
    }
    getTypeScriptType(table, field) {
        const fieldObj = this.tables[table][field];
        return this.getTypeScriptFieldType(fieldObj, "type");
    }
    getTypeScriptFieldType(fieldObj, attr) {
        const rawFieldType = fieldObj[attr] || '';
        const fieldType = String(rawFieldType).toLowerCase();
        let jsType;
        if (this.isArray(fieldType)) {
            const eltype = this.getTypeScriptFieldType(fieldObj, "elementType");
            jsType = eltype + '[]';
        }
        else if (this.isNumber(fieldType)) {
            jsType = 'number';
        }
        else if (this.isBoolean(fieldType)) {
            jsType = 'boolean';
        }
        else if (this.isDate(fieldType)) {
            jsType = 'Date';
        }
        else if (this.isString(fieldType)) {
            jsType = 'string';
        }
        else if (this.isEnum(fieldType)) {
            const values = this.getEnumValues(fieldObj);
            jsType = values.join(' | ');
        }
        else if (this.isJSON(fieldType)) {
            jsType = 'object';
        }
        else {
            console.log(`Missing TypeScript type: ${fieldType || fieldObj['type']}`);
            jsType = 'any';
        }
        return jsType;
    }
    getEnumValues(fieldObj) {
        if (fieldObj.special) {
            // postgres
            return fieldObj.special.map((v) => `"${v}"`);
        }
        else {
            // mysql
            return fieldObj.type.substring(5, fieldObj.type.length - 1).split(',');
        }
    }
    isTimestampField(field) {
        const additional = this.options.additional;
        if (additional.timestamps === false) {
            return false;
        }
        return ((!additional.createdAt && (0, types_1.recase)('c', field) === 'createdAt') || additional.createdAt === field)
            || ((!additional.updatedAt && (0, types_1.recase)('c', field) === 'updatedAt') || additional.updatedAt === field);
    }
    isParanoidField(field) {
        const additional = this.options.additional;
        if (additional.timestamps === false || additional.paranoid === false) {
            return false;
        }
        return ((!additional.deletedAt && (0, types_1.recase)('c', field) === 'deletedAt') || additional.deletedAt === field);
    }
    isIgnoredField(field) {
        return (this.options.skipFields && this.options.skipFields.includes(field));
    }
    escapeSpecial(val) {
        if (typeof (val) !== "string") {
            return val;
        }
        return val
            .replace(/[\\]/g, '\\\\')
            .replace(/[\"]/g, '\\"')
            .replace(/[\/]/g, '\\/')
            .replace(/[\b]/g, '\\b')
            .replace(/[\f]/g, '\\f')
            .replace(/[\n]/g, '\\n')
            .replace(/[\r]/g, '\\r')
            .replace(/[\t]/g, '\\t');
    }
    /** Quote the name if it is not a valid identifier */
    quoteName(name) {
        return (/^[$A-Z_][0-9A-Z_$]*$/i.test(name) ? name : "'" + name + "'");
    }
    isNumber(fieldType) {
        return /^(smallint|mediumint|tinyint|int|bigint|float|money|smallmoney|double|decimal|numeric|real|oid)/.test(fieldType);
    }
    isBoolean(fieldType) {
        return /^(boolean|bit)/.test(fieldType);
    }
    isDate(fieldType) {
        return /^(datetime|timestamp)/.test(fieldType);
    }
    isString(fieldType) {
        return /^(char|nchar|string|varying|varchar|nvarchar|text|longtext|mediumtext|tinytext|ntext|uuid|uniqueidentifier|date|time|inet|cidr|macaddr)/.test(fieldType);
    }
    isArray(fieldType) {
        return /(^array)|(range$)/.test(fieldType);
    }
    isEnum(fieldType) {
        return /^(enum)/.test(fieldType);
    }
    isJSON(fieldType) {
        return /^(json|jsonb)/.test(fieldType);
    }
}
exports.AutoGenerator = AutoGenerator;
//# sourceMappingURL=auto-generator.js.map