"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IonDecoderBase = void 0;
const tslib_1 = require("tslib");
const Reader_1 = require("@jsonjoy.com/buffers/lib/Reader");
const sharedCachedUtf8Decoder_1 = tslib_1.__importDefault(require("@jsonjoy.com/buffers/lib/utf8/sharedCachedUtf8Decoder"));
const Import_1 = require("./Import");
class IonDecoderBase {
    constructor(reader) {
        this.reader = (reader ?? new Reader_1.Reader());
        this.utf8Decoder = sharedCachedUtf8Decoder_1.default;
    }
    val() {
        const typedesc = this.reader.u8();
        const type = (typedesc >> 4) & 0xf;
        const length = typedesc & 0xf;
        switch (type) {
            case 0 /* TYPE.NULL */:
                return this.readNull(length);
            case 1 /* TYPE.BOOL */:
                return this.readBool(length);
            case 2 /* TYPE.UINT */:
                return this.readUint(length);
            case 3 /* TYPE.NINT */:
                return this.readNint(length);
            case 4 /* TYPE.FLOT */:
                return this.readFloat(length);
            case 8 /* TYPE.STRI */:
                return this.readString(length);
            case 10 /* TYPE.BINA */:
                return this.readBinary(length);
            case 11 /* TYPE.LIST */:
                return this.readList(length);
            case 13 /* TYPE.STRU */:
                return this.readStruct(length);
            case 14 /* TYPE.ANNO */:
                return this.readAnnotation(length);
            default:
                throw new Error(`Unknown Ion type: 0x${type.toString(16)}`);
        }
    }
    readNull(length) {
        if (length === 15)
            return null;
        if (length === 0) {
            // NOP padding - skip bytes
            this.val(); // Read and discard next value
            return null;
        }
        if (length === 14) {
            // Extended length NOP padding
            const padLength = this.readVUint();
            this.reader.x += padLength;
            this.val(); // Read and discard next value
            return null;
        }
        // Regular NOP padding
        this.reader.x += length;
        this.val(); // Read and discard next value
        return null;
    }
    readBool(length) {
        if (length === 15)
            return null;
        if (length === 0)
            return false;
        if (length === 1)
            return true;
        throw new Error(`Invalid bool length: ${length}`);
    }
    readUint(length) {
        if (length === 15)
            return null;
        if (length === 0)
            return 0;
        let value = 0;
        for (let i = 0; i < length; i++) {
            value = value * 256 + this.reader.u8();
        }
        return value;
    }
    readNint(length) {
        if (length === 15)
            return null;
        if (length === 0)
            throw new Error('Negative zero is illegal');
        let value = 0;
        for (let i = 0; i < length; i++) {
            value = value * 256 + this.reader.u8();
        }
        return -value;
    }
    readFloat(length) {
        if (length === 15)
            return null;
        if (length === 0)
            return 0.0;
        if (length === 4)
            return this.reader.f32();
        if (length === 8)
            return this.reader.f64();
        throw new Error(`Unsupported float length: ${length}`);
    }
    readString(length) {
        if (length === 15)
            return null;
        let actualLength = length;
        if (length === 14) {
            actualLength = this.readVUint();
        }
        if (actualLength === 0)
            return '';
        return this.reader.utf8(actualLength);
    }
    readBinary(length) {
        if (length === 15)
            return null;
        let actualLength = length;
        if (length === 14) {
            actualLength = this.readVUint();
        }
        if (actualLength === 0)
            return new Uint8Array(0);
        return this.reader.buf(actualLength);
    }
    readList(length) {
        if (length === 15)
            return null;
        let actualLength = length;
        if (length === 14) {
            actualLength = this.readVUint();
        }
        if (actualLength === 0)
            return [];
        const endPos = this.reader.x + actualLength;
        const list = [];
        while (this.reader.x < endPos) {
            list.push(this.val());
        }
        if (this.reader.x !== endPos) {
            throw new Error('List parsing error: incorrect length');
        }
        return list;
    }
    readStruct(length) {
        if (length === 15)
            return null;
        let actualLength = length;
        if (length === 14) {
            actualLength = this.readVUint();
        }
        if (actualLength === 0)
            return {};
        const endPos = this.reader.x + actualLength;
        const struct = {};
        while (this.reader.x < endPos) {
            const fieldNameId = this.readVUint();
            const fieldName = this.getSymbolText(fieldNameId);
            const fieldValue = this.val();
            struct[fieldName] = fieldValue;
        }
        if (this.reader.x !== endPos) {
            throw new Error('Struct parsing error: incorrect length');
        }
        return struct;
    }
    readAnnotation(length) {
        if (length < 3) {
            throw new Error('Annotation wrapper must have at least 3 bytes');
        }
        let _actualLength = length;
        if (length === 14) {
            _actualLength = this.readVUint();
        }
        const annotLength = this.readVUint();
        const endAnnotPos = this.reader.x + annotLength;
        // Skip annotations for now - just read and ignore them
        while (this.reader.x < endAnnotPos) {
            this.readVUint(); // Skip annotation symbol ID
        }
        if (this.reader.x !== endAnnotPos) {
            throw new Error('Annotation parsing error: incorrect annotation length');
        }
        // Return the actual value, ignoring annotations
        return this.val();
    }
    readVUint() {
        let value = 0;
        let byte;
        do {
            byte = this.reader.u8();
            value = (value << 7) | (byte & 0x7f);
        } while ((byte & 0x80) === 0);
        return value;
    }
    readVInt() {
        const firstByte = this.reader.u8();
        // Single byte case
        if (firstByte & 0x80) {
            const sign = firstByte & 0x40 ? -1 : 1;
            const magnitude = firstByte & 0x3f;
            return sign * magnitude;
        }
        // Multi-byte case
        const sign = firstByte & 0x40 ? -1 : 1;
        let magnitude = firstByte & 0x3f;
        let byte;
        do {
            byte = this.reader.u8();
            magnitude = (magnitude << 7) | (byte & 0x7f);
        } while ((byte & 0x80) === 0);
        return sign * magnitude;
    }
    getSymbolText(symbolId) {
        if (!this.symbols) {
            throw new Error('No symbol table available');
        }
        const symbol = this.symbols.getText(symbolId);
        if (symbol === undefined) {
            throw new Error(`Unknown symbol ID: ${symbolId}`);
        }
        return symbol;
    }
    validateBVM() {
        const bvm = this.reader.u32();
        if (bvm !== 0xe00100ea) {
            throw new Error(`Invalid Ion Binary Version Marker: 0x${bvm.toString(16)}`);
        }
    }
    readSymbolTable() {
        // Check if there's enough data and if the next byte indicates an annotation
        if (this.reader.x < this.reader.uint8.length) {
            const nextByte = this.reader.peak();
            const type = (nextByte >> 4) & 0xf;
            if (type === 14 /* TYPE.ANNO */) {
                // This might be a symbol table annotation
                const annotValue = this.val();
                // The annotated value should be a struct with a 'symbols' field
                if (annotValue && typeof annotValue === 'object' && !Array.isArray(annotValue)) {
                    const symbolsKey = 'symbols'; // This is what symbol ID 7 maps to
                    const obj = annotValue;
                    if (symbolsKey in obj && Array.isArray(obj[symbolsKey])) {
                        // Update the symbol table with new symbols
                        const newSymbols = obj[symbolsKey];
                        this.symbols = new Import_1.Import(this.symbols || null, newSymbols);
                    }
                }
            }
        }
    }
}
exports.IonDecoderBase = IonDecoderBase;
//# sourceMappingURL=IonDecoderBase.js.map