"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "ThreeMFParser", {
    enumerable: true,
    get: function() {
        return ThreeMFParser;
    }
});
const _promises = /*#__PURE__*/ _interop_require_wildcard(require("node:fs/promises"));
const _nodepath = /*#__PURE__*/ _interop_require_wildcard(require("node:path"));
const _admzip = /*#__PURE__*/ _interop_require_default(require("adm-zip"));
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
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 ThreeMFParser {
    async parse(filePath) {
        const stats = await _promises.stat(filePath);
        const fileName = _nodepath.basename(filePath);
        const zip = new _admzip.default(filePath);
        const zipEntries = zip.getEntries();
        const metadataJsonEntry = zipEntries.find((e)=>e.entryName === "metadata.json");
        let metadata = {};
        if (metadataJsonEntry) {
            const jsonContent = metadataJsonEntry.getData().toString("utf8");
            const jsonData = JSON.parse(jsonContent);
            metadata = this.normalizeJsonMetadata(jsonData);
        } else {
            const modelEntry = zipEntries.find((e)=>e.entryName === "3D/3dmodel.model" || e.entryName === "Metadata/model_settings.config");
            metadata = modelEntry ? this.extractMetadataFromXML(modelEntry.getData().toString("utf8")) : {};
        }
        const plates = this.extractPlates(zipEntries);
        const isMultiPlate = plates.length > 1;
        const thumbnails = this.extractThumbnails(zipEntries);
        let topLevelPrintTime = this.parseTime(metadata.printTime);
        let topLevelFilamentWeight = this.parseFloat(metadata.totalFilamentWeight || metadata.filamentWeight);
        let topLevelLayers = this.parseInt(metadata.layerCount);
        let topLevelFilamentUsedMm = this.parseFloat(metadata.filamentUsed);
        let topLevelFilamentUsedCm3 = this.parseFloat(metadata.filamentVolume);
        let topLevelFilamentDensity = this.parseFloat(metadata.filamentDensity);
        let topLevelMaxZ = this.parseFloat(metadata.maxZ);
        let topLevelSlicerVersion = metadata.slicerVersion || metadata.generator;
        let topLevelNozzleDiameter = this.parseFloat(metadata.nozzleDiameter);
        let topLevelLayerHeight = this.parseFloat(metadata.layerHeight || metadata.layer_height);
        let topLevelFirstLayerHeight = this.parseFloat(metadata.firstLayerHeight || metadata.first_layer_height);
        let topLevelBedTemp = this.parseFloat(metadata.bedTemp || metadata.bed_temperature);
        let topLevelNozzleTemp = this.parseFloat(metadata.nozzleTemp || metadata.nozzle_temperature);
        let topLevelFillDensity = metadata.infillDensity || metadata.infill_density || metadata.fill_density || null;
        let topLevelFilamentType = metadata.filamentType || metadata.filament_type || null;
        let topLevelPrinterModel = metadata.printerModel || metadata.printer_model || null;
        let topLevelFilamentDiameter = this.parseFloat(metadata.filamentDiameter || metadata.filament_diameter) || 1.75;
        if (plates.length >= 1 && plates[0]) {
            if (plates.length === 1) {
                const plate = plates[0];
                topLevelPrintTime = plate.gcodePrintTimeSeconds ?? topLevelPrintTime;
                topLevelFilamentWeight = plate.filamentUsedGrams ?? topLevelFilamentWeight;
                topLevelLayers = plate.totalLayers ?? topLevelLayers;
                topLevelFilamentUsedMm = plate.filamentUsedMm ?? topLevelFilamentUsedMm;
                topLevelFilamentUsedCm3 = plate.filamentUsedCm3 ?? topLevelFilamentUsedCm3;
                topLevelFilamentDensity = plate.filamentDensityGramsCm3 ?? topLevelFilamentDensity;
                topLevelMaxZ = plate.maxLayerZ ?? topLevelMaxZ;
                topLevelSlicerVersion = plate.slicerVersion ?? topLevelSlicerVersion;
                topLevelNozzleDiameter = plate.nozzleDiameterMm ?? topLevelNozzleDiameter;
                topLevelLayerHeight = plate.layerHeight ?? topLevelLayerHeight;
                topLevelFirstLayerHeight = plate.firstLayerHeight ?? topLevelFirstLayerHeight;
                topLevelBedTemp = plate.bedTemperature ?? topLevelBedTemp;
                topLevelNozzleTemp = plate.nozzleTemperature ?? topLevelNozzleTemp;
                topLevelFillDensity = plate.fillDensity ?? topLevelFillDensity;
                topLevelFilamentType = plate.filamentType ?? topLevelFilamentType;
                topLevelPrinterModel = plate.printerModel ?? topLevelPrinterModel;
                topLevelFilamentDiameter = plate.filamentDiameterMm ?? topLevelFilamentDiameter;
            } else {
                topLevelPrintTime = plates.reduce((sum, p)=>sum + (p.gcodePrintTimeSeconds || 0), 0);
                topLevelFilamentWeight = plates.reduce((sum, p)=>sum + (p.filamentUsedGrams || 0), 0);
                topLevelFilamentUsedMm = plates.reduce((sum, p)=>sum + (p.filamentUsedMm || 0), 0);
                topLevelFilamentUsedCm3 = plates.reduce((sum, p)=>sum + (p.filamentUsedCm3 || 0), 0);
                topLevelLayers = Math.max(...plates.map((p)=>p.totalLayers || 0));
                topLevelMaxZ = Math.max(...plates.map((p)=>p.maxLayerZ || 0));
                const firstPlate = plates[0];
                topLevelFilamentDensity = firstPlate.filamentDensityGramsCm3 ?? topLevelFilamentDensity;
                topLevelSlicerVersion = firstPlate.slicerVersion ?? topLevelSlicerVersion;
                topLevelNozzleDiameter = firstPlate.nozzleDiameterMm ?? topLevelNozzleDiameter;
                topLevelLayerHeight = firstPlate.layerHeight ?? topLevelLayerHeight;
                topLevelFirstLayerHeight = firstPlate.firstLayerHeight ?? topLevelFirstLayerHeight;
                topLevelBedTemp = firstPlate.bedTemperature ?? topLevelBedTemp;
                topLevelNozzleTemp = firstPlate.nozzleTemperature ?? topLevelNozzleTemp;
                topLevelFillDensity = firstPlate.fillDensity ?? topLevelFillDensity;
                topLevelFilamentType = firstPlate.filamentType ?? topLevelFilamentType;
                topLevelPrinterModel = firstPlate.printerModel ?? topLevelPrinterModel;
                topLevelFilamentDiameter = firstPlate.filamentDiameterMm ?? topLevelFilamentDiameter;
            }
        }
        const normalized = {
            fileName,
            fileFormat: "3mf",
            fileSize: stats.size,
            isMultiPlate,
            totalPlates: plates.length || 1,
            gcodePrintTimeSeconds: topLevelPrintTime,
            nozzleDiameterMm: topLevelNozzleDiameter,
            filamentDiameterMm: topLevelFilamentDiameter,
            filamentDensityGramsCm3: topLevelFilamentDensity,
            filamentUsedMm: topLevelFilamentUsedMm,
            filamentUsedCm3: topLevelFilamentUsedCm3,
            filamentUsedGrams: topLevelFilamentWeight,
            totalFilamentUsedGrams: topLevelFilamentWeight,
            layerHeight: topLevelLayerHeight,
            firstLayerHeight: topLevelFirstLayerHeight,
            bedTemperature: topLevelBedTemp,
            nozzleTemperature: topLevelNozzleTemp,
            fillDensity: topLevelFillDensity,
            filamentType: topLevelFilamentType,
            printerModel: topLevelPrinterModel,
            slicerVersion: topLevelSlicerVersion,
            maxLayerZ: topLevelMaxZ,
            totalLayers: topLevelLayers,
            plates: plates.length > 0 ? plates : undefined
        };
        return {
            raw: {
                _thumbnails: thumbnails,
                plates: plates.length > 0 ? plates : undefined
            },
            normalized,
            plates: plates.length > 0 ? plates : undefined
        };
    }
    normalizeJsonMetadata(jsonData) {
        const metadata = {};
        if (jsonData.nozzleDiameter !== undefined) metadata.nozzleDiameter = String(jsonData.nozzleDiameter);
        if (jsonData.estimatedPrintTimeSec !== undefined) metadata.printTime = String(jsonData.estimatedPrintTimeSec);
        if (jsonData.filamentDiameter !== undefined) metadata.filamentDiameter = String(jsonData.filamentDiameter);
        if (jsonData.filamentDensity !== undefined) metadata.filamentDensity = String(jsonData.filamentDensity);
        if (jsonData.filamentUsedGrams !== undefined) metadata.filamentWeight = String(jsonData.filamentUsedGrams);
        if (jsonData.layerHeight !== undefined) metadata.layerHeight = String(jsonData.layerHeight);
        if (jsonData.firstLayerHeight !== undefined) metadata.firstLayerHeight = String(jsonData.firstLayerHeight);
        if (jsonData.bedTemp !== undefined) metadata.bedTemp = String(jsonData.bedTemp);
        if (jsonData.nozzleTemp !== undefined) metadata.nozzleTemp = String(jsonData.nozzleTemp);
        if (jsonData.fillDensity !== undefined) metadata.infillDensity = String(jsonData.fillDensity);
        if (jsonData.filamentType !== undefined) metadata.filamentType = jsonData.filamentType;
        if (jsonData.printerModel !== undefined) metadata.printerModel = jsonData.printerModel;
        return metadata;
    }
    extractMetadataFromXML(xml) {
        const metadata = {};
        const patterns = [
            /<printtime>([^<]+)<\/printtime>/i,
            /<layerheight>([^<]+)<\/layerheight>/i,
            /<filamentused>([^<]+)<\/filamentused>/i,
            /<filamenttype>([^<]+)<\/filamenttype>/i,
            /<nozzlediameter>([^<]+)<\/nozzlediameter>/i,
            /<bedtemperature>([^<]+)<\/bedtemperature>/i,
            /<nozzletemperature>([^<]+)<\/nozzletemperature>/i
        ];
        for (const pattern of patterns){
            const match = xml.match(pattern);
            if (match) {
                const key = pattern.source.match(/<([^>]+)>/)?.[1] || "";
                metadata[key] = match[1];
            }
        }
        const generatorMatch = xml.match(/generator="([^"]+)"/);
        if (generatorMatch) {
            metadata.generator = generatorMatch[1];
            metadata.slicerVersion = generatorMatch[1];
        }
        return metadata;
    }
    extractPlates(zipEntries) {
        const plates = [];
        const plateEntries = zipEntries.filter((e)=>e.entryName.match(/Metadata\/plate_\d+\.gcode$/) && !e.entryName.endsWith('.md5'));
        if (plateEntries.length === 0) {
            return [];
        }
        for (const entry of plateEntries){
            const plateMatch = entry.entryName.match(/plate_(\d+)\.gcode/);
            if (!plateMatch) continue;
            const plateNumber = Number.parseInt(plateMatch[1]);
            const gcodeContent = entry.getData().toString("utf8", 0, Math.min(50000, entry.getData().length));
            const metadata = this.parseGCodeHeader(gcodeContent);
            const plateThumbs = zipEntries.filter((e)=>e.entryName.includes(`plate_${plateMatch[1]}`) && (e.entryName.endsWith(".png") || e.entryName.endsWith(".jpg")));
            const printTime = this.parseTime(metadata.model_printing_time || metadata.total_estimated_time || metadata.print_time);
            const filamentWeight = this.parseFloat(metadata.total_filament_weight_g || metadata.filament_weight || metadata.total_filament_weight);
            const layerCount = this.parseInt(metadata.total_layer_number || metadata.layer_count || metadata.total_layers);
            const plateMetadata = {
                plateNumber,
                gcodePrintTimeSeconds: printTime,
                filamentUsedGrams: filamentWeight,
                totalLayers: layerCount,
                filamentUsedMm: this.parseFloat(metadata.total_filament_length_mm || metadata.filament_length_mm || metadata.filament_used_mm),
                filamentUsedCm3: this.parseFloat(metadata.total_filament_volume_cm3 || metadata.filament_volume_cm3),
                filamentDensityGramsCm3: this.parseFloat(metadata.filament_density),
                filamentDiameterMm: this.parseFloat(metadata.filament_diameter) || 1.75,
                maxLayerZ: this.parseFloat(metadata.max_z_height || metadata.max_layer_z),
                slicerVersion: metadata.bambustudio || metadata.slicer_version || metadata.slicer || null,
                nozzleDiameterMm: this.parseFloat(metadata.nozzle_diameter),
                layerHeight: this.parseFloat(metadata.layer_height),
                firstLayerHeight: this.parseFloat(metadata.first_layer_height || metadata.initial_layer_height || metadata.initial_layer_print_height),
                bedTemperature: this.parseFloat(metadata.bed_temperature_actual || metadata.bed_temperature || metadata.bed_temp || metadata.bed_temperature_initial_layer),
                nozzleTemperature: this.parseFloat(metadata.nozzle_temperature || metadata.nozzle_temp || metadata.nozzle_temperature_initial_layer),
                fillDensity: metadata.sparse_infill_density || metadata.infill_density || metadata.fill_density || null,
                filamentType: metadata.filament_type || null,
                printerModel: metadata.printer_model || null,
                objects: [],
                thumbnails: plateThumbs.map((t)=>({
                        path: t.entryName,
                        size: t.getData().length,
                        type: t.entryName.endsWith(".png") ? "PNG" : "JPG"
                    }))
            };
            plates.push(plateMetadata);
        }
        return plates.sort((a, b)=>a.plateNumber - b.plateNumber);
    }
    parseGCodeHeader(gcode) {
        const metadata = {};
        const lines = gcode.split("\n").slice(0, 1000);
        for (const line of lines){
            if (line.startsWith(";")) {
                if (line.includes("_BLOCK_START") || line.includes("_BLOCK_END")) continue;
                const bambuMatch = line.match(/;\s*BambuStudio\s+([\d.]+)/i);
                if (bambuMatch) {
                    metadata.bambustudio = `BambuStudio ${bambuMatch[1]}`;
                    continue;
                }
                const match = line.match(/;\s*([^=:]+?)\s*[:=]\s*(.+)/);
                if (match) {
                    let key = match[1].trim().toLowerCase().replace(/\s+/g, "_");
                    let value = match[2].trim();
                    key = key.replace(/\[([^\]]+)\]/g, (_, unit)=>unit.replace(/\^/g, ''));
                    value = value.split(';')[0].trim();
                    value = value.replace(/\s*\[.*?\]\s*/g, '');
                    if (!metadata[key]) {
                        metadata[key] = value;
                    }
                }
            } else {
                if (!metadata.bed_temperature_actual) {
                    const bedTempMatch = line.match(/^M1(40|90)\s+S(\d+)/);
                    if (bedTempMatch && Number.parseInt(bedTempMatch[2]) > 0) {
                        metadata.bed_temperature_actual = bedTempMatch[2];
                    }
                }
            }
        }
        return metadata;
    }
    extractThumbnails(zipEntries) {
        const thumbnails = [];
        const thumbEntries = zipEntries.filter((e)=>e.entryName.match(/Metadata\/.*\.(png|jpg|jpeg)/i) || e.entryName.match(/Thumbnails\/.*/i));
        for (const entry of thumbEntries){
            const sizeMatch = entry.entryName.match(/(\d+)x(\d+)/);
            const width = sizeMatch ? Number.parseInt(sizeMatch[1]) : 0;
            const height = sizeMatch ? Number.parseInt(sizeMatch[2]) : 0;
            const format = entry.entryName.match(/\.(png|jpg|jpeg)$/i)?.[1].toUpperCase() || "PNG";
            const imageData = entry.getData();
            const base64Data = imageData.toString("base64");
            thumbnails.push({
                width,
                height,
                format,
                path: entry.entryName,
                data: base64Data
            });
        }
        return thumbnails;
    }
    parseFloat(value) {
        if (!value) return null;
        const num = Number.parseFloat(value);
        return Number.isNaN(num) ? null : num;
    }
    parseInt(value) {
        if (!value) return null;
        const num = Number.parseInt(value, 10);
        return Number.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 = Number.parseInt(match[1] || "0");
            const minutes = Number.parseInt(match[2] || "0");
            const secs = Number.parseInt(match[3] || "0");
            return hours * 3600 + minutes * 60 + secs;
        }
        const seconds = Number.parseFloat(value);
        if (!Number.isNaN(seconds)) return seconds;
        return null;
    }
}

//# sourceMappingURL=3mf.parser.js.map