import { io, Socket } from 'socket.io-client';

export enum SOCKET_STATUS {
    CONNECTED = 'CONNECTED',
    ACTIVE = 'ACTIVE',
    RECOVERED = 'RECOVERED',
    DISCONNECTED = 'DISCONNECTED',
    UNDEFINED = 'UNDEFINED',
}

export class WebSocketClient {
    private socket: Socket;
    private intervalPingPongId;

    constructor(
        auth: {
            userId?: string;
            clientId?: string;
            token?: string;
            apiKey?: string;
        },
        // Options
        {
            domain,
            isAutoPingPongEnable = true,
            ignoreClientId = false,
        }: {
            domain?: string;
            isAutoPingPongEnable?: boolean;
            ignoreClientId?: boolean;
        } = {},
    ) {
        const serverUrl: string =
            domain ??
            process.env.PRIVATE_DOMAIN ??
            process.env.DOMAIN ??
            'http://localhost:3000/';

        this.socket = io(serverUrl, {
            auth,
            path: '/websocket',
            query: {
                ignoreClientId,
            },
        });

        if (isAutoPingPongEnable) {
            const intervalTime = 60000; // 60000 ms = 1 minute;
            this.intervalPingPongId = setInterval(() => {
                this.publish({
                    eventName: 'ping',
                    data: {},
                });
            }, intervalTime);
        }
    }

    public publish({
        eventName,
        data,
        isPublic = false,
    }: {
        eventName: string;
        data: Record<string, any>;
        isPublic?: boolean;
    }): void {
        this.socket.emit(eventName, { isPublic, data });
    }

    public publishAndDisconnect({
        eventName,
        data,
        isPublic = false,
    }: {
        eventName: string;
        data: Record<string, any>;
        isPublic?: boolean;
    }): void {
        this.socket.emit(eventName, { isPublic, data }, () => {
            this.socket.disconnect();
        });
    }

    public subscribe(
        eventName: string,
        callback: (message: any) => void,
    ): this {
        this.socket.on(eventName, callback);
        return this;
    }

    public onError(callback: (message: any) => void) {
        this.socket.on('connect_error', (error) => {
            return callback(error);
        });
    }

    public status(): SOCKET_STATUS {
        if (this.socket.disconnected) return SOCKET_STATUS.DISCONNECTED;
        if (this.socket.connected) return SOCKET_STATUS.CONNECTED;
        if (this.socket.recovered) return SOCKET_STATUS.RECOVERED;
        if (this.socket.active) return SOCKET_STATUS.ACTIVE;
        return SOCKET_STATUS.UNDEFINED;
    }

    public connect(): void {
        this.socket.connect();
    }

    public disconnect(): void {
        this.socket.disconnect();
        if (this.intervalPingPongId) {
            clearInterval(this.intervalPingPongId);
        }
    }
}
