// This defined the setupWebSocket function, which is the same
// across all levels that utilize docker. However, this function
// will not be called here, individual levels will need to call
// it directly.

/**
 * @typedef {Object} WebSocketMessage
 * @property {string} type - The type of the message (e.g. 'docker.ready', 'docker.error', ...)
 * @property {string} message - The content of the message
 */

/**
 * Sets up a WebSocket connection, updating a notice box with connection status.
 * Non-status related messages will be passed over to `messageCallback`
 * @param {function(WebSocket, WebSocketMessage): void} messageCallback - Callback function to handle non-status related messages
 * @param number level - The level of the vulnerability
 * @returns {{ws: WebSocket, getDockerReady: function(): boolean}} An object containing the WebSocket, and a function to get the Docker ready state.
 */
function setupWebSocket(messageCallback, level) {
  const protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
  const url = `${protocol}${window.location.host}/ws/vulnerabilities/level/${level}`;
  const ws = new WebSocket(url);
  const noticeBox = document.getElementById("docker-websocket-notice-box");
  const noticeText = document.getElementById("docker-websocket-notice-content");
  let dockerReady = false;
  let errorMessages = [];

  const TRY_REFRESHING =
    '<div class="d-block">Try refreshing the page. If that doesn\'t help, wait for a few seconds (~20), the server is likely cleaning up your older containers.</div>';

  ws.onmessage = (event) => {
    /** @type {WebSocketMessage} */
    const data = JSON.parse(event.data);

    switch (data.type) {
      case "consumer.waiting":
      case "docker.waiting":
      case "docker.init":
        noticeText.innerHTML = `<i class="fa-solid fa-spinner fa-spin"></i> ${data.message}`;
        noticeBox.className = "alert alert-secondary";
        break;
      case "docker.ready":
        noticeText.innerHTML = data.message;
        noticeBox.className = "alert alert-info";
        dockerReady = true;
        break;
      case "docker.error":
      case "docker.stopped":
      case "consumer.error":
        // Accumulate the error messages in a list and show all of them
        errorMessages.push(data.message);
        noticeText.innerHTML =
          errorMessages
            .map((msg) => `<div class="d-block"><i class="fa-solid fa-exclamation-circle"></i> ${msg}</div>`)
            .join("") + TRY_REFRESHING;
        noticeBox.className = "alert alert-danger";
        dockerReady = false;
        break;
      default:
        if (dockerReady) {
          messageCallback(ws, data);
        } else {
          console.warn("Got unknown message type before docker ready: " + data.type + " message: " + data.message);
        }
    }
  };

  ws.onerror = (event) => {
    noticeText.innerHTML =
      errorMessages
        .map((msg) => `<div class="d-block"><i class="fa-solid fa-exclamation-circle"></i> ${msg}</div>`)
        .join("") + TRY_REFRESHING;
    noticeBox.className = "alert alert-danger";
    dockerReady = false;
    console.error("WebSocket error: ", event);
  };

  ws.onclose = (event) => {
    dockerReady = false;

    // WebSocket was closed cleanly, without sending any error messages before.
    // This can happen if the server is shutting down.
    if (errorMessages.length === 0) {
      noticeText.innerHTML =
        '<div class="d-block"><i class="fa-solid fa-exclamation-circle"></i> WebSocket connection closed</div>' +
        TRY_REFRESHING +
        "</div>";
      noticeBox.className = "alert alert-secondary";
      return;
    }

    // WebSocket was closed after sending error messages.
    noticeText.innerHTML =
      errorMessages
        .map((msg) => `<div class="d-block"><i class="fa-solid fa-exclamation-circle"></i> ${msg}</div>`)
        .join("") +
      '<div class="d-block">WebSocket connection closed.' +
      TRY_REFRESHING +
      "</div>";
    noticeBox.className = "alert alert-danger";
    dockerReady = false;
  };

  return { ws, getDockerReady: () => dockerReady };
}
