import _ from 'lodash';
import JDLObject from '../models/jdl-object.js';
import { JDLEntity } from '../models/index.js';
import JDLField from '../models/jdl-field.js';
import JDLRelationship from '../models/jdl-relationship.js';
import JDLUnaryOption from '../models/jdl-unary-option.js';
import JDLBinaryOption from '../models/jdl-binary-option.js';
import JDLValidation from '../models/jdl-validation.js';
import { unaryOptions, binaryOptions, relationshipTypes } from '../jhipster/index.js';
const { ONE_TO_ONE, ONE_TO_MANY, MANY_TO_ONE, MANY_TO_MANY } = relationshipTypes;
const { CLIENT_ROOT_FOLDER, DTO, PAGINATION, SERVICE } = binaryOptions.Options;
const { FILTER } = unaryOptions;
export default {
    convertEntitiesToJDL,
};
const USER_ENTITY_NAME = 'User';
const USER_ENTITY = new JDLEntity({ name: USER_ENTITY_NAME });
let jdlObject;
let skippedUserManagement;
let datatype;
let pdManModules;
export function convertEntitiesToJDL(params) {
    if (!params.modules) {
        throw new Error('Modules have to be passed to be converted.');
    }
    init(params);
    addEntities();
    addRelationshipsToJDL();
    return jdlObject;
}
function init(params) {
    pdManModules = params.modules;
    datatype = params.dataTypeDomains.datatype;
    jdlObject = new JDLObject();
}
function addEntities() {
    pdManModules.forEach(pdManmodule => {
        pdManmodule.entities.forEach(entity => {
            entity.moduleName = _.camelCase(pdManmodule.name.replace(/[^a-zA-Z0-9]/g, ''));
            addEntity(entity);
        });
    });
}
function addEntity(entity) {
    jdlObject.addEntity(convertJSONToJDLEntity(entity));
    addEntityOptionsToJDL(entity);
}
function convertJSONToJDLEntity(pdmanEntity) {
    const jdlEntity = new JDLEntity({
        name: _.upperFirst(_.camelCase(pdmanEntity.title)),
        tableName: pdmanEntity.title,
        comment: pdmanEntity.chnname || pdmanEntity.remark,
    });
    jdlEntity.pdManTitle = pdmanEntity.title;
    addFields(jdlEntity, pdmanEntity);
    return jdlEntity;
}
function addFields(jdlEntity, entity) {
    entity.fields.forEach(field => {
        jdlEntity.addField(convertJSONToJDLField(field));
    });
}
function convertJSONToJDLField(field) {
    const jdlField = new JDLField({
        name: _.camelCase(field.name),
        comment: field.chnname || field.remark,
        ...getJdlTypeFromDatatype(field.type),
    });
    jdlField.options = {};
    if (field.pk) {
        jdlField.options.id = true;
    }
    jdlField.options.dbColumnName = field.name;
    jdlField.pdManName = field.name;
    return jdlField;
}
function getJdlTypeFromDatatype(code) {
    const result = {};
    if (datatype && datatype.length > 0) {
        const pdtype = datatype.find(data => data.code === code);
        if (pdtype) {
            switch (pdtype.apply.JAVA.type) {
                case 'String':
                    Object.assign(result, transformDataTypeToJdlType(pdtype, pdtype.apply.JAVA.type));
                    break;
                case 'Double':
                    result.type = 'Double';
                    break;
                case 'Integer':
                    result.type = 'Integer';
                    break;
                case 'Long':
                    result.type = 'Long';
                    break;
                case 'Date':
                    Object.assign(result, transformDataTypeToJdlType(pdtype, pdtype.apply.JAVA.type));
                    break;
            }
            return result;
        }
    }
    return null;
}
function transformDataTypeToJdlType(pdtype, jdlType) {
    const result = { validations: {} };
    const regExt = /VARCHAR\((\d+)\)/;
    let maxLength;
    switch (jdlType) {
        case 'String':
            if (pdtype.apply.MYSQL.type === 'TEXT' ||
                pdtype.apply.ORACLE.type === 'CLOB' ||
                pdtype.apply.SQLServer.type === 'NTEXT' ||
                pdtype.apply.PostgreSQL.type === 'TEXT') {
                result.type = 'byte[]';
                result.fieldTypeBlobContent = 'text';
            }
            else {
                result.type = 'String';
            }
            if (pdtype.apply.MYSQL.type && regExt.test(pdtype.apply.MYSQL.type)) {
                maxLength = RegExp.$1;
            }
            else if (pdtype.apply.PostgreSQL.type && regExt.test(pdtype.apply.PostgreSQL.type)) {
                maxLength = RegExp.$1;
            }
            else if (pdtype.apply.SQLServer.type && regExt.test(pdtype.apply.SQLServer.type)) {
                maxLength = RegExp.$1;
            }
            if (maxLength) {
                result.validations.maxlength = new JDLValidation({ name: 'maxlength', value: maxLength });
            }
        case 'Date':
            if (pdtype.apply.MYSQL.type === 'DATETIME') {
                result.type = 'ZonedDateTime';
            }
            if (pdtype.apply.MYSQL.type === 'DATE') {
                result.type = 'LocalDate';
            }
        default:
    }
    return result;
}
function addRelationshipsToJDL() {
    pdManModules.forEach(pdManModule => {
        if (pdManModule.associations && pdManModule.associations.length > 0) {
            pdManModule.associations.forEach(association => {
                convertAssociationToRelationship(association, pdManModule.name);
            });
        }
    });
    Object.keys(jdlObject.entities).forEach(entityName => {
        dealWithRelationships(jdlObject.entities[entityName].jsonRelationships, entityName);
    });
}
function convertAssociationToRelationship(association, moduleName) {
    const relationFrom = {};
    if (association.relation === '0,n:1') {
        relationFrom.relationshipType = 'many-to-one';
        relationFrom.ownerSide = true;
    }
    if (association.relation === '1,n:1') {
        relationFrom.relationshipType = 'many-to-one';
        relationFrom.relationshipValidateRules = 'required';
        relationFrom.ownerSide = true;
    }
    if (association.relation === '0,1:1') {
        relationFrom.relationshipType = 'one-to-one';
        relationFrom.ownerSide = true;
    }
    if (association.relation === '1,1:1') {
        relationFrom.relationshipType = 'one-to-one';
        relationFrom.relationshipValidateRules = 'required';
        relationFrom.ownerSide = true;
    }
    if (association.relation === '1:0,n') {
        relationFrom.relationshipType = 'one-to-one';
        relationFrom.ownerSide = true;
    }
    if (association.relation === '1:1,n') {
        relationFrom.relationshipType = 'one-to-one';
        relationFrom.relationshipValidateRules = 'required';
        relationFrom.ownerSide = true;
    }
    relationFrom.otherEntityName = _.camelCase(association.to.entity);
    relationFrom.javadoc = getFieldChnnameFromEntity(moduleName, association.from.entity, association.from.field);
    relationFrom.otherEntityField = _.camelCase(association.to.field);
    let relationshipName = _.camelCase(association.from.field);
    relationshipName = _.endsWith(relationshipName, 'Id') ? relationshipName.slice(0, -2) : relationshipName;
    relationFrom.relationshipName = relationshipName;
    relationFrom.javadoc = `${relationFrom.javadoc}\n config: ${JSON.stringify({ joinColumnName: association.from.field })}`;
    const fromEntity = jdlObject.entities[_.upperFirst(_.camelCase(association.from.entity))];
    if (fromEntity) {
        if (fromEntity.jsonRelationships) {
            fromEntity.jsonRelationships.push(relationFrom);
        }
        else {
            fromEntity.jsonRelationships = [relationFrom];
        }
    }
}
function getFieldChnnameFromEntity(moduleName, pdManEntityTitle, fieldName) {
    const pdModule = pdManModules.find(pdManModule => pdManModule.name === moduleName);
    const entity = pdModule.entities.find(entity => entity.title === pdManEntityTitle);
    const field = entity.fields.find(field => field.name === fieldName);
    if (field) {
        return field.chnname || field.remark || '';
    }
    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.title));
    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],
    }));
}
