import { decode } from '@cloudpss/ubjson';
import type { RpcErrorLike, RpcPayload } from '../types/payload.js';

/** 反序列化 */
export function decodePayload(data: unknown): RpcPayload | undefined {
    if (!data || typeof data == 'string') return undefined;
    try {
        const payload = decode(data as BufferSource) as RpcPayload;
        if (!payload || typeof payload != 'object') return undefined;
        if (typeof payload.type !== 'string') return undefined;
        if (typeof payload.seq !== 'number') return undefined;
        return payload;
    } catch (ex) {
        return undefined;
    }
}

const serializing = Symbol('serializing error');
/** 序列化错误信息 */
export function serializeError(error: unknown): RpcErrorLike {
    if (error == null)
        return {
            name: 'Error',
            message: '',
        };
    if (typeof error != 'object')
        return {
            name: 'Error',

            message: String(error),
        };
    try {
        const reenter = !!(error as { [serializing]?: true })[serializing];
        (error as { [serializing]?: true })[serializing] = true;
        const { message, stack, name, errors, ...rest } = error as AggregateError;
        return {
            ...rest,
            name: String(name ?? 'Error'),
            message: String(message ?? ''),
            stack: stack,
            errors: reenter ? undefined : Array.isArray(errors) ? errors.map((e) => serializeError(e)) : errors,
        };
    } finally {
        delete (error as { [serializing]?: true })[serializing];
    }
}
/** 反序列化错误信息 */
export function deserializeError(error: RpcErrorLike): Error {
    if (error == null || typeof error != 'object') return new Error(error ?? '');
    const { message, stack, name, errors, ...rest } = error;
    let ex;
    if (Array.isArray(errors)) {
        ex = new AggregateError(
            errors.map((e) => deserializeError(e)),
            message,
        );
    } else {
        ex = new Error(message);
    }
    if (name !== ex?.name)
        Object.defineProperty(ex, 'name', { value: String(name), configurable: true, writable: true });
    Object.defineProperty(ex, 'stack', {
        value: stack ? String(stack) : undefined,
        configurable: true,
        writable: true,
    });
    Object.assign(ex, rest);
    return ex;
}
