import { onUnmounted } from "vue";
import { useTimers } from "./useTimers";

/** 调用就连接，被断开就连接，onUnmouted就断开连接，同时清除自动连接的机制
 * onMessage 接收websocket的消息
 * 本应用没有发消息的场景
 */
export function useWebSocket() {
  let ws: WebSocket;
  let heartbeatIds: number[] = [];
  const RECONNECT_INTERVAL = 6 * 1000;
  let reconnectIds: number[] = [];
  const HEARTBEAT_INTERVAL = 10 * 1000;
  let reconnectNumber = 0;

  let closeExplicitly = false;

  let url: string;
  let onMessage: (e: MessageEvent) => void;
  let byWho: string;

  const { addTimeout, addInterval } = useTimers();

  function startWS(
    _url: string,
    _onMessage: (e: MessageEvent) => void,
    _byWho: string
  ) {
    closeExplicitly = false;
    url = _url;
    onMessage = _onMessage;
    byWho = _byWho;
    openWS();
  }

  // new WebSocket -> open -> heartbeat -> onmessage    happy path
  // new WebSocket -> onerror -> reconnect
  // new WebSocket -> onclose(by server) -> reconnect
  function openWS() {
    logger({
      cnl: ["server", "console"],
      key: byWho,
      act: "准备连接",
      // 连接websocket时，ws要么还没初始化，要么是closed，否则均不正常
      dtl: [undefined, 3].includes(ws?.readyState)
        ? "websocket未初始化或已关闭"
        : "不可能的事情发生了",
      ext: { url, websocketState: ws?.readyState },
    });
    try {
      ws = new WebSocket(url);
    } catch (error) {
      // 理论上不该出现：SECURITY_ERR SyntaxError
      $message.error("Websocket初始化失败", { duration: 5, closable: true });
      logger({
        cnl: ["server", "console"],
        key: byWho,
        act: "Websocket初始化失败",
        possibleError: error,
        ext: { url },
      });
    }

    ws.onopen = () => {
      logger({
        cnl: ["server", "console"],
        key: byWho,
        act: "连接成功",
        ext: { url },
      });

      // 后台是通过onmessage来发送事件告知status的，这种情况下404判断不了reconnect
      // try {
      //   const msg = JSON.parse((<any>event).data as string);
      //   if (msg.status.code === "200") {
      //     reconnectNumber = 0;
      //   }
      // } catch (error) {
      //   logger({ cnl: ["server"], key: byWho, act: "open message not valid" });
      // }
      reconnectNumber = 0;

      heartbeat();
    };

    ws.onmessage = onMessage;

    ws.onclose = (event) => {
      logger({
        cnl: ["server", "console"],
        key: byWho,
        act: "ws closed by server",
        dtl: JSON.stringify(event),
        ext: { url },
      });
      for (const heartbeatId of heartbeatIds) {
        clearInterval(heartbeatId);
      }
      heartbeatIds = [];

      if (!closeExplicitly) reconnect("onclose-by-server");
    };

    function reconnect(cause: string) {
      reconnectIds.forEach((id) => clearTimeout(id));
      reconnectIds = [];
      const tid = addTimeout(() => {
        reconnectNumber++;
        if (reconnectNumber >= 5) {
          reconnectNumber = 0;
          $message.error("Websocket重连失败", {
            duration: 5,
            closable: true,
          });
        }
        logger({
          cnl: ["server", "console"],
          key: byWho,
          act: "连接被关闭后-准备连接-" + cause,
          ext: { url },
        });
        // 会让断开就重连
        openWS();
      }, RECONNECT_INTERVAL);
      reconnectIds.push(tid);
    }

    ws.onerror = (event) => {
      logger({
        cnl: ["server", "console"],
        key: byWho,
        act: "onerror",
        dtl: JSON.stringify(event),
        ext: { url },
      });
      if (!closeExplicitly) reconnect("onerror");
    };
  }

  function heartbeat() {
    for (const heartbeatId of heartbeatIds) {
      clearInterval(heartbeatId);
    }
    heartbeatIds = [];

    const heartbeatId = addInterval(() => {
      logger({
        lvl: "debug",
        cnl: ["server", "console"],
        key: byWho,
        act: "websocket heartbeat",
        ext: { url },
      });
      ws.send(JSON.stringify({ eventType: "HEARTBEAT" }));
    }, HEARTBEAT_INTERVAL);

    heartbeatIds.push(heartbeatId);
  }

  function closeWsWithoutReconnect() {
    closeExplicitly = true;
    logger({
      cnl: ["server", "console"],
      key: byWho,
      act: "客户端准备关闭ws。",
      ext: { url },
    });

    try {
      // The WebSocket.close() method closes the WebSocket connection or connection attempt, if any.
      // If the connection is already CLOSED, this method does nothing.
      if (ws) ws.close();
    } catch (e) {
      logger({
        cnl: ["server", "console"],
        key: byWho,
        act: "关闭ws异常。",
        possibleError: e,
        ext: { url },
      });
    }
  }

  onUnmounted(closeWsWithoutReconnect);

  return { startWS };
}
