import { WebSocket } from '@cloudpss/fetch';
import { v4 } from 'uuid';
import { waitAuth } from './auth.js';
import { RpcSocket } from './socket.js';
import type { ConnectionID, RpcMetadata } from './types/payload.js';
import type { RpcObject } from './types/utils.js';
import { send } from './utils/messaging.js';
import { VERSION } from './version.js';
import { logger } from './logger.js';

const INIT_RECONNECT_TIMEOUT = 250;
const MAX_RECONNECT_TIMEOUT = 10000;

/** 由 WS Client 建立的 RPC 连接 */
export class RpcClientSocket<TRemote extends object, TLocal extends object> extends RpcSocket<TRemote, TLocal> {
    /** 建立连接使用的 WebSocket 实现 */
    static WebSocket: typeof WebSocket | null = null;

    constructor(url: string | URL, metadata?: RpcMetadata, local?: RpcObject<TLocal>) {
        super(v4() as ConnectionID);
        this.#local = local;
        this._localMetadata = metadata;
        this.url = typeof url == 'string' ? url : url.href;
        this.seq = 2;
        logger('[%s] client socket created. url=', this.id, this.url);
        this.connect();
    }
    readonly #local: RpcObject<TLocal> | undefined;
    /** @inheritdoc */
    protected override get local(): RpcObject<TLocal> | undefined {
        return this.#local;
    }
    readonly url: string;
    /** 等待连接成功或失败 */
    get connected(): Promise<void> {
        return Promise.resolve(this.ready.value);
    }
    /** @inheritdoc */
    protected override async authSocket(): Promise<RpcMetadata> {
        logger('[%s] authenticating...', this.id);
        const { socket } = this;
        const seq = this.nextSeq();
        send(socket, 'auth', {
            seq,
            id: this.id,
            version: VERSION,
            metadata: this.localMetadata ?? {},
        });
        const metadata = (await waitAuth(socket, seq, this.id))[2];
        logger('[%s] authenticated. remoteMeta=%o', this.id, metadata);
        return metadata;
    }

    #reconnectTimeout = INIT_RECONNECT_TIMEOUT;
    /** @inheritdoc */
    protected override onClose(ev: CloseEvent): void {
        super.onClose(ev);
        if (this.destroyed) return;

        logger('[%s] reconnecting in %dms...', this.id, this.#reconnectTimeout);
        setTimeout(() => {
            this.connect();
        }, this.#reconnectTimeout);
    }

    /** 建立连接 */
    protected connect(): void {
        const socket = new ((this.constructor as typeof RpcClientSocket).WebSocket ?? WebSocket)(this.url);
        socket.binaryType = 'arraybuffer';
        this.socket = socket;
        logger('[%s] connecting...', this.id);
        this.connected.then(
            () => {
                logger('[%s] connected.', this.id);
                this.#reconnectTimeout = INIT_RECONNECT_TIMEOUT;
            },
            () => {
                if (this.destroyed) return;
                this.resetReady();
                this.#reconnectTimeout = Math.min(this.#reconnectTimeout * 2, MAX_RECONNECT_TIMEOUT);
                logger('[%s] connect failed. retrying in %dms...', this.id, this.#reconnectTimeout);
                setTimeout(() => {
                    this.connect();
                }, this.#reconnectTimeout);
            },
        );
    }
}
