import { Observable, type OperatorFunction } from 'rxjs';
import { toUint8Array } from '../helper/utils.js';
import { read } from '../helper/decode-ae.js';
import type { DecodeOptions } from '../options.js';
import { UnexpectedEofError } from '../helper/errors.js';

const EMPTY_BUFFER = new Uint8Array(0);
const EMPTY_VIEW = new DataView(EMPTY_BUFFER.buffer);

/** 流式解码 UBJSON */
export function decode(options?: DecodeOptions): OperatorFunction<BufferSource, unknown> {
    return (observable) =>
        new Observable<unknown>((subscriber) => {
            const data = EMPTY_BUFFER;
            const cursor = {
                view: EMPTY_VIEW,
                data,
                capacity: 0,
                size: 0,
                offset: 0,
                options,
            };
            /** reader 返回的还需接收的字节数 */
            let required = 1;
            let reader = read(cursor);
            return observable.subscribe({
                next(value) {
                    const chunk = toUint8Array(value);
                    const chunkSize = chunk.byteLength;
                    if (cursor.capacity - cursor.size < chunkSize) {
                        // 当前缓冲区不足，需要扩容
                        const newSize = Math.max(
                            // 不缩小缓冲区
                            cursor.capacity,
                            // 扩大缓冲区到足够容纳 2 倍当前数据
                            chunkSize * 2 + cursor.size - cursor.offset,
                        );
                        if (newSize > cursor.capacity) {
                            // 需要增大缓冲区
                            const newData = new Uint8Array(newSize);
                            newData.set(cursor.data.subarray(cursor.offset, cursor.size), 0);
                            newData.set(chunk, cursor.size - cursor.offset);
                            cursor.data = newData;
                            cursor.view = new DataView(newData.buffer, newData.byteOffset, newData.byteLength);
                            cursor.capacity = newSize;
                        } else {
                            // 无需增大缓冲区，直接移动数据
                            cursor.data.copyWithin(0, cursor.offset, cursor.size);
                            cursor.data.set(chunk, cursor.size - cursor.offset);
                        }
                        cursor.size = cursor.size - cursor.offset + chunkSize;
                        cursor.offset = 0;
                    } else {
                        // 当前缓冲区足够，直接写入
                        cursor.data.set(chunk, cursor.size);
                        cursor.size += chunkSize;
                    }

                    required -= chunkSize;
                    // 未读够数据，继续等待
                    if (required > 0) return;

                    try {
                        for (;;) {
                            const result = reader.next();
                            if (result.done) {
                                // 读取完成，新建 reader 读取下一个值
                                subscriber.next(result.value);
                                reader = read(cursor);
                                if (cursor.offset === cursor.size) {
                                    break;
                                }
                            } else {
                                required = result.value;
                                break;
                            }
                        }
                    } catch (ex) {
                        subscriber.error(ex);
                    }
                },
                error(err) {
                    subscriber.error(err);
                    cursor.data = EMPTY_BUFFER;
                    cursor.view = EMPTY_VIEW;
                },
                complete() {
                    if (cursor.size > cursor.offset) {
                        subscriber.error(new UnexpectedEofError());
                    } else {
                        subscriber.complete();
                    }
                    cursor.data = EMPTY_BUFFER;
                    cursor.view = EMPTY_VIEW;
                },
            });
        });
}
