"use strict";
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 __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 ExportTransactionService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExportTransactionService = void 0;
const common_1 = require("@nestjs/common");
const core_1 = require("@nestjs/core");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const crud_service_1 = require("./crud.service");
const lodash_1 = require("lodash");
const string_helper_1 = require("../helpers/string.helper");
const class_validator_1 = require("class-validator");
const create_export_transaction_dto_1 = require("../dtos/create-export-transaction.dto");
const csv_service_1 = require("./csv.service");
const excel_service_1 = require("./excel.service");
const mediaStorageProviders_1 = require("./mediaStorageProviders");
const solid_introspect_service_1 = require("./solid-introspect.service");
const error_messages_1 = require("../constants/error-messages");
const model_metadata_helper_service_1 = require("../helpers/model-metadata-helper.service");
const export_transaction_repository_1 = require("../repository/export-transaction.repository");
const field_metadata_repository_1 = require("../repository/field-metadata.repository");
const model_metadata_repository_1 = require("../repository/model-metadata.repository");
const EXPORT_CHUNK_SIZE = 100;
var ExportStatus;
(function (ExportStatus) {
    ExportStatus["STARTED"] = "started";
    ExportStatus["COMPLETED"] = "completed";
    ExportStatus["FAILED"] = "failed";
})(ExportStatus || (ExportStatus = {}));
var ExportFormat;
(function (ExportFormat) {
    ExportFormat["CSV"] = "csv";
    ExportFormat["EXCEL"] = "excel";
})(ExportFormat || (ExportFormat = {}));
let ExportTransactionService = ExportTransactionService_1 = class ExportTransactionService extends crud_service_1.CRUDService {
    constructor(entityManager, repo, introspectService, excelService, csvService, fieldRepo, modelMetadataRepo, moduleRef, modelMetadataHelperService) {
        super(entityManager, repo, 'exportTransaction', 'solid-core', moduleRef);
        this.entityManager = entityManager;
        this.repo = repo;
        this.introspectService = introspectService;
        this.excelService = excelService;
        this.csvService = csvService;
        this.fieldRepo = fieldRepo;
        this.modelMetadataRepo = modelMetadataRepo;
        this.moduleRef = moduleRef;
        this.modelMetadataHelperService = modelMetadataHelperService;
        this.logger = new common_1.Logger(ExportTransactionService_1.name);
    }
    async triggerExportSync(id, exportTransactionEntity, updateDto, filters) {
        try {
            const modeldata = await this.modelMetadataRepo.findOne({
                where: { id: updateDto?.modelMetadataId },
                relations: { fields: true },
            });
            const modelName = modeldata?.singularName;
            const modelTemplateName = modelName;
            const fields = JSON.parse(updateDto?.fields);
            const templateFormat = updateDto?.templateFormat;
            const { exportStream, templateName, uuid, exportTransaction } = await this.getExportStreamDetails(modelName, modelTemplateName, fields, modeldata, templateFormat, id, exportTransactionEntity, filters);
            this.updateExportTransaction(id, ExportStatus.COMPLETED);
            const fileName = this.getFileName(templateName, uuid, templateFormat);
            const mimeType = this.getMimeType(templateFormat);
            return { exportStream, fileName, mimeType, exportTransaction };
        }
        catch (error) {
            this.updateExportTransaction(id, ExportStatus.FAILED, error.message);
            throw error;
        }
    }
    async triggerExportAsync(id, exportTransactionEntity, updateDto, filters) {
        try {
            const modeldata = await this.modelMetadataRepo.findOne({
                where: { id: updateDto?.modelMetadataId },
                relations: { fields: true },
            });
            const modelName = modeldata?.singularName;
            const modelTemplateName = modelName;
            const fields = JSON.parse(updateDto?.fields);
            const templateFormat = updateDto?.templateFormat;
            const { exportStream, templateName, uuid, exportTransaction } = await this.getExportStreamDetails(modelName, modelTemplateName, fields, modeldata, templateFormat, id, exportTransactionEntity, filters);
            await this.storeExportStream(exportStream, exportTransaction, this.getFileName(templateName, uuid, templateFormat));
            this.updateExportTransaction(id, ExportStatus.COMPLETED);
        }
        catch (error) {
            this.updateExportTransaction(id, ExportStatus.FAILED, error.message);
            throw error;
        }
    }
    async loadExportTransaction(id) {
        return await this.repo.findOne({
            where: { id: id },
            relations: { exportTemplate: { modelMetadata: { fields: true } } },
        });
    }
    async updateExportTransaction(id, status, error) {
        await this.repo.update(id, { status, error });
    }
    async getExportStreamDetails(modelName, templateName, fields, modelData, templateFormat, id, exportTransaction, filters) {
        const modelService = this.introspectService.getProvider(`${(0, string_helper_1.classify)(modelName)}Service`);
        const uuid = String(id);
        const dataRecordsFunc = await this.getDataRecordsFunc(fields, modelService, modelData, filters);
        let exportStream = await this.getExportStream(templateFormat, dataRecordsFunc);
        return { exportStream, templateName, uuid, exportTransaction };
    }
    async getExportStream(templateFormat, dataRecordsFunc) {
        let exportStream = null;
        switch (templateFormat) {
            case ExportFormat.EXCEL:
                exportStream = await this.excelService.createExcelStream(dataRecordsFunc, EXPORT_CHUNK_SIZE);
                break;
            case ExportFormat.CSV:
                exportStream = await this.csvService.createCsvStream(dataRecordsFunc, EXPORT_CHUNK_SIZE);
                break;
            default:
                throw new Error(error_messages_1.ERROR_MESSAGES.INVALID_FORMAT('export' + templateFormat));
        }
        return exportStream;
    }
    async storeExportStream(exportStream, exportTransaction, fileName) {
        const exportedFileMediaField = await this.fieldRepo.findOne({
            where: {
                name: 'exportedFile',
                model: {
                    singularName: 'exportTransaction'
                },
            },
            relations: ['model', 'mediaStorageProvider'],
        });
        const storageProviderMetadata = exportedFileMediaField.mediaStorageProvider;
        const storageProviderType = storageProviderMetadata.type;
        const storageProvider = await (0, mediaStorageProviders_1.getMediaStorageProvider)(this.moduleRef, storageProviderType);
        await storageProvider.storeStreams([[exportStream, fileName]], exportTransaction, exportedFileMediaField);
    }
    getFileName(templateName, exportTransactionUUID, fileFormat) {
        const extension = (fileFormat === ExportFormat.EXCEL) ? 'xlsx' : 'csv';
        return `${(0, lodash_1.kebabCase)(templateName)}-${exportTransactionUUID}.${extension}`;
    }
    getMimeType(fileFormat) {
        return (fileFormat === ExportFormat.EXCEL) ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'text/csv';
    }
    async getDataRecordsFunc(fields, modelService, modelMetadata, filters) {
        const allModelFields = await this.modelMetadataHelperService.loadFieldHierarchy(modelMetadata.singularName);
        const modelFields = allModelFields.filter((f) => fields.includes(f.name));
        const relatedModelsUserKeyMap = new Map();
        for (const field of modelFields) {
            if (field.relationType && field.relationCoModelSingularName) {
                const relatedModelMetadata = await this.modelMetadataRepo.findOne({
                    where: { singularName: field.relationCoModelSingularName },
                    relations: ['userKeyField'],
                });
                if (relatedModelMetadata?.userKeyField?.name) {
                    relatedModelsUserKeyMap.set(field.name, relatedModelMetadata.userKeyField.name);
                }
            }
        }
        const fieldNameToDisplayName = new Map();
        for (const field of modelFields || []) {
            if (field.name) {
                fieldNameToDisplayName.set(field.name, field.displayName ?? field.name);
            }
        }
        return async (chunkIndex, chunkSize) => {
            const offset = chunkIndex * chunkSize;
            const recordFilterDto = {
                limit: chunkSize,
                offset,
                populate: modelFields
                    .filter((f) => f.relationType !== null)
                    .map((f) => f.name),
            };
            const cleanedFilters = cleanNullsFromObject(filters);
            if (cleanedFilters && Object.keys(cleanedFilters).length > 0) {
                recordFilterDto.filters = cleanedFilters;
            }
            const data = await modelService.instance.find(recordFilterDto);
            const records = data.records ?? [];
            const cleanedRecords = records.map((record) => {
                const newRecord = {};
                for (const field of modelFields) {
                    if (!field.relationType) {
                        const displayKey = fieldNameToDisplayName.get(field.name) ?? field.name;
                        const fieldMeta = modelFields.find(f => f.name === field.name);
                        if ((fieldMeta?.type === 'datetime' || fieldMeta?.type === 'date') && record[field.name]) {
                            newRecord[displayKey] = new Date(record[field.name]).toISOString();
                        }
                        else {
                            newRecord[displayKey] = record[field.name];
                        }
                    }
                }
                for (const [relatedFieldName, userKeyFieldName] of relatedModelsUserKeyMap.entries()) {
                    const relatedData = record[relatedFieldName];
                    const displayKey = fieldNameToDisplayName.get(relatedFieldName) ?? relatedFieldName;
                    if (Array.isArray(relatedData)) {
                        const values = relatedData
                            .map(item => {
                            const val = item?.[userKeyFieldName];
                            const relatedFieldMeta = modelFields.find(f => f.name === relatedFieldName);
                            if ((relatedFieldMeta?.type === 'datetime' || relatedFieldMeta?.type === 'date') && val) {
                                return new Date(val).toISOString();
                            }
                            return val;
                        })
                            .filter(Boolean);
                        newRecord[displayKey] = values.join(', ');
                    }
                    else if (relatedData && typeof relatedData === 'object') {
                        let val = relatedData?.[userKeyFieldName] ?? null;
                        const relatedFieldMeta = modelFields.find(f => f.name === relatedFieldName);
                        if ((relatedFieldMeta?.type === 'datetime' || relatedFieldMeta?.type === 'date') && val) {
                            val = new Date(val).toISOString();
                        }
                        newRecord[displayKey] = val;
                    }
                    else {
                        newRecord[displayKey] = null;
                    }
                }
                return newRecord;
            });
            return cleanedRecords;
        };
    }
    async toDto(data) {
        const dto = new create_export_transaction_dto_1.CreateExportTransactionDto(data);
        const errors = await (0, class_validator_1.validate)(dto);
        if (errors.length > 0) {
            console.error("Validation failed:", errors);
            return null;
        }
        return dto;
    }
};
exports.ExportTransactionService = ExportTransactionService;
exports.ExportTransactionService = ExportTransactionService = ExportTransactionService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectEntityManager)()),
    __metadata("design:paramtypes", [typeorm_2.EntityManager,
        export_transaction_repository_1.ExportTransactionRepository,
        solid_introspect_service_1.SolidIntrospectService,
        excel_service_1.ExcelService,
        csv_service_1.CsvService,
        field_metadata_repository_1.FieldMetadataRepository,
        model_metadata_repository_1.ModelMetadataRepository,
        core_1.ModuleRef,
        model_metadata_helper_service_1.ModelMetadataHelperService])
], ExportTransactionService);
function cleanNullsFromObject(obj) {
    if (Array.isArray(obj)) {
        return obj
            .filter(item => item !== null && item !== undefined)
            .map(cleanNullsFromObject);
    }
    else if (typeof obj === 'object' && obj !== null) {
        const newObj = {};
        for (const key in obj) {
            const value = obj[key];
            if (value !== null && value !== undefined) {
                const cleanedValue = cleanNullsFromObject(value);
                if ((typeof cleanedValue === 'object' && Object.keys(cleanedValue).length > 0) ||
                    (Array.isArray(cleanedValue) && cleanedValue.length > 0) ||
                    typeof cleanedValue !== 'object') {
                    newObj[key] = cleanedValue;
                }
            }
        }
        return newObj;
    }
    return obj;
}
//# sourceMappingURL=export-transaction.service.js.map