import WebSocket from 'ws';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { WSServer } from '../../utils/webSocketServer.js';
import { ECLWSEventsEnum } from '../../models/ecl.model.js';
export class ECLWebSocketClient {
    constructor() {
        this.logger = Logger;
        this.common = Common;
        this.wsServer = WSServer;
        this.webSocketClients = [];
        this.reconnectTimeOut = null;
        this.waitTime = 0.5;
        this.reconnet = (eclWsClt) => {
            if (this.reconnectTimeOut) {
                return;
            }
            this.waitTime = (this.waitTime >= 64) ? 64 : (this.waitTime * 2);
            this.reconnectTimeOut = setTimeout(() => {
                if (eclWsClt.selectedNode) {
                    this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Reconnecting to the Eclair\'s Websocket Server..' });
                    this.connect(eclWsClt.selectedNode);
                }
                this.reconnectTimeOut = null;
            }, this.waitTime * 1000);
        };
        this.connect = (selectedNode) => {
            try {
                const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index);
                if (!clientExists) {
                    if (selectedNode.settings.lnServerUrl) {
                        const newWebSocketClient = { selectedNode: selectedNode, reConnect: true, webSocketClient: null };
                        this.connectWithClient(newWebSocketClient);
                        this.webSocketClients.push(newWebSocketClient);
                    }
                }
                else {
                    if ((!clientExists.webSocketClient || clientExists.webSocketClient.readyState !== WebSocket.OPEN) && selectedNode.settings.lnServerUrl) {
                        clientExists.reConnect = true;
                        this.connectWithClient(clientExists);
                    }
                }
            }
            catch (err) {
                throw new Error(err);
            }
        };
        this.connectWithClient = (eclWsClt) => {
            this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Connecting to the Eclair\'s Websocket Server..' });
            const UpdatedLNServerURL = (eclWsClt.selectedNode.settings.lnServerUrl)?.replace(/^http/, 'ws');
            const firstSubStrIndex = (UpdatedLNServerURL.indexOf('//') + 2);
            const WS_LINK = UpdatedLNServerURL.slice(0, firstSubStrIndex) + ':' + eclWsClt.selectedNode.authentication.lnApiPassword + '@' + UpdatedLNServerURL.slice(firstSubStrIndex) + '/ws';
            eclWsClt.webSocketClient = new WebSocket(WS_LINK);
            eclWsClt.webSocketClient.onopen = () => {
                this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Connected to the Eclair\'s Websocket Server..' });
                this.waitTime = 0.5;
                this.heartbeat(eclWsClt);
            };
            eclWsClt.webSocketClient.onclose = (e) => {
                if (eclWsClt && eclWsClt.selectedNode && eclWsClt.selectedNode.lnImplementation === 'ECL') {
                    this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Web socket disconnected, will reconnect again...' });
                    eclWsClt.webSocketClient.close();
                    if (eclWsClt.reConnect) {
                        this.reconnet(eclWsClt);
                    }
                }
            };
            eclWsClt.webSocketClient.onmessage = (msg) => {
                this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Received message from the server..', data: msg.data });
                msg = (typeof msg.data === 'string') ? JSON.parse(msg.data) : msg.data;
                if (msg.type && msg.type !== ECLWSEventsEnum.PAY_RELAYED && msg.type !== ECLWSEventsEnum.PAY_SETTLING_ONCHAIN && msg.type !== ECLWSEventsEnum.ONION_MESSAGE_RECEIVED) {
                    msg['source'] = 'ECL';
                    const msgStr = JSON.stringify(msg);
                    this.wsServer.sendEventsToAllLNClients(msgStr, eclWsClt.selectedNode);
                }
            };
            eclWsClt.webSocketClient.onerror = (err) => {
                if (eclWsClt.selectedNode.lnVersion === '' || !eclWsClt.selectedNode.lnVersion || this.common.isVersionCompatible(eclWsClt.selectedNode.ln, '0.5.0')) {
                    this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'ERROR', fileName: 'ECLWebSocket', msg: 'Web socket error', error: err });
                    const errStr = ((typeof err === 'object' && err.message) ? JSON.stringify({ error: err.message }) : (typeof err === 'object') ? JSON.stringify({ error: err }) : ('{ "error": ' + err + ' }'));
                    this.wsServer.sendErrorToAllLNClients(errStr, eclWsClt.selectedNode);
                    eclWsClt.webSocketClient.close();
                    if (eclWsClt.reConnect) {
                        this.reconnet(eclWsClt);
                    }
                }
                else {
                    eclWsClt.reConnect = false;
                }
            };
        };
        this.disconnect = (selectedNode) => {
            const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index);
            if (clientExists && clientExists.webSocketClient && clientExists.webSocketClient.readyState === WebSocket.OPEN) {
                this.logger.log({ selectedNode: clientExists.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Disconnecting from the Eclair\'s Websocket Server..' });
                clientExists.reConnect = false;
                clientExists.webSocketClient.close();
                const clientIdx = this.webSocketClients.findIndex((wsc) => wsc.selectedNode.index === selectedNode.index);
                this.webSocketClients.splice(clientIdx, 1);
            }
        };
        this.updateSelectedNode = (newSelectedNode) => {
            const clientIdx = this.webSocketClients.findIndex((wsc) => +wsc.selectedNode.index === +newSelectedNode.index);
            let newClient = this.webSocketClients[clientIdx];
            if (!newClient) {
                newClient = { selectedNode: null, reConnect: true, webSocketClient: null };
            }
            newClient.selectedNode = JSON.parse(JSON.stringify(newSelectedNode));
            this.webSocketClients[clientIdx] = newClient;
        };
        this.heartbeat = (eclWsClt) => {
            this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Websocket Server Heartbeat..' });
            if (!eclWsClt.webSocketClient) {
                return;
            }
            if (eclWsClt.webSocketClient.readyState !== 1) {
                return;
            }
            eclWsClt.webSocketClient.ping();
            setTimeout(() => {
                this.heartbeat(eclWsClt);
            }, 59 * 1000);
        };
        this.wsServer.eventEmitterECL.on('CONNECT', (nodeIndex) => {
            this.connect(this.common.findNode(+nodeIndex));
        });
        this.wsServer.eventEmitterECL.on('DISCONNECT', (nodeIndex) => {
            this.disconnect(this.common.findNode(+nodeIndex));
        });
    }
}
export const ECLWSClient = new ECLWebSocketClient();
