"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.vintDecode = exports.getSizeMask = exports.vintEncode = exports.UNKNOWN_SIZE = exports.getEBMLByteLength = exports.build = exports.unknownSizeElement = exports.element = exports.string = exports.decodeVintEncodedNumber = exports.vintEncodedNumber = exports.float = exports.number = exports.bytes = exports.Element = exports.Value = void 0;
const typedArrayUtils_1 = require("./typedArrayUtils");
class Value {
    constructor(bytes) {
        Object.defineProperty(this, "bytes", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: bytes
        });
    }
    write(buf, pos) {
        buf.set(this.bytes, pos);
        return pos + this.bytes.length;
    }
    countSize() {
        return this.bytes.length;
    }
}
exports.Value = Value;
class Element {
    constructor(id, children, isSizeUnknown) {
        Object.defineProperty(this, "id", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: id
        });
        Object.defineProperty(this, "children", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: children
        });
        Object.defineProperty(this, "size", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "sizeMetaData", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        const bodySize = this.children.reduce((p, c) => p + c.countSize(), 0);
        this.sizeMetaData = isSizeUnknown
            ? exports.UNKNOWN_SIZE
            : (0, exports.vintEncode)((0, typedArrayUtils_1.numberToByteArray)(bodySize, (0, exports.getEBMLByteLength)(bodySize)));
        this.size = this.id.length + this.sizeMetaData.length + bodySize;
    }
    write(buf, pos) {
        buf.set(this.id, pos);
        buf.set(this.sizeMetaData, pos + this.id.length);
        return this.children.reduce((p, c) => c.write(buf, p), pos + this.id.length + this.sizeMetaData.length);
    }
    countSize() {
        return this.size;
    }
}
exports.Element = Element;
const bytes = (data) => {
    return new Value(data);
};
exports.bytes = bytes;
const number = (num) => {
    return (0, exports.bytes)((0, typedArrayUtils_1.numberToByteArray)(num));
};
exports.number = number;
const float = (num) => (0, exports.bytes)((0, typedArrayUtils_1.float32bit)(num));
exports.float = float;
const vintEncodedNumber = (num) => {
    return (0, exports.bytes)((0, exports.vintEncode)((0, typedArrayUtils_1.numberToByteArray)(num, (0, exports.getEBMLByteLength)(num))));
};
exports.vintEncodedNumber = vintEncodedNumber;
/**
 * Decode a vint-encoded unsigned integer previously produced by `vintEncodedNumber`.
 * Returns its numeric value (number if within MAX_SAFE_INTEGER, otherwise bigint) and the length in bytes.
 * Throws if the value is the EBML unknown-size sentinel.
 */
const decodeVintEncodedNumber = (buf, offset = 0) => {
    const { value, length, unknown } = (0, exports.vintDecode)(buf, offset);
    if (unknown || value === undefined) {
        throw new Error("decodeVintEncodedNumber: value is unknown size sentinel");
    }
    return { value, length };
};
exports.decodeVintEncodedNumber = decodeVintEncodedNumber;
const string = (str) => {
    return (0, exports.bytes)((0, typedArrayUtils_1.stringToByteArray)(str));
};
exports.string = string;
const element = (id, child) => {
    return new Element(id, Array.isArray(child) ? child : [child], false);
};
exports.element = element;
const unknownSizeElement = (id, child) => {
    return new Element(id, Array.isArray(child) ? child : [child], true);
};
exports.unknownSizeElement = unknownSizeElement;
const build = (v) => {
    const b = new Uint8Array(v.countSize());
    v.write(b, 0);
    return b;
};
exports.build = build;
const getEBMLByteLength = (num) => {
    if (num < 0x7f) {
        return 1;
    }
    else if (num < 0x3fff) {
        return 2;
    }
    else if (num < 0x1fffff) {
        return 3;
    }
    else if (num < 0xfffffff) {
        return 4;
    }
    else if (num < 0x7ffffffff) {
        return 5;
    }
    else if (num < 0x3ffffffffff) {
        return 6;
    }
    else if (num < 0x1ffffffffffff) {
        return 7;
    }
    else if (num < 0x20000000000000n) {
        return 8;
    }
    else if (num < 0xffffffffffffffn) {
        throw new Error("EBMLgetEBMLByteLength: number exceeds Number.MAX_SAFE_INTEGER");
    }
    else {
        throw new Error("EBMLgetEBMLByteLength: data size must be less than or equal to " +
            (2 ** 56 - 2));
    }
};
exports.getEBMLByteLength = getEBMLByteLength;
exports.UNKNOWN_SIZE = new Uint8Array([
    0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
]);
const vintEncode = (byteArray) => {
    byteArray[0] = (0, exports.getSizeMask)(byteArray.length) | byteArray[0];
    return byteArray;
};
exports.vintEncode = vintEncode;
const getSizeMask = (byteLength) => {
    return 0x80 >> (byteLength - 1);
};
exports.getSizeMask = getSizeMask;
/**
 * Decode an EBML variable size integer (VINT) from the provided buffer.
 *
 * Encoding recap (EBML spec):
 *   The length (N, 1..8) is indicated by the position of the first set bit in the first byte.
 *   Patterns:
 *     1xxxxxxx -> 1 byte total  (7 value bits)
 *     01xxxxxx xxxxxxxx -> 2 bytes total (14 value bits)
 *     001xxxxx ...... -> 3 bytes total (21 value bits)
 *     ...
 *     00000001 [7 subsequent bytes] -> 8 bytes total (56 value bits)
 *
 * The marker bit itself is cleared from the first byte to recover the value bits.
 * Total value bits = 7 * N.
 * A value with all value bits set to 1 is reserved for the "unknown size" sentinel.
 */
const vintDecode = (buf, offset = 0) => {
    if (offset >= buf.length) {
        throw new Error("vintDecode: offset out of range");
    }
    const first = buf[offset];
    if (first === 0) {
        throw new Error("vintDecode: invalid first byte 0x00 (no leading 1 bit)");
    }
    // Determine length by locating first set bit (MSB towards LSB)
    let length = 0;
    for (let i = 0; i < 8; i++) {
        const mask = 0x80 >> i;
        if (first & mask) {
            length = i + 1; // Length is position index + 1
            break;
        }
    }
    if (length === 0) {
        throw new Error("vintDecode: could not determine length");
    }
    if (offset + length > buf.length) {
        throw new Error("vintDecode: insufficient bytes for declared length");
    }
    // Mask out the length marker bit(s) in the first byte
    const lengthMarker = (0, exports.getSizeMask)(length);
    let valueBig = BigInt(first & ~lengthMarker);
    for (let i = 1; i < length; i++) {
        valueBig = (valueBig << 8n) | BigInt(buf[offset + i]);
    }
    // Maximum value (all value bits = 1) indicates unknown size
    const allOnes = (1n << BigInt(7 * length)) - 1n; // 7 value bits per byte
    const unknown = valueBig === allOnes;
    let value;
    if (unknown) {
        value = undefined;
    }
    else if (valueBig <= BigInt(Number.MAX_SAFE_INTEGER)) {
        value = Number(valueBig);
    }
    else {
        value = valueBig; // preserve precision as bigint
    }
    return { value, length, unknown };
};
exports.vintDecode = vintDecode;
//# sourceMappingURL=ebml.js.map