"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); }
};
var ModuleMetadataService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModuleMetadataService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const default_settings_provider_service_1 = require("./settings/default-settings-provider.service");
const typeorm_2 = require("typeorm");
const module_metadata_entity_1 = require("../entities/module-metadata.entity");
const string_helper_1 = require("../helpers/string.helper");
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const error_messages_1 = require("../constants/error-messages");
const disallow_in_production_decorator_1 = require("../decorators/disallow-in-production.decorator");
const module_metadata_helper_service_1 = require("../helpers/module-metadata-helper.service");
const module_metadata_repository_1 = require("../repository/module-metadata.repository");
const permission_metadata_seeder_service_1 = require("../seeders/permission-metadata-seeder.service");
const file_1 = require("./file");
const schematic_service_1 = require("../helpers/schematic.service");
const command_service_1 = require("../helpers/command.service");
const solid_registry_1 = require("../helpers/solid-registry");
const crud_helper_service_1 = require("./crud-helper.service");
const model_metadata_service_1 = require("./model-metadata.service");
const setting_service_1 = require("./setting.service");
let ModuleMetadataService = ModuleMetadataService_1 = class ModuleMetadataService {
    constructor(dataSource, moduleMetadataRepo, crudHelperService, schematicService, commandService, fileService, settingService, permissionsSeederService, modelMetadataService, solidRegistry, moduleMetadataHelperService) {
        this.dataSource = dataSource;
        this.moduleMetadataRepo = moduleMetadataRepo;
        this.crudHelperService = crudHelperService;
        this.schematicService = schematicService;
        this.commandService = commandService;
        this.fileService = fileService;
        this.settingService = settingService;
        this.permissionsSeederService = permissionsSeederService;
        this.modelMetadataService = modelMetadataService;
        this.solidRegistry = solidRegistry;
        this.moduleMetadataHelperService = moduleMetadataHelperService;
        this.logger = new common_1.Logger(ModuleMetadataService_1.name);
    }
    async findMany(basicFilterDto) {
        const alias = 'moduleMetadata';
        let { limit, offset } = basicFilterDto;
        var qb = await this.moduleMetadataRepo.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 find(basicFilterDto) {
        return this.findMany(basicFilterDto);
    }
    async findOneByUserKey(name, relations = {}) {
        if (!name) {
            throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.ENTITY_NAME_REQUIRED);
        }
        const entity = await this.moduleMetadataRepo.findOne({
            where: {
                name: name,
            },
            relations: relations,
        });
        return entity;
    }
    async findOne(id, relations = {}) {
        if (!id) {
            throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.ENTITY_ID_REQUIRED);
        }
        const entity = await this.moduleMetadataRepo.findOne({
            where: {
                id: id,
            },
            relations: relations,
        });
        if (!entity) {
            throw new common_1.NotFoundException(error_messages_1.ERROR_MESSAGES.ENTITY_NOT_FOUND());
        }
        return entity;
    }
    async create(createDto, files = []) {
        try {
            return await this.dataSource.transaction(async (manager) => {
                const module = await this.createInDB(manager, createDto, files);
                await this.createInFile(module);
                return module;
            });
        }
        catch (error) {
            this.logger.error('Transaction failed:', error);
            throw error;
        }
    }
    async createInDB(manager, createDto, files = []) {
        if (files.length > 0) {
            const fileStoragePath = this.getFullFilePathForDisk(this.getFileName(files[0]));
            this.fileService.copy(files[0].path, fileStoragePath);
            this.fileService.delete(files[0].path);
            createDto.menuIconUrl = fileStoragePath;
        }
        const moduleMetadata = this.moduleMetadataRepo.create(createDto);
        return manager.save(moduleMetadata);
    }
    async createInFile(module) {
        try {
            const moduleMetaDataJson = {
                moduleMetadata: {
                    name: module?.name,
                    displayName: module?.displayName,
                    description: module?.description,
                    defaultDataSource: module?.defaultDataSource,
                    menuIconUrl: module?.menuIconUrl,
                    menuSequenceNumber: module?.menuSequenceNumber,
                    isSystem: module?.isSystem,
                    models: [],
                },
                roles: [],
                users: [],
                actions: [
                    {
                        displayName: `${module?.name} Home`,
                        name: `${module?.name}-home-action`,
                        type: "custom",
                        domain: "",
                        context: "",
                        customComponent: `/admin/core/${module?.name}/home`,
                        customIsModal: true,
                        serverEndpoint: "",
                        viewUserKey: "",
                        moduleUserKey: module?.name,
                        modelUserKey: ""
                    }
                ],
                menus: [
                    {
                        displayName: "Home",
                        name: `${module?.name}-home-menu`,
                        sequenceNumber: 1,
                        actionUserKey: `${module?.name}-home-action`,
                        moduleUserKey: module?.name,
                        parentMenuItemUserKey: "",
                        iconName: "home"
                    }
                ],
                views: [],
                emailTemplates: [],
                smsTemplates: [],
                mediaStorageProviders: [],
                securityRules: [],
            };
            const metadataJson = JSON.stringify(moduleMetaDataJson, null, 2);
            const folderPath = path.resolve(process.cwd(), 'module-metadata', module.name);
            const filePath = await this.moduleMetadataHelperService.getModuleMetadataFilePath(module.name);
            await fs.mkdir(folderPath, { recursive: true });
            await fs.writeFile(filePath, metadataJson);
        }
        catch (error) {
            this.logger.error('File creation failed:', error);
            throw new Error(error_messages_1.ERROR_MESSAGES.FILE_WRITE_FAILED);
        }
    }
    async update(id, updateModuleMetadataDto, files = []) {
        try {
            return await this.dataSource.transaction(async (manager) => {
                const module = await this.updateInDB(manager, id, updateModuleMetadataDto, files);
                await this.updateInFile(module);
                return module;
            });
        }
        catch (error) {
            this.logger.error('Transaction failed:', error);
            throw error;
        }
    }
    async updateInDB(manager, id, updateModuleMetadataDto, files = []) {
        const module = await this.moduleMetadataRepo.preload({
            id,
            ...updateModuleMetadataDto,
        });
        if (!module) {
            throw new common_1.NotFoundException(error_messages_1.ERROR_MESSAGES.MODULE_ID_NOT_FOUND(id));
        }
        if (files.length > 0) {
            const fileStoragePath = this.getFullFilePathForDisk(this.getFileName(files[0]));
            this.fileService.copy(files[0].path, fileStoragePath);
            this.fileService.delete(files[0].path);
            module.menuIconUrl = fileStoragePath;
        }
        return manager.save(module_metadata_entity_1.ModuleMetadata, module);
    }
    async updateInFile(module) {
        try {
            const filePath = await this.moduleMetadataHelperService.getModuleMetadataFilePath(module.name);
            let metaData;
            try {
                metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
            }
            catch (error) {
                metaData = {
                    moduleMetadata: {
                        name: null,
                        displayName: null,
                        description: null,
                        defaultDataSource: null,
                        menuIconUrl: null,
                        menuSequenceNumber: null,
                        isSystem: null,
                        models: [],
                    },
                    roles: [],
                    users: [],
                    actions: [],
                    menus: [],
                    views: [],
                    emailTemplates: [],
                    smsTemplates: [],
                    mediaStorageProviders: [],
                };
            }
            metaData.moduleMetadata.name = module?.name;
            metaData.moduleMetadata.displayName = module?.displayName;
            metaData.moduleMetadata.description = module?.description;
            metaData.moduleMetadata.defaultDataSource = module?.defaultDataSource;
            metaData.moduleMetadata.menuIconUrl = module?.menuIconUrl;
            metaData.moduleMetadata.menuSequenceNumber = module?.menuSequenceNumber;
            metaData.moduleMetadata.isSystem = module?.isSystem;
            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(updateModuleMetadataDto) {
        this.logger.debug(`Module Upsert called for : ${updateModuleMetadataDto.name}`);
        const existingModuleMetadata = await this.moduleMetadataRepo.findOne({
            where: {
                name: updateModuleMetadataDto.name
            }
        });
        const { models, ...restUpdateModuleMetadataDto } = updateModuleMetadataDto;
        if (existingModuleMetadata) {
            const updatedModuleMetadata = { ...existingModuleMetadata, ...restUpdateModuleMetadataDto };
            return this.moduleMetadataRepo.save(updatedModuleMetadata);
        }
        else {
            const moduleMetadata = this.moduleMetadataRepo.create(restUpdateModuleMetadataDto);
            return this.moduleMetadataRepo.save(moduleMetadata);
        }
    }
    async removeByName(name) {
        const entity = await this.findOneByUserKey(name);
        if (entity) {
            await this.moduleMetadataRepo.remove(entity);
        }
    }
    async remove(id) {
        const entity = await this.findOne(id);
        await this.cleanupOnDelete(entity.id);
        const r = await this.moduleMetadataRepo.remove(entity);
        return r;
    }
    async cleanupOnDelete(moduleEntityId) {
        const moduleEntity = await this.moduleMetadataRepo.findOne({
            where: {
                id: moduleEntityId,
            }
        });
        if (!moduleEntity) {
            this.logger.log(`Invalid moduleEntityId: ${moduleEntityId} unable to resolve model metadata`);
            return;
        }
        if (moduleEntity.id !== moduleEntityId) {
            this.logger.log(`Invalid moduleEntityId: ${moduleEntityId} unable to resolve model metadata id ${moduleEntity.id} not matching with the one passed as argument ${moduleEntityId}`);
            return;
        }
        this.logger.log(`Cleaning up for module: ${moduleEntity.name}.`);
        const modulePath = await this.moduleMetadataHelperService.getModulePath(moduleEntity.name);
        if (modulePath) {
            this.logger.log(`Module path: ${modulePath}`);
            const moduleMetadataPAth = await this.moduleMetadataHelperService.getModuleMetadataFolderPath(moduleEntity.name);
            this.logger.log(`About to delete module metadata path: ${moduleMetadataPAth}`);
            try {
                await fs.rm(modulePath, { recursive: true, force: true });
                await fs.rm(moduleMetadataPAth, { recursive: true, force: true });
                this.logger.log(`Deleted file: ${moduleMetadataPAth}`);
            }
            catch (error) {
                this.logger.error(`Error deleting file: ${moduleMetadataPAth}`, error);
                throw new Error(error_messages_1.ERROR_MESSAGES.FILE_DELETE_FAILED);
            }
        }
        await this.dataSource.query(`CALL cleanup_module_metadata($1, $2)`, [moduleEntity.name, true]);
    }
    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.moduleMetadataRepo.findOne({
                where: {
                    id: id,
                }
            });
            removedEntities.push(await this.moduleMetadataRepo.remove(entity));
        }
        return removedEntities;
    }
    async refreshPermission() {
        await this.permissionsSeederService.seed();
        return true;
    }
    async generateCodeViaCtl(moduleId) {
        const module = await this.findOne(moduleId);
        return this.commandService.executeCommandWithArgs({
            command: 'npx',
            args: ['@solidxai/solidctl@latest', 'generate', 'module', `--name=${module.name}`],
            cwd: path.join(process.cwd(), '..'),
        });
    }
    async generateCode(options) {
        if (!options.moduleId && !options.moduleUserKey) {
            throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.MODEL_REQUIRED_FOR_CODE_GENERATION);
        }
        const module = options.moduleId ? await this.findOne(options.moduleId) : await this.findOneByUserKey(options.moduleUserKey);
        if (!module) {
            throw new common_1.NotFoundException(error_messages_1.ERROR_MESSAGES.MODULE_ID_NOT_FOUND(options.moduleId));
        }
        const moduleInstance = this.solidRegistry.getModule(`${(0, string_helper_1.classify)(module.name)}Module`);
        if (!moduleInstance) {
            const addModuleOutput = await this.generateAddModuleCode(options);
            const refreshModuleOutput = await this.generateRefreshModuleCode(options);
            return `${addModuleOutput}\n${refreshModuleOutput}`;
        }
        else {
            return await this.generateRefreshModuleCode(options);
        }
    }
    async generateAddModuleCode(options = { dryRun: false }) {
        if (!options.moduleId && !options.moduleUserKey) {
            throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.MODEL_REQUIRED_FOR_CODE_GENERATION);
        }
        const module = options.moduleId ? await this.findOne(options.moduleId) : await this.findOneByUserKey(options.moduleUserKey);
        const output = await this.schematicService.executeSchematicCommand(schematic_service_1.ADD_MODULE_COMMAND, { module: module.name }, options.dryRun ?? false);
        this.logger.debug(`Schematic output : ${output}`);
        return output;
    }
    async generateRefreshModuleCode(options) {
        const query = {
            relations: { models: { fields: true } },
        };
        const module = options.moduleId ? await this.findOne(options.moduleId, query.relations) : await this.findOneByUserKey(options.moduleUserKey, query.relations);
        const outputLines = [];
        for (const model of module.models) {
            const codeGenerationOptions = {
                modelId: model.id,
                dryRun: options.dryRun,
            };
            const output = await this.modelMetadataService.handleGenerateCode(codeGenerationOptions);
            this.logger.debug(`Schematic output : ${output}`);
            outputLines.push(output);
        }
        return outputLines.join('\n');
    }
    getFullFilePathForDisk(fileName) {
        const base = this.settingService.getConfigValue("fileStorageDir")
            || default_settings_provider_service_1.DEFAULT_MEDIA_FILE_STORAGE_DIR;
        if (path.isAbsolute(fileName) || fileName.startsWith(`${base}/`)) {
            return fileName;
        }
        return `${base}/${fileName}`;
    }
    getFileName(file) {
        return `${file.filename}-${file.originalname}`;
    }
};
exports.ModuleMetadataService = ModuleMetadataService;
__decorate([
    (0, disallow_in_production_decorator_1.DisallowInProduction)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number]),
    __metadata("design:returntype", Promise)
], ModuleMetadataService.prototype, "remove", null);
__decorate([
    (0, disallow_in_production_decorator_1.DisallowInProduction)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number]),
    __metadata("design:returntype", Promise)
], ModuleMetadataService.prototype, "generateCodeViaCtl", null);
__decorate([
    (0, disallow_in_production_decorator_1.DisallowInProduction)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], ModuleMetadataService.prototype, "generateCode", null);
exports.ModuleMetadataService = ModuleMetadataService = ModuleMetadataService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectDataSource)()),
    __param(8, (0, common_1.Inject)((0, common_1.forwardRef)(() => model_metadata_service_1.ModelMetadataService))),
    __metadata("design:paramtypes", [typeorm_2.DataSource,
        module_metadata_repository_1.ModuleMetadataRepository,
        crud_helper_service_1.CrudHelperService,
        schematic_service_1.SchematicService,
        command_service_1.CommandService,
        file_1.DiskFileService,
        setting_service_1.SettingService,
        permission_metadata_seeder_service_1.PermissionMetadataSeederService,
        model_metadata_service_1.ModelMetadataService,
        solid_registry_1.SolidRegistry,
        module_metadata_helper_service_1.ModuleMetadataHelperService])
], ModuleMetadataService);
//# sourceMappingURL=module-metadata.service.js.map