import { upperFirst } from 'lodash-es';
import BaseGenerator from '../base/index.js';
import { JHIPSTER_CONFIG_DIR } from '../generator-constants.js';
import { mutateData } from '../../lib/utils/object.js';
import { GENERATOR_BOOTSTRAP_APPLICATION, GENERATOR_BOOTSTRAP_APPLICATION_BASE, GENERATOR_BOOTSTRAP_APPLICATION_CLIENT, GENERATOR_BOOTSTRAP_APPLICATION_SERVER, } from '../generator-list.js';
import { getEntitiesFromDir } from './support/index.js';
import { CUSTOM_PRIORITIES, PRIORITY_NAMES, QUEUES } from './priorities.js';
const { LOADING, PREPARING, POST_PREPARING, CONFIGURING_EACH_ENTITY, LOADING_ENTITIES, PREPARING_EACH_ENTITY, PREPARING_EACH_ENTITY_FIELD, PREPARING_EACH_ENTITY_RELATIONSHIP, POST_PREPARING_EACH_ENTITY, DEFAULT, WRITING, POST_WRITING, WRITING_ENTITIES, POST_WRITING_ENTITIES, PRE_CONFLICTS, INSTALL, END, } = PRIORITY_NAMES;
const { CONFIGURING_EACH_ENTITY_QUEUE, LOADING_ENTITIES_QUEUE, PREPARING_EACH_ENTITY_QUEUE, PREPARING_EACH_ENTITY_FIELD_QUEUE, PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE, POST_PREPARING_EACH_ENTITY_QUEUE, WRITING_ENTITIES_QUEUE, POST_WRITING_ENTITIES_QUEUE, } = QUEUES;
const asPriority = BaseGenerator.asPriority;
export default class BaseApplicationGenerator extends BaseGenerator {
    static CONFIGURING_EACH_ENTITY = asPriority(CONFIGURING_EACH_ENTITY);
    static LOADING_ENTITIES = asPriority(LOADING_ENTITIES);
    static PREPARING_EACH_ENTITY = asPriority(PREPARING_EACH_ENTITY);
    static PREPARING_EACH_ENTITY_FIELD = asPriority(PREPARING_EACH_ENTITY_FIELD);
    static PREPARING_EACH_ENTITY_RELATIONSHIP = asPriority(PREPARING_EACH_ENTITY_RELATIONSHIP);
    static POST_PREPARING_EACH_ENTITY = asPriority(POST_PREPARING_EACH_ENTITY);
    static WRITING_ENTITIES = asPriority(WRITING_ENTITIES);
    static POST_WRITING_ENTITIES = asPriority(POST_WRITING_ENTITIES);
    initJdls = [];
    initJdl = false;
    constructor(args, options, features) {
        super(args, options, features);
        if (this.options.help) {
            return;
        }
        this.registerPriorities(CUSTOM_PRIORITIES);
        this.on('queueOwnTasks', () => {
            this.log.debug('Queueing entity tasks');
            this.queueEntityTasks();
        });
        if (this.options.applicationWithEntities) {
            this.log.warn('applicationWithEntities option is deprecated');
            this.config.set({
                ...this.config.getAll(),
                ...this.options.applicationWithEntities.config,
            });
            if (this.options.applicationWithEntities.entities) {
                const entities = this.options.applicationWithEntities.entities.map(entity => {
                    const entityName = upperFirst(entity.name);
                    const file = this.getEntityConfigPath(entityName);
                    this.fs.writeJSON(file, { ...this.fs.readJSON(file), ...entity });
                    return entityName;
                });
                this.jhipsterConfig.entities = [...new Set((this.jhipsterConfig.entities || []).concat(entities))];
            }
            delete this.options.applicationWithEntities;
        }
    }
    dependsOnBootstrapApplication(options) {
        return this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION, options);
    }
    dependsOnBootstrapApplicationBase(options) {
        return this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_BASE, options);
    }
    dependsOnBootstrapApplicationServer(options) {
        return this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_SERVER, options);
    }
    dependsOnBootstrapApplicationClient(options) {
        return this.dependsOnJHipster(GENERATOR_BOOTSTRAP_APPLICATION_CLIENT, options);
    }
    getEntitiesConfigPath(...args) {
        return this.destinationPath(JHIPSTER_CONFIG_DIR, ...args);
    }
    getEntityConfigPath(entityName) {
        return this.getEntitiesConfigPath(`${upperFirst(entityName)}.json`);
    }
    getEntityConfig(entityName, create = false) {
        const entityPath = this.getEntityConfigPath(entityName);
        if (!create && !this.fs.exists(entityPath))
            return undefined;
        return this.createStorage(entityPath);
    }
    getExistingEntityNames() {
        return this.getExistingEntities().map(entity => entity.name);
    }
    getExistingEntities() {
        function isBefore(e1, e2) {
            return (e1.definition.annotations?.changelogDate ?? 0) - (e2.definition.annotations?.changelogDate ?? 0);
        }
        const configDir = this.getEntitiesConfigPath();
        const entities = [];
        for (const entityName of [...new Set((this.jhipsterConfig.entities || []).concat(getEntitiesFromDir(configDir)))]) {
            const definition = this.getEntityConfig(entityName)?.getAll();
            if (definition) {
                entities.push({ name: entityName, definition });
            }
        }
        entities.sort(isBefore);
        this.jhipsterConfig.entities = entities.map(({ name }) => name);
        return entities;
    }
    get configuringEachEntity() {
        return {};
    }
    get preparingEachEntity() {
        return {};
    }
    get preparingEachEntityField() {
        return {};
    }
    get preparingEachEntityRelationship() {
        return {};
    }
    get postPreparingEachEntity() {
        return {};
    }
    get writingEntities() {
        return {};
    }
    get postWritingEntities() {
        return {};
    }
    asConfiguringEachEntityTaskGroup(taskGroup) {
        return taskGroup;
    }
    asLoadingEntitiesTaskGroup(taskGroup) {
        return taskGroup;
    }
    asPreparingEachEntityTaskGroup(taskGroup) {
        return taskGroup;
    }
    asPreparingEachEntityFieldTaskGroup(taskGroup) {
        return taskGroup;
    }
    asPreparingEachEntityRelationshipTaskGroup(taskGroup) {
        return taskGroup;
    }
    asPostPreparingEachEntityTaskGroup(taskGroup) {
        return taskGroup;
    }
    asWritingEntitiesTaskGroup(taskGroup) {
        return taskGroup;
    }
    asPostWritingEntitiesTaskGroup(taskGroup) {
        return taskGroup;
    }
    resetEntitiesFakeData(seed) {
        seed = `${this.sharedData.getApplication().baseName}-${seed}`;
        this.log.debug(`Resetting entities seed with '${seed}'`);
        this.sharedData.getEntities().forEach(({ entity }) => {
            entity.resetFakerSeed(seed);
        });
    }
    getArgsForPriority(priorityName) {
        const args = super.getArgsForPriority(priorityName);
        let firstArg = this.getTaskFirstArgForPriority(priorityName);
        if (args.length > 0) {
            firstArg = { ...args[0], ...firstArg };
        }
        return [firstArg];
    }
    getTaskFirstArgForPriority(priorityName) {
        if (![
            LOADING,
            PREPARING,
            POST_PREPARING,
            CONFIGURING_EACH_ENTITY,
            LOADING_ENTITIES,
            PREPARING_EACH_ENTITY,
            PREPARING_EACH_ENTITY_FIELD,
            PREPARING_EACH_ENTITY_RELATIONSHIP,
            POST_PREPARING_EACH_ENTITY,
            DEFAULT,
            WRITING,
            WRITING_ENTITIES,
            POST_WRITING,
            POST_WRITING_ENTITIES,
            PRE_CONFLICTS,
            INSTALL,
            END,
        ].includes(priorityName)) {
            return {};
        }
        if (!this.jhipsterConfig.baseName) {
            throw new Error(`BaseName (${this.jhipsterConfig.baseName}) application not available for priority ${priorityName}`);
        }
        const application = this.sharedData.getApplication();
        if ([PREPARING, LOADING].includes(priorityName)) {
            return {
                application,
                applicationDefaults: data => mutateData(application, { __override__: false, ...data }),
            };
        }
        if (LOADING_ENTITIES === priorityName) {
            return {
                application,
                entitiesToLoad: this.getEntitiesDataToLoad(),
            };
        }
        if ([DEFAULT].includes(priorityName)) {
            return {
                application,
                ...this.getEntitiesDataForPriorities(),
            };
        }
        if ([WRITING_ENTITIES, POST_WRITING_ENTITIES].includes(priorityName)) {
            const applicationAndEntities = {
                application,
                ...this.getEntitiesDataToWrite(),
            };
            if (priorityName === WRITING_ENTITIES) {
                return applicationAndEntities;
            }
            return {
                ...applicationAndEntities,
                source: this.sharedData.getSource(),
            };
        }
        return { application };
    }
    getEntitiesDataToConfigure() {
        return this.getExistingEntityNames().map(entityName => {
            const entityStorage = this.getEntityConfig(entityName, true);
            return { entityName, entityStorage, entityConfig: entityStorage.createProxy() };
        });
    }
    getEntitiesDataToLoad() {
        const application = this.sharedData.getApplication();
        const builtInEntities = [];
        if (application.generateBuiltInUserEntity) {
        }
        if (application.generateBuiltInUserEntity && application.generateUserManagement) {
        }
        if (application.generateBuiltInAuthorityEntity) {
        }
        const entitiesToLoad = [...new Set([...builtInEntities, ...this.getExistingEntityNames()])];
        return entitiesToLoad.map(entityName => {
            const generator = this;
            if (!this.sharedData.hasEntity(entityName)) {
                this.sharedData.setEntity(entityName, { name: entityName });
            }
            const entityBootstrap = this.sharedData.getEntity(entityName);
            return {
                entityName,
                get entityStorage() {
                    return generator.getEntityConfig(entityName, true);
                },
                get entityConfig() {
                    return generator.getEntityConfig(entityName, true).createProxy();
                },
                entityBootstrap,
            };
        });
    }
    getEntitiesDataToPrepare() {
        return this.sharedData.getEntities().map(({ entityName, ...data }) => ({
            description: entityName,
            entityName,
            ...data,
        }));
    }
    getEntitiesFieldsDataToPrepare() {
        return this.getEntitiesDataToPrepare()
            .map(({ entity, entityName, ...data }) => {
            if (!entity.fields)
                return [];
            return entity.fields.map(field => ({
                entity,
                entityName,
                ...data,
                field,
                fieldName: field.fieldName,
                description: `${entityName}#${field.fieldName}`,
            }));
        })
            .flat();
    }
    getEntitiesRelationshipsDataToPrepare() {
        return this.getEntitiesDataToPrepare()
            .map(({ entity, entityName, ...data }) => {
            if (!entity.relationships)
                return [];
            return entity.relationships.map(relationship => ({
                entity,
                entityName,
                ...data,
                relationship,
                relationshipName: relationship.relationshipName,
                description: `${entityName}#${relationship.relationshipName}`,
            }));
        })
            .flat();
    }
    getEntitiesDataToPostPrepare() {
        return this.getEntitiesDataToPrepare();
    }
    getEntitiesDataForPriorities() {
        const entitiesDefinitions = this.sharedData.getEntities();
        return { entities: entitiesDefinitions.map(({ entity }) => entity) };
    }
    getEntitiesDataToWrite() {
        const { entities = [] } = this.options;
        const data = this.getEntitiesDataForPriorities();
        if (entities.length === 0)
            return data;
        const filteredEntities = data.entities.filter(entity => entities.includes(entity.name));
        return { ...data, entities: filteredEntities };
    }
    queueEntityTasks() {
        this.queueTask({
            queueName: CONFIGURING_EACH_ENTITY_QUEUE,
            taskName: 'queueConfiguringEachEntity',
            cancellable: true,
            method: () => {
                if (this.options.skipPriorities?.includes(CONFIGURING_EACH_ENTITY))
                    return;
                this.log.debug(`Queueing entity tasks ${CONFIGURING_EACH_ENTITY}`);
                const tasks = this.extractTasksFromPriority(CONFIGURING_EACH_ENTITY, { skip: false });
                this.getEntitiesDataToConfigure().forEach(({ entityName, entityStorage, entityConfig }) => {
                    this.log.debug(`Queueing entity tasks ${CONFIGURING_EACH_ENTITY} for ${entityName}`);
                    const args = this.getArgsForPriority(CONFIGURING_EACH_ENTITY);
                    tasks.forEach(task => {
                        this.queueTask({
                            ...task,
                            args: [{ ...args[0], entityName, entityStorage, entityConfig }],
                        });
                    });
                });
            },
        });
        this.queueTask({
            queueName: LOADING_ENTITIES_QUEUE,
            taskName: 'queueLoadingEntities',
            cancellable: true,
            method: () => {
                if (this.options.skipPriorities?.includes(LOADING_ENTITIES))
                    return;
                this.log.debug(`Queueing entity tasks ${LOADING_ENTITIES}`);
                const tasks = this.extractTasksFromPriority(LOADING_ENTITIES, { skip: false });
                this.log.debug(`Queueing entity tasks ${LOADING_ENTITIES}`);
                const args = this.getArgsForPriority(LOADING_ENTITIES);
                tasks.forEach(task => {
                    this.queueTask({
                        ...task,
                        args,
                    });
                });
            },
        });
        this.queueTask({
            queueName: PREPARING_EACH_ENTITY_QUEUE,
            taskName: 'queuePreparingEachEntity',
            cancellable: true,
            method: () => {
                if (this.options.skipPriorities?.includes(PREPARING_EACH_ENTITY))
                    return;
                this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY}`);
                const tasks = this.extractTasksFromPriority(PREPARING_EACH_ENTITY, { skip: false });
                this.getEntitiesDataToPrepare().forEach(({ description, ...data }) => {
                    this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY} for ${description}`);
                    const args = this.getArgsForPriority(PREPARING_EACH_ENTITY);
                    tasks.forEach(task => {
                        this.queueTask({
                            ...task,
                            args: [{ ...args[0], description, ...data }],
                        });
                    });
                });
            },
        });
        this.queueTask({
            queueName: PREPARING_EACH_ENTITY_FIELD_QUEUE,
            taskName: 'queuePreparingEachEntityField',
            cancellable: true,
            method: () => {
                if (this.options.skipPriorities?.includes(PREPARING_EACH_ENTITY_FIELD))
                    return;
                const tasks = this.extractTasksFromPriority(PREPARING_EACH_ENTITY_FIELD, { skip: false });
                this.getEntitiesFieldsDataToPrepare().forEach(({ description, ...data }) => {
                    this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY_FIELD} for ${description}`);
                    const args = this.getArgsForPriority(PREPARING_EACH_ENTITY_FIELD);
                    tasks.forEach(task => {
                        this.queueTask({
                            ...task,
                            args: [{ ...args[0], description, ...data }],
                        });
                    });
                });
            },
        });
        this.queueTask({
            queueName: PREPARING_EACH_ENTITY_RELATIONSHIP_QUEUE,
            taskName: 'queuePreparingEachEntityRelationship',
            cancellable: true,
            method: () => {
                if (this.options.skipPriorities?.includes(PREPARING_EACH_ENTITY_RELATIONSHIP))
                    return;
                const tasks = this.extractTasksFromPriority(PREPARING_EACH_ENTITY_RELATIONSHIP, { skip: false });
                this.getEntitiesRelationshipsDataToPrepare().forEach(({ description, ...data }) => {
                    this.log.debug(`Queueing entity tasks ${PREPARING_EACH_ENTITY_RELATIONSHIP} for ${description}`);
                    const args = this.getArgsForPriority(PREPARING_EACH_ENTITY_RELATIONSHIP);
                    tasks.forEach(task => {
                        this.queueTask({
                            ...task,
                            args: [{ ...args[0], description, ...data }],
                        });
                    });
                });
            },
        });
        this.queueTask({
            queueName: POST_PREPARING_EACH_ENTITY_QUEUE,
            taskName: 'queuePostPreparingEachEntity',
            cancellable: true,
            method: () => {
                if (this.options.skipPriorities?.includes(POST_PREPARING_EACH_ENTITY))
                    return;
                const tasks = this.extractTasksFromPriority(POST_PREPARING_EACH_ENTITY, { skip: false });
                this.getEntitiesDataToPostPrepare().forEach(({ description, ...data }) => {
                    this.log.debug(`Queueing entity tasks ${POST_PREPARING_EACH_ENTITY} for ${description}`);
                    const args = this.getArgsForPriority(POST_PREPARING_EACH_ENTITY);
                    tasks.forEach(task => {
                        this.queueTask({
                            ...task,
                            args: [{ ...args[0], description, ...data }],
                        });
                    });
                });
            },
        });
        this.queueTask({
            queueName: WRITING_ENTITIES_QUEUE,
            taskName: 'queueWritingEachEntity',
            cancellable: true,
            method: () => {
                if (this.options.skipWriting || this.options.skipPriorities?.includes(WRITING_ENTITIES))
                    return;
                const tasks = this.extractTasksFromPriority(WRITING_ENTITIES, { skip: false });
                const args = this.getArgsForPriority(WRITING_ENTITIES);
                tasks.forEach(task => {
                    this.queueTask({
                        ...task,
                        args,
                    });
                });
            },
        });
        this.queueTask({
            queueName: POST_WRITING_ENTITIES_QUEUE,
            taskName: 'queuePostWritingEachEntity',
            cancellable: true,
            method: () => {
                if (this.options.skipWriting || this.options.skipPriorities?.includes(POST_WRITING_ENTITIES))
                    return;
                const tasks = this.extractTasksFromPriority(POST_WRITING_ENTITIES, { skip: false });
                const args = this.getArgsForPriority(POST_WRITING_ENTITIES);
                tasks.forEach(task => {
                    this.queueTask({
                        ...task,
                        args,
                    });
                });
            },
        });
    }
}
