/**
 * 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 { glob } from 'node:fs/promises';
import { join } from 'node:path';
import { startCase } from 'lodash-es';
import BaseApplicationGenerator from "../../../base-application/generator.js";
import { QUEUES } from "../../../base-application/priorities.js";
import { getEnumInfo } from "../../../base-application/support/enum.js";
import { CLIENT_MAIN_SRC_DIR } from "../../../generator-constants.js";
import { findLanguageForTag } from "../../../languages/support/languages.js";
import TranslationData, { createTranslationsFileFilter, createTranslationsFilter } from "../../../languages/translation-data.js";
import { ClientApplicationGenerator } from "../../generator.js";
export default class I18NGenerator extends ClientApplicationGenerator {
    translationData;
    languagesToGenerate;
    async beforeQueue() {
        if (!this.fromBlueprint) {
            await this.composeWithBlueprints();
        }
        await this.dependsOnBootstrap('languages');
        await this.dependsOnBootstrap('client');
        // TODO entityClass is required for translations, should be moved to base application.
        await this.dependsOnBootstrap('java');
    }
    get configuring() {
        return this.asConfiguringTaskGroup({
            migrateLanguages({ control }) {
                if (control.isJhipsterVersionLessThan('7.10.0')) {
                    this.migrateLanguages({ in: 'id' });
                }
            },
        });
    }
    get [BaseApplicationGenerator.CONFIGURING]() {
        return this.delegateTasksToBlueprint(() => this.configuring);
    }
    get composing() {
        return this.asComposingTaskGroup({
            async bootstrap() {
                // Make sure generators languages callbacks are correctly initialized.
                const { clientFramework = 'no' } = this.jhipsterConfigWithDefaults;
                if (clientFramework !== 'no') {
                    try {
                        await this.dependsOnBootstrap(clientFramework);
                    }
                    catch {
                        this.log.warn(`${clientFramework} bootstrap generator not found. Client languages may not be updated.`);
                    }
                }
            },
        });
    }
    get [BaseApplicationGenerator.COMPOSING]() {
        return this.delegateTasksToBlueprint(() => this.composing);
    }
    get preparing() {
        return this.asPreparingTaskGroup({
            prepareForTemplates({ application, source }) {
                source.addEntityTranslationKey = ({ translationKey, translationValue, language }) => {
                    this.mergeDestinationJson(`${application.clientI18nDir}${language}/global.json`, {
                        global: {
                            menu: {
                                entities: {
                                    [translationKey]: translationValue,
                                },
                            },
                        },
                    });
                };
            },
        });
    }
    get [ClientApplicationGenerator.PREPARING]() {
        return this.delegateTasksToBlueprint(() => this.preparing);
    }
    get default() {
        return this.asDefaultTaskGroup({
            languagesToGenerate({ application }) {
                this.languagesToGenerate =
                    application.enableTranslation && application.languagesToGenerateDefinition ?
                        application.languagesToGenerateDefinition
                        // Generate native language + English as fallback, ensuring no duplicates if native language is English. Translation will fallback to English if a translation is missing for the native language.
                        : [...new Set([application.nativeLanguage, 'en'])].map(lang => findLanguageForTag(lang, application.supportedLanguages));
            },
            async loadNativeLanguage({ application }) {
                application.translations = application.translations ?? {};
                this.translationData = new TranslationData({ generator: this, translations: application.translations });
                const { clientI18nDir, enableTranslation, nativeLanguage } = application;
                const fallbackLanguage = 'en';
                this.queueLoadLanguages({ clientI18nDir, enableTranslation, nativeLanguage, fallbackLanguage });
                const filter = createTranslationsFilter({ clientI18nDir, nativeLanguage, fallbackLanguage });
                const listener = (filePath) => {
                    if (filter(filePath)) {
                        this.env.sharedFs.removeListener('change', listener);
                        this.queueLoadLanguages({ clientI18nDir, enableTranslation, nativeLanguage, fallbackLanguage });
                    }
                };
                this.env.sharedFs.on('change', listener);
                application.getWebappTranslation = (...args) => this.translationData.getClientTranslation(...args);
            },
        });
    }
    get [ClientApplicationGenerator.DEFAULT]() {
        return this.delegateTasksToBlueprint(() => this.default);
    }
    // Public API method used by the getter and also by Blueprints
    get writing() {
        return this.asWritingTaskGroup({
            async writeClientTranslations({ application }) {
                await Promise.all(this.languagesToGenerate.map(({ languageTag }) => this.writeFiles({
                    templates: [
                        {
                            condition: (data) => data.enableTranslation && (data.clientFrameworkVue || (data.clientFrameworkReact && !data.microfrontend)),
                            sourceFile: data => `${CLIENT_MAIN_SRC_DIR}/i18n/index_${data.clientBundler}.js`,
                            destinationFile: data => `${data.clientI18nDir}${data.lang}/${data.lang}.js`,
                        },
                    ],
                    context: {
                        ...application,
                        lang: languageTag,
                    },
                })));
            },
        });
    }
    get [BaseApplicationGenerator.WRITING]() {
        return this.delegateTasksToBlueprint(() => this.writing);
    }
    get writingEntities() {
        return this.asWritingEntitiesTaskGroup({
            async writeGlobalTranslations({ application }) {
                if ((application.commandName === 'entity' || application.commandName === 'entities') && application.enableTranslation) {
                    // For entity/entities sub generators, we need to load existing translations into mem-fs to be able to load them in the translation data.
                    for await (const file of glob(`${application.clientI18nDir}**/*.json`, { cwd: this.destinationRoot(), withFileTypes: true })) {
                        if (file.isFile()) {
                            this.env.sharedFs.get(join(file.parentPath, file.name));
                        }
                    }
                    return;
                }
                // writing/postWriting priorities are skipped by entity command, we need to write global translations here
                await Promise.all(this.languagesToGenerate.map(({ languageTag }) => this.writeFiles({
                    sections: {
                        clientI18nFiles: [
                            {
                                from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`,
                                to: context => `${context.clientI18nDir}${context.lang}/`,
                                transform: false,
                                templates: ['error.json', 'login.json', 'password.json', 'register.json', 'sessions.json', 'settings.json'],
                            },
                            {
                                from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`,
                                to: context => `${context.clientI18nDir}${context.lang}/`,
                                templates: ['activate.json', 'global.json', 'home.json', 'reset.json'],
                            },
                            {
                                condition: context => context.withAdminUi,
                                from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`,
                                to: context => `${context.clientI18nDir}${context.lang}/`,
                                transform: false,
                                templates: [
                                    'configuration.json',
                                    'logs.json',
                                    'metrics.json',
                                    {
                                        transform: true,
                                        file: 'health.json',
                                    },
                                ],
                            },
                            {
                                condition: (context) => context.communicationSpringWebsocket,
                                from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`,
                                to: context => `${context.clientI18nDir}${context.lang}/`,
                                transform: false,
                                templates: ['tracker.json'],
                            },
                            {
                                condition: context => context.applicationTypeGateway,
                                from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`,
                                to: context => `${context.clientI18nDir}${context.lang}/`,
                                transform: false,
                                templates: ['gateway.json'],
                            },
                        ],
                    },
                    context: {
                        ...application,
                        lang: languageTag,
                    },
                })));
            },
            async writeEntityFiles({ application, entities }) {
                const entitiesToWriteTranslationFor = entities.filter(entity => !entity.skipClient && !entity.builtInUser);
                if (application.userManagement?.skipClient) {
                    entitiesToWriteTranslationFor.push(application.userManagement);
                }
                // Copy each
                const { clientI18nDir, baseName, clientSrcDir, frontendAppName } = application;
                for (const entity of entitiesToWriteTranslationFor) {
                    for (const { languageTag } of this.languagesToGenerate) {
                        if (entity.builtInUserManagement) {
                            await this.writeFiles({
                                blocks: [
                                    {
                                        from: context => `${CLIENT_MAIN_SRC_DIR}/i18n/${context.lang}/`,
                                        to: context => `${context.clientI18nDir}${context.lang}/`,
                                        transform: false,
                                        templates: ['user-management.json'],
                                    },
                                ],
                                context: { ...entity, baseName, clientSrcDir, clientI18nDir, frontendAppName, lang: languageTag },
                            });
                        }
                        else {
                            await this.writeFiles({
                                sections: {
                                    entityBaseFiles: [
                                        {
                                            templates: [
                                                {
                                                    sourceFile: context => `entity/i18n/entity_${context.lang}.json.ejs`,
                                                    destinationFile: context => `${context.clientI18nDir}${context.lang}/${context.entityTranslationKey}.json`,
                                                },
                                            ],
                                        },
                                    ],
                                },
                                context: { ...entity, baseName, clientSrcDir, clientI18nDir, frontendAppName, lang: languageTag },
                            });
                        }
                    }
                }
                await Promise.all(entities
                    .filter(entity => !entity.skipClient && !entity.builtInUser)
                    .flatMap(entity => entity.fields.flatMap(field => {
                    if (!field.fieldIsEnum)
                        return [];
                    return this.languagesToGenerate.map(({ languageTag }) => this.writeFiles({
                        sections: {
                            enumBaseFiles: [
                                {
                                    templates: [
                                        {
                                            sourceFile: 'entity/i18n/enum.json.ejs',
                                            destinationFile: context => `${context.clientI18nDir}${context.lang}/${context.clientRootFolder}${context.enumInstance}.json`,
                                        },
                                    ],
                                },
                            ],
                        },
                        context: {
                            ...getEnumInfo(field, entity.clientRootFolder),
                            lang: languageTag,
                            frontendAppName,
                            clientI18nDir,
                            clientSrcDir,
                        },
                    }));
                })));
            },
        });
    }
    get [BaseApplicationGenerator.WRITING_ENTITIES]() {
        return this.delegateTasksToBlueprint(() => this.writingEntities);
    }
    get postWritingEntities() {
        return this.asPostWritingEntitiesTaskGroup({
            addEntities({ entities, source }) {
                for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtInUser)) {
                    for (const { languageTag } of this.languagesToGenerate) {
                        source.addEntityTranslationKey?.({
                            language: languageTag,
                            translationKey: entity.entityTranslationKeyMenu,
                            translationValue: entity.entityNameHumanized ?? startCase(entity.entityNameCapitalized),
                        });
                    }
                }
            },
        });
    }
    get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() {
        return this.delegateTasksToBlueprint(() => this.postWritingEntities);
    }
    migrateLanguages(languagesToMigrate) {
        const { languages, nativeLanguage } = this.jhipsterConfig;
        if (languagesToMigrate[nativeLanguage]) {
            this.jhipsterConfig.nativeLanguage = languagesToMigrate[nativeLanguage];
        }
        if (languages?.some(lang => languagesToMigrate[lang])) {
            this.jhipsterConfig.languages = languages.map(lang => languagesToMigrate[lang] ?? lang);
        }
    }
    queueLoadLanguages({ enableTranslation, clientI18nDir, nativeLanguage, fallbackLanguage = 'en', }) {
        this.queueTask({
            method: async () => {
                const filter = createTranslationsFileFilter({ clientI18nDir, nativeLanguage, fallbackLanguage });
                await this.pipeline({
                    name: 'loading translations',
                    filter: file => file.path.startsWith(this.destinationPath()) && filter(file),
                    pendingFiles: false,
                    refresh: true,
                }, this.translationData.loadFromStreamTransform({
                    enableTranslation,
                    clientI18nDir,
                    nativeLanguage,
                    fallbackLanguage,
                }));
            },
            taskName: 'loadingTranslations',
            queueName: QUEUES.LOADING_TRANSLATIONS_QUEUE,
            once: true,
        });
    }
}
