import EntityValidator from './entity-validator.js';
import FieldValidator from './field-validator.js';
import { fieldTypes, applicationOptions, relationshipOptions } from '../jhipster/index.js';
import ValidationValidator from './validation-validator.js';
import RelationshipValidator from './relationship-validator.js';
import EnumValidator from './enum-validator.js';
import DeploymentValidator from './deployment-validator.js';
import UnaryOptionValidator from './unary-option-validator.js';
import BinaryOptionValidator from './binary-option-validator.js';
const { OptionNames } = applicationOptions;
const { BUILT_IN_ENTITY } = relationshipOptions;
const { BLUEPRINTS, BASE_NAME } = OptionNames;
export default function createValidator(jdlObject, logger = console) {
    if (!jdlObject) {
        throw new Error('A JDL object must be passed to check for business errors.');
    }
    return {
        checkForErrors: () => {
            jdlObject.forEachApplication(jdlApplication => {
                const blueprints = jdlApplication.getConfigurationOptionValue(BLUEPRINTS);
                const checkReservedKeywords = (blueprints?.length ?? 0) === 0;
                checkForNamespaceConfigErrors(jdlApplication);
                checkForRelationshipErrors();
                checkForEntityErrors(jdlApplication, { checkReservedKeywords });
                checkForEnumErrors({ checkReservedKeywords });
                if (!checkReservedKeywords) {
                    logger.warn('Blueprints are being used, the JDL validation phase is skipped.');
                    return;
                }
                checkDeploymentsErrors();
                checkForOptionErrors();
            });
            checkForRelationshipsBetweenApplications();
        },
    };
    function checkForNamespaceConfigErrors(jdlApplication) {
        jdlApplication.forEachNamespaceConfiguration(config => {
            const blueprints = jdlApplication.config.getOption('blueprints');
            if (!blueprints || !blueprints.getValue().some(blueprint => blueprint === config.namespace)) {
                throw new Error(`Blueprint namespace config ${config.namespace} requires the blueprint ${config.namespace}`);
            }
        });
    }
    function checkForEntityErrors(jdlApplication, options) {
        if (jdlObject.getEntityQuantity() === 0) {
            return;
        }
        const validator = new EntityValidator();
        jdlObject.forEachEntity(jdlEntity => {
            if (!jdlApplication.hasEntityName(jdlEntity.name)) {
                return;
            }
            validator.validate(jdlEntity, options);
            checkForFieldErrors(jdlEntity.name, jdlEntity.fields, jdlApplication);
        });
    }
    function checkForFieldErrors(_entityName, jdlFields, _jdlApplication) {
        const validator = new FieldValidator();
        Object.keys(jdlFields).forEach(fieldName => {
            const jdlField = jdlFields[fieldName];
            validator.validate(jdlField);
            const isAnEnum = jdlObject.hasEnum(jdlField.type);
            checkForValidationErrors(jdlField, isAnEnum);
        });
    }
    function checkForValidationErrors(jdlField, isAnEnum) {
        const validator = new ValidationValidator();
        Object.keys(jdlField.validations).forEach(validationName => {
            const jdlValidation = jdlField.validations[validationName];
            validator.validate(jdlValidation);
            if (!fieldTypes.hasValidation(jdlField.type, jdlValidation.name, isAnEnum)) {
                throw new Error(`The validation '${jdlValidation.name}' isn't supported for the type '${jdlField.type}'.`);
            }
        });
    }
    function checkForRelationshipErrors() {
        if (jdlObject.getRelationshipQuantity() === 0) {
            return;
        }
        const validator = new RelationshipValidator();
        jdlObject.forEachRelationship(jdlRelationship => {
            validator.validate(jdlRelationship);
            checkForAbsentEntities({
                jdlRelationship,
                doesEntityExist: entityName => !!jdlObject.getEntity(entityName),
            });
        });
    }
    function checkForEnumErrors(options) {
        if (jdlObject.getEnumQuantity() === 0) {
            return;
        }
        const validator = new EnumValidator();
        jdlObject.forEachEnum(jdlEnum => {
            validator.validate(jdlEnum, options);
        });
    }
    function checkDeploymentsErrors() {
        if (jdlObject.getDeploymentQuantity() === 0) {
            return;
        }
        const validator = new DeploymentValidator();
        jdlObject.forEachDeployment(deployment => {
            validator.validate(deployment);
        });
    }
    function checkForOptionErrors() {
        if (jdlObject.getOptionQuantity() === 0) {
            return;
        }
        const unaryOptionValidator = new UnaryOptionValidator();
        const binaryOptionValidator = new BinaryOptionValidator();
        jdlObject.getOptions().forEach(option => {
            if (option.getType() === 'UNARY') {
                unaryOptionValidator.validate(option);
            }
            else {
                binaryOptionValidator.validate(option);
            }
        });
    }
    function checkForRelationshipsBetweenApplications() {
        const applicationsPerEntityNames = getApplicationsPerEntityNames(jdlObject);
        jdlObject.forEachRelationship(jdlRelationship => {
            checkIfRelationshipIsBetweenApplications({
                jdlRelationship,
                applicationsPerEntityName: applicationsPerEntityNames,
            });
        });
    }
}
function checkForAbsentEntities({ jdlRelationship, doesEntityExist, }) {
    const absentEntities = [];
    if (!doesEntityExist(jdlRelationship.from)) {
        absentEntities.push(jdlRelationship.from);
    }
    if (!doesEntityExist(jdlRelationship.to) && !jdlRelationship.options.global[BUILT_IN_ENTITY]) {
        absentEntities.push(jdlRelationship.to);
    }
    if (absentEntities.length !== 0) {
        throw new Error(`In the relationship between ${jdlRelationship.from} and ${jdlRelationship.to}, ` +
            `${absentEntities.join(' and ')} ${absentEntities.length === 1 ? 'is' : 'are'} not declared. If '${jdlRelationship.to}' is a built-in entity declare like '${jdlRelationship.from} to ${jdlRelationship.to} with builtInEntity'.`);
    }
}
function checkIfRelationshipIsBetweenApplications({ jdlRelationship, applicationsPerEntityName }) {
    let applicationsForSourceEntity = applicationsPerEntityName[jdlRelationship.from];
    let applicationsForDestinationEntity = applicationsPerEntityName[jdlRelationship.to];
    if (!applicationsForDestinationEntity || !applicationsForSourceEntity) {
        return;
    }
    applicationsForSourceEntity = applicationsForSourceEntity.map(jdlApplication => jdlApplication.getConfigurationOptionValue(BASE_NAME));
    applicationsForDestinationEntity = applicationsForDestinationEntity.map(jdlApplication => jdlApplication.getConfigurationOptionValue(BASE_NAME));
    const difference = applicationsForSourceEntity.filter(application => !applicationsForDestinationEntity.includes(application));
    if (difference.length !== 0) {
        throw new Error(`Entities for the ${jdlRelationship.type} relationship from '${jdlRelationship.from}' to '${jdlRelationship.to}' do not belong to the same application.`);
    }
}
function getApplicationsPerEntityNames(jdlObject) {
    const applicationsPerEntityName = {};
    jdlObject.forEachApplication(jdlApplication => {
        jdlApplication.forEachEntityName(entityName => {
            applicationsPerEntityName[entityName] = applicationsPerEntityName[entityName] || [];
            applicationsPerEntityName[entityName].push(jdlApplication);
        });
    });
    return applicationsPerEntityName;
}
