import { Readable } from 'node:stream';
import type { ReadableStream as ReadableStream2 } from 'node:stream/web';
import { encodeMany, decodeMany } from '../../dist/stream/index.js';
import { buffer } from 'node:stream/consumers';
import { finished } from 'node:stream/promises';

/**
 * iterableToAsyncIterable
 */
function iterableToAsyncIterable<T>(iterable: Iterable<T>): AsyncIterable<T> {
    return {
        [Symbol.asyncIterator]: () => {
            const iterator = iterable[Symbol.iterator]();
            return {
                // eslint-disable-next-line unicorn/no-useless-promise-resolve-reject
                next: async () => Promise.resolve(iterator.next()),
            };
        },
    };
}

/**
 * asyncIterableToArray
 */
async function asyncIterableToArray<T>(asyncIterable: AsyncIterable<T>): Promise<T[]> {
    const array: T[] = [];
    for await (const item of asyncIterable) {
        array.push(item);
    }
    return array;
}

test('encode/decode many', async () => {
    const data = [
        {
            a: 1,
            b: 2,
            c: 3,
            d: 4,
            e: 5,
        },
        1,
        null,
        'x',
        true,
        [1, 2, 3],
        Uint8Array.of(3, 3, 1),
    ];
    const encoded = await buffer(encodeMany(iterableToAsyncIterable(data)));
    const decoded = await asyncIterableToArray(
        decodeMany(Readable.toWeb(Readable.from([encoded])) as ReadableStream<Uint8Array<ArrayBuffer>>),
    );
    expect(decoded).toEqual([
        {
            a: 1,
            b: 2,
            c: 3,
            d: 4,
            e: 5,
        },
        1,
        // null is not allowed in stream
        'x',
        true,
        [1, 2, 3],
        Uint8Array.of(3, 3, 1),
    ]);
});

test('encode/decode many with invalid value', async () => {
    await expect(async () => {
        const readable = Readable.fromWeb(encodeMany(iterableToAsyncIterable([1, () => 1])) as ReadableStream2);
        const f = finished(readable);
        readable.resume();
        await f;
    }).rejects.toThrow(/Unsupported type Function/);
});

test('encode/decode many with error', async () => {
    await expect(async () => {
        const stream = encodeMany(
            (async function* () {
                yield 1;
                await Promise.resolve();
                throw new Error('test error');
            })(),
        );
        for await (const _ of stream) {
            //
        }
    }).rejects.toThrow('test error');
});

test('encode/decode many with undefined', async () => {
    const data = [1, null, undefined, [undefined]];
    const encoded = await buffer(encodeMany(iterableToAsyncIterable(data)));
    const decoded = await asyncIterableToArray(
        decodeMany(Readable.toWeb(Readable.from([encoded])) as ReadableStream<Uint8Array<ArrayBuffer>>),
    );
    expect(decoded).toEqual([
        1,
        // null is not allowed in stream
        [null],
    ]);
});
