"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModelMetadataService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const typeorm_2 = require("typeorm");
const model_metadata_entity_1 = require("../entities/model-metadata.entity");
const module_metadata_entity_1 = require("../entities/module-metadata.entity");
const lodash_1 = require("lodash");
const string_helper_1 = require("../helpers/string.helper");
const error_messages_1 = require("../constants/error-messages");
const disallow_in_production_decorator_1 = require("../decorators/disallow-in-production.decorator");
const create_field_metadata_dto_1 = require("../dtos/create-field-metadata.dto");
const module_metadata_helper_service_1 = require("../helpers/module-metadata-helper.service");
const field_metadata_repository_1 = require("../repository/field-metadata.repository");
const model_metadata_repository_1 = require("../repository/model-metadata.repository");
const action_metadata_entity_1 = require("../entities/action-metadata.entity");
const field_metadata_entity_1 = require("../entities/field-metadata.entity");
const menu_item_metadata_entity_1 = require("../entities/menu-item-metadata.entity");
const view_metadata_entity_1 = require("../entities/view-metadata.entity");
const schematic_service_1 = require("../helpers/schematic.service");
const command_service_1 = require("../helpers/command.service");
const crud_helper_service_1 = require("./crud-helper.service");
const field_metadata_service_1 = require("./field-metadata.service");
const media_storage_provider_metadata_service_1 = require("./media-storage-provider-metadata.service");
const role_metadata_service_1 = require("./role-metadata.service");
const solid_introspect_service_1 = require("./solid-introspect.service");
const solid_ts_morph_service_1 = require("./solid-ts-morph.service");
let ModelMetadataService = class ModelMetadataService {
    constructor(modelMetadataRepo, fieldMetadataRepo, schematicService, commandService, dataSource, crudHelperService, mediaStorageProviderMetadataService, fieldMetadataService, roleService, moduleMetadataHelperService, introspectService, solidTsMorphService) {
        this.modelMetadataRepo = modelMetadataRepo;
        this.fieldMetadataRepo = fieldMetadataRepo;
        this.schematicService = schematicService;
        this.commandService = commandService;
        this.dataSource = dataSource;
        this.crudHelperService = crudHelperService;
        this.mediaStorageProviderMetadataService = mediaStorageProviderMetadataService;
        this.fieldMetadataService = fieldMetadataService;
        this.roleService = roleService;
        this.moduleMetadataHelperService = moduleMetadataHelperService;
        this.introspectService = introspectService;
        this.solidTsMorphService = solidTsMorphService;
        this.logger = new common_1.Logger('ModelMetadataService');
    }
    async find(basicFilterDto) {
        return this.findMany(basicFilterDto);
    }
    async findMany(basicFilterDto) {
        const alias = 'modelMetadata';
        let { limit, offset } = basicFilterDto;
        var qb = await this.modelMetadataRepo.createSecurityRuleAwareQueryBuilder(alias);
        qb = await this.crudHelperService.buildFilterQuery(qb, basicFilterDto, alias);
        const [entities, count] = await qb.getManyAndCount();
        const currentPage = Math.floor(offset / limit) + 1;
        const totalPages = Math.ceil(count / limit);
        const nextPage = currentPage < totalPages ? currentPage + 1 : null;
        const prevPage = currentPage > 1 ? currentPage - 1 : null;
        const r = {
            meta: {
                totalRecords: count,
                currentPage: currentPage,
                nextPage: nextPage,
                prevPage: prevPage,
                totalPages: totalPages,
                perPage: +limit,
            },
            records: entities
        };
        return r;
    }
    async findOne(id, query) {
        const entity = await this.modelMetadataRepo.findOne({
            where: {
                id: id,
            },
            relations: query?.populate,
        });
        if (!entity) {
            throw new common_1.NotFoundException(error_messages_1.ERROR_MESSAGES.ENTITY_NOT_FOUND(`#${id}`));
        }
        return entity;
    }
    async findOneBySingularName(singularName, relations = {}) {
        const entity = await this.modelMetadataRepo.findOne({
            where: {
                singularName: singularName,
            },
            relations: relations,
        });
        if (!entity) {
            throw new common_1.NotFoundException(error_messages_1.ERROR_MESSAGES.ENTITY_NOT_FOUND(singularName));
        }
        return entity;
    }
    async findOneByUserKey(singularName, relations = {}) {
        const entity = await this.modelMetadataRepo.findOne({
            where: {
                singularName: singularName,
            },
            relations: relations,
        });
        if (!entity) {
            throw new common_1.NotFoundException(error_messages_1.ERROR_MESSAGES.ENTITY_NOT_FOUND(singularName));
        }
        return entity;
    }
    async create(createDto) {
        try {
            return await this.dataSource.transaction(async (manager) => {
                const modelRepository = manager.getRepository(model_metadata_entity_1.ModelMetadata);
                const fieldRepository = manager.getRepository(field_metadata_entity_1.FieldMetadata);
                const model = await this.createInDB(manager, createDto);
                await this.createInFile(model.id, modelRepository);
                await this.handleInverseRelationFieldsUpdates(model, fieldRepository, modelRepository);
                return model;
            });
        }
        catch (error) {
            this.logger.error('Transaction failed:', error);
            throw error;
        }
    }
    async handleInverseRelationFieldsUpdates(model, fieldRepository, modelRepository) {
        const fields = await this.getRelationInverseFields(model.id, fieldRepository);
        for (const field of fields) {
            await this.fieldMetadataService.updateInverseField(field, fieldRepository, modelRepository);
        }
    }
    async update(id, updateModelMetaDataDto) {
        try {
            return await this.dataSource.transaction(async (manager) => {
                const modelRepository = manager.getRepository(model_metadata_entity_1.ModelMetadata);
                const fieldRepository = manager.getRepository(field_metadata_entity_1.FieldMetadata);
                const model = await this.updateInDb(manager, id, updateModelMetaDataDto);
                await this.updateInFile(model.id, modelRepository);
                await this.handleInverseRelationFieldsUpdates(model, fieldRepository, modelRepository);
            });
        }
        catch (error) {
            this.logger.error('Transaction failed:', error);
            throw error;
        }
    }
    async createInDB(manager, createDto) {
        const resolvedModule = await this.dataSource
            .getRepository(module_metadata_entity_1.ModuleMetadata)
            .findOne({
            where: {
                id: createDto['moduleId'],
            },
            relations: {},
        });
        createDto['module'] = resolvedModule;
        if (createDto['parentModelId']) {
            const resolvedParentModel = await this.dataSource
                .getRepository(model_metadata_entity_1.ModelMetadata)
                .findOne({
                where: {
                    id: createDto['parentModelId'],
                },
                relations: {},
            });
            createDto['parentModel'] = resolvedParentModel;
        }
        const { fields: fieldsMetadata, ...modelMetaDataWithoutFields } = createDto;
        const modelMetadata = this.modelMetadataRepo.create(modelMetaDataWithoutFields);
        let model = await manager.save(modelMetadata);
        let userKeyField = null;
        const listViewLayout = [];
        const formViewLayout = [];
        const treeViewLayout = [];
        for (let k = 0; k < fieldsMetadata.length; k++) {
            const fieldMetadata = fieldsMetadata[k];
            fieldMetadata['model'] = model;
            if (fieldMetadata.mediaStorageProviderId) {
                fieldMetadata['mediaStorageProvider'] = await this.mediaStorageProviderMetadataService.findOne(fieldMetadata.mediaStorageProviderId);
            }
            const fieldMetadataObject = this.fieldMetadataRepo.create(fieldMetadata);
            const affectedField = await manager.save(fieldMetadataObject);
            if (fieldMetadata.isUserKey) {
                userKeyField = affectedField;
            }
            listViewLayout.push({ type: "field", attrs: { name: `${affectedField.name}` } });
            formViewLayout.push({ type: "field", attrs: { name: `${affectedField.name}` } });
            treeViewLayout.push({ type: "field", attrs: { name: `${affectedField.name}` } });
        }
        if (userKeyField) {
            modelMetaDataWithoutFields['userKeyField'] = userKeyField;
            const updatedModelMetadataDto = this.modelMetadataRepo.merge(model, modelMetaDataWithoutFields);
            model = await manager.save(updatedModelMetadataDto);
        }
        return model;
    }
    async createInFile(modelId, repo) {
        try {
            const model = await repo.findOne({
                where: {
                    id: modelId,
                },
                relations: ["fields", "fields.mediaStorageProvider", "module", "parentModel"],
            });
            const filePath = await this.moduleMetadataHelperService.getModuleMetadataFilePath(model.module.name);
            const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
            const modelMetaData = {
                singularName: model.singularName,
                pluralName: model.pluralName,
                displayName: model.displayName,
                description: model.description,
                dataSource: model.dataSource,
                dataSourceType: model.dataSourceType,
                tableName: model.tableName,
                userKeyFieldUserKey: model.fields.find(field => field.isUserKey)?.name,
                isChild: model?.isChild,
                isLegacyTable: model?.isLegacyTable,
                isLegacyTableWithId: model?.isLegacyTableWithId,
                parentModelUserKey: model?.parentModel?.singularName,
                enableAuditTracking: model?.enableAuditTracking,
                enableSoftDelete: model?.enableSoftDelete,
                draftPublishWorkflow: model?.draftPublishWorkflow,
                internationalisation: model?.internationalisation,
                fields: []
            };
            for (let i = 0; i < model.fields.length; i++) {
                const field = model.fields[i];
                if (field.isSystem)
                    continue;
                const fieldObject = await this.fieldMetadataService.createFieldConfig(field);
                modelMetaData.fields.push(fieldObject);
            }
            metaData.moduleMetadata.models.push(modelMetaData);
            const updatedContent = JSON.stringify(metaData, null, 2);
            await fs.writeFile(filePath, updatedContent);
        }
        catch (error) {
            this.logger.error('File creation failed:', error);
            throw new Error(error_messages_1.ERROR_MESSAGES.FILE_WRITE_FAILED);
        }
    }
    async updateInDb(manager, id, updateModelMetaDataDto) {
        const { fields: fieldsMetadata, ...modelMetaDataWithoutFields } = updateModelMetaDataDto;
        const modelRepo = manager.getRepository(model_metadata_entity_1.ModelMetadata);
        const fieldRepo = manager.getRepository(field_metadata_entity_1.FieldMetadata);
        let existingModel = await modelRepo.findOne({
            where: {
                singularName: updateModelMetaDataDto.singularName
            },
            relations: ["fields", "module"],
        });
        if (!existingModel) {
            throw new Error(error_messages_1.ERROR_MESSAGES.MODEL_NOT_FOUND(updateModelMetaDataDto.singularName));
        }
        const updatedModel = modelRepo.merge(existingModel, modelMetaDataWithoutFields);
        await modelRepo.save(updatedModel);
        const existingFields = existingModel.fields || [];
        const existingFieldIds = existingFields.map((field) => field.id);
        let userKeyField = null;
        const fieldsToSave = [];
        const fieldsToDelete = [];
        for (const fieldMetadata of fieldsMetadata) {
            if (fieldMetadata.id) {
                const existingField = existingFields.find((field) => field.id === fieldMetadata.id);
                if (existingField) {
                    if (fieldMetadata.mediaStorageProviderId) {
                        fieldMetadata['mediaStorageProvider'] = await this.mediaStorageProviderMetadataService.findOne(fieldMetadata.mediaStorageProviderId);
                    }
                    Object.assign(existingField, fieldMetadata);
                    fieldsToSave.push(existingField);
                }
            }
            else {
                fieldMetadata['model'] = updatedModel;
                if (fieldMetadata.mediaStorageProviderId) {
                    fieldMetadata['mediaStorageProvider'] = await this.mediaStorageProviderMetadataService.findOne(fieldMetadata.mediaStorageProviderId);
                }
                const createdField = fieldRepo.create(fieldMetadata);
                fieldsToSave.push(createdField);
            }
        }
        fieldsToDelete.push(...existingFields.filter((field) => !fieldsMetadata.some((f) => f.id === field.id)));
        if (fieldsToSave.length > 0) {
            await fieldRepo.save(fieldsToSave);
        }
        if (fieldsToDelete.length > 0) {
            fieldsToDelete.forEach(field => { field.isMarkedForRemoval = true; });
            await fieldRepo.save(fieldsToDelete);
        }
        const finalModel = await modelRepo.findOne({
            where: { id: updatedModel.id },
            relations: ["fields", "userKeyField"]
        });
        const userKeyFields = fieldsMetadata.filter(field => field.isUserKey);
        if (userKeyFields.length > 0) {
            const newUserKeyField = userKeyFields[userKeyFields.length - 1];
            const savedUserKeyField = await fieldRepo.findOne({ where: { id: newUserKeyField.id } });
            if (savedUserKeyField) {
                finalModel.userKeyField = savedUserKeyField;
                await modelRepo.save(finalModel);
            }
            const otherUserKeyFields = userKeyFields.filter(field => field.id !== newUserKeyField.id);
            for (const field of otherUserKeyFields) {
                const existingField = await fieldRepo.findOne({ where: { id: field.id } });
                if (existingField) {
                    existingField.isUserKey = false;
                    await fieldRepo.save(existingField);
                }
            }
        }
        else {
            if (finalModel.userKeyField) {
                finalModel.userKeyField = null;
                await modelRepo.save(finalModel);
            }
        }
        return updatedModel;
    }
    async updateInFile(modelId, repo) {
        try {
            const model = await repo.findOne({
                where: {
                    id: modelId,
                },
                relations: ["fields", "fields.mediaStorageProvider", "module", "parentModel"],
                order: {
                    fields: {
                        id: "ASC",
                    },
                },
            });
            const filePath = await this.moduleMetadataHelperService.getModuleMetadataFilePath(model.module.name);
            const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
            const modelMetaData = {
                singularName: model.singularName,
                pluralName: model.pluralName,
                displayName: model.displayName,
                description: model.description,
                dataSource: model.dataSource,
                dataSourceType: model.dataSourceType,
                tableName: model.tableName,
                userKeyFieldUserKey: model.fields.find(field => field.isUserKey)?.name,
                isChild: model?.isChild,
                parentModelUserKey: model?.parentModel?.singularName,
                enableAuditTracking: model?.enableAuditTracking,
                enableSoftDelete: model?.enableSoftDelete,
                draftPublishWorkflow: model?.draftPublishWorkflow,
                internationalisation: model?.internationalisation,
                fields: []
            };
            for (let i = 0; i < model.fields.length; i++) {
                const field = model.fields[i];
                if (!field.isSystem) {
                    const fieldObject = await this.fieldMetadataService.createFieldConfig(field);
                    if (field.isMarkedForRemoval) {
                        fieldObject.isMarkedForRemoval = true;
                    }
                    modelMetaData.fields.push(fieldObject);
                }
            }
            const existingModelIndex = metaData.moduleMetadata.models.findIndex((existingModel) => existingModel.singularName === modelMetaData.singularName);
            if (existingModelIndex !== -1) {
                metaData.moduleMetadata.models[existingModelIndex] = modelMetaData;
            }
            else {
                metaData.moduleMetadata.models.push(modelMetaData);
            }
            const updatedContent = JSON.stringify(metaData, null, 2);
            await fs.writeFile(filePath, updatedContent);
        }
        catch (error) {
            this.logger.error('File creation failed:', error);
            throw new Error(error_messages_1.ERROR_MESSAGES.FILE_WRITE_FAILED);
        }
    }
    async upsert(updateDto) {
        const existingModelMetadata = await this.modelMetadataRepo.findOne({
            where: {
                singularName: updateDto.singularName
            }
        });
        if (existingModelMetadata) {
            const updatedModelMetadata = { ...existingModelMetadata, ...updateDto };
            const updatedModel = await this.modelMetadataRepo.save(updatedModelMetadata);
            return updatedModel;
        }
        else {
            const modelMetadata = this.modelMetadataRepo.create(updateDto);
            return this.modelMetadataRepo.save(modelMetadata);
        }
    }
    async removeBySingularName(singularName) {
        try {
            const entity = await this.findOneBySingularName(singularName);
            await this.cleanupOnDelete(entity.id);
            const r = await this.modelMetadataRepo.remove(entity);
            return r;
        }
        catch (error) {
        }
    }
    async deleteMany(ids) {
        if (!ids || ids.length === 0) {
            throw new Error(error_messages_1.ERROR_MESSAGES.DELETE_IDS_REQUIRED);
        }
        const removedEntities = [];
        for (let i = 0; i < ids.length; i++) {
            const id = ids[i];
            const entity = await this.modelMetadataRepo.findOne({
                where: {
                    id: id,
                }
            });
            await this.cleanupOnDelete(entity.id);
            const r = await this.modelMetadataRepo.remove(entity);
            removedEntities.push(r);
        }
        return removedEntities;
    }
    async remove(id) {
        const entity = await this.findOne(id);
        await this.cleanupOnDelete(entity.id);
        const r = await this.modelMetadataRepo.remove(entity);
        return r;
    }
    async cleanupOnDelete(modelEntityId) {
        const modelEntity = await this.modelMetadataRepo.findOne({
            where: {
                id: modelEntityId,
            },
            relations: ['module']
        });
        if (!modelEntity) {
            this.logger.log(`Invalid modelEntityId: ${modelEntityId} unable to resolve model metadata`);
            return;
        }
        if (modelEntity.id !== modelEntityId) {
            this.logger.log(`Invalid modelEntityId: ${modelEntityId} unable to resolve model metadata id ${modelEntity.id} not matching with the one passed as argument ${modelEntityId}`);
            return;
        }
        this.logger.log(`Cleaning up for model: ${modelEntity.singularName} belonging to module: ${modelEntity.module?.name}`);
        const modulePath = await this.moduleMetadataHelperService.getModulePath(modelEntity.module?.name);
        if (modulePath) {
            this.logger.log(`Module path: ${modulePath}`);
            const filesToDelete = [];
            const entityFilePath = `${modulePath}/entities/${(0, lodash_1.kebabCase)(modelEntity.singularName)}.entity.ts`;
            filesToDelete.push(entityFilePath);
            this.logger.log(`About to delete entity file path: ${entityFilePath}`);
            const createDtoFilePath = `${modulePath}/dtos/create-${(0, lodash_1.kebabCase)(modelEntity.singularName)}.dto.ts`;
            filesToDelete.push(createDtoFilePath);
            this.logger.log(`About to delete create DTO file path: ${createDtoFilePath}`);
            const updateDtoFilePath = `${modulePath}/dtos/update-${(0, lodash_1.kebabCase)(modelEntity.singularName)}.dto.ts`;
            filesToDelete.push(updateDtoFilePath);
            this.logger.log(`About to delete update DTO file path: ${updateDtoFilePath}`);
            const repositoryFilePath = `${modulePath}/repositories/${(0, lodash_1.kebabCase)(modelEntity.singularName)}.repository.ts`;
            filesToDelete.push(repositoryFilePath);
            this.logger.log(`About to delete repository file path: ${repositoryFilePath}`);
            const serviceFilePath = `${modulePath}/services/${(0, lodash_1.kebabCase)(modelEntity.singularName)}.service.ts`;
            filesToDelete.push(serviceFilePath);
            this.logger.log(`About to delete service file path: ${serviceFilePath}`);
            const controllerFilePath = `${modulePath}/controllers/${(0, lodash_1.kebabCase)(modelEntity.singularName)}.controller.ts`;
            filesToDelete.push(controllerFilePath);
            this.logger.log(`About to delete controller file path: ${controllerFilePath}`);
            for (let i = 0; i < filesToDelete.length; i++) {
                const fileToDelete = filesToDelete[i];
                try {
                    await fs.unlink(fileToDelete);
                    this.logger.log(`Deleted file: ${fileToDelete}`);
                }
                catch (error) {
                    this.logger.error(`Error deleting file: ${fileToDelete}`, error);
                }
            }
        }
        await this.dataSource.query(`CALL cleanup_model_metadata($1, $2)`, [modelEntity.singularName, true]);
        const filePath = await this.moduleMetadataHelperService.getModuleMetadataFilePath(modelEntity.module?.name);
        const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
        if (metaData) {
            const existingModelIndex = metaData.moduleMetadata.models.findIndex((existingModel) => existingModel.singularName === modelEntity.singularName);
            if (existingModelIndex !== -1) {
                metaData.moduleMetadata.models.splice(existingModelIndex, 1);
            }
            metaData.moduleMetadata.menus = metaData.moduleMetadata?.menus?.filter((menu) => menu.modelUserKey !== modelEntity.singularName);
            metaData.moduleMetadata.actions = metaData.moduleMetadata?.actions?.filter((action) => action.modelUserKey !== modelEntity.singularName);
            metaData.moduleMetadata.views = metaData.moduleMetadata?.views?.filter((view) => view.modelUserKey !== modelEntity.singularName);
            const updatedContent = JSON.stringify(metaData, null, 2);
            await fs.writeFile(filePath, updatedContent);
        }
        if (modulePath) {
            const moduleFilePath = path.resolve(modulePath, `${(0, lodash_1.kebabCase)(modelEntity.module?.name)}.module.ts`);
            this.logger.log(`Removing model '${modelEntity.singularName}' references from module file: ${moduleFilePath}`);
            try {
                this.solidTsMorphService.begin();
                const modelPathSegment = `/${(0, lodash_1.kebabCase)(modelEntity.singularName)}.`;
                const { removedIdentifiers } = this.solidTsMorphService.removeImports(moduleFilePath, spec => spec.includes(modelPathSegment));
                this.solidTsMorphService.removeModuleMembers(moduleFilePath, removedIdentifiers);
                await this.solidTsMorphService.commit();
            }
            catch (error) {
                this.solidTsMorphService.rollback();
                this.logger.error(`Failed to clean up module file for model '${modelEntity.singularName}':`, error);
            }
        }
    }
    async generateCodeViaCtl(modelId) {
        const model = await this.findOne(modelId);
        return this.commandService.executeCommandWithArgs({
            command: 'npx',
            args: ['@solixai/solidctl@latest', 'generate', 'model', `--name=${model.singularName}`],
            cwd: path.join(process.cwd(), '..'),
        });
    }
    async handleGenerateCode(options) {
        const affectedModelIds = [], refreshModelCodeOutputLines = [], removeFieldCodeOutputLines = [];
        const { model, removeFieldCodeOuput, refreshModelCodeOutput } = await this.generateCode(options);
        affectedModelIds.push(model.id);
        refreshModelCodeOutputLines.push(refreshModelCodeOutput);
        removeFieldCodeOutputLines.push(removeFieldCodeOuput);
        await this.generateCodeForInverseModels(model, options, affectedModelIds, refreshModelCodeOutputLines, removeFieldCodeOutputLines);
        for (const modelId of affectedModelIds) {
            await this.generateVAMConfig(modelId);
        }
        return `${removeFieldCodeOutputLines.join('\n')} \n ${refreshModelCodeOutputLines.join('\n')}`;
    }
    async generateCodeForInverseModels(model, options, affectedModelIds, refreshModelCodeOutputLines, removeFieldCodeOutputLines) {
        const coModelSingularNames = model.fields.
            filter(field => field.type === create_field_metadata_dto_1.SolidFieldType.relation && field.relationCreateInverse === true)
            .map(field => field.relationCoModelSingularName);
        for (const singularName of coModelSingularNames) {
            const coModel = await this.findOneBySingularName(singularName);
            const inverseOptions = {
                modelId: coModel.id,
                dryRun: options.dryRun
            };
            const { removeFieldCodeOuput, refreshModelCodeOutput } = await this.generateCode(inverseOptions);
            affectedModelIds.push(coModel.id);
            refreshModelCodeOutputLines.push(refreshModelCodeOutput);
            removeFieldCodeOutputLines.push(removeFieldCodeOuput);
        }
    }
    async generateVAMConfig(modelId) {
        try {
            return await this.dataSource.transaction(async (manager) => {
                const modelRepository = manager.getRepository(model_metadata_entity_1.ModelMetadata);
                const model = await modelRepository.findOne({
                    where: {
                        id: modelId
                    },
                    relations: ["fields", "module"]
                });
                await this.populateVAMConfigInDb(model);
                await this.populateVAMConfigInFile(model);
            });
        }
        catch (error) {
            this.logger.error('generateVAMConfig Transaction failed:', error);
            throw error;
        }
    }
    async populateVAMConfigInFile(model) {
        try {
            const filePath = await this.moduleMetadataHelperService.getModuleMetadataFilePath(model.module.name);
            const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
            const listViewLayoutFields = [{ type: "field", attrs: { name: `id` } }];
            const treeViewLayoutFields = [{ type: "field", attrs: { name: `id` } }];
            const formViewLayoutFields = [];
            for (let i = 0; i < model.fields.length; i++) {
                const field = model.fields[i];
                if (field.isSystem)
                    continue;
                listViewLayoutFields.push({ type: "field", attrs: { name: `${field.name}` } });
                formViewLayoutFields.push({ type: "field", attrs: { name: `${field.name}` } });
                treeViewLayoutFields.push({ type: "field", attrs: { name: `${field.name}` } });
            }
            this.populateVAMConfigInFileInternal(formViewLayoutFields, model, listViewLayoutFields, treeViewLayoutFields, metaData);
            const updatedContent = JSON.stringify(metaData, null, 2);
            await fs.writeFile(filePath, updatedContent);
        }
        catch (error) {
            this.logger.error('File updation failed for View, action, menus config:', error);
            throw new Error('File updation failed for View, action, menus config');
        }
    }
    populateVAMConfigInFileInternal(formViewLayoutFields, model, listViewLayoutFields, treeViewLayoutFields, metaData) {
        const column1Fields = [];
        for (let i = 0; i < formViewLayoutFields.length; i++) {
            column1Fields.push(formViewLayoutFields[i]);
        }
        const actionName = `${model.singularName}-list-action`;
        const treeViewActionName = `${model.singularName}-tree-action`;
        const listViewName = `${model.singularName}-list-view`;
        const treeViewName = `${model.singularName}-tree-view`;
        const formViewName = `${model.singularName}-form-view`;
        const menuName = `${model.singularName}-menu-item`;
        const nextMenuSequenceNumber = (metaData.menus?.length ?? 0) + 1;
        const action = {
            displayName: `${model.displayName} List Action`,
            name: actionName,
            type: "solid",
            domain: "",
            context: "",
            customComponent: ``,
            customIsModal: true,
            serverEndpoint: "",
            viewUserKey: listViewName,
            moduleUserKey: `${model.module.name}`,
            modelUserKey: `${model.singularName}`
        };
        const treeViewAction = {
            displayName: `${model.displayName} Tree View Action`,
            name: treeViewActionName,
            type: "solid",
            domain: "",
            context: "",
            customComponent: ``,
            customIsModal: true,
            serverEndpoint: "",
            viewUserKey: treeViewName,
            moduleUserKey: `${model.module.name}`,
            modelUserKey: `${model.singularName}`
        };
        const menu = {
            displayName: `${model.displayName}`,
            name: menuName,
            sequenceNumber: nextMenuSequenceNumber,
            actionUserKey: actionName,
            moduleUserKey: `${model.module.name}`,
            parentMenuItemUserKey: "",
            iconName: ""
        };
        const modelListview = {
            name: listViewName,
            displayName: `${model.displayName}`,
            type: "list",
            context: "{}",
            moduleUserKey: `${model.module.name}`,
            modelUserKey: `${model.singularName}`,
            layout: {
                type: "list",
                attrs: {
                    pagination: true,
                    pageSizeOptions: [
                        10,
                        25,
                        50
                    ],
                    enableGlobalSearch: true,
                    create: true,
                    edit: true,
                    delete: true
                },
                children: listViewLayoutFields
            }
        };
        const modelTreeview = {
            name: treeViewName,
            displayName: `${model.displayName}`,
            type: "tree",
            context: "{}",
            moduleUserKey: `${model.module.name}`,
            modelUserKey: `${model.singularName}`,
            layout: {
                type: "tree",
                attrs: {
                    pagination: true,
                    pageSizeOptions: [
                        10,
                        25,
                        50
                    ],
                    enableGlobalSearch: true,
                    create: true,
                    edit: true,
                    delete: true
                },
                children: treeViewLayoutFields
            }
        };
        const modelFormView = {
            name: formViewName,
            displayName: `${model.displayName}`,
            type: "form",
            context: "{}",
            moduleUserKey: `${model.module.name}`,
            modelUserKey: `${model.singularName}`,
            layout: {
                type: "form",
                attrs: { name: "form-1", label: `${model.displayName}`, className: "grid" },
                children: [
                    {
                        type: "sheet",
                        attrs: { name: "sheet-1" },
                        children: [
                            {
                                type: "row",
                                attrs: { name: "sheet-1" },
                                children: [
                                    {
                                        type: "column",
                                        attrs: { name: "group-1", label: "", className: "col-12 sm:col-12 md:col-6 lg:col-6" },
                                        children: column1Fields
                                    },
                                ]
                            },
                        ]
                    }
                ]
            }
        };
        const notExists = (arr, name) => !arr.some(item => item.name === name);
        if (notExists(metaData.menus, menuName)) {
            metaData.menus.push(menu);
        }
        if (notExists(metaData.actions, actionName)) {
            metaData.actions.push(action);
        }
        if (notExists(metaData.actions, treeViewActionName)) {
            metaData.actions.push(treeViewAction);
        }
        if (notExists(metaData.views, listViewName)) {
            metaData.views.push(modelListview);
        }
        if (notExists(metaData.views, treeViewName)) {
            metaData.views.push(modelTreeview);
        }
        if (notExists(metaData.views, formViewName)) {
            metaData.views.push(modelFormView);
        }
    }
    async populateVAMConfigInDb(model) {
        const jsonFieldsList = model.fields.filter((field) => field.isSystem !== true);
        const listViewLayout = jsonFieldsList.map(field => ({
            type: "field",
            attrs: {
                name: `${field.name}`,
                isSearchable: true,
            }
        }));
        const treeViewLayout = jsonFieldsList.map(field => ({
            type: "field",
            attrs: {
                name: `${field.name}`,
                isSearchable: true,
            }
        }));
        const formViewLayout = jsonFieldsList.map(field => ({
            type: "field",
            attrs: {
                name: `${field.name}`
            }
        }));
        const midIndex = Math.ceil(formViewLayout.length / 2);
        const firstHalf = formViewLayout.slice(0, midIndex);
        const secondHalf = formViewLayout.slice(midIndex);
        const resolvedModule = await this.dataSource.getRepository(module_metadata_entity_1.ModuleMetadata).findOne({
            where: { id: model.module.id }
        });
        const viewRepo = this.dataSource.getRepository(view_metadata_entity_1.ViewMetadata);
        const actionRepo = this.dataSource.getRepository(action_metadata_entity_1.ActionMetadata);
        const menuRepo = this.dataSource.getRepository(menu_item_metadata_entity_1.MenuItemMetadata);
        const modelViews = [
            {
                name: `${model.singularName}-list-view`,
                displayName: `${model.displayName}`,
                type: 'list',
                context: "{}",
                module: resolvedModule,
                model: model,
                layout: JSON.stringify({
                    type: "list",
                    attrs: {
                        pagination: true,
                        pageSizeOptions: [10, 25, 50],
                        enableGlobalSearch: true,
                        create: true,
                        edit: true,
                        delete: true
                    },
                    children: listViewLayout
                }, null, 3)
            },
            {
                name: `${model.singularName}-tree-view`,
                displayName: `${model.displayName}`,
                type: 'tree',
                context: "{}",
                module: resolvedModule,
                model: model,
                layout: JSON.stringify({
                    type: "tree",
                    attrs: {
                        pagination: true,
                        pageSizeOptions: [10, 25, 50],
                        enableGlobalSearch: true,
                        create: true,
                        edit: true,
                        delete: true
                    },
                    children: treeViewLayout
                }, null, 3)
            },
            {
                name: `${model.singularName}-form-view`,
                displayName: `${model.displayName}`,
                type: 'form',
                context: "{}",
                module: resolvedModule,
                model: model,
                layout: JSON.stringify({
                    type: "form",
                    attrs: { name: "form-1", label: `${model.displayName}`, className: "grid" },
                    children: [
                        {
                            type: "sheet",
                            attrs: { name: "sheet-1" },
                            children: [
                                {
                                    type: "row",
                                    attrs: { name: "group-1", label: "", className: "" },
                                    children: [
                                        {
                                            type: "column",
                                            attrs: { name: "group-1", label: "", className: "col-12 sm:col-12 md:col-6 lg:col-6" },
                                            children: firstHalf
                                        },
                                        {
                                            type: "column",
                                            attrs: { name: "group-2", label: "", className: "col-12 sm:col-12 md:col-6 lg:col-6" },
                                            children: secondHalf
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }, null, 3)
            }
        ];
        for (const view of modelViews) {
            const existingView = await viewRepo.findOne({ where: { name: view.name } });
            if (!existingView) {
                const createdView = viewRepo.create(view);
                await viewRepo.save(createdView);
            }
        }
        let view = await viewRepo.findOne({ where: { name: `${model.singularName}-list-view` } });
        let treeView = await viewRepo.findOne({ where: { name: `${model.singularName}-tree-view` } });
        const actionData = {
            displayName: `${model.displayName} List Action`,
            name: `${model.singularName}-list-action`,
            type: "solid",
            domain: "",
            context: "",
            customComponent: "",
            customIsModal: true,
            serverEndpoint: "",
            view: view,
            module: resolvedModule,
            model: model
        };
        const treeViewActionData = {
            displayName: `${model.displayName} Tree View Action`,
            name: `${model.singularName}-tree-view-action`,
            type: "solid",
            domain: "",
            context: "",
            customComponent: ``,
            customIsModal: true,
            serverEndpoint: "",
            view: treeView,
            module: resolvedModule,
            model: model
        };
        let existingAction = await actionRepo.findOne({ where: { name: actionData.name } });
        let existingTreeViewAction = await actionRepo.findOne({ where: { name: treeViewActionData.name } });
        if (!existingAction) {
            const createdAction = actionRepo.create(actionData);
            existingAction = await actionRepo.save(createdAction);
        }
        if (!existingTreeViewAction) {
            const createdTreeViewAction = actionRepo.create(treeViewActionData);
            existingTreeViewAction = await actionRepo.save(createdTreeViewAction);
        }
        const adminRole = await this.roleService.findRoleByName('Admin');
        const menuData = {
            displayName: `${model.displayName}`,
            name: `${model.singularName}-menu-item`,
            sequenceNumber: 1,
            action: existingAction,
            module: resolvedModule,
            roles: [adminRole],
            parentMenuItemUserKey: ""
        };
        let existingMenu = await menuRepo.findOne({ where: { name: menuData.name } });
        if (!existingMenu) {
            const createdMenu = menuRepo.create(menuData);
            await menuRepo.save(createdMenu);
        }
    }
    async generateCode(options) {
        const query = {
            populate: ["module", "fields"]
        };
        const model = options.modelId
            ? await this.findOne(options.modelId, query)
            : await this.findOneByUserKey(options.modelUserKey, query.populate);
        options.fieldIdsForRemoval = model.fields
            .filter(field => field.isMarkedForRemoval)
            .map(field => field.id);
        const refreshModelCodeOutput = await this.generateModelCode(options);
        const removeFieldCodeOuput = await this.generateRemoveFieldsCode(options);
        return { model, removeFieldCodeOuput, refreshModelCodeOutput };
    }
    async generateRemoveFieldsCode(options) {
        if (!options.modelId && !options.modelUserKey) {
            throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.MODEL_REQUIRED_FOR_CODE_GENERATION);
        }
        if (!options.fieldIdsForRemoval || options.fieldIdsForRemoval.length === 0) {
            return "";
        }
        const query = {
            populate: ["module", "fields"]
        };
        const model = options.modelId ? await this.findOne(options.modelId, query) : await this.findOneByUserKey(options.modelUserKey, query.populate);
        const fieldsForRemoval = model.fields.filter((field) => options.fieldIdsForRemoval.includes(+field.id));
        const removeOutput = await this.executeRemoveFieldsCommand(model, fieldsForRemoval, options.dryRun);
        fieldsForRemoval.forEach((field) => {
            if (field.isMarkedForRemoval) {
                this.fieldMetadataService.delete(field.id);
            }
        });
        const filePath = await this.moduleMetadataHelperService.getModuleMetadataFilePath(model.module.name);
        const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
        const existingModelIndex = metaData.moduleMetadata.models.findIndex((existingModel) => existingModel.singularName === model.singularName);
        const modelMetaData = metaData.moduleMetadata.models[existingModelIndex];
        modelMetaData.fields = modelMetaData.fields.filter((field) => field.isMarkedForRemoval !== true);
        if (existingModelIndex !== -1) {
            metaData.moduleMetadata.models[existingModelIndex] = modelMetaData;
        }
        else {
            metaData.moduleMetadata.models.push(modelMetaData);
        }
        const updatedContent = JSON.stringify(metaData, null, 2);
        await fs.writeFile(filePath, updatedContent);
        return removeOutput;
    }
    async generateModelCode(options) {
        if (!options.modelId && !options.modelUserKey) {
            throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.MODEL_REQUIRED_FOR_CODE_GENERATION);
        }
        const query = {
            populate: ["module", "fields", "parentModel", "parentModel.module"]
        };
        const model = options.modelId ? await this.findOne(options.modelId, query) : await this.findOneByUserKey(options.modelUserKey, query.populate);
        const refreshOuput = await this.executeRefreshModelCommand(model, options.dryRun);
        return `${refreshOuput}`;
    }
    async executeRefreshModelCommand(model, dryRun = false) {
        const output = await this.schematicService.executeSchematicCommand(schematic_service_1.REFRESH_MODEL_COMMAND, {
            module: model.module.name,
            model: model.singularName,
        }, dryRun);
        this.logger.debug(`Schematic output : ${output}`);
        return output;
    }
    async executeRemoveFieldsCommand(model, fieldsForRemoval, dryRun = false) {
        if (!fieldsForRemoval || fieldsForRemoval.length === 0) {
            return "";
        }
        const output = await this.schematicService.executeSchematicCommand(schematic_service_1.REMOVE_FIELDS_COMMAND, {
            module: model.module.name,
            model: model.singularName,
            fieldNamesForRemoval: fieldsForRemoval.map(field => field.name),
        }, dryRun);
        this.logger.debug(`Schematic output : ${output}`);
        return output;
    }
    async updateUserKey(data) {
        const { modelName, fieldName } = data;
        const model = await this.modelMetadataRepo.findOne({
            where: { singularName: modelName },
            relations: ['fields', 'userKeyField'],
        });
        if (!model) {
            throw new Error(`Model with name ${modelName} not found`);
        }
        if (model.userKeyField) {
            throw new Error(`User key is already set to ${model.userKeyField.name}. No changes were made.`);
        }
        const fieldToUpdate = model.fields.find(field => field.name === fieldName);
        if (!fieldToUpdate) {
            throw new Error(`Field with name ${fieldName} not found in model ${modelName}`);
        }
        fieldToUpdate.isUserKey = true;
        model.userKeyField = fieldToUpdate;
        await this.modelMetadataRepo.save(model);
        return {
            message: `User key has been successfully updated to ${fieldName}.`,
            success: true
        };
    }
    async getRelationInverseFields(modelId, repo) {
        return await repo.find({
            where: {
                model: {
                    id: modelId
                },
                type: create_field_metadata_dto_1.SolidFieldType.relation,
                relationCreateInverse: true
            },
            relations: {
                model: {
                    module: true
                }
            }
        });
    }
    async navigation(navigationDto) {
        const { recordId, modelName, ...basicFilterDto } = navigationDto;
        const modelServiceInstanceWrapper = this.introspectService.getProvider(`${(0, string_helper_1.classify)(modelName)}Service`);
        if (!modelServiceInstanceWrapper) {
            throw new common_1.BadRequestException(`Invalid model name (${modelName}) specified in Navigation.`);
        }
        const modelService = modelServiceInstanceWrapper.instance;
        const recs = await modelService.find(basicFilterDto);
        if (!('records' in recs) || !('currentPage' in recs.meta)) {
            return {
                prev: null,
                next: null,
                meta: null,
            };
        }
        const { records, meta } = recs;
        const index = records.findIndex(r => String(r.id) === String(recordId));
        if (index === -1) {
            throw new common_1.BadRequestException(`Record not found in current page`);
        }
        const limit = meta.perPage;
        const currentOffset = (meta.currentPage - 1) * meta.perPage;
        const currentIndexGlobal = currentOffset + index + 1;
        let prev = null;
        let next = null;
        if (index > 0) {
            prev = {
                record: records[index - 1],
                offset: currentOffset,
                limit,
            };
        }
        else if (meta.prevPage !== null) {
            const prevOffset = (meta.prevPage - 1) * meta.perPage;
            const prevPage = await modelService.find({
                ...basicFilterDto,
                offset: prevOffset,
                limit,
            });
            if ('records' in prevPage) {
                const record = prevPage.records.at(-1) ?? null;
                if (record) {
                    prev = {
                        record,
                        offset: prevOffset,
                        limit,
                    };
                }
            }
        }
        if (index < records.length - 1) {
            next = {
                record: records[index + 1],
                offset: currentOffset,
                limit,
            };
        }
        else if (meta.nextPage !== null) {
            const nextOffset = (meta.nextPage - 1) * meta.perPage;
            const nextPage = await modelService.find({
                ...basicFilterDto,
                offset: nextOffset,
                limit,
            });
            if ('records' in nextPage) {
                const record = nextPage.records[0] ?? null;
                if (record) {
                    next = {
                        record,
                        offset: nextOffset,
                        limit,
                    };
                }
            }
        }
        return {
            prev: prev
                ? {
                    recordId: prev.record.id,
                    offset: prev.offset,
                    limit: prev.limit,
                }
                : null,
            next: next
                ? {
                    recordId: next.record.id,
                    offset: next.offset,
                    limit: next.limit,
                }
                : null,
            meta: {
                totalRecords: meta.totalRecords,
                perPage: meta.perPage,
                currentPage: meta.currentPage,
                totalPages: meta.totalPages,
                currentIndexInPage: index,
                currentIndexGlobal,
                hasPrev: !!prev,
                hasNext: !!next,
                prevPage: meta.prevPage,
                nextPage: meta.nextPage,
            },
        };
    }
};
exports.ModelMetadataService = ModelMetadataService;
__decorate([
    (0, disallow_in_production_decorator_1.DisallowInProduction)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number]),
    __metadata("design:returntype", Promise)
], ModelMetadataService.prototype, "remove", null);
__decorate([
    (0, disallow_in_production_decorator_1.DisallowInProduction)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number]),
    __metadata("design:returntype", Promise)
], ModelMetadataService.prototype, "generateCodeViaCtl", null);
__decorate([
    (0, disallow_in_production_decorator_1.DisallowInProduction)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], ModelMetadataService.prototype, "handleGenerateCode", null);
exports.ModelMetadataService = ModelMetadataService = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, common_1.Inject)((0, common_1.forwardRef)(() => model_metadata_repository_1.ModelMetadataRepository))),
    __param(4, (0, typeorm_1.InjectDataSource)()),
    __metadata("design:paramtypes", [model_metadata_repository_1.ModelMetadataRepository,
        field_metadata_repository_1.FieldMetadataRepository,
        schematic_service_1.SchematicService,
        command_service_1.CommandService,
        typeorm_2.DataSource,
        crud_helper_service_1.CrudHelperService,
        media_storage_provider_metadata_service_1.MediaStorageProviderMetadataService,
        field_metadata_service_1.FieldMetadataService,
        role_metadata_service_1.RoleMetadataService,
        module_metadata_helper_service_1.ModuleMetadataHelperService,
        solid_introspect_service_1.SolidIntrospectService,
        solid_ts_morph_service_1.SolidTsMorphService])
], ModelMetadataService);
//# sourceMappingURL=model-metadata.service.js.map