import _ from 'lodash';
import JDLObject from '../core/models/jdl-object.js';
import { JDLEntity, JDLEnum } from '../core/models/index.js';
import JDLField from '../core/models/jdl-field.js';
import JDLRelationship from '../core/models/jdl-relationship.js';
import JDLUnaryOption from '../core/models/jdl-unary-option.js';
import JDLBinaryOption from '../core/models/jdl-binary-option.js';
import JDLValidation from '../core/models/jdl-validation.js';
import { relationshipTypes } from '../core/basic-types/index.js';
import { binaryOptions, unaryOptions, validations } from '../core/built-in-options/index.js';
const { FILTER } = unaryOptions;
const { CLIENT_ROOT_FOLDER, DTO, PAGINATION, SERVICE } = binaryOptions.Options;
const { Validations: { MINBYTES, PATTERN, MAXLENGTH, UNIQUE, REQUIRED, MAXBYTES, MINLENGTH, MIN, MAX }, } = validations;
const { ONE_TO_ONE, ONE_TO_MANY, MANY_TO_ONE, MANY_TO_MANY } = relationshipTypes;
export default {
    convertEntitiesToJDL,
};
const USER_ENTITY_NAME = 'User';
const USER_ENTITY = new JDLEntity({ name: USER_ENTITY_NAME });
let jdlObject;
let skippedUserManagement;
let databaseServer;
let schemas;
export function convertEntitiesToJDL(params) {
    if (!params.server) {
        throw new Error('Server have to be passed to be converted.');
    }
    init(params);
    addEntities();
    addRelationshipsToJDL();
    return jdlObject;
}
function init(params) {
    databaseServer = {
        objectType: params.server.objectType,
        name: params.server.name,
        serverVersion: params.server.serverVersion,
        edition: params.server.edition,
        lowerCaseTableNames: params.server.lowerCaseTableNames,
    };
    schemas = params.server.schemas.filter(schema => schema.name !== 'Default');
    jdlObject = new JDLObject();
}
function addEntities() {
    schemas.forEach(schema => {
        schema.tables.forEach(table => {
            table.schemaName = schema.name;
            addEntity(table);
        });
    });
}
function addEntity(table) {
    const entityObject = convertJSONToJDLEntity(table);
    jdlObject.addEntity(entityObject);
    addIndexes(entityObject, table);
    addEntityOptionsToJDL(table);
    addPrimaryKey(table, entityObject);
}
function convertJSONToJDLEntity(table) {
    const jdlEntity = new JDLEntity({
        name: _.upperFirst(_.camelCase(table.name)),
        tableName: table.name,
        comment: table.comment,
    });
    addFields(jdlEntity, table);
    addPrimaryKey(table, jdlEntity);
    return jdlEntity;
}
function addPrimaryKey(table, jdlEntity) {
    if (table.primaryKey) {
        table.primaryKey.fields.forEach(primaryField => {
            if (primaryField.objectType === 'IndexField_MYSQL' || primaryField.objectType === 'IndexField_MARIADB') {
                const fieldKey = Object.keys(jdlEntity.fields).find(key => jdlEntity.fields[key].ndmName === primaryField.name);
                if (fieldKey && jdlEntity.fields[fieldKey]) {
                    if (jdlEntity.fields[fieldKey].options) {
                        jdlEntity.fields[fieldKey].options.Id = true;
                    }
                    else {
                        jdlEntity.fields[fieldKey].options = { Id: true };
                    }
                    delete jdlEntity.fields[fieldKey].ndmName;
                    delete jdlEntity.fields[fieldKey].options.increment;
                    if (jdlEntity.fields[fieldKey].validations) {
                        delete jdlEntity.fields[fieldKey].validations[REQUIRED];
                    }
                }
            }
        });
    }
}
function addFields(jdlEntity, table) {
    table.fields.forEach(field => {
        jdlEntity.addField(convertJSONToJDLField(field));
    });
}
function addIndexes(jdlEntity, table) {
    table.indexes.forEach(index => {
    });
}
function convertJSONToJDLField(field) {
    const jdlField = new JDLField({
        name: _.camelCase(field.name),
        comment: field.comment,
        ...getJdlTypeFromDatatype(field),
    });
    jdlField.options = {};
    if (!field.isNullable) {
        const validation = new JDLValidation({
            name: REQUIRED,
            value: '',
        });
        jdlField.addValidation(validation);
    }
    if (field.isAutoInc) {
        jdlField.options.increment = true;
    }
    if (field.defaultValue) {
        jdlField.options.defaultValue = field.defaultValue;
    }
    if (field.setEnumValues) {
        jdlField.setEnumValues = field.setEnumValues;
    }
    if (field.isOnUpdateCurrentTimestamp) {
        jdlField.options.isOnUpdateCurrentTimestamp = field.isOnUpdateCurrentTimestamp;
    }
    jdlField.ndmName = field.name;
    return jdlField;
}
function addEnumToJdlObject(jdlEnum) {
    jdlObject.addEnum(jdlEnum);
}
function fieldTypeFromMySQL(field) {
    const result = {};
    switch (field.type.toUpperCase()) {
        case 'TINYINT':
        case 'SMALLINT':
        case 'MEDIUMINT':
        case 'INT':
        case 'INTEGER':
            result.type = 'Integer';
            break;
        case 'BIGINT':
            result.type = 'Long';
            break;
        case 'BIT':
            result.type = 'Boolean';
            break;
        case 'FLOAT':
            result.type = 'Float';
            break;
        case 'DOUBLE':
            result.type = 'Double';
            break;
        case 'DECIMAL':
            result.type = 'BigDecimal';
            break;
        case 'DATE':
            result.type = 'LocalDate';
            break;
        case 'DATETIME':
        case 'TIMESTAMP':
            result.type = 'ZonedDateTime';
            break;
        case 'TIME':
            result.type = 'ZonedDateTime';
            break;
        case 'CHAR':
        case 'VARCHAR':
        case 'TINYTEXT':
            result.type = 'String';
            break;
        case 'TINYBLOB':
        case 'BLOB':
        case 'MEDIUMBLOB':
        case 'LONGBLOB':
            result.type = 'AnyBlob';
            break;
        case 'JSON':
        case 'TEXT':
        case 'MEDIUMTEXT':
        case 'LONGTEXT':
            result.type = 'TextBlob';
            break;
        case 'ENUM':
            result.type = `${field.name}Enum`;
            const jdlEnum = new JDLEnum({
                name: `${field.name}Enum`,
                comment: field.comment,
                values: field.setEnumValues
                    .replaceAll("'", '')
                    .split(',')
                    .map(value => {
                    return { key: value, value, comment: value };
                }),
            });
            addEnumToJdlObject(jdlEnum);
            break;
        default:
            console.log(`Unknown type ${field.type} for field ${field.name}`);
    }
    return result;
}
function fieldTypeFromMariaDB(field) {
    const result = {};
    switch (field.type.toUpperCase()) {
        case 'TINYINT':
        case 'SMALLINT':
        case 'MEDIUMINT':
        case 'INT':
        case 'INTEGER':
            result.type = 'Integer';
            break;
        case 'BIGINT':
            result.type = 'Long';
            break;
        case 'BIT':
            result.type = 'Boolean';
            break;
        case 'FLOAT':
            result.type = 'Float';
            break;
        case 'DOUBLE':
            result.type = 'Double';
            break;
        case 'DECIMAL':
            result.type = 'BigDecimal';
            break;
        case 'DATE':
            result.type = 'LocalDate';
            break;
        case 'DATETIME':
        case 'TIMESTAMP':
            result.type = 'ZonedDateTime';
            break;
        case 'TIME':
            result.type = 'ZonedDateTime';
            break;
        case 'CHAR':
        case 'VARCHAR':
        case 'TINYTEXT':
            result.type = 'String';
            break;
        case 'TINYBLOB':
        case 'BLOB':
        case 'MEDIUMBLOB':
        case 'LONGBLOB':
            result.type = 'AnyBlob';
            break;
        case 'TEXT':
        case 'MEDIUMTEXT':
        case 'LONGTEXT':
            result.type = 'TextBlob';
            break;
    }
    return result;
}
function fieldTypeFromPostgre(field) {
    const result = {};
    switch (field.type.toUpperCase()) {
        case 'SMALLINT':
        case 'INTEGER':
        case 'SERIAL':
        case 'SMALLSERIAL':
            result.type = 'Integer';
            break;
        case 'BIGINT':
        case 'BIGSERIAL':
            result.type = 'Long';
            break;
        case 'REAL':
            result.type = 'Float';
            break;
        case 'DOUBLE':
            result.type = 'Double';
            break;
        case 'DECIMAL':
        case 'NUMERIC':
            result.type = 'BigDecimal';
            break;
        case 'DATE':
            result.type = 'LocalDate';
            break;
        case 'DATETIME':
        case 'TIMESTAMP':
            result.type = 'ZonedDateTime';
            break;
        case 'CHARACTER':
        case 'VARCHAR':
        case 'TINYTEXT':
            result.type = 'String';
            break;
        case 'TINYBLOB':
        case 'BYTEA':
        case 'MEDIUMBLOB':
            result.type = 'AnyBlob';
            break;
        case 'TEXT':
        case 'MEDIUMTEXT':
        case 'LONGTEXT':
            result.type = 'TextBlob';
            break;
        case 'UUID':
            result.type = 'UUID';
            break;
        case 'BOOLEAN':
            result.type = 'Boolean';
            break;
        case 'TIME':
            result.type = 'ZonedDateTime';
            break;
        default:
            console.log(`Unknown type ${field.type} for field ${field.name}`);
    }
}
function getJdlTypeFromDatatype(field) {
    switch (databaseServer.objectType) {
        case 'Server_MYSQL':
            return fieldTypeFromMySQL(field);
        case 'Server_POSTGRESQL':
            return fieldTypeFromPostgre(field);
        case 'Server_MARIADB':
            return fieldTypeFromMariaDB(field);
        default:
            console.log('databaseService: ', databaseServer.objectType);
            console.log('field: ', field);
            return null;
    }
}
function addRelationshipsToJDL() {
    schemas.forEach(schema => {
        schema.tables.forEach(table => {
            if (table.foreignKeys && table.foreignKeys.length > 0) {
                table.foreignKeys.forEach(foreignKey => {
                    convertforeignKeyToRelationship(foreignKey, table);
                });
            }
            addEntity(table);
        });
    });
    Object.keys(jdlObject.entities).forEach(entityName => {
        dealWithRelationships(jdlObject.entities[entityName].jsonRelationships, entityName);
    });
}
function convertforeignKeyToRelationship(foreignKey, table) {
    const relationFrom = {};
    const ownerEntityTableName = table.name;
    const oreignKeyConstraintName = foreignKey.name;
    const fieldName = foreignKey.fields[0];
    const otherEntityTableName = foreignKey.referenceTable;
    const otherEntityReferenceField = foreignKey.referenceFields[0];
    if (foreignKey.sourceCardinality === 'ZeroOrManyRelationship' && foreignKey.targetCardinality === 'ZeroOrOneRelationship') {
        relationFrom.relationshipType = 'many-to-one';
        relationFrom.ownerSide = true;
    }
    if (foreignKey.sourceCardinality === 'ZeroOrManyRelationship' && foreignKey.targetCardinality === 'OneAndOnlyOneRelationship') {
        relationFrom.relationshipType = 'many-to-one';
        relationFrom.relationshipValidateRules = 'required';
        relationFrom.ownerSide = true;
    }
    let relationshipName = _.camelCase(fieldName);
    relationshipName = _.endsWith(relationshipName, 'Id') ? relationshipName.slice(0, -2) : relationshipName;
    relationFrom.relationshipName = relationshipName;
    relationFrom.javadoc = getFieldJavaDocFromEntity(_.upperFirst(_.camelCase(table.name)), fieldName);
    relationFrom.otherEntityName = _.camelCase(otherEntityTableName);
    relationFrom.oreignKeyConstraintName = oreignKeyConstraintName;
    relationFrom.otherEntityField = _.camelCase(otherEntityReferenceField);
    const relationshipEntity = jdlObject.entities[_.camelCase(ownerEntityTableName)];
    if (relationshipEntity) {
        if (relationshipEntity.jsonRelationships) {
            relationshipEntity.jsonRelationships.push(relationFrom);
        }
        else {
            relationshipEntity.jsonRelationships = [relationFrom];
        }
    }
}
function getFieldJavaDocFromEntity(entityName, fieldName) {
    const field = jdlObject.entities[entityName].fields[_.camelCase(fieldName)];
    if (field) {
        field.isRelationshipField = true;
        return field.comment;
    }
    return '';
}
function dealWithRelationships(relationships, entityName) {
    if (!relationships) {
        return;
    }
    relationships.forEach(relationship => {
        const jdlRelationship = getRelationship(relationship, entityName);
        if (jdlRelationship) {
            jdlObject.addRelationship(jdlRelationship);
        }
    });
}
function getRelationship(relationship, entityName) {
    let relationshipConfiguration = {
        sourceEntity: null,
        destinationEntity: null,
        type: null,
        injectedFieldInSourceEntity: null,
        injectedFieldInDestinationEntity: null,
        injectedFieldInDestinationIsRequired: false,
        injectedFieldInSourceIsRequired: false,
        commentForSourceEntity: null,
        commentForDestinationEntity: null,
        destinationEntityAsJDLEntity: null,
        options: {},
    };
    const sourceEntitySideAttributes = getSourceEntitySideAttributes(entityName, relationship);
    relationshipConfiguration = {
        ...relationshipConfiguration,
        ...sourceEntitySideAttributes,
    };
    relationshipConfiguration.options = getRelationshipOptions(relationship);
    if (shouldHandleUserEntity(relationship)) {
        relationshipConfiguration.destinationEntityAsJDLEntity = USER_ENTITY;
    }
    else {
        const destinationEntity = _.upperFirst(relationship.otherEntityName);
        relationshipConfiguration.destinationEntity = jdlObject.getEntity(destinationEntity);
        relationshipConfiguration.destinationEntityAsJDLEntity = jdlObject.getEntity(destinationEntity);
        if (!relationshipConfiguration.destinationEntity) {
            return undefined;
        }
        const isEntityTheDestinationSideEntity = (otherEntityName, otherEntityRelationshipName) => otherEntityName === entityName && otherEntityRelationshipName === relationship.relationshipName;
        const destinationSideAttributes = getDestinationEntitySideAttributes(isEntityTheDestinationSideEntity, relationshipConfiguration.destinationEntity.jsonRelationships);
        relationshipConfiguration = {
            ...relationshipConfiguration,
            ...destinationSideAttributes,
        };
    }
    if (relationship.relationshipType === 'many-to-one') {
        if (relationshipConfiguration.injectedFieldInDestinationEntity) {
            return getBidirectionalOneToManyRelationship(relationshipConfiguration);
        }
        relationshipConfiguration.type = MANY_TO_ONE;
    }
    else if (relationship.relationshipType === 'one-to-one' && relationship.ownerSide === true) {
        relationshipConfiguration.type = ONE_TO_ONE;
    }
    else if (relationship.relationshipType === 'many-to-many' && relationship.ownerSide === true) {
        relationshipConfiguration.type = MANY_TO_MANY;
    }
    if (relationshipConfiguration.type) {
        return getDefaultRelationship(relationshipConfiguration);
    }
    return undefined;
}
function shouldHandleUserEntity(relationship) {
    return relationship.otherEntityName.toLowerCase() === USER_ENTITY_NAME.toLowerCase() && !skippedUserManagement;
}
function getSourceEntitySideAttributes(entityName, relationship) {
    return {
        sourceEntity: entityName,
        injectedFieldInSourceEntity: getInjectedFieldInSourceEntity(relationship),
        commentForSourceEntity: relationship.javadoc,
    };
}
function getDestinationEntitySideAttributes(isEntityTheDestinationSideEntity, destinationEntityRelationships) {
    if (!destinationEntityRelationships) {
        return {};
    }
    const foundDestinationSideEntity = destinationEntityRelationships.find(destinationEntityFromRelationship => {
        return isEntityTheDestinationSideEntity(_.upperFirst(destinationEntityFromRelationship.otherEntityName), destinationEntityFromRelationship.otherEntityRelationshipName);
    });
    if (!foundDestinationSideEntity) {
        return {};
    }
    let injectedFieldInDestinationEntity = foundDestinationSideEntity.relationshipName;
    if (!!foundDestinationSideEntity.otherEntityField && foundDestinationSideEntity.otherEntityField !== 'id') {
        injectedFieldInDestinationEntity += `(${foundDestinationSideEntity.otherEntityField})`;
    }
    const injectedFieldInDestinationIsRequired = !!foundDestinationSideEntity.relationshipValidateRules;
    const commentForDestinationEntity = foundDestinationSideEntity.javadoc;
    return {
        injectedFieldInDestinationEntity,
        injectedFieldInDestinationIsRequired,
        commentForDestinationEntity,
    };
}
function getRelationshipOptions(relationship) {
    const options = relationship.options || {
        global: {},
        source: {},
        destination: {},
    };
    if (relationship.useJPADerivedIdentifier) {
        relationship.id = true;
    }
    return options;
}
function getDefaultRelationship(relationshipConfiguration) {
    return new JDLRelationship({
        from: relationshipConfiguration.sourceEntity,
        to: relationshipConfiguration.destinationEntityAsJDLEntity.name,
        type: relationshipConfiguration.type,
        injectedFieldInFrom: relationshipConfiguration.injectedFieldInSourceEntity,
        injectedFieldInTo: relationshipConfiguration.injectedFieldInDestinationEntity,
        isInjectedFieldInFromRequired: relationshipConfiguration.injectedFieldInSourceIsRequired,
        isInjectedFieldInToRequired: relationshipConfiguration.injectedFieldInDestinationIsRequired,
        commentInFrom: relationshipConfiguration.commentForSourceEntity,
        commentInTo: relationshipConfiguration.commentForDestinationEntity,
        options: relationshipConfiguration.options,
    });
}
function getBidirectionalOneToManyRelationship(relationshipConfiguration) {
    return new JDLRelationship({
        from: relationshipConfiguration.destinationEntityAsJDLEntity.name,
        to: relationshipConfiguration.sourceEntity,
        type: ONE_TO_MANY,
        injectedFieldInFrom: relationshipConfiguration.injectedFieldInDestinationEntity,
        injectedFieldInTo: relationshipConfiguration.injectedFieldInSourceEntity,
        isInjectedFieldInFromRequired: relationshipConfiguration.injectedFieldInDestinationIsRequired,
        isInjectedFieldInToRequired: relationshipConfiguration.injectedFieldInSourceIsRequired,
        commentInFrom: relationshipConfiguration.commentForDestinationEntity,
        commentInTo: relationshipConfiguration.commentForSourceEntity,
        options: relationshipConfiguration.options,
    });
}
function getInjectedFieldInSourceEntity(relationship) {
    return (relationship.relationshipName +
        (relationship.otherEntityField && relationship.otherEntityField !== 'id' ? `(${relationship.otherEntityField})` : ''));
}
function addEntityOptionsToJDL(entity) {
    const entityName = _.upperFirst(_.camelCase(entity.name));
    addBinaryOptionToJDL(SERVICE, 'serviceClass', entityName);
    addBinaryOptionToJDL(PAGINATION, 'pagination', entityName);
    addBinaryOptionToJDL(DTO, 'mapstruct', entityName);
    addUnaryOptionToJDL(FILTER, entityName);
    if (entity.moduleName) {
        addBinaryOptionToJDL(CLIENT_ROOT_FOLDER, entity.moduleName, entityName);
    }
}
function addUnaryOptionToJDL(unaryOption, entityName) {
    jdlObject.addOption(new JDLUnaryOption({
        name: unaryOption,
        entityNames: [entityName],
    }));
}
function addBinaryOptionToJDL(binaryOption, value, entityName) {
    jdlObject.addOption(new JDLBinaryOption({
        name: binaryOption,
        value,
        entityNames: [entityName],
    }));
}
