import { watch, onUnmounted } from 'vue';
import { buildUUID } from '/@/utils/uuid';
import { useGlobSetting } from '/@/hooks/setting';
import { useUserStore } from '/@/store/modules/user';
import { JVxeDataProps, JVxeTableMethods, JVxeTableProps } from '../types';
import { isArray } from '/@/utils/is';
import { getToken } from '/@/utils/auth';

// vxe socket
const vs = {
  // 页面唯一 id，用于标识同一用户，不同页面的websocket
  pageId: buildUUID(),
  // webSocket 对象
  ws: null,
  // 一些常量
  constants: {
    // 消息类型
    TYPE: 'type',
    // 消息数据
    DATA: 'data',
    // 消息类型：心跳检测
    TYPE_HB: 'heart_beat',
    // 消息类型：更新vxe table数据
    TYPE_UVT: 'update_vxe_table',
  },
  // 心跳检测
  heartCheck: {
    // 间隔时间，间隔多久发送一次心跳消息
    interval: 10000,
    // 心跳消息超时时间，心跳消息多久没有回复后重连
    timeout: 6000,
    timeoutTimer: -1,
    clear() {
      clearTimeout(this.timeoutTimer);
      return this;
    },
    start() {
      vs.sendMessage(vs.constants.TYPE_HB, '');
      // 如果超过一定时间还没重置，说明后端主动断开了
      this.timeoutTimer = window.setTimeout(() => {
        vs.reconnect();
      }, this.timeout);
      return this;
    },
    // 心跳消息返回
    back() {
      this.clear();
      window.setTimeout(() => this.start(), this.interval);
    },
  },

  /** 初始化 WebSocket */
  initialWebSocket() {
    if (this.ws === null) {
      const userId = useUserStore().getUserInfo?.id;
      const domainURL = useGlobSetting().uploadUrl!;
      const domain = domainURL.replace('https://', 'wss://').replace('http://', 'ws://');
      const url = `${domain}/vxeSocket/${userId}/${this.pageId}`;
      // 代码逻辑说明: v2.4.6 的 websocket 服务端，存在性能和安全问题。 #3278
      let token = (getToken() || '') as string;
      this.ws = new WebSocket(url, [token]);
      this.ws.onopen = this.on.open.bind(this);
      this.ws.onerror = this.on.error.bind(this);
      this.ws.onmessage = this.on.message.bind(this);
      this.ws.onclose = this.on.close.bind(this);
    }
  },

  // 发送消息
  sendMessage(type, message) {
    try {
      let ws = this.ws;
      if (ws != null && ws.readyState === ws.OPEN) {
        ws.send(
          JSON.stringify({
            type: type,
            data: message,
          })
        );
      }
    } catch (err: any) {
      console.warn('【JVxeWebSocket】发送消息失败：(' + err.code + ')');
    }
  },

  /** 绑定全局VXE表格 */
  tableMap: new Map(),
  /** 添加绑定 */
  addBind(map, key, value: VmArgs) {
    let binds = map.get(key);
    if (isArray(binds)) {
      binds.push(value);
    } else {
      map.set(key, [value]);
    }
  },
  /** 移除绑定 */
  removeBind(map, key, value: VmArgs) {
    let binds = map.get(key);
    if (isArray(binds)) {
      for (let i = 0; i < binds.length; i++) {
        let bind = binds[i];
        if (bind === value) {
          binds.splice(i, 1);
          break;
        }
      }
      if (binds.length === 0) {
        map.delete(key);
      }
    } else {
      map.delete(key);
    }
  },
  // 呼叫绑定的表单
  callBind(map, key, callback) {
    let binds = map.get(key);
    if (isArray(binds)) {
      binds.forEach(callback);
    }
  },

  lockReconnect: false,
  /** 尝试重连 */
  reconnect() {
    if (this.lockReconnect) return;
    this.lockReconnect = true;
    setTimeout(() => {
      if (this.ws && this.ws.close) {
        this.ws.close();
      }
      this.ws = null;
      console.info('【JVxeWebSocket】尝试重连...');
      this.initialWebSocket();
      this.lockReconnect = false;
    }, 5000);
  },

  on: {
    open() {
      console.info('【JVxeWebSocket】连接成功');
      this.heartCheck.start();
    },
    error(e) {
      console.warn('【JVxeWebSocket】连接发生错误:', e);
      this.reconnect();
    },
    message(e) {
      // 解析消息
      let json;
      try {
        json = JSON.parse(e.data);
      } catch (e: any) {
        console.warn('【JVxeWebSocket】收到无法解析的消息:', e.data);
        return;
      }
      let type = json[this.constants.TYPE];
      let data = json[this.constants.DATA];
      switch (type) {
        // 心跳检测
        case this.constants.TYPE_HB:
          this.heartCheck.back();
          break;
        // 更新form数据
        case this.constants.TYPE_UVT:
          this.callBind(this.tableMap, data.socketKey, (args) => this.onVM.onUpdateTable(args, ...data.args));
          break;
        default:
          console.warn('【JVxeWebSocket】收到不识别的消息类型:' + type);
          break;
      }
    },
    close(e) {
      console.info('【JVxeWebSocket】连接被关闭:', e);
      this.reconnect();
    },
  },

  onVM: {
    /** 收到更新表格的消息 */
    onUpdateTable({ props, data, methods }: VmArgs, row, caseId) {
      if (data.caseId !== caseId) {
        const tableRow = methods.getIfRowById(row.id).row;
        // 局部保更新数据
        if (tableRow) {
          if (props.reloadEffect) {
            data.reloadEffectRowKeysMap[row.id] = true;
          }
          Object.assign(tableRow, row, { id: tableRow.id });
          methods.getXTable().reloadRow(tableRow);
        }
      }
    },
  },
} as {
  ws: Nullable<WebSocket>;
} & Recordable;

type VmArgs = {
  props: JVxeTableProps;
  data: JVxeDataProps;
  methods: JVxeTableMethods;
};

export function useWebSocket(props: JVxeTableProps, data: JVxeDataProps, methods) {
  const args: VmArgs = { props, data, methods };
  watch(
    () => props.socketReload,
    (socketReload: boolean) => {
      if (socketReload) {
        vs.initialWebSocket();
        vs.addBind(vs.tableMap, props.socketKey, args);
      } else {
        vs.removeBind(vs.tableMap, props.socketKey, args);
      }
    },
    { immediate: true }
  );

  /** 发送socket消息更新行 */
  function socketSendUpdateRow(row) {
    vs.sendMessage(vs.constants.TYPE_UVT, {
      socketKey: props.socketKey,
      args: [row, data.caseId],
    });
  }

  onUnmounted(() => {
    vs.removeBind(vs.tableMap, props.socketKey, args);
  });

  return {
    socketSendUpdateRow,
  };
}
