"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "GCodeParser", {
    enumerable: true,
    get: function() {
        return GCodeParser;
    }
});
const _promises = /*#__PURE__*/ _interop_require_wildcard(require("node:fs/promises"));
const _nodereadline = /*#__PURE__*/ _interop_require_wildcard(require("node:readline"));
const _nodefs = require("node:fs");
const _bgcodethumbnailparser = require("../bgcode/bgcode-thumbnail.parser");
function _getRequireWildcardCache(nodeInterop) {
    if (typeof WeakMap !== "function") return null;
    var cacheBabelInterop = new WeakMap();
    var cacheNodeInterop = new WeakMap();
    return (_getRequireWildcardCache = function(nodeInterop) {
        return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
    })(nodeInterop);
}
function _interop_require_wildcard(obj, nodeInterop) {
    if (!nodeInterop && obj && obj.__esModule) {
        return obj;
    }
    if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
        return {
            default: obj
        };
    }
    var cache = _getRequireWildcardCache(nodeInterop);
    if (cache && cache.has(obj)) {
        return cache.get(obj);
    }
    var newObj = {
        __proto__: null
    };
    var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
    for(var key in obj){
        if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
            var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
            if (desc && (desc.get || desc.set)) {
                Object.defineProperty(newObj, key, desc);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    newObj.default = obj;
    if (cache) {
        cache.set(obj, newObj);
    }
    return newObj;
}
class GCodeParser {
    maxHeaderLinesToRead = 500;
    maxFooterLinesToRead = 500;
    async parse(filePath) {
        const stats = await _promises.stat(filePath);
        const fileName = filePath.split(/[/\\]/).pop() || filePath;
        const metadata = await this.extractMetadata(filePath);
        const thumbnails = await this.extractThumbnails(filePath);
        const normalized = {
            fileName,
            fileFormat: "gcode",
            fileSize: stats.size,
            gcodePrintTimeSeconds: this.parseTime(metadata.estimated_printing_time_normal_mode || metadata.estimated_printing_time || metadata.print_time),
            nozzleDiameterMm: this.parseFloat(metadata.nozzle_diameter),
            filamentDiameterMm: this.parseFloat(metadata.filament_diameter),
            filamentDensityGramsCm3: this.parseFloat(metadata.filament_density),
            filamentUsedMm: this.parseFloat(metadata.filament_used_mm),
            filamentUsedCm3: this.parseFloat(metadata.filament_used_cm3),
            filamentUsedGrams: this.parseFloat(metadata.filament_used_g),
            totalFilamentUsedGrams: this.parseFloat(metadata.total_filament_used_g || metadata.filament_used_g),
            layerHeight: this.parseFloat(metadata.layer_height),
            firstLayerHeight: this.parseFloat(metadata.first_layer_height || metadata.initial_layer_height),
            bedTemperature: this.parseFloat(metadata.bed_temperature || metadata.first_layer_bed_temperature),
            nozzleTemperature: this.parseFloat(metadata.temperature || metadata.first_layer_temperature),
            fillDensity: metadata.fill_density || null,
            filamentType: metadata.filament_type || null,
            printerModel: metadata.printer_model || metadata.printer_name || null,
            slicerVersion: metadata.generated_by || metadata.slicer_version || null,
            maxLayerZ: this.parseFloat(metadata.max_layer_z),
            totalLayers: this.parseInt(metadata.total_layers) || this.parseInt(metadata.layer_count),
            generatedBy: metadata.generated_by,
            thumbnails: thumbnails.length > 0 ? thumbnails.map((t)=>({
                    width: t.width,
                    height: t.height,
                    format: t.format,
                    dataLength: t.data?.length || 0
                })) : undefined
        };
        return {
            raw: {
                _thumbnails: thumbnails,
                metadata
            },
            normalized
        };
    }
    async extractMetadata(filePath) {
        const metadata = {};
        await this.extractMetadataFromStart(filePath, metadata);
        await this.extractMetadataFromEnd(filePath, metadata);
        return metadata;
    }
    async extractMetadataFromStart(filePath, metadata) {
        let linesRead = 0;
        const fileStream = (0, _nodefs.createReadStream)(filePath);
        const rl = _nodereadline.createInterface({
            input: fileStream,
            crlfDelay: Infinity
        });
        for await (const line of rl){
            if (linesRead >= this.maxHeaderLinesToRead) break;
            linesRead++;
            this.parseMetadataLine(line, metadata);
        }
        rl.close();
        fileStream.close();
    }
    async extractMetadataFromEnd(filePath, metadata) {
        const stats = await _promises.stat(filePath);
        const fileSize = stats.size;
        const estimatedBytes = this.maxFooterLinesToRead * 50;
        const startPosition = Math.max(0, fileSize - estimatedBytes);
        const fileHandle = await _promises.open(filePath, 'r');
        try {
            const buffer = Buffer.alloc(estimatedBytes);
            const { bytesRead } = await fileHandle.read(buffer, 0, estimatedBytes, startPosition);
            const text = buffer.toString('utf8', 0, bytesRead);
            const lines = text.split('\n');
            for (const line of lines){
                this.parseMetadataLine(line, metadata);
            }
        } finally{
            await fileHandle.close();
        }
    }
    parseMetadataLine(line, metadata) {
        if (!line.startsWith(";")) return;
        const generatedByMatch = line.match(/^;\s*generated by\s+([^\s]+)/i);
        if (generatedByMatch && !metadata.generated_by) {
            metadata.generated_by = generatedByMatch[1];
            return;
        }
        const prusaMatch = line.match(/^;\s*([^=]+?)\s*=\s*(.+)$/);
        if (prusaMatch) {
            let key = prusaMatch[1].trim().toLowerCase().replace(/\s+/g, "_");
            let value = prusaMatch[2].trim();
            key = key.replace(/\[([^\]]+)\]/g, (_, unit)=>'_' + unit.replace(/\^/g, ''));
            key = key.replace(/\(([^)]+)\)/g, (_, content)=>'_' + content.replace(/\s+/g, '_'));
            key = key.replace(/_+/g, '_');
            if (!metadata[key]) {
                metadata[key] = value.trim();
            }
            return;
        }
        const curaMatch = line.match(/^;([A-Z_]+):(.+)$/);
        if (curaMatch) {
            const [, key, value] = curaMatch;
            const normalizedKey = key.toLowerCase();
            if (!metadata[normalizedKey]) {
                metadata[normalizedKey] = value.trim();
            }
            return;
        }
        const s3dMatch = line.match(/^;\s*([^:]+?):\s*(.+)$/);
        if (s3dMatch) {
            const [, key, value] = s3dMatch;
            const normalizedKey = key.trim().toLowerCase().replace(/\s+/g, "_");
            if (!metadata[normalizedKey]) {
                metadata[normalizedKey] = value.trim();
            }
        }
    }
    async extractThumbnails(filePath) {
        const thumbnails = [];
        let linesRead = 0;
        let inThumbnail = false;
        let thumbnailData = [];
        let currentWidth = 0;
        let currentHeight = 0;
        let currentFormat = "PNG";
        const fileStream = (0, _nodefs.createReadStream)(filePath);
        const rl = _nodereadline.createInterface({
            input: fileStream,
            crlfDelay: Infinity
        });
        for await (const line of rl){
            if (linesRead >= this.maxHeaderLinesToRead && !inThumbnail) break;
            linesRead++;
            const thumbnailStart = line.match(/;\s*thumbnail begin (\d+)x(\d+)\s*(\w+)?/i);
            if (thumbnailStart) {
                inThumbnail = true;
                currentWidth = parseInt(thumbnailStart[1]);
                currentHeight = parseInt(thumbnailStart[2]);
                const thirdParam = thumbnailStart[3];
                if (thirdParam && /^(PNG|JPG|JPEG|QOI)$/i.test(thirdParam)) {
                    currentFormat = thirdParam.toUpperCase();
                } else {
                    currentFormat = "PNG";
                }
                thumbnailData = [];
                continue;
            }
            if (inThumbnail) {
                if (line.match(/;\s*thumbnail end/i)) {
                    let base64Data = thumbnailData.join("");
                    let format = currentFormat.toUpperCase();
                    if (format === "QOI") {
                        try {
                            const qoiBuffer = Buffer.from(base64Data, 'base64');
                            const pngBuffer = (0, _bgcodethumbnailparser.convertQoiToPng)(qoiBuffer);
                            base64Data = pngBuffer.toString('base64');
                            format = "PNG";
                        } catch  {}
                    }
                    thumbnails.push({
                        width: currentWidth,
                        height: currentHeight,
                        format,
                        data: base64Data
                    });
                    inThumbnail = false;
                    thumbnailData = [];
                } else if (line.startsWith(";")) {
                    const data = line.substring(1).trim();
                    if (data) {
                        thumbnailData.push(data);
                    }
                }
            }
        }
        rl.close();
        fileStream.close();
        return thumbnails;
    }
    parseFloat(value) {
        if (!value) return null;
        const num = parseFloat(value);
        return isNaN(num) ? null : num;
    }
    parseInt(value) {
        if (!value) return null;
        const num = parseInt(value, 10);
        return isNaN(num) ? null : num;
    }
    parseTime(value) {
        if (!value) return null;
        const match = value.match(/(?:(\d+)h)?(?:\s*(\d+)m)?(?:\s*(\d+)s)?/);
        if (match && (match[1] || match[2] || match[3])) {
            const hours = parseInt(match[1] || "0");
            const minutes = parseInt(match[2] || "0");
            const secs = parseInt(match[3] || "0");
            return hours * 3600 + minutes * 60 + secs;
        }
        const seconds = parseFloat(value);
        if (!isNaN(seconds)) return seconds;
        return null;
    }
}

//# sourceMappingURL=gcode.parser.js.map