import type { AllEventKeys } from "../events/event-bus.js";
import { EventEmitter, type AllEvents } from "../events/event-emitter.js";
import type { EventListener, EventOptions } from "../events/shared.js";
import { IsomorphicBuffer } from "../helpers.js";
import { SocketAdapterAkalaEventMap, SocketAdapter } from "./shared.js";
import { Deferred } from "../promiseHelpers.js";
import { type Subscription, StatefulSubscription } from "../teardown-manager.js";

/**
 * json-rpc-ws connection
 *
 * @constructor
 * @param {Socket} socket - web socket for this connection
 * @param {Object} parent - parent that controls this connection
 */
export class WebSocketAdapter extends EventEmitter<SocketAdapterAkalaEventMap> implements SocketAdapter
{
    constructor(private readonly socket: WebSocket)
    {
        super();
    }

    pipe(socket: SocketAdapter)
    {
        this.on('message', (message) => socket.send(message));
        this.on('close', () => socket.close());
    }

    get open(): boolean
    {
        return this.socket.readyState == WebSocket.OPEN;
    }

    close(): Promise<void>
    {
        const deferred = new Deferred<void>();
        this.socket.addEventListener('close', () => deferred.resolve());
        this.socket.close();
        return deferred;
    }

    send(data: string | IsomorphicBuffer): Promise<void>
    {
        this.socket.send(data instanceof IsomorphicBuffer ? data.toArray() : data);
        return Promise.resolve();
    }

    private readonly messageListeners: [(ev: unknown) => void, (ev: unknown) => void][] = [];

    public off<const TEvent extends AllEventKeys<SocketAdapterAkalaEventMap>>(
        event: TEvent,
        handler: EventListener<AllEvents<SocketAdapterAkalaEventMap>[TEvent]>
    ): boolean
    {
        switch (event)
        {
            case 'message':
                {
                    let listeners = this.messageListeners;
                    if (handler)
                        listeners = listeners.filter(f => f[0] == handler);
                    listeners.forEach(l => this.socket.removeEventListener('message', l[1]));
                }
                break;
            case 'close':
            case 'error':
            case 'open':
                //eslint-disable-next-line @typescript-eslint/no-explicit-any
                this.socket.removeEventListener(event, handler as any);
                break;
            default:
                throw new Error(`Unsupported event ${String(event)}`);
        }
        return true;
    }

    public on<const TEvent extends AllEventKeys<SocketAdapterAkalaEventMap>>(
        event: TEvent,
        handler: EventListener<AllEvents<SocketAdapterAkalaEventMap>[TEvent]>,
        options?: EventOptions<AllEvents<SocketAdapterAkalaEventMap>[TEvent]>
    ): Subscription
    {
        switch (event)
        {
            case 'message':
                {
                    const x = function (ev) { return (handler as EventListener<SocketAdapterAkalaEventMap['message']>).call(this, typeof ev.data === 'string' ? ev.data : IsomorphicBuffer.fromArrayBuffer(ev.data)); };
                    this.messageListeners.push([handler, x]);
                    this.socket.addEventListener('message', x, options);
                    return new StatefulSubscription(() =>
                    {
                        this.messageListeners.splice(this.messageListeners.findIndex(x => x[0] === handler), 1);
                        this.socket.removeEventListener('message', x);
                    }).unsubscribe;
                }

            case 'close':
            case 'error':
            case 'open':
                //eslint-disable-next-line @typescript-eslint/no-explicit-any
                this.socket.addEventListener(event, handler as any);
                return new StatefulSubscription(() =>
                {
                    this.socket.removeEventListener(event, handler as any);
                }).unsubscribe;
            case Symbol.dispose:
                return super.on(event, handler, options);
            default:
                throw new Error(`Unsupported event ${String(event)}`);
        }
    }

    public once<const TEvent extends AllEventKeys<SocketAdapterAkalaEventMap>>(
        event: TEvent,
        handler: EventListener<AllEvents<SocketAdapterAkalaEventMap>[TEvent]>
    ): Subscription
    {
        switch (event)
        {
            case 'message':
                return this.on(event, handler, { once: true } as EventOptions<AllEvents<SocketAdapterAkalaEventMap>[TEvent]>);
            case 'close':
            case 'error':
            case 'open':
            case Symbol.dispose:
                return this.on(event, handler, { once: true } as EventOptions<AllEvents<SocketAdapterAkalaEventMap>[TEvent]>);
            default:
                let x: never = event;
                throw new Error(`Unsupported event ${x}`);
        }
    }
}
