import * as fs from 'node:fs';
import { join } from 'node:path';
import { IndentationText, Node, Project, QuoteKind } from 'ts-morph';
import { NewLineKind } from 'typescript';
import { ADMIN_CONTAINER, API_CONTAINER, APP_CONTAINER, DASHBOARD_CONTAINER, DASHBOARD_TRANSLATIONS, SCHEMA_DEFINITION_EXTENSION, } from '../../domain/config.js';
import { getForeignRelationshipProperties, getWithoutTimestampsProperties, hasI18nProperties, uuid, } from '../../domain/property-filters.js';
import { toCamelCase, toKebabCase, toPascalCase, } from '../../utils/string-utils.js';
import { formatCode } from '../prettier-engine.js';
import { ArrayDriver } from './drivers/array.driver.js';
import { ImportDriver } from './drivers/import.driver.js';
import { getInitializer } from './helpers.js';
export class CodeWriter {
    aggregateName;
    apiDirectory;
    appDirectory;
    boundedContextName;
    hasI18n;
    moduleName;
    moduleNames;
    srcDirectory;
    project;
    savedFiles = new Set();
    constructor(opts) {
        this.aggregateName = opts.aggregateName;
        this.apiDirectory = opts.apiDirectory;
        this.appDirectory = opts.appDirectory;
        this.boundedContextName = opts.boundedContextName;
        this.hasI18n = opts.hasI18n;
        this.moduleName = opts.moduleName;
        this.moduleNames = opts.moduleNames;
        this.srcDirectory = opts.srcDirectory;
        this.project = new Project({
            manipulationSettings: {
                indentationText: IndentationText.TwoSpaces,
                newLineKind: NewLineKind.LineFeed,
                quoteKind: QuoteKind.Single,
                usePrefixAndSuffixTextForRename: false,
                useTrailingCommas: false,
            },
            tsConfigFilePath: join(process.cwd(), opts.srcDirectory, '..', 'tsconfig.json'),
        });
    }
    addSeedPermissions() {
        const bcKebab = toKebabCase(this.boundedContextName);
        const bcCamel = toCamelCase(this.boundedContextName);
        const modCamel = toCamelCase(this.moduleName);
        const seedFilePath = join(process.cwd(), this.srcDirectory, this.appDirectory, bcKebab, `${bcKebab}.seed.ts`);
        if (!fs.existsSync(seedFilePath))
            return;
        const sourceFile = this.project.addSourceFileAtPath(seedFilePath);
        const regionTag = `${bcCamel}.${modCamel}`;
        if (sourceFile.getFullText().includes(`'${regionTag}.access'`)) {
            return;
        }
        const permissionsDecl = sourceFile.getVariableDeclaration('permissions');
        if (!permissionsDecl)
            return;
        const permissionsArray = getInitializer(permissionsDecl);
        if (!permissionsArray)
            return;
        // boundedContextId comes from the first entry in `boundedContexts`
        const bcDecl = sourceFile.getVariableDeclaration('boundedContexts');
        if (!bcDecl)
            return;
        const bcArray = getInitializer(bcDecl);
        const firstBc = bcArray.getElements()[0];
        const idProp = firstBc.getPropertyOrThrow('id');
        const boundedContextId = getInitializer(idProp)
            ?.getText()
            .slice(1, -1);
        // Deterministic UUIDs per (module, action) keep regen idempotent.
        const actions = ['access', 'get', 'create', 'update', 'upsert', 'delete'];
        const entries = actions.map((action) => {
            const permName = `${regionTag}.${action}`;
            return [
                '  {',
                `    id: '${uuid(permName)}',`,
                `    name: '${permName}',`,
                `    boundedContextId: '${boundedContextId}',`,
                '    roleIds: [],',
                '  },',
            ].join('\n');
        });
        const block = [
            '',
            `  /* #region ${regionTag} */`,
            entries.join('\n'),
            `  /* #endregion ${regionTag} */`,
        ].join('\n');
        const arrayEnd = permissionsArray.getEnd() - 1;
        sourceFile.insertText(arrayEnd, block + '\n');
        this.saveAndTrack(sourceFile);
    }
    declareBackApplicationItemsInModule() {
        const bcKebab = toKebabCase(this.boundedContextName);
        const bcPascal = toPascalCase(this.boundedContextName);
        const modKebab = toKebabCase(this.moduleName);
        const modPascal = toPascalCase(this.moduleName);
        const sourceFile = this.project.addSourceFileAtPath(join(process.cwd(), this.srcDirectory, this.apiDirectory, bcKebab, `${bcKebab}.module.ts`));
        const decoratorArgs = this.getModuleDecoratorArguments(sourceFile, `${bcPascal}Module`, 'Module');
        const providersArray = getInitializer(decoratorArgs.getProperty('providers'));
        const controllersArray = getInitializer(decoratorArgs.getProperty('controllers'));
        const appImports = [
            `${bcPascal}Models`,
            `${bcPascal}Handlers`,
            `${bcPascal}Services`,
            `${bcPascal}Repositories`,
        ];
        ImportDriver.createImportItems(sourceFile, `${APP_CONTAINER}/${bcKebab}`, appImports);
        const apiImports = [
            `${bcPascal}${modPascal}ApiControllers`,
            `${bcPascal}${modPascal}ApiResolvers`,
        ];
        ImportDriver.createImportItems(sourceFile, `./${modKebab}`, apiImports);
        const modelArrayArgument = this.getModelArrayArgument(decoratorArgs);
        ArrayDriver.addArrayItem(sourceFile, `...${bcPascal}Models`, modelArrayArgument);
        const providerItems = [
            `...${bcPascal}Handlers`,
            `...${bcPascal}Services`,
            `...${bcPascal}Repositories`,
        ];
        providerItems.push(`...${bcPascal}${modPascal}ApiResolvers`);
        ArrayDriver.addArrayItems(sourceFile, providerItems, providersArray);
        ArrayDriver.addArrayItem(sourceFile, `...${bcPascal}${modPascal}ApiControllers`, controllersArray);
        this.saveAndTrack(sourceFile);
    }
    declareBackBoundedContextModuleInApplicationModule() {
        const bcKebab = toKebabCase(this.boundedContextName);
        const bcPascal = toPascalCase(this.boundedContextName);
        const sourceFile = this.project.addSourceFileAtPath(join(process.cwd(), this.srcDirectory, 'app.module.ts'));
        const decoratorArgs = this.getModuleDecoratorArguments(sourceFile, 'AppModule', 'Module');
        const modules = ImportDriver.getImportedDeclarations(sourceFile);
        if (modules.includes(`${bcPascal}Module`))
            return;
        ImportDriver.createImportItems(sourceFile, `${API_CONTAINER}/${bcKebab}/${bcKebab}.module`, [`${bcPascal}Module`]);
        const importsArray = getInitializer(decoratorArgs.getProperty('imports'));
        ArrayDriver.addArrayItems(sourceFile, [`${bcPascal}Module`], importsArray);
        this.saveAndTrack(sourceFile);
    }
    declareFrontBoundedContext() {
        const bcKebab = toKebabCase(this.boundedContextName);
        const routesPath = join(process.cwd(), this.srcDirectory, ADMIN_CONTAINER, 'routes.ts');
        if (!fs.existsSync(routesPath))
            return;
        const sourceFile = this.project.addSourceFileAtPath(routesPath);
        // Find the children array inside the default-exported route object.
        // Structure: export default [{ path: '', children: [ ... ] }]
        const defaultExport = sourceFile.getExportAssignment((d) => !d.isExportEquals());
        if (!defaultExport)
            return;
        // Handle both `export default [...]` and `export default [...] satisfies Routes`
        let expr = defaultExport.getExpression();
        if (expr && 'getExpression' in expr && expr.getKindName() === 'SatisfiesExpression') {
            expr = expr.getExpression();
        }
        const routesArray = expr;
        if (!routesArray?.getElements)
            return;
        // First element is the layout route with `children`
        const layoutRoute = routesArray.getElements()[0];
        if (!layoutRoute)
            return;
        const childrenProp = layoutRoute.getProperty('children');
        if (!childrenProp)
            return;
        const childrenArray = getInitializer(childrenProp);
        for (const element of childrenArray.getElements()) {
            const pathProp = element.getProperty('path');
            if (!pathProp)
                continue;
            const pathValue = getInitializer(pathProp);
            if (pathValue.getText() === `'${bcKebab}'`)
                return;
        }
        childrenArray.addElement(`{\n  path: '${bcKebab}',\n  loadChildren: () => import('./bounded-contexts/${bcKebab}/${bcKebab}.routes').then((m) => m.${toCamelCase(this.boundedContextName)}Routes),\n}`, { useNewLines: true });
        this.saveAndTrack(sourceFile);
    }
    declareFrontComponents() {
        const bcKebab = toKebabCase(this.boundedContextName);
        const bcPascal = toPascalCase(this.boundedContextName);
        const modKebab = toKebabCase(this.moduleName);
        const modPascal = toPascalCase(this.moduleName);
        const sourceFile = this.project.addSourceFileAtPath(join(process.cwd(), this.srcDirectory, DASHBOARD_CONTAINER, bcKebab, `${bcKebab}.module.ts`));
        const decoratorArgs = this.getModuleDecoratorArguments(sourceFile, `${bcPascal}Module`, 'NgModule');
        const declarationsArray = getInitializer(decoratorArgs.getProperty('declarations'));
        ImportDriver.createImportItems(sourceFile, `./${modKebab}/${modKebab}-list.component`, [`${modPascal}ListComponent`]);
        ImportDriver.createImportItems(sourceFile, `./${modKebab}/${modKebab}-detail.component`, [`${modPascal}DetailComponent`]);
        ArrayDriver.addArrayItems(sourceFile, [`${modPascal}DetailComponent`, `${modPascal}ListComponent`], declarationsArray);
        this.saveAndTrack(sourceFile);
    }
    async formatSavedFiles() {
        for (const filePath of this.savedFiles) {
            const code = fs.readFileSync(filePath, 'utf8');
            // eslint-disable-next-line no-await-in-loop
            const formatted = await formatCode(code, filePath);
            if (formatted !== code) {
                fs.writeFileSync(filePath, formatted, 'utf8');
            }
        }
    }
    generateBackAppBoundedContextReferences(properties, excluded = []) {
        if (excluded.includes('src/@app/**'))
            return;
        const bcKebab = toKebabCase(this.boundedContextName);
        const bcPascal = toPascalCase(this.boundedContextName);
        const modKebab = toKebabCase(this.moduleName);
        const modPascal = toPascalCase(this.moduleName);
        const sourceFile = this.project.addSourceFileAtPath(join(process.cwd(), this.srcDirectory, this.appDirectory, bcKebab, 'index.ts'));
        const imports = [
            `${bcPascal}${modPascal}Handlers`,
            `${bcPascal}${modPascal}Services`,
            `${bcPascal}${modPascal}Model`,
            `${bcPascal}Sequelize${modPascal}Repository`,
        ];
        ImportDriver.createImportItems(sourceFile, `./${modKebab}`, imports);
        ArrayDriver.addArrayItem(sourceFile, `...${bcPascal}${modPascal}Handlers`, `${bcPascal}Handlers`);
        ArrayDriver.addArrayItem(sourceFile, `...${bcPascal}${modPascal}Services`, `${bcPascal}Services`);
        ArrayDriver.addArrayItem(sourceFile, `${bcPascal}${modPascal}Model`, `${bcPascal}Models`);
        ArrayDriver.addArrayItem(sourceFile, `${bcPascal}Sequelize${modPascal}Repository`, `${bcPascal}Repositories`);
        if (hasI18nProperties(properties)) {
            const i18nImports = [
                `${bcPascal}${modPascal}I18nModel`,
                `${bcPascal}Sequelize${modPascal}I18nRepository`,
            ];
            ImportDriver.createImportItems(sourceFile, `./${modKebab}`, i18nImports);
            ArrayDriver.addArrayItem(sourceFile, `${bcPascal}${modPascal}I18nModel`, `${bcPascal}Models`);
            ArrayDriver.addArrayItem(sourceFile, `${bcPascal}Sequelize${modPascal}I18nRepository`, `${bcPascal}Repositories`);
        }
        this.saveAndTrack(sourceFile);
    }
    generateBackTestingForeignReferences(properties) {
        const bcKebab = toKebabCase(this.boundedContextName);
        const foreignRelationships = getForeignRelationshipProperties(properties, this.boundedContextName);
        const cliterDir = join(process.cwd(), 'cliter', bcKebab);
        if (!fs.existsSync(cliterDir))
            return;
        const yamlFiles = fs.readdirSync(cliterDir);
        for (const foreignRelationship of foreignRelationships) {
            if (!foreignRelationship.relationship?.modulePath)
                return;
            for (const yamlFile of yamlFiles) {
                const e2eTestFile = yamlFile.replace(SCHEMA_DEFINITION_EXTENSION, '.e2e-spec.ts');
                const e2eTestPath = join(process.cwd(), 'test', 'acceptance', bcKebab, e2eTestFile);
                if (fs.existsSync(e2eTestPath)) {
                    const sourceFile = this.project.addSourceFileAtPath(e2eTestPath);
                    // NOTE: Foreign module imports/registrations are disabled upstream
                    // (can be micro-services). Keeping the save in case future logic is added.
                    this.saveAndTrack(sourceFile);
                }
            }
        }
    }
    /**
     * Emit `{bc}-icons.providers.ts` next to `{bc}.navigation.ts`. The aggregator exports a
     * single object literal `{bcCamel}Icons` whose keys are every `lucide*` icon referenced
     * by `NavItem.icon` / `NavSubItem.icon` in the navigation file (root + nested `items[]`),
     * deduplicated and sorted alphabetically. The file is `@aurora-catalyst-generated` and is
     * fully overwritten on each regen — no preservation regions, no incremental prune.
     * No-op when `{bc}.navigation.ts` is missing or has no `{bcCamel}Navigation` declaration.
     */
    generateFrontIconsProviders(srcDirectory, boundedContextName) {
        const bcKebab = toKebabCase(boundedContextName);
        const bcCamel = toCamelCase(boundedContextName);
        const navFilePath = join(process.cwd(), srcDirectory, DASHBOARD_CONTAINER, bcKebab, `${bcKebab}.navigation.ts`);
        if (!fs.existsSync(navFilePath))
            return;
        const sourceFile = this.project.addSourceFileAtPath(navFilePath);
        const navigation = sourceFile.getVariableDeclaration(`${bcCamel}Navigation`);
        if (!navigation)
            return;
        const navArray = getInitializer(navigation);
        if (!navArray?.getElements)
            return;
        const icons = new Set();
        const collectIcons = (item) => {
            const iconProp = item.getProperty('icon');
            if (iconProp) {
                const iconValue = getInitializer(iconProp);
                const literal = iconValue.getLiteralValue?.() ?? '';
                if (literal)
                    icons.add(literal);
            }
            const itemsProp = item.getProperty('items');
            if (!itemsProp)
                return;
            const itemsArr = getInitializer(itemsProp);
            if (!itemsArr?.getElements)
                return;
            for (const child of itemsArr.getElements()) {
                collectIcons(child);
            }
        };
        for (const root of navArray.getElements()) {
            collectIcons(root);
        }
        const sortedIcons = [...icons].sort();
        const aggregatorPath = join(process.cwd(), srcDirectory, DASHBOARD_CONTAINER, bcKebab, `${bcKebab}-icons.providers.ts`);
        const content = buildIconsAggregatorContent(bcKebab, bcCamel, sortedIcons);
        const aggregatorSourceFile = this.project.createSourceFile(aggregatorPath, content, { overwrite: true });
        this.saveAndTrack(aggregatorSourceFile);
    }
    /**
     * Append a NavItem for the current module inside `{bc}.navigation.ts` of the admin dashboard.
     * Idempotent — silently skips if the entry url already exists. No-op when the BC does not
     * use the dashboard (file/variable/items array missing). When `outlineIcon` is provided
     * it becomes the NavItem `icon`.
     */
    // eslint-disable-next-line max-params
    generateFrontNavigation(srcDirectory, boundedContextName, moduleName, moduleNames, outlineIcon) {
        const bcKebab = toKebabCase(boundedContextName);
        const bcCamel = toCamelCase(boundedContextName);
        const modKebab = toKebabCase(moduleName);
        const modNamesPascal = toPascalCase(moduleNames);
        const navFilePath = join(process.cwd(), srcDirectory, DASHBOARD_CONTAINER, bcKebab, `${bcKebab}.navigation.ts`);
        if (!fs.existsSync(navFilePath))
            return;
        const sourceFile = this.project.addSourceFileAtPath(navFilePath);
        // Locate `export const {bcCamel}Navigation: NavItem[] = [{ ..., items: [] }]`
        const navigation = sourceFile.getVariableDeclaration(`${bcCamel}Navigation`);
        if (!navigation)
            return;
        const navArray = getInitializer(navigation);
        if (!navArray?.getElements)
            return;
        const bcNavItem = navArray.getElements()[0];
        if (!bcNavItem)
            return;
        const itemsProp = bcNavItem.getProperty('items');
        if (!itemsProp)
            return;
        const itemsArray = getInitializer(itemsProp);
        // Idempotent: skip when a NavItem with this url already exists
        const targetUrl = `'/${bcKebab}/${modKebab}'`;
        for (const element of itemsArray.getElements()) {
            const urlProp = element.getProperty('url');
            if (!urlProp)
                continue;
            const urlValue = getInitializer(urlProp);
            if (urlValue.getText() === targetUrl)
                return;
        }
        const iconPart = outlineIcon ? `, icon: '${outlineIcon}'` : '';
        itemsArray.addElement(`{ title: '${modNamesPascal}', translation: 'navigation.${modNamesPascal}', url: '/${bcKebab}/${modKebab}'${iconPart} }`);
        this.saveAndTrack(sourceFile);
    }
    /**
     * Ensure `{DASHBOARD_TRANSLATIONS}/navigation/{langCode}.json` contains a key for this module.
     * Key is `toPascalCase(moduleNames)` — must match the `translation: 'navigation.{key}'`
     * used by `generateFrontNavigation`. Missing keys get the PascalCase default placeholder.
     * Writes JSON directly (not TS AST), so it does not flow through Prettier.
     */
    generateFrontNavigationTranslation(langCode) {
        const translationPath = join(process.cwd(), this.srcDirectory, '..', DASHBOARD_TRANSLATIONS, 'navigation', `${langCode}.json`);
        const translationObject = JSON.parse(fs.readFileSync(translationPath, 'utf8'));
        const modsPascal = toPascalCase(this.moduleNames);
        if (translationObject[modsPascal] === undefined) {
            translationObject[modsPascal] = modsPascal;
        }
        const sorted = sortObjectByKeys(translationObject);
        fs.writeFileSync(translationPath, JSON.stringify(sorted, null, 2));
    }
    generateFrontRoutes() {
        const bcKebab = toKebabCase(this.boundedContextName);
        const modKebab = toKebabCase(this.moduleName);
        const modCamel = toCamelCase(this.moduleName);
        const routesFilePath = join(process.cwd(), this.srcDirectory, DASHBOARD_CONTAINER, bcKebab, `${bcKebab}.routes.ts`);
        if (!fs.existsSync(routesFilePath))
            return;
        const sourceFile = this.project.addSourceFileAtPath(routesFilePath);
        const bcCamel = toCamelCase(this.boundedContextName);
        const routesVarName = `${bcCamel}Routes`;
        const routesDecl = sourceFile.getVariableDeclaration(routesVarName);
        if (!routesDecl)
            return;
        const routesArray = getInitializer(routesDecl);
        if (!routesArray)
            return;
        for (const element of routesArray.getElements()) {
            const pathProp = element.getProperty('path');
            if (!pathProp)
                continue;
            const pathValue = getInitializer(pathProp);
            if (pathValue.getText() === `'${modKebab}'`)
                return;
        }
        ImportDriver.createImportItems(sourceFile, `./${modKebab}/data-access/${modKebab}.resolvers`, [
            `${modCamel}DetailResolver`,
            `${modCamel}ListResolver`,
            `${modCamel}NewResolver`,
        ]);
        // list, new, edit
        routesArray.addElement(`{\n  path: '${modKebab}',\n  loadComponent: () => import('./${modKebab}/features/${modKebab}-list.component'),\n  resolve: { pagination: ${modCamel}ListResolver },\n}`, { useNewLines: true });
        routesArray.addElement(`{\n  path: '${modKebab}/new',\n  loadComponent: () => import('./${modKebab}/features/${modKebab}-detail.component'),\n  data: { mode: 'new' },\n  resolve: { init: ${modCamel}NewResolver },\n}`, { useNewLines: true });
        const editPath = this.hasI18n
            ? `${modKebab}/edit/:id/:langId`
            : `${modKebab}/edit/:id`;
        routesArray.addElement(`{\n  path: '${editPath}',\n  loadComponent: () => import('./${modKebab}/features/${modKebab}-detail.component'),\n  data: { mode: 'edit' },\n  resolve: { item: ${modCamel}DetailResolver },\n}`, { useNewLines: true });
        this.saveAndTrack(sourceFile);
    }
    /**
     * Ensure the bounded-context i18n JSON for `langCode` has keys for every non-timestamp,
     * non-id property of the aggregate, plus the fixed module-level keys
     * (`Title`, `{Module}`, `{Module}Details`, `{Module}DetailsDescription`).
     * Target: `{DASHBOARD_TRANSLATIONS}/{bcKebab}/{langCode}.json`. Outer key is the module
     * in kebab-case; inner object is `Record<string, string>` (PascalCase keys).
     * Missing entries get a PascalCase default placeholder; the English helper-text template
     * is written for every language and translated manually afterwards.
     * Writes JSON directly (not TS AST), so it does not flow through Prettier.
     */
    generateFrontTranslations(properties, langCode) {
        const bcKebab = toKebabCase(this.boundedContextName);
        const modKebab = toKebabCase(this.moduleName);
        const translationPath = join(process.cwd(), this.srcDirectory, '..', DASHBOARD_TRANSLATIONS, bcKebab, `${langCode}.json`);
        const translationObject = JSON.parse(fs.readFileSync(translationPath, 'utf8'));
        if (!translationObject[modKebab] || typeof translationObject[modKebab] !== 'object') {
            translationObject[modKebab] = {};
        }
        const moduleTranslations = translationObject[modKebab];
        const names = getWithoutTimestampsProperties(properties)
            .filter((p) => p.name !== 'id')
            .map((p) => p.name);
        for (const name of names) {
            const pascalName = toPascalCase(name);
            if (moduleTranslations[pascalName] === undefined) {
                moduleTranslations[pascalName] = pascalName;
            }
        }
        const pluralPascal = toPascalCase(this.moduleNames);
        if (moduleTranslations.Title === undefined) {
            moduleTranslations.Title = pluralPascal;
        }
        const singularPascal = toPascalCase(this.moduleName);
        if (moduleTranslations[singularPascal] === undefined) {
            moduleTranslations[singularPascal] = singularPascal;
        }
        // Card heading + helper text used on the detail view.
        const singularLower = modKebab.replaceAll('-', ' ');
        const detailsKey = `${singularPascal}Details`;
        if (moduleTranslations[detailsKey] === undefined) {
            moduleTranslations[detailsKey] = `${singularPascal} details`;
        }
        const detailsDescriptionKey = `${singularPascal}DetailsDescription`;
        if (moduleTranslations[detailsDescriptionKey] === undefined) {
            moduleTranslations[detailsDescriptionKey] =
                `Fill in the ${singularLower} information.`;
        }
        // Empty-list message: `t('<bc>.<mod>.No<Plural>Found')` is consumed by every
        // generated list-component as `<au-data-table>`'s [emptyMessage]. Default is
        // language-aware; manually-edited values are preserved (idempotent default-only).
        const pluralLower = this.moduleNames.replaceAll('-', ' ');
        const noResultsKey = `No${pluralPascal}Found`;
        if (moduleTranslations[noResultsKey] === undefined) {
            const defaultFn = NO_RESULTS_FOUND_DEFAULTS[langCode] ?? NO_RESULTS_FOUND_DEFAULTS.en;
            moduleTranslations[noResultsKey] = defaultFn(pluralLower);
        }
        translationObject[modKebab] = sortObjectByKeys(moduleTranslations);
        const sorted = sortObjectByKeys(translationObject);
        fs.writeFileSync(translationPath, JSON.stringify(sorted, null, 2));
    }
    /**
     * Idempotent bootstrap: ensure `nav-main.ts` imports `{bcCamel}Icons` from the aggregator
     * and spreads `...{bcCamel}Icons` into the `provideIcons({...})` argument object. Mutates
     * ONLY the import list and the `provideIcons` argument — never reorders, renames, or removes
     * existing entries. Skips with a warning when `provideIcons` is invoked with anything other
     * than an inline `ObjectLiteralExpression` (developer-customized shape).
     */
    registerBcAggregatorInNavMain(srcDirectory, boundedContextName) {
        const bcKebab = toKebabCase(boundedContextName);
        const bcCamel = toCamelCase(boundedContextName);
        const navMainPath = join(process.cwd(), srcDirectory, 'app/domains/admin/layout/sidebar/nav-main.ts');
        if (!fs.existsSync(navMainPath))
            return;
        const sourceFile = this.project.addSourceFileAtPath(navMainPath);
        const componentClass = sourceFile
            .getClasses()
            .find((c) => c.getDecorator('Component'));
        if (!componentClass)
            return;
        const componentArg = componentClass
            .getDecorator('Component')
            .getArguments()[0];
        if (!componentArg)
            return;
        const providersProp = componentArg.getProperty('providers');
        if (!providersProp)
            return;
        const providersArr = getInitializer(providersProp);
        if (!providersArr?.getElements)
            return;
        let provideIconsCall;
        for (const elem of providersArr.getElements()) {
            if (Node.isCallExpression(elem) && elem.getExpression().getText() === 'provideIcons') {
                provideIconsCall = elem;
                break;
            }
        }
        if (!provideIconsCall)
            return;
        const argv = provideIconsCall.getArguments();
        if (argv.length !== 1 || !Node.isObjectLiteralExpression(argv[0])) {
            console.warn(`[NAV-MAIN BOOTSTRAP SKIPPED] provideIcons argument is not an inline object literal in ${navMainPath}`);
            return;
        }
        const iconsObj = argv[0];
        const spreadName = `${bcCamel}Icons`;
        for (const prop of iconsObj.getProperties()) {
            if (Node.isSpreadAssignment(prop) &&
                prop.getExpression().getText() === spreadName) {
                return;
            }
        }
        const importPath = `../../bounded-contexts/${bcKebab}/${bcKebab}-icons.providers`;
        const hasImport = sourceFile
            .getImportDeclarations()
            .some((decl) => decl.getModuleSpecifierValue() === importPath);
        if (!hasImport) {
            sourceFile.addImportDeclaration({
                moduleSpecifier: importPath,
                namedImports: [spreadName],
            });
        }
        iconsObj.addSpreadAssignment({ expression: spreadName });
        this.saveAndTrack(sourceFile);
    }
    // ─── Formatting ─────────────────────────────────────────────────
    registerFrontNavigation() {
        const bcKebab = toKebabCase(this.boundedContextName);
        const bcCamel = toCamelCase(this.boundedContextName);
        const navDataPath = join(process.cwd(), this.srcDirectory, ADMIN_CONTAINER, 'navigation-data.ts');
        if (!fs.existsSync(navDataPath))
            return;
        const sourceFile = this.project.addSourceFileAtPath(navDataPath);
        const existingImport = sourceFile.getImportDeclaration((d) => d.getModuleSpecifierValue().includes(`bounded-contexts/${bcKebab}`));
        if (existingImport)
            return;
        ImportDriver.createImportItems(sourceFile, `./bounded-contexts/${bcKebab}`, [`${bcCamel}Navigation`]);
        // Insert `...{bcCamel}Navigation` before the closing `]` of `navMain`
        const navMainText = sourceFile.getText();
        if (navMainText.includes(`...${bcCamel}Navigation`))
            return;
        const navMainMatch = navMainText.match(/navMain:\s*\[([^]*?)\]\s*satisfies/);
        if (navMainMatch) {
            const insertPos = navMainText.indexOf(navMainMatch[0]) + navMainMatch[0].indexOf('] satisfies');
            // Ensure the last array element before the spread has a trailing comma so
            // the inserted spread does not produce invalid syntax.
            const textBeforeInsert = navMainText.slice(0, insertPos).trimEnd();
            const needsComma = !textBeforeInsert.endsWith(',') && !textBeforeInsert.endsWith('[');
            const prefix = needsComma ? ',\n' : '';
            sourceFile.replaceText([insertPos, insertPos], `${prefix}    ...${bcCamel}Navigation,\n  `);
        }
        this.saveAndTrack(sourceFile);
    }
    getModelArrayArgument(decoratorArgs) {
        const importsArray = getInitializer(decoratorArgs.getProperty('imports'));
        const sequelizeElement = importsArray
            .getElements()
            .find((el) => el.getText().startsWith('SequelizeModule.forFeature'));
        if (!sequelizeElement) {
            throw new Error('SequelizeModule.forFeature is not defined in the module.');
        }
        return sequelizeElement.getArguments()[0];
    }
    getModuleDecoratorArguments(sourceFile, className, decoratorName) {
        const cls = sourceFile.getClass(className);
        const decorator = cls?.getDecorator(decoratorName);
        return decorator.getArguments()[0];
    }
    saveAndTrack(sourceFile) {
        sourceFile.saveSync();
        this.savedFiles.add(sourceFile.getFilePath());
    }
}
const NO_RESULTS_FOUND_DEFAULTS = {
    en: (plural) => `No ${plural} found.`,
    es: (plural) => `No se encontraron ${plural}.`,
};
function buildIconsAggregatorContent(bcKebab, bcCamel, icons) {
    const header = [
        '/**',
        ' * @aurora-catalyst-generated',
        ` * @source frontend/src/app/domains/admin/bounded-contexts/${bcKebab}/${bcKebab}.navigation.ts`,
        ' */',
    ].join('\n');
    if (icons.length === 0) {
        return `${header}\n\nexport const ${bcCamel}Icons = {};\n`;
    }
    const importLine = `import { ${icons.join(', ')} } from '@ng-icons/lucide';`;
    const objectBody = icons.map((icon) => `  ${icon},`).join('\n');
    return `${header}\n${importLine}\n\nexport const ${bcCamel}Icons = {\n${objectBody}\n};\n`;
}
function sortObjectByKeys(obj) {
    const sorted = {};
    for (const key of Object.keys(obj).sort()) {
        sorted[key] = obj[key];
    }
    return sorted;
}
