/* ==============================================
   WebSocket
   ============================================== */
let _wsReconnectAttempt = 0;
window._wsConnected = false;

function connectWs() {
  if (wsClient && (wsClient.readyState === WebSocket.OPEN || wsClient.readyState === WebSocket.CONNECTING)) {
    return;
  }
  const apiKey = getApiKey();
  const keyQuery = apiKey ? ('?api_key=' + encodeURIComponent(apiKey)) : '';
  const wsUrl = (location.protocol==='https:'?'wss:':'ws:') + '//' + location.host + '/api/v1/ws' + keyQuery;
  dbg('ws: connecting to ' + wsUrl);
  const ws = new WebSocket(wsUrl);
  wsClient = ws;
  let lastMsgTime = Date.now();
  ws.onopen = () => {
    dbg('ws: connected');
    window._wsConnected = true;
    _wsReconnectAttempt = 0;
  };
  ws.onmessage = (e) => {
    lastMsgTime = Date.now();
    const msg = e.data;
    dbg('ws: ' + msg);
    if (msg === 'projects') {
      const oldPid = currentProject;
      loadProjects().then(() => {
        if (oldPid && projects.find(p => p.id === oldPid)) {
          selectProject(oldPid);
        }
      });
    } else if (msg.startsWith('graph:')) {
      const pid = msg.slice(6);
      if (pid === currentProject) loadGraph();
    }
  };
  ws.onclose = () => {
    wsClient = null;
    window._wsConnected = false;
    if (getApiKey()) {
      const delay = Math.min(2000 * Math.pow(2, _wsReconnectAttempt) + Math.random() * 1000, 30000);
      dbg('ws: closed, reconnect in ' + Math.round(delay) + 'ms');
      _wsReconnectAttempt++;
      setTimeout(connectWs, delay);
    }
  };
  ws.onerror = () => ws.close();

  // Heartbeat: close and reconnect if no message for 60s
  const heartbeatInterval = setInterval(() => {
    if (wsClient && wsClient.readyState === WebSocket.OPEN) {
      if (Date.now() - lastMsgTime > 60000) {
        dbg('ws: heartbeat timeout, reconnecting');
        clearInterval(heartbeatInterval);
        wsClient.close();
      }
    } else {
      clearInterval(heartbeatInterval);
    }
  }, 10000);
}
