/**
 * Copyright 2013-2026 the original author or authors from the JHipster project.
 *
 * This file is part of the JHipster project, see https://www.jhipster.tech/
 * for more information.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { upperFirst } from 'lodash-es';
import BaseGenerator from "../base-simple-application/index.js";
import { JHIPSTER_CONFIG_DIR } from "../generator-constants.js";
import { CUSTOM_PRIORITIES, PRIORITY_NAMES, QUEUES } from "./priorities.js";
import { CONTEXT_DATA_APPLICATION_ENTITIES_KEY, getEntitiesFromDir } from "./support/index.js";
const { LOADING, COMPOSING_BOOTSTRAP, 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;
const PRIORITY_WITH_ENTITIES_TO_LOAD = new Set([LOADING_ENTITIES]);
const PRIORITY_WITH_ENTITIES = new Set([DEFAULT]);
const PRIORITY_WITH_FILTERED_ENTITIES = new Set([WRITING_ENTITIES, POST_WRITING_ENTITIES]);
const PRIORITY_WITH_SOURCE = new Set([PREPARING, POST_PREPARING, POST_WRITING, POST_WRITING_ENTITIES]);
const PRIORITY_WITH_APPLICATION_DEFAULTS = new Set([PREPARING, LOADING]);
const PRIORITY_WITH_APPLICATION = new Set([
    LOADING,
    COMPOSING_BOOTSTRAP,
    PREPARING,
    POST_PREPARING,
    DEFAULT,
    WRITING,
    POST_WRITING,
    PRE_CONFLICTS,
    INSTALL,
    END,
    CONFIGURING_EACH_ENTITY,
    LOADING_ENTITIES,
    PREPARING_EACH_ENTITY,
    PREPARING_EACH_ENTITY_FIELD,
    PREPARING_EACH_ENTITY_RELATIONSHIP,
    POST_PREPARING_EACH_ENTITY,
    WRITING_ENTITIES,
    POST_WRITING_ENTITIES,
]);
const getFirstArgForPriority = (priorityName) => ({
    source: PRIORITY_WITH_SOURCE.has(priorityName),
    application: PRIORITY_WITH_APPLICATION.has(priorityName),
    applicationDefaults: PRIORITY_WITH_APPLICATION_DEFAULTS.has(priorityName),
    entitiesToLoad: PRIORITY_WITH_ENTITIES_TO_LOAD.has(priorityName),
    entities: PRIORITY_WITH_ENTITIES.has(priorityName),
    filteredEntities: PRIORITY_WITH_FILTERED_ENTITIES.has(priorityName),
});
/**
 * This is the base class for a generator that generates entities.
 */
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);
    constructor(args, options, features) {
        super(args, options, features);
        if (this.options.help) {
            return;
        }
        this.registerPriorities(CUSTOM_PRIORITIES);
        /* Add tasks allowing entities priorities to match normal priorities pattern */
        this.on('queueOwnTasks', () => {
            this.log.debug('Queueing entity tasks');
            this.#queueEntityTasks();
        });
        this.on('before:render', (sourceBasename, context) => {
            const seed = `${context.entityClass}-${sourceBasename}${context.fakerSeed ?? ''}`;
            this.resetEntitiesFakeData(seed);
        });
    }
    get #entities() {
        return this.getContextData(CONTEXT_DATA_APPLICATION_ENTITIES_KEY, { factory: () => new Map() });
    }
    get #entitiesForTasks() {
        return [...this.#entities.entries()].map(([key, value]) => ({
            description: key,
            entityName: key,
            entity: value,
        }));
    }
    /**
     * @deprecated use dependsOnBootstrap('app')
     */
    dependsOnBootstrapApplication(options) {
        return this.dependsOnJHipster('jhipster:base-application:bootstrap', options);
    }
    /**
     * @deprecated use dependsOnBootstrap('server')
     */
    dependsOnBootstrapApplicationServer(options) {
        return this.dependsOnJHipster('jhipster:server:bootstrap', options);
    }
    /**
     * @deprecated use dependsOnBootstrap('client')
     */
    dependsOnBootstrapApplicationClient(options) {
        return this.dependsOnJHipster('jhipster:client:bootstrap', options);
    }
    /**
     * Get Entities configuration path
     */
    getEntitiesConfigPath(...args) {
        return this.destinationPath(JHIPSTER_CONFIG_DIR, ...args);
    }
    /**
     * Get Entity configuration path
     * @param entityName Entity name
     */
    getEntityConfigPath(entityName) {
        return this.getEntitiesConfigPath(`${upperFirst(entityName)}.json`);
    }
    /**
     * Get all the generator configuration from the .yo-rc.json file
     * @param entityName - Name of the entity to load.
     * @param create - Create storage if doesn't exists.
     */
    getEntityConfig(entityName, create = false) {
        const entityPath = this.getEntityConfigPath(entityName);
        if (!create && !this.fs.exists(entityPath))
            return undefined;
        return this.createStorage(entityPath, { transform: this.features.configTransform });
    }
    /**
     * get sorted list of entity names according to changelog date (i.e. the order in which they were added)
     */
    getExistingEntityNames() {
        return this.getExistingEntities().map(entity => entity.name);
    }
    /**
     * get sorted list of entities according to changelog date (i.e. the order in which they were added)
     */
    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 || []), ...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;
    }
    /**
     * Priority API stub for blueprints.
     *
     * Configuring each entity should configure entities.
     */
    get configuringEachEntity() {
        return {};
    }
    get loadingEntities() {
        return {};
    }
    get preparingEachEntity() {
        return {};
    }
    /**
     * Priority API stub for blueprints.
     */
    get preparingEachEntityField() {
        return {};
    }
    /**
     * Priority API stub for blueprints.
     */
    get preparingEachEntityRelationship() {
        return {};
    }
    /**
     * Priority API stub for blueprints.
     */
    get postPreparingEachEntity() {
        return {};
    }
    /**
     * Priority API stub for blueprints.
     */
    get writingEntities() {
        return {};
    }
    /**
     * Priority API stub for blueprints.
     */
    get postWritingEntities() {
        return {};
    }
    /**
     * Utility method to get typed objects for autocomplete.
     */
    asConfiguringEachEntityTaskGroup(taskGroup) {
        return taskGroup;
    }
    /**
     * Utility method to get typed objects for autocomplete.
     */
    asLoadingEntitiesTaskGroup(taskGroup) {
        return taskGroup;
    }
    /**
     * Utility method to get typed objects for autocomplete.
     */
    asPreparingEachEntityTaskGroup(taskGroup) {
        return taskGroup;
    }
    /**
     * Utility method to get typed objects for autocomplete.
     */
    asPreparingEachEntityFieldTaskGroup(taskGroup) {
        return taskGroup;
    }
    /**
     * Utility method to get typed objects for autocomplete.
     */
    asPreparingEachEntityRelationshipTaskGroup(taskGroup) {
        return taskGroup;
    }
    /**
     * Utility method to get typed objects for autocomplete.
     */
    asPostPreparingEachEntityTaskGroup(taskGroup) {
        return taskGroup;
    }
    /**
     * Utility method to get typed objects for autocomplete.
     */
    asWritingEntitiesTaskGroup(taskGroup) {
        return taskGroup;
    }
    /**
     * Utility method to get typed objects for autocomplete.
     */
    asPostWritingEntitiesTaskGroup(taskGroup) {
        return taskGroup;
    }
    /**
     * Reset entities fake data seed.
     * @param {string} seed
     */
    resetEntitiesFakeData(seed) {
        seed = `${this.context.baseName}-${seed}`;
        this.log.debug(`Resetting entities seed with '${seed}'`);
        for (const entity of this.#entities.values()) {
            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];
    }
    /**
     * @protected
     */
    getTaskFirstArgForPriority(priorityName) {
        const { application, applicationDefaults, source, entitiesToLoad, entities, filteredEntities } = getFirstArgForPriority(priorityName);
        const args = this.getApplicationArgForPriority({ application, applicationDefaults, source });
        if (entitiesToLoad) {
            args.entitiesToLoad = this.#getEntitiesDataToLoad();
        }
        if (entities) {
            args.entities = [...this.#entities.values()];
        }
        if (filteredEntities) {
            const { entities: entitiesToFilter = [] } = this.options;
            if (entitiesToFilter.length === 0) {
                args.entities = [...this.#entities.values()];
            }
            else {
                args.entities = [...this.#entities.values()].filter(entity => entitiesToFilter.includes(entity.name));
            }
        }
        return args;
    }
    /**
     * @private
     * Get entities to configure.
     * This method doesn't filter entities. An filtered config can be changed at this priority.
     * @returns {string[]}
     */
    #getEntitiesDataToConfigure() {
        return this.getExistingEntityNames().map(entityName => {
            const entityStorage = this.getEntityConfig(entityName, true);
            return { entityName, entityStorage, entityConfig: entityStorage.createProxy() };
        });
    }
    /**
     * @private
     * Get entities to load.
     * This method doesn't filter entities. An filtered config can be changed at this priority.
     * @returns {string[]}
     */
    #getEntitiesDataToLoad() {
        const application = this.context;
        const builtInEntities = [];
        if (application.generateBuiltInUserEntity) {
            // Reorder User entity to be the first one to be loaded
            builtInEntities.push('User');
        }
        if (application.generateBuiltInUserEntity && application.generateUserManagement) {
            // Reorder User entity to be the first one to be loaded
            builtInEntities.push('UserManagement');
        }
        if (application.generateBuiltInAuthorityEntity) {
            // Reorder User entity to be the first one to be loaded
            builtInEntities.push('Authority');
        }
        const entitiesToLoad = [...new Set([...builtInEntities, ...this.getExistingEntityNames()])];
        return entitiesToLoad.map(entityName => {
            const generator = this;
            if (!this.#entities.has(entityName)) {
                this.#entities.set(entityName, { name: entityName, fields: [], relationships: [] });
            }
            const entityBootstrap = this.#entities.get(entityName);
            return {
                entityName,
                get entityStorage() {
                    return generator.getEntityConfig(entityName, true);
                },
                get entityConfig() {
                    return generator.getEntityConfig(entityName, true).createProxy();
                },
                entityBootstrap,
            };
        });
    }
    /**
     * @private
     * Get entities to prepare.
     * @returns {object[]}
     */
    #getEntitiesDataToPrepare() {
        return this.#entitiesForTasks;
    }
    /**
     * @private
     * Get entities and fields to prepare.
     * @returns {object[]}
     */
    #getEntitiesFieldsDataToPrepare() {
        return this.#getEntitiesDataToPrepare().flatMap(({ entity, entityName, ...data }) => {
            if (!entity.fields)
                return [];
            return entity.fields.map(field => ({
                entity,
                entityName,
                ...data,
                field,
                fieldName: field.fieldName,
                description: `${entityName}#${field.fieldName}`,
            }));
        });
    }
    /**
     * @private
     * Get entities and relationships to prepare.
     * @returns {object[]}
     */
    #getEntitiesRelationshipsDataToPrepare() {
        return this.#getEntitiesDataToPrepare().flatMap(({ entity, entityName, ...data }) => {
            if (!entity.relationships)
                return [];
            return entity.relationships.map(relationship => ({
                entity,
                entityName,
                ...data,
                relationship,
                relationshipName: relationship.relationshipName,
                description: `${entityName}#${relationship.relationshipName}`,
            }));
        });
    }
    /**
     * @private
     * Get entities to post prepare.
     * @returns {object[]}
     */
    #getEntitiesDataToPostPrepare() {
        return this.#getEntitiesDataToPrepare();
    }
    /**
     * @private
     * Queue entity tasks.
     */
    #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.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.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,
                    });
                });
            },
        });
    }
}
