/* eslint max-depth: ["error", 6] */
import * as varint from 'uint8-varint';
import { Uint8ArrayList } from 'uint8arraylist';
import { InvalidDataLengthError, InvalidDataLengthLengthError, InvalidMessageLengthError, UnexpectedEOFError } from './errors.js';
import { isAsyncIterable } from './utils.js';
// Maximum length of the length section of the message
export const MAX_LENGTH_LENGTH = 8; // Varint.encode(Number.MAX_SAFE_INTEGER).length
// Maximum length of the data section of the message
export const MAX_DATA_LENGTH = 1024 * 1024 * 4;
var ReadMode;
(function (ReadMode) {
    ReadMode[ReadMode["LENGTH"] = 0] = "LENGTH";
    ReadMode[ReadMode["DATA"] = 1] = "DATA";
})(ReadMode || (ReadMode = {}));
const defaultDecoder = (buf) => {
    const length = varint.decode(buf);
    defaultDecoder.bytes = varint.encodingLength(length);
    return length;
};
defaultDecoder.bytes = 0;
export function decode(source, options) {
    const buffer = new Uint8ArrayList();
    let mode = ReadMode.LENGTH;
    let dataLength = -1;
    const lengthDecoder = options?.lengthDecoder ?? defaultDecoder;
    const maxLengthLength = options?.maxLengthLength ?? MAX_LENGTH_LENGTH;
    const maxDataLength = options?.maxDataLength ?? MAX_DATA_LENGTH;
    function* maybeYield() {
        while (buffer.byteLength > 0) {
            if (mode === ReadMode.LENGTH) {
                // read length, ignore errors for short reads
                try {
                    dataLength = lengthDecoder(buffer);
                    if (dataLength < 0) {
                        throw new InvalidMessageLengthError('Invalid message length');
                    }
                    if (dataLength > maxDataLength) {
                        throw new InvalidDataLengthError('Message length too long');
                    }
                    const dataLengthLength = lengthDecoder.bytes;
                    buffer.consume(dataLengthLength);
                    if (options?.onLength != null) {
                        options.onLength(dataLength);
                    }
                    mode = ReadMode.DATA;
                }
                catch (err) {
                    if (err instanceof RangeError) {
                        if (buffer.byteLength > maxLengthLength) {
                            throw new InvalidDataLengthLengthError('Message length length too long');
                        }
                        break;
                    }
                    throw err;
                }
            }
            if (mode === ReadMode.DATA) {
                if (buffer.byteLength < dataLength) {
                    // not enough data, wait for more
                    break;
                }
                const data = buffer.sublist(0, dataLength);
                buffer.consume(dataLength);
                if (options?.onData != null) {
                    options.onData(data);
                }
                yield data;
                mode = ReadMode.LENGTH;
            }
        }
    }
    if (isAsyncIterable(source)) {
        return (async function* () {
            for await (const buf of source) {
                buffer.append(buf);
                yield* maybeYield();
            }
            if (buffer.byteLength > 0) {
                throw new UnexpectedEOFError('Unexpected end of input');
            }
        })();
    }
    return (function* () {
        for (const buf of source) {
            buffer.append(buf);
            yield* maybeYield();
        }
        if (buffer.byteLength > 0) {
            throw new UnexpectedEOFError('Unexpected end of input');
        }
    })();
}
decode.fromReader = (reader, options) => {
    let byteLength = 1; // Read single byte chunks until the length is known
    const varByteSource = (async function* () {
        while (true) {
            try {
                const { done, value } = await reader.next(byteLength);
                if (done === true) {
                    return;
                }
                if (value != null) {
                    yield value;
                }
            }
            catch (err) {
                if (err.code === 'ERR_UNDER_READ') {
                    return { done: true, value: null };
                }
                throw err;
            }
            finally {
                // Reset the byteLength so we continue to check for varints
                byteLength = 1;
            }
        }
    }());
    /**
     * Once the length has been parsed, read chunk for that length
     */
    const onLength = (l) => { byteLength = l; };
    return decode(varByteSource, {
        ...(options ?? {}),
        onLength
    });
};
//# sourceMappingURL=decode.js.map