// ============================================================
// WebSocket 客户端（自动重连，无房间号）
// ============================================================
let ws = null;
let wsUrl = '';
let wsReconnectTimer = null;
let wsManualClose = false;
let _wsRecvRate = 0;
let _wsRecvWindow = 0;

const _XOR_KEY = new Uint8Array([0x4E,0x67,0x55,0x43,0x68,0x33,0x61,0x74,0x52,0x61,0x64,0x61,0x72,0x47,0x63,0x6D]);

const _B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

function _b64Decode(b64) {
    const lookup = new Uint8Array(256);
    for (let i = 0; i < _B64.length; i++) lookup[_B64.charCodeAt(i)] = i;
    let buf = '';
    for (let i = 0; i < b64.length; i++) {
        if (b64[i] !== '=') buf += b64[i];
    }
    const bytes = new Uint8Array(Math.floor(buf.length * 3 / 4));
    let pos = 0;
    for (let i = 0; i < buf.length; i += 4) {
        const a = lookup[buf.charCodeAt(i)] || 0;
        const b = lookup[buf.charCodeAt(i + 1)] || 0;
        const c = (i + 2 < buf.length) ? lookup[buf.charCodeAt(i + 2)] : 0;
        const d = (i + 3 < buf.length) ? lookup[buf.charCodeAt(i + 3)] : 0;
        bytes[pos++] = (a << 2) | (b >> 4);
        if (pos < bytes.length) bytes[pos++] = ((b & 0xF) << 4) | (c >> 2);
        if (pos < bytes.length) bytes[pos++] = ((c & 0x3) << 6) | d;
    }
    return bytes.subarray(0, pos);
}

function _decryptPayload(b64) {
    try {
        const raw = _b64Decode(b64);
        const xored = new Uint8Array(raw.length);
        for (let i = 0; i < raw.length; i++)
            xored[i] = raw[i] ^ _XOR_KEY[i % _XOR_KEY.length];
        const text = new TextDecoder().decode(xored);
        return JSON.parse(text);
    } catch (e) {
        console.error('[XOR] decrypt failed:', e);
        return null;
    }
}
function _fmtRate(b) { return b>=1048576?(b/1048576).toFixed(1)+' MB/s':b>=1024?(b/1024).toFixed(1)+' KB/s':b+' B/s'; }
setInterval(() => {
  _wsRecvRate = _wsRecvWindow;
  _wsRecvWindow = 0;
  const el = document.getElementById('stSize');
  if (el) el.textContent = _fmtRate(_wsRecvRate);
}, 1000);

function setConn(ok, msg) {
  document.getElementById('connDot').className = 'conn-dot' + (ok ? ' ok' : ' err');
  document.getElementById('connText').textContent = msg || (ok ? '已连接' : '未连接');
  const isReceiving = ok && msg && msg.indexOf('数据接收中') >= 0;
  const tabs = document.getElementById('mapTabs');
  if (tabs) tabs.style.display = isReceiving ? 'none' : '';
  document.querySelectorAll('.topbar-sep').forEach(sep => {
    sep.style.display = isReceiving ? 'none' : '';
  });
}

let _lastMapName = '';
const _entityJsonCache = {};

function handleEntityMessage(type, data) {
  if (!leafletMap) return;

  if (type === 'self' && data && data.map !== undefined) {
    const mapKey = resolveMapKey(data.map);
    if (!mapKey) {
      if (data.map !== _lastMapName) {
        _lastMapName = data.map;
      }
      _entityCache.players = []; _entityCache.items = [];
      _entityCache.deadbodies = []; _entityCache.self = null;
      clearEntityLayers();
      const ps = document.getElementById('panelStats');
      if (ps) ps.style.display = 'none';
      return;
    }
    if (!currentMapInfo || currentMapInfo.id !== mapKey) {
      const info = MAP_PREFIX_TABLE.find(m => m.key === mapKey);
      switchMap(mapKey);
      showToast('自动切换: ' + (info ? info.name : mapKey));
    }
    _lastMapName = data.map;
    const ps = document.getElementById('panelStats');
    if (ps) ps.style.display = '';
  }

  const _newJson = JSON.stringify(data);
  if (_entityJsonCache[type] === _newJson) return;
  _entityJsonCache[type] = _newJson;
  _entityCache[type] = data;
  _updateLerpData();
  _scheduleCanvasRender();
}

function connectWs() {
  if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
  wsManualClose = false;

  const port = location.port || '5000';
  wsUrl = `ws://${location.hostname}:${port}`;

  setConn(false, '连接中...');
  ws = new WebSocket(wsUrl);

  ws.onopen = () => {
    ws.send(JSON.stringify({ action: 'auth', role: 'sub' }));
    setConn(true, '认证中...');
    clearTimeout(wsReconnectTimer);
    const btn = document.getElementById('btnPoll');
    if (btn) btn.textContent = '断开';
  };

  ws.binaryType = 'arraybuffer';
  async function _processWsMsg(rawData) {
    try {
      const _rawSize = (rawData instanceof ArrayBuffer) ? rawData.byteLength : (typeof rawData === 'string' ? rawData.length : 0);
      _wsRecvWindow += _rawSize;
      let jsonStr;
      if (rawData instanceof ArrayBuffer) {
        const data = new Uint8Array(rawData);
        const mode = (data[0] === 0x78) ? 'deflate' : 'deflate-raw';
        try {
          const ds = new DecompressionStream(mode);
          const writer = ds.writable.getWriter();
          writer.write(data); writer.close();
          const reader = ds.readable.getReader();
          const chunks = []; let total = 0;
          while (true) { const {value, done} = await reader.read(); if (done) break; chunks.push(value); total += value.byteLength; }
          const out = new Uint8Array(total); let off = 0;
          for (const c of chunks) { out.set(c, off); off += c.byteLength; }
          jsonStr = new TextDecoder().decode(out);
        } catch(e1) {
          try { jsonStr = new TextDecoder().decode(new Uint8Array(rawData)); }
          catch(e2) { console.error('[WS] decompress failed:', e1); return; }
        }
      } else {
        jsonStr = rawData;
      }
      const msg = JSON.parse(jsonStr);
      if (msg.ok !== undefined) {
        if (msg.ok) {
          if (msg.pub) {
            setConn(true, `数据接收中 (${msg.subs||1}人在线)`);
          } else {
            setConn(true, '等待数据源...');
          }
        }
        else { console.error('[Relay] Auth failed:', msg.error); ws.close(); }
        return;
      }
      if (msg.type === 'subCount') {
        setConn(true, `数据接收中 (${msg.data||1}人在线)`);
        return;
      }
      if (msg.type === 'status') {
        if (msg.data && !msg.data.connected) {
          setConn(true, '等待数据源...');
          _entityCache.self = null;
          _entityCache.players = []; _entityCache.items = [];
          _entityCache.deadbodies = [];
          clearEntityLayers();
          _scheduleCanvasRender();
        }
        return;
      }
      if (msg.type) {
        if (typeof msg.data === 'string') {
            const decrypted = _decryptPayload(msg.data);
            if (decrypted !== null) {
                handleEntityMessage(msg.type, decrypted);
            }
        } else {
            handleEntityMessage(msg.type, msg.data);
        }
      } else {
        if (msg.self)       _entityCache.self       = msg.self;
        if (msg.players)    _entityCache.players    = msg.players;
        if (msg.items)      _entityCache.items      = msg.items;
        if (msg.deadbodies) _entityCache.deadbodies = msg.deadbodies;
        _updateLerpData();
        _scheduleCanvasRender();
      }
    } catch(err) { console.error('[WS] parse error:', err.message); }
  }
  ws.onmessage = async (e) => {
    _processWsMsg(e.data);
  };

  ws.onclose = () => {
    setConn(false, '连接断开');
    const btn = document.getElementById('btnPoll');
    if (btn) btn.textContent = '断开';
    _entityCache.self = null;
    _entityCache.players = []; _entityCache.items = [];
    _entityCache.deadbodies = [];
    clearEntityLayers();
    _scheduleCanvasRender();
    if (!wsManualClose) {
      wsReconnectTimer = setTimeout(connectWs, 3000);
      const el = document.getElementById('wsStatusText');
      if (el) el.textContent = '3秒后重连...';
    }
  };

  ws.onerror = () => setConn(false, '连接错误');
}

function disconnectWs() {
  wsManualClose = true;
  clearTimeout(wsReconnectTimer);
  if (ws) { ws.close(); ws = null; }
  setConn(false, '已手动断开');
  const btn = document.getElementById('btnPoll');
  if (btn) btn.textContent = '连接';
}

// 后台标签页自动断开/重连
let _visibilityDisconnected = false;
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    if (ws && ws.readyState === WebSocket.OPEN) {
      _visibilityDisconnected = true;
      wsManualClose = true;
      clearTimeout(wsReconnectTimer);
      ws.close();
      ws = null;
    }
  } else {
    if (_visibilityDisconnected) {
      _visibilityDisconnected = false;
      wsManualClose = false;
      setTimeout(connectWs, 300);
    }
  }
});
