import { sessionGet } from '@/utils/cache-session-utils';
import { getAccessToken } from './auth-token';
import { localGet } from '@/utils/cache-local-utils';
import { CACHE_LOCAL_I18N } from '@/constants/cache-keys-constants';
import { TOKEN_RESPONSE_FIELD } from '@/constants/token-constants';

/**连接参数类型 */
export type OptionsType = {
  /**WebSocket服务地址 */
  url: string;
  /**地址栏参数 */
  params?: Record<string, string | number | boolean | undefined>;
  /**onopen事件的回调函数 */
  onopen?: Function;
  /**message事件的回调函数 */
  onmessage: (data: Record<string, any>) => void;
  /**error事件的回调函数 */
  onerror: Function;
  /**close事件的回调函数 */
  onclose?: (code: number) => void;
  /**心跳周期 若为0则不启用 */
  heartTimer?: number;
  /**重连等待 若为0则不启用 */
  reconnectTimer?: number;
};

/**
 * WebSocket 使用方法
 *
 * import { OptionsType, WS } from '@/plugins/ws-websocket';
 *
 * const ws = new WS();
 *
 * 创建连接
 * const options: OptionsType = { };
 * ws.connect(options);
 *
 * 手动关闭
 * ws.close();
 */
export class WS {
  /**ws 实例 */
  private ws: WebSocket | null = null;
  /**ws 连接参数 */
  private options: OptionsType | null = null;
  /**心跳调度器 */
  private heartInterval: number = 0;
  /**重连定时器 */
  private reconnectTimeout: number = 0;

  /**
   * 构造函数
   * @param {object} params 构造函数参数
   */
  constructor(options?: OptionsType) {
    if (!window.WebSocket) {
      // 检测浏览器支持
      console.error('Sorry! Browser does not support websocket');
      return;
    }
    options && this.connect(options);
  }

  /**
   * 创建链接
   * @param {object} options 连接参数
   */
  public connect(options: OptionsType) {
    this.options = options;
    try {
      if (!options.url.startsWith('ws')) {
        const uri = options.url.startsWith('/')
          ? options.url
          : `/${options.url}`;
        // 兼容旧前端可改配置文件
        const wsUrl = import.meta.env.PROD
          ? sessionGet('wsUrl') || import.meta.env.VITE_API_BASE_URL
          : '/socket';
        if (wsUrl.startsWith('ws')) {
          options.url = `${wsUrl}${uri}`;
        } else if (wsUrl.startsWith('https')) {
          options.url = `${wsUrl.replace('https', 'wss')}${uri}`;
        } else if (wsUrl.startsWith('http')) {
          options.url = `${wsUrl.replace('http', 'ws')}${uri}`;
        } else {
          const protocol =
            window.location.protocol === 'https:' ? 'wss://' : 'ws://';
          options.url = `${protocol}${location.host}${wsUrl}${uri}`;
        }
      }

      // 地址栏参数
      let params = Object.assign({}, options.params, {
        // 设置 token
        [TOKEN_RESPONSE_FIELD]: getAccessToken(),
        // 多语言
        ['language']: localGet(CACHE_LOCAL_I18N) || 'en_US',
      });
      let paramStr = '';
      for (const key in params) {
        const value = params[key];
        // 空字符或未定义的值不作为参数发送
        if (value === '' || value === undefined) continue;
        paramStr += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
      }
      if (paramStr && paramStr.startsWith('&')) {
        options.url = `${options.url}?${paramStr.substring(1)}`;
      }

      const ws = new WebSocket(options.url, 'omc-ws');
      // 用于指定连接成功后的回调函数。
      ws.onopen = ev => {
        if (options.heartTimer && options.heartTimer > 0) {
          this.heartCheck(options.heartTimer);
        }
        if (typeof options.onopen === 'function') {
          options.onopen(ev);
        }
      };
      // 用于指定当从服务器接受到信息时的回调函数。
      ws.onmessage = ev => {
        if (ev.type !== 'message') return;
        // 解析文本消息
        try {
          const jsonData = JSON.parse(ev.data);
          if (typeof options.onmessage === 'function') {
            options.onmessage(jsonData);
          }
        } catch (error) {
          console.error('websocket message formatting error', error);
        }
      };
      // 用于指定连接关闭后的回调函数。
      ws.onclose = ev => {
        if (typeof options.onclose === 'function') {
          options.onclose(ev.code);
        }
      };
      // 用于指定连接失败后的回调函数。
      ws.onerror = ev => {
        console.error('websocket connection anomaly', ev);

        if (typeof options.onerror === 'function') {
          options.onerror(ev);
        }
        if (options.reconnectTimer && options.reconnectTimer > 0) {
          this.reconnect(options.reconnectTimer);
        }
      };
      this.ws = ws;
    } catch (error) {
      if (typeof options.onerror === 'function') {
        options.onerror(error);
      }
      if (options.reconnectTimer && options.reconnectTimer > 0) {
        this.reconnect(options.reconnectTimer);
      }
    }
  }

  /**
   * 发送消息
   * @param data JSON数据
   * @returns
   */
  public send(data: Record<string, any>): boolean {
    if (!this.ws) {
      console.warn('websocket unavailable');
      return false;
    }

    // 非正常状态关闭
    if (
      this.ws.readyState === WebSocket.CLOSED ||
      this.ws.readyState === WebSocket.CLOSING
    ) {
      this.close();
      return false;
    }
    // 正在连接时
    if (this.ws.readyState === WebSocket.CONNECTING) {
      setTimeout(() => {
        this.ws && this.ws.send(JSON.stringify(data));
      }, 1000);
    }
    // 在连接的
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
      return true;
    }
    return false;
  }

  /**连接状态
   *
   * WebSocket.OPEN
   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState)
   */
  public state(): number {
    if (!this.ws) {
      return -1;
    }
    return this.ws.readyState;
  }

  // 手动关闭socket
  public close() {
    this.heartInterval && clearInterval(this.heartInterval);
    this.reconnectTimeout && clearTimeout(this.reconnectTimeout);
    if (!this.ws) {
      console.warn('websocket unavailable');
      return;
    }
    this.ws.close(1000, 'user initiated closure');
  }

  // 周期性发送ping 保活
  private heartCheck(heartTimer: number) {
    this.heartInterval = window.setInterval(() => {
      this.send({
        requestId: `${Date.now()}`,
        type: 'PING',
      });
    }, heartTimer);
  }

  // 断线重连
  private reconnect(reconnectTimer: number) {
    if (this.reconnectTimeout > 0) return;
    clearTimeout(this.reconnectTimeout);
    this.reconnectTimeout = window.setTimeout(() => {
      if (this.options) {
        this.connect(this.options);
        this.reconnectTimeout = 0;
      }
    }, reconnectTimer);
  }
}
