"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 ExcelService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExcelService = void 0;
const common_1 = require("@nestjs/common");
const ExcelJS = __importStar(require("exceljs"));
const error_messages_1 = require("../constants/error-messages");
const date_helper_1 = require("../helpers/date.helper");
const stream_1 = require("stream");
const DEFAULT_PAGE_SIZE = 100;
let ExcelService = ExcelService_1 = class ExcelService {
    constructor() {
        this.logger = new common_1.Logger(ExcelService_1.name);
    }
    async createExcelStream(getDataRecords, chunkSize = 100, headers = []) {
        if (headers.length === 0 && typeof getDataRecords !== 'function') {
            throw new Error(error_messages_1.ERROR_MESSAGES.MISSING_HEADERS_OR_FUNCTION);
        }
        if (getDataRecords && chunkSize <= 0) {
            throw new Error(error_messages_1.ERROR_MESSAGES.INVALID_CHUNK_SIZE);
        }
        const passThrough = new stream_1.PassThrough();
        try {
            const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: passThrough });
            const worksheet = workbook.addWorksheet('Data');
            let isHeaderWritten = false;
            if (headers.length > 0) {
                worksheet.columns = headers.map((header) => ({
                    header: header,
                    key: header,
                    width: 20,
                }));
                isHeaderWritten = true;
            }
            if (typeof getDataRecords !== 'function') {
                workbook.commit();
                return passThrough;
            }
            let chunkIndex = 0;
            while (true) {
                const records = await getDataRecords(chunkIndex, chunkSize);
                if (records.length === 0)
                    break;
                if (!isHeaderWritten) {
                    worksheet.columns = Object.keys(records[0]).map((key) => ({
                        header: key.toUpperCase(),
                        key: key,
                        width: 20,
                    }));
                    isHeaderWritten = true;
                }
                records.forEach((item) => {
                    worksheet.addRow(item).commit();
                });
                chunkIndex++;
                this.logger.debug(`✅ Chunk ${chunkIndex} written to Excel`);
            }
            workbook.commit();
        }
        catch (error) {
            this.logger.error(`❌ Error writing Excel: ${error.message}`);
            passThrough.destroy(error);
            throw error;
        }
        return passThrough;
    }
    async *readExcelInPagesFromStream(stream, options) {
        const { pageSize = DEFAULT_PAGE_SIZE, hasHeaderRow = true, providedHeaders = [] } = options || {};
        const workbookReader = new ExcelJS.stream.xlsx.WorkbookReader(stream, {});
        let headers = [];
        let page = [];
        let isFirstRow = true;
        let hasYieldedData = false;
        for await (const worksheet of workbookReader) {
            for await (const row of worksheet) {
                const values = Array.isArray(row.values) ? row.values.slice(1) : [];
                if (isFirstRow) {
                    isFirstRow = false;
                    if (hasHeaderRow) {
                        headers = values.map(v => v?.toString().trim() || '');
                        continue;
                    }
                    else if (providedHeaders.length) {
                        headers = providedHeaders;
                    }
                    else {
                        headers = values.map((_, idx) => `${idx}`);
                    }
                }
                while (values.length < headers.length)
                    values.push(null);
                if (values.length > headers.length)
                    values.length = headers.length;
                const record = headers.reduce((acc, key, i) => {
                    acc[key] = values[i] ?? null;
                    return acc;
                }, {});
                if (Object.values(record).every(v => v === null || v === ''))
                    continue;
                page.push(record);
                if (page.length === pageSize) {
                    yield { headers, data: page };
                    hasYieldedData = true;
                    page = [];
                }
            }
        }
        if (page.length > 0) {
            yield { headers, data: page };
            hasYieldedData = true;
        }
        if (!hasYieldedData && headers.length > 0) {
            yield { headers, data: [] };
        }
    }
    cleanString(value) {
        return (value === null || value === undefined ? '' : String(value))
            .replace(/\uFEFF/g, '')
            .replace(/\u00A0/g, ' ')
            .replace(/\s+/g, ' ')
            .trim();
    }
    normalizeCellValue(value, sharedStrings) {
        if (value === undefined || value === null)
            return null;
        if (typeof value === 'object' && value && 'sharedString' in value) {
            const idx = value.sharedString;
            const resolved = sharedStrings?.[idx];
            if (resolved === undefined || resolved === null)
                return null;
            if (typeof resolved === 'string')
                return resolved;
            if (typeof resolved === 'object') {
                if ('text' in resolved && typeof resolved.text === 'string')
                    return resolved.text;
                if ('richText' in resolved && Array.isArray(resolved.richText)) {
                    return resolved.richText.map((item) => item?.text ?? '').join('');
                }
                if ('value' in resolved)
                    return resolved.value;
            }
            return resolved;
        }
        if (typeof value === 'object' && value) {
            if ('text' in value && typeof value.text === 'string')
                return value.text;
            if ('richText' in value && Array.isArray(value.richText)) {
                return value.richText.map((item) => item?.text ?? '').join('');
            }
            if ('result' in value)
                return value.result;
            if ('formula' in value)
                return value.formula;
            if ('hyperlink' in value && typeof value.hyperlink === 'string') {
                return value.text ?? value.hyperlink;
            }
            if ('value' in value)
                return this.normalizeCellValue(value.value, sharedStrings);
        }
        return value;
    }
    async readExcelFromStreamNonStreaming(stream, options) {
        const { hasHeaderRow = true, providedHeaders = [], worksheetIndex = 0, maxRows, } = options || {};
        const chunks = [];
        for await (const chunk of stream) {
            chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
        }
        const buffer = Buffer.concat(chunks);
        const workbook = new ExcelJS.Workbook();
        await workbook.xlsx.load(buffer);
        const worksheet = workbook.worksheets?.[worksheetIndex];
        if (!worksheet) {
            return { headers: [], rows: [] };
        }
        let headers = [];
        const firstRow = worksheet.getRow(1);
        const firstRowValues = Array.isArray(firstRow.values) ? firstRow.values.slice(1) : [];
        const normalizeNonStreamingCell = (v) => {
            return this.normalizeCellValue(v);
        };
        if (hasHeaderRow) {
            headers = firstRowValues.map((v) => this.cleanString(normalizeNonStreamingCell(v)));
        }
        else if (providedHeaders.length) {
            headers = providedHeaders.map((h) => this.cleanString(h));
        }
        else {
            headers = firstRowValues.map((_, idx) => `${idx}`);
        }
        if (hasHeaderRow && headers.length > 0 && headers.every((h) => !h)) {
            this.logger.warn(`ExcelService.readExcelFromStreamNonStreaming: header row appears blank`);
        }
        const rows = [];
        const startRowNumber = hasHeaderRow ? 2 : 1;
        const lastRowNumber = worksheet.rowCount || 0;
        for (let r = startRowNumber; r <= lastRowNumber; r++) {
            if (maxRows && rows.length >= maxRows)
                break;
            const row = worksheet.getRow(r);
            const rawValues = Array.isArray(row.values) ? row.values.slice(1) : [];
            const values = rawValues.map((v) => normalizeNonStreamingCell(v));
            while (values.length < headers.length)
                values.push(null);
            if (values.length > headers.length)
                values.length = headers.length;
            const record = headers.reduce((acc, key, i) => {
                acc[key] = values[i] ?? null;
                return acc;
            }, {});
            if (Object.values(record).every((v) => v === null || this.cleanString(v) === ''))
                continue;
            rows.push(record);
        }
        return { headers, rows };
    }
    parseAndValidateDate(value) {
        return (0, date_helper_1.parseFlexibleDate)(value);
    }
};
exports.ExcelService = ExcelService;
exports.ExcelService = ExcelService = ExcelService_1 = __decorate([
    (0, common_1.Injectable)()
], ExcelService);
//# sourceMappingURL=excel.service.js.map