const socketUrl = 'ws://localhost:3100/plugin-server/healthcheck';
 
let socketLogs: string = "";

const subscribers: Map<string, Set<(...args: any[]) => void>> = new Map();
export function subscribe(event: string, cb: (...args: any[]) => void) {
  let cbSet = subscribers.get(event);
  if (!cbSet) {
    cbSet = new Set();
    subscribers.set(event, cbSet);
  }

  cbSet.add(cb);

  return () => {
    if (cbSet) {
      cbSet.delete(cb);
    }
  }
} 

export async function openSocket() {
  const tenMinutes = 10 * 60 * 1000;

  const exponentialBackoffs = [500, 1000, 2000, 3000, 5000, 10000, 10000, ...(new Array(100).fill(tenMinutes))];
  // This promise never resolves since we want the process to run forever
  let nextBackoffIndex = 0;
  let ws: WebSocket|null = null;
  let connectionInProgress = false;
  let timeout: ReturnType<typeof setTimeout>|null = null;
  function establishConnection(withDelay: boolean) {
    if (withDelay) {
      if (timeout === null) {
        const delay = exponentialBackoffs[nextBackoffIndex];
        nextBackoffIndex = (nextBackoffIndex + 1) % exponentialBackoffs.length;
        console.log("Queueing connection request with delay", delay);
        timeout = setTimeout(() => {
          timeout = null;
          establishConnection(false);
        }, delay);
      } else {
        console.log("Not queuing a connection request because one is already in queue");
      }
    } else {
      if (!connectionInProgress) {
        console.log("Trying to connect to: ", socketUrl);
        if (ws) {
          ws.close();
          ws = null;
        }
        ws = new WebSocket(socketUrl)
        connectionInProgress = true;
        ws.onerror = (err) => {
          connectionInProgress = false;
          console.error("Socket error", err);
          ws?.close();
        };

        ws.onclose = () => {
          console.log("Connection was closed! Will retry");
          establishConnection(true);
        };

        ws.onopen = () => {
          connectionInProgress = false;
          console.log("Socket is open");
          ws?.send(`{"type": "register", "kind": "cliui"}`);
        };

        ws.onmessage = async (event) => {
          const rawMessage = event.data;
          let message: any;
          try {
            message = JSON.parse(rawMessage.toString());
          } catch (err) {
            console.log("Unparsable message: ", err);
          }
          if (message.type === "status") {
            const subs = subscribers.get("status");
            if (subs) {
              subs.forEach(sub => sub(message))
            }
          } else if (message.type === "log") {
            socketLogs = socketLogs + '\n' + message.message;
            if (socketLogs.length > 100000) {
              socketLogs = socketLogs.slice(socketLogs.length - 100000);
            } 
            const subs = subscribers.get("onLog");
            if (subs) {
              subs.forEach(sub => sub(socketLogs))
            }
          } else {
            console.log("socket message: ", rawMessage); 
          }
        }; 
      } else {
        console.log("Ignoring connection request as one is already in progress");        
      }
    }
  }

  return establishConnection(false);
}
