"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get decompressBlock () {
        return decompressBlock;
    },
    get extractMetadataFromBlocks () {
        return extractMetadataFromBlocks;
    },
    get getBlockData () {
        return getBlockData;
    },
    get getBlockParsedParameters () {
        return getBlockParsedParameters;
    },
    get parseBlockHeader () {
        return parseBlockHeader;
    },
    get parseBlockHeaders () {
        return parseBlockHeaders;
    },
    get parseBlockParameters () {
        return parseBlockParameters;
    },
    get parseFileHeader () {
        return parseFileHeader;
    }
});
const _bgcodeconstants = require("./bgcode.constants");
const _bgcodetypes = require("./bgcode.types");
const _nodezlib = require("node:zlib");
const _heatshrinkdecoder = require("./heatshrink-decoder");
async function parseFileHeader(fileHandle) {
    const buffer = Buffer.alloc(_bgcodeconstants.BGCODE_HEADER_SIZE);
    await fileHandle.read(buffer);
    if (buffer.length < _bgcodeconstants.BGCODE_HEADER_SIZE) {
        throw new Error("File too small to be a valid BGCode file");
    }
    const magic = buffer.toString("ascii", 0, 4);
    if (magic !== _bgcodeconstants.BGCODE_HEADER_MARKER) {
        throw new Error(`Invalid BGCode file: magic number not found (got "${magic}", expected ${_bgcodeconstants.BGCODE_HEADER_MARKER})`);
    }
    const version = buffer.readUInt32LE(4);
    const checksumType = buffer.readUInt16LE(8);
    return {
        magic,
        version,
        checksumType
    };
}
async function parseBlockHeaders(fileHandle, fileSize, checksumType, skipGcode) {
    let offset = _bgcodeconstants.BGCODE_HEADER_SIZE;
    const blockHeaders = [];
    let currentExpectedHeaderIndex = 0;
    while(offset < fileSize){
        const blockHeader = await parseBlockHeader(fileHandle, fileSize, offset, checksumType);
        if (skipGcode && blockHeader.type == _bgcodetypes.BgCodeBlockTypes.GCode) {
            break;
        }
        const expectedType = _bgcodeconstants.BGCODE_EXPECTED_HEADERS[currentExpectedHeaderIndex];
        if (blockHeader.type !== expectedType) {
            const nextExpectedType = _bgcodeconstants.BGCODE_EXPECTED_HEADERS[currentExpectedHeaderIndex + 1];
            if (blockHeader.type !== nextExpectedType) {
                throw new Error(`Unexpected header header type ${blockHeader.type}, expected either ${expectedType} or next ${nextExpectedType}`);
            }
            currentExpectedHeaderIndex++;
        }
        if (Number.isNaN(blockHeader.blockSize)) {
            throw new Error(`Unexpected blockHeader.blockSize ${blockHeader.blockSize}`);
        }
        offset += blockHeader.blockSize;
        blockHeaders.push(blockHeader);
    }
    return blockHeaders;
}
async function parseBlockHeader(fileHandle, fileSize, blockStartOffset, checksumType) {
    if (blockStartOffset + _bgcodeconstants.BGCODE_MAX_BLOCK_HEADER_SIZE > fileSize) {
        throw new Error("Not enough data for block header");
    }
    const buffer = Buffer.alloc(_bgcodeconstants.BGCODE_MAX_BLOCK_HEADER_SIZE);
    await fileHandle.read({
        buffer,
        offset: 0,
        length: _bgcodeconstants.BGCODE_MAX_BLOCK_HEADER_SIZE,
        position: blockStartOffset
    });
    const type = buffer.readUInt16LE(0);
    if (type > _bgcodeconstants.BGCODE_MAX_BLOCK_TYPE) {
        throw new Error(`Block header type ${type} exceeds ${_bgcodeconstants.BGCODE_MAX_BLOCK_TYPE}, cant parse block`);
    }
    const compression = buffer.readUInt16LE(2);
    if (compression > _bgcodeconstants.BGCODE_MAX_COMPRESSION) {
        throw new Error(`Block header compression ${compression} exceeds ${_bgcodeconstants.BGCODE_MAX_COMPRESSION}, cant parse block`);
    }
    const uncompressedSize = buffer.readUInt32LE(4);
    if (uncompressedSize === 0) {
        throw new Error(`Uncompressed Size is 0`);
    }
    let compressedSize = 0;
    let headerSize = _bgcodeconstants.BGCODE_MIN_BLOCK_HEADER_SIZE;
    if (compression > 0) {
        compressedSize = buffer.readUInt32LE(8);
        if (compressedSize === 0) {
            throw new Error(`Compression is ${_bgcodetypes.BgCodeCompressionName[compression]} but compressed size is ${compressedSize}}`);
        }
        headerSize = _bgcodeconstants.BGCODE_MAX_BLOCK_HEADER_SIZE;
    }
    const checksumSize = calculateChecksumSize(checksumType);
    const blockSize = calculateBlockSize(checksumSize, type, compression, uncompressedSize, compressedSize);
    const parameterOffset = blockStartOffset + headerSize;
    const parametersSize = calculateParameterSize(type);
    const parameterBuffer = await getBlockParsedParameters(fileHandle, parameterOffset, parametersSize);
    const parameters = parseBlockParameters(type, parameterBuffer);
    const dataOffset = parameterOffset + parametersSize;
    const dataSize = blockSize - parametersSize - checksumSize;
    const checksumOffset = dataOffset + dataSize;
    return {
        type,
        compression,
        uncompressedSize,
        compressedSize,
        blockSize,
        blockStartOffset,
        headerSize,
        parameterOffset,
        parameters,
        parametersSize,
        dataOffset,
        dataSize,
        checksumOffset,
        checksumSize,
        checksumType
    };
}
async function getBlockParsedParameters(fileHandle, parameterOffset, parametersSize) {
    const parameters = Buffer.alloc(parametersSize);
    if (parametersSize > 0) {
        await fileHandle.read({
            buffer: parameters,
            offset: 0,
            length: parametersSize,
            position: parameterOffset
        });
    }
    return parameters;
}
async function getBlockData(fileHandle, blockHeader) {
    if (blockHeader.blockSize === 0) {
        throw new Error("Cant get block data for block with size 0");
    }
    if (blockHeader.blockSize === 1E3) {
        throw new Error(`Cant get block data for block with size ${blockHeader.blockSize} > ${1E3}`);
    }
    const data = Buffer.alloc(blockHeader.dataSize);
    await fileHandle.read({
        buffer: data,
        offset: 0,
        length: blockHeader.dataSize,
        position: blockHeader.dataOffset
    });
    return data;
}
function decompressBlock(compression, data) {
    const info = _bgcodetypes.BgCodeCompressionInfo[compression];
    switch(info.kind){
        case "none":
            return data;
        case "deflate":
            return (0, _nodezlib.inflateSync)(data);
        case "heatshrink":
            const decoder = new _heatshrinkdecoder.HeatshrinkDecoder(info.window, info.lookahead);
            return decoder.decompress(data);
        default:
            throw new Error(`Unknown compression type: ${compression}`);
    }
}
function parseBlockParameters(blockType, parameters) {
    const parameterSize = _bgcodetypes.BgCodeBlockParameterSizes[blockType];
    if (parameters.length !== parameterSize) {
        throw new Error(`Block Parameters should be exactly ${parameterSize} bytes long`);
    }
    if (blockType === _bgcodetypes.BgCodeBlockTypes.Thumbnail) {
        return {
            format: calculateThumbnailFormat(parameters.readUInt16LE(0)),
            width: parameters.readUInt16LE(2),
            height: parameters.readUInt16LE(4)
        };
    }
    return {
        encoding: parameters.readUInt16LE(0)
    };
}
function calculateBlockSize(checksumSize, blockType, compression, uncompressedSize, compressedSize) {
    const headerSize = compression > 0 ? 12 : 8;
    const dataSize = compression === 0 ? uncompressedSize : compressedSize;
    const parametersSize = blockType === _bgcodetypes.BgCodeBlockTypes.Thumbnail ? 6 : 2;
    return headerSize + parametersSize + dataSize + checksumSize;
}
function calculateChecksumSize(checksumType) {
    if (checksumType > _bgcodeconstants.BGCODE_MAX_CHECKSUM_TYPE) {
        throw new Error(`Checksum type ${checksumType} exceeds max ${_bgcodeconstants.BGCODE_MAX_CHECKSUM_TYPE}`);
    }
    return _bgcodetypes.BgCodeChecksumTypeSize[checksumType];
}
function calculateParameterSize(blockType) {
    if (blockType > _bgcodeconstants.BGCODE_MAX_BLOCK_TYPE) {
        throw new Error(`Checksum type ${blockType} exceeds max ${_bgcodeconstants.BGCODE_MAX_BLOCK_TYPE}`);
    }
    return _bgcodetypes.BgCodeBlockParameterSizes[blockType];
}
function calculateThumbnailFormat(formatParameter) {
    if (formatParameter > _bgcodeconstants.BGCODE_PARAMETER_THUMBNAIL_MAX_FORMAT) {
        throw new Error(`Thumbnail format type ${formatParameter} exceeds max ${_bgcodeconstants.BGCODE_PARAMETER_THUMBNAIL_MAX_FORMAT}`);
    }
    return formatParameter;
}
async function extractMetadataFromBlocks(fileHandle, blockHeaders) {
    const metadata = {};
    const metadataBlocks = blockHeaders.filter((b)=>b.type === _bgcodetypes.BgCodeBlockTypes.FileMetadata || b.type === _bgcodetypes.BgCodeBlockTypes.SlicerMetadata || b.type === _bgcodetypes.BgCodeBlockTypes.PrinterMetadata || b.type === _bgcodetypes.BgCodeBlockTypes.PrintMetadata);
    for (const header of metadataBlocks){
        const blockData = await getBlockData(fileHandle, header);
        const data = decompressBlock(header.compression, blockData);
        const text = data.toString("utf8");
        extractMetadataFromText(text, metadata);
    }
    return metadata;
}
const METADATA_KEY_NORMALIZATION = {
    'producer': 'producer',
    'produced on': 'produced_on',
    'print time': 'print_time',
    'estimated printing time (silent mode)': 'estimated_printing_time_silent_mode',
    'estimated printing time (normal mode)': 'estimated_printing_time_normal_mode',
    'layer height': 'layer_height',
    'first layer height': 'first_layer_height',
    'initial layer height': 'initial_layer_height',
    'nozzle diameter': 'nozzle_diameter',
    'filament diameter': 'filament_diameter',
    'filament density': 'filament_density',
    'filament used [mm]': 'filament_used_mm',
    'filament used [cm3]': 'filament_used_cm3',
    'filament used [g]': 'filament_used_g',
    'bed temperature': 'bed_temperature',
    'temperature': 'temperature',
    'fill density': 'fill_density',
    'filament type': 'filament_type',
    'printer model': 'printer_model',
    'max layer z': 'max_layer_z',
    'total layers': 'total_layers',
    'layer count': 'layer_count'
};
function extractMetadataFromText(text, metadata) {
    const lines = text.split('\n');
    for (const line of lines){
        const keyValuePair = parseMetadataLine(line);
        if (!keyValuePair) {
            continue;
        }
        const { key, value } = keyValuePair;
        metadata[key] = value;
    }
}
function parseMetadataLine(line) {
    const equalIndex = line.indexOf('=');
    if (equalIndex === -1) {
        return null;
    }
    const rawKey = line.substring(0, equalIndex).trim().toLowerCase();
    const value = line.substring(equalIndex + 1).trim();
    if (!rawKey || !value) {
        return null;
    }
    const normalizedKey = METADATA_KEY_NORMALIZATION[rawKey] || rawKey.replace(/\s+/g, '_');
    return {
        key: normalizedKey,
        value
    };
}

//# sourceMappingURL=bgcode.utils.js.map