/**
 * BON (Binary Object Notation) 协议实现
 * 基于提供的真实 BON 源码重新实现
 */
import lz4 from 'lz4js';

// -----------------------------
// BON 编解码器核心实现
// -----------------------------

export class Int64 {
  constructor(high, low) {
    this.high = high;
    this.low = low;
  }
}

export class DataReader {
  constructor(bytes) {
    this._data = bytes || new Uint8Array(0);
    this._view = null;
    this.position = 0;
  }

  get data() { return this._data; }
  get dataView() {
    return this._view || (this._view = new DataView(this._data.buffer, this._data.byteOffset, this._data.byteLength));
  }

  reset(bytes) {
    this._data = bytes;
    this.position = 0;
    this._view = null;
  }

  validate(n) {
    if (this.position + n > this._data.length) {
      console.error('read eof');
      return false;
    }
    return true;
  }

  readUInt8() {
    if (!this.validate(1)) return;
    return this._data[this.position++];
  }

  readInt16() {
    if (!this.validate(2)) return;
    const v = this._data[this.position++] | (this._data[this.position++] << 8);
    return (v << 16) >> 16;
  }

  readInt32() {
    if (!this.validate(4)) return;
    const v = this._data[this.position++] | (this._data[this.position++] << 8) | (this._data[this.position++] << 16) | (this._data[this.position++] << 24);
    return v | 0;
  }

  readInt64() {
    const lo = this.readInt32();
    if (lo === undefined) return;
    let _lo = lo;
    if (_lo < 0) _lo += 0x100000000;
    const hi = this.readInt32();
    if (hi === undefined) return;
    return _lo + 0x100000000 * hi;
  }

  readFloat32() {
    if (!this.validate(4)) return;
    const v = this.dataView.getFloat32(this.position, true);
    this.position += 4;
    return v;
  }

  readFloat64() {
    if (!this.validate(8)) return;
    const v = this.dataView.getFloat64(this.position, true);
    this.position += 8;
    return v;
  }

  read7BitInt() {
    let value = 0;
    let shift = 0;
    let b = 0;
    let count = 0;
    do {
      if (count++ === 35) throw new Error('Format_Bad7BitInt32');
      b = this.readUInt8();
      value |= (b & 0x7F) << shift;
      shift += 7;
    } while ((b & 0x80) !== 0);
    return value >>> 0;
  }

  readUTF() {
    const len = this.read7BitInt();
    return this.readUTFBytes(len);
  }

  readUint8Array(length, copy = false) {
    const start = this.position;
    const end = start + length;
    const out = copy ? this._data.slice(start, end) : this._data.subarray(start, end);
    this.position = end;
    return out;
  }

  readUTFBytes(length) {
    if (length === 0) return '';
    if (!this.validate(length)) return;
    const str = new TextDecoder('utf8').decode(this._data.subarray(this.position, this.position + length));
    this.position += length;
    return str;
  }
}

let _shared = new Uint8Array(524288); // 512 KB initial buffer

export class DataWriter {
  constructor() {
    this.position = 0;
    this._view = null;
    this.data = _shared;
  }

  get dataView() {
    return this._view || (this._view = new DataView(this.data.buffer, 0, this.data.byteLength));
  }

  reset() {
    this.data = _shared;
    this._view = null;
    this.position = 0;
  }

  ensureBuffer(size) {
    if (this.position + size <= _shared.byteLength) return;
    const prev = _shared;
    const need = this.position + size;
    const nextLen = Math.max(Math.floor((_shared.byteLength * 12) / 10), need);
    _shared = new Uint8Array(nextLen);
    _shared.set(prev, 0);
    this.data = _shared;
    this._view = null;
  }

  writeInt8(v) {
    this.ensureBuffer(1);
    this.data[this.position++] = v | 0;
  }

  writeInt16(v) {
    this.ensureBuffer(2);
    this.data[this.position++] = v | 0;
    this.data[this.position++] = (v >> 8) & 0xFF;
  }

  writeInt32(v) {
    this.ensureBuffer(4);
    this.data[this.position++] = v | 0;
    this.data[this.position++] = (v >> 8) & 0xFF;
    this.data[this.position++] = (v >> 16) & 0xFF;
    this.data[this.position++] = (v >> 24) & 0xFF;
  }

  writeInt64(v) {
    this.writeInt32(v);
    if (v < 0) {
      this.writeInt32(~Math.floor((-v) / 0x100000000));
    } else {
      this.writeInt32(Math.floor(v / 0x100000000) | 0);
    }
  }

  writeFloat32(v) {
    this.ensureBuffer(4);
    this.dataView.setFloat32(this.position, v, true);
    this.position += 4;
  }

  writeFloat64(v) {
    this.ensureBuffer(8);
    this.dataView.setFloat64(this.position, v, true);
    this.position += 8;
  }

  _write7BitInt(v) {
    let n = v >>> 0;
    while (n >= 0x80) {
      this.data[this.position++] = (n & 0xFF) | 0x80;
      n >>>= 7;
    }
    this.data[this.position++] = n & 0x7F;
  }

  write7BitInt(v) {
    this.ensureBuffer(5);
    this._write7BitInt(v);
  }

  _7BitIntLen(v) {
    return v < 0 ? 5
      : v < 0x80 ? 1
      : v < 0x4000 ? 2
      : v < 0x200000 ? 3
      : v < 0x10000000 ? 4
      : 5;
  }

  writeUTF(str) {
    const t = str.length;
    if (t === 0) {
      this.write7BitInt(0);
      return;
    }
    const max = 6 * t;
    this.ensureBuffer(5 + max);
    const start = this.position;
    this.position += this._7BitIntLen(max);
    const from = this.position;
    const reserved = from - start;
    
    const encoder = new TextEncoder();
    const { written } = encoder.encodeInto(str, this.data.subarray(this.position));
    this.position += written;
    const after = this.position;
    const size = after - from;
    
    this.position = start;
    this._write7BitInt(size);
    const used = this.position - start;
    if (used !== reserved) {
      this.data.copyWithin(from + (used - reserved), from, after);
    }
    this.position = from + size + (used - reserved);
  }

  writeUint8Array(src, offset = 0, length) {
    const start = offset | 0;
    const end = Math.min(src.byteLength, start + (length ?? src.byteLength));
    const n = end - start;
    if (n <= 0) return;
    this.ensureBuffer(n);
    this.data.set(src.subarray(start, end), this.position);
    this.position += n;
  }

  writeUTFBytes(str) {
    this.ensureBuffer(6 * str.length);
    const encoder = new TextEncoder();
    const { written } = encoder.encodeInto(str, this.data.subarray(this.position));
    this.position += written;
  }

  getBytes(clone = false) {
    return clone ? this.data.slice(0, this.position) : this.data.subarray(0, this.position);
  }
}

export class BonEncoder {
  constructor() {
    this.dw = new DataWriter();
    this.strMap = new Map();
  }

  reset() {
    this.dw.reset();
    this.strMap.clear();
  }

  encodeInt(v) {
    this.dw.writeInt8(1);
    this.dw.writeInt32(v | 0);
  }

  encodeLong(v) {
    this.dw.writeInt8(2);
    if (typeof v === 'number') {
      this.dw.writeInt64(v);
    } else {
      this.dw.writeInt32(v.low | 0);
      this.dw.writeInt32(v.high | 0);
    }
  }

  encodeFloat(v) {
    this.dw.writeInt8(3);
    this.dw.writeFloat32(v);
  }

  encodeDouble(v) {
    this.dw.writeInt8(4);
    this.dw.writeFloat64(v);
  }

  encodeNumber(v) {
    if ((v | 0) === v) this.encodeInt(v);
    else if (Math.floor(v) === v) this.encodeLong(v);
    else this.encodeDouble(v);
  }

  encodeString(s) {
    const hit = this.strMap.get(s);
    if (hit !== undefined) {
      this.dw.writeInt8(99); // StringRef
      this.dw.write7BitInt(hit);
      return;
    }
    this.dw.writeInt8(5); // String
    this.dw.writeUTF(s);
    this.strMap.set(s, this.strMap.size);
  }

  encodeBoolean(b) {
    this.dw.writeInt8(6);
    this.dw.writeInt8(b ? 1 : 0);
  }

  encodeNull() {
    this.dw.writeInt8(0);
  }

  encodeDateTime(d) {
    this.dw.writeInt8(10);
    this.dw.writeInt64(d.getTime());
  }

  encodeBinary(u8) {
    this.dw.writeInt8(7);
    this.dw.write7BitInt(u8.byteLength);
    this.dw.writeUint8Array(u8);
  }

  encodeArray(arr) {
    this.dw.writeInt8(9);
    this.dw.write7BitInt(arr.length);
    for (let i = 0; i < arr.length; i++) this.encode(arr[i]);
  }

  encodeMap(mp) {
    this.dw.writeInt8(8);
    this.dw.write7BitInt(mp.size);
    mp.forEach((v, k) => {
      this.encode(k);
      this.encode(v);
    });
  }

  encodeObject(obj) {
    this.dw.writeInt8(8);
    const keys = [];
    for (const k in obj) {
      if (!Object.prototype.hasOwnProperty.call(obj, k)) continue;
      if (k.startsWith('_')) continue;
      const type = typeof obj[k];
      if (type === 'function' || type === 'undefined') continue;
      keys.push(k);
    }
    this.dw.write7BitInt(keys.length);
    for (const k of keys) {
      this.encode(k);
      this.encode(obj[k]);
    }
  }

  encode(v) {
    if (v == null) {
      this.encodeNull();
      return;
    }
    switch (v.constructor) {
      case Number:
        this.encodeNumber(v);
        return;
      case Boolean:
        this.encodeBoolean(v);
        return;
      case String:
        this.encodeString(v);
        return;
      case Int64:
        this.encodeLong(v);
        return;
      case Array:
        this.encodeArray(v);
        return;
      case Map:
        this.encodeMap(v);
        return;
      case Date:
        this.encodeDateTime(v);
        return;
      case Uint8Array:
        this.encodeBinary(v);
        return;
      default:
        if (typeof v !== 'object') {
          this.encodeNull();
          return;
        }
        this.encodeObject(v);
        return;
    }
  }

  getBytes(clone = false) {
    return this.dw.getBytes(clone);
  }
}

export class BonDecoder {
  constructor() {
    this.dr = new DataReader(new Uint8Array(0));
    this.strArr = [];
  }

  reset(bytes) {
    this.dr.reset(bytes);
    this.strArr.length = 0;
  }

  decode() {
    const tag = this.dr.readUInt8();
    switch (tag) {
      default:
        return null;
      case 1:
        return this.dr.readInt32();
      case 2:
        return this.dr.readInt64();
      case 3:
        return this.dr.readFloat32();
      case 4:
        return this.dr.readFloat64();
      case 5: {
        const s = this.dr.readUTF();
        this.strArr.push(s);
        return s;
      }
      case 6:
        return this.dr.readUInt8() === 1;
      case 7: {
        const len = this.dr.read7BitInt();
        return this.dr.readUint8Array(len, false);
      }
      case 8: {
        const count = this.dr.read7BitInt();
        const obj = {};
        for (let i = 0; i < count; i++) {
          const k = this.decode();
          const v = this.decode();
          obj[k] = v;
        }
        return obj;
      }
      case 9: {
        const len = this.dr.read7BitInt();
        const arr = new Array(len);
        for (let i = 0; i < len; i++) arr[i] = this.decode();
        return arr;
      }
      case 10:
        return new Date(this.dr.readInt64());
      case 99:
        return this.strArr[this.dr.read7BitInt()];
    }
  }
}

// 单例实例
const _enc = new BonEncoder();
const _dec = new BonDecoder();

// BON 编解码函数
export const bon = {
  encode: (value, clone = true) => {
    _enc.reset();
    _enc.encode(value);
    return _enc.getBytes(clone);
  },
  decode: (bytes) => {
    _dec.reset(bytes);
    return _dec.decode();
  }
};

/** —— 协议消息包装，与原 ProtoMsg 类等价 —— */
export class ProtoMsg {
  constructor(raw) {
    if (raw?.cmd) {
      raw.cmd = raw.cmd.toLowerCase();
    }
    this._raw = raw;
    this._rawData = undefined;
    this._data = undefined;
    this._t = undefined;
    this._sendMsg = undefined;
    this.rtt = 0;
  }

  get sendMsg() { return this._sendMsg; }
  get seq() { return this._raw.seq; }
  get resp() { return this._raw.resp; }
  get ack() { return this._raw.ack; }
  get cmd() { return this._raw?.cmd && this._raw?.cmd.toLowerCase(); }
  get code() { return ~~this._raw.code; }
  get error() { return this._raw.error; }
  get time() { return this._raw.time; }
  get body() { return this._raw.body; }

  /** 惰性 decode body → rawData（bon.decode） */
  get rawData() {
    if (this._rawData !== undefined || this.body === undefined) return this._rawData;
    this._rawData = bon.decode(this.body);
    return this._rawData;
  }

  /** 指定数据类型 */
  setDataType(t) {
    if (t) this._t = { name: t.name ?? 'Anonymous', ctor: t };
    return this;
  }

  /** 配置"请求"对象，让 respType 自动对齐 */
  setSendMsg(msg) {
    this._sendMsg = msg;
    return this.setDataType(msg.respType);
  }

  /** 将 rawData 反序列化为业务对象 */
  getData(clazz) {
    if (this._data !== undefined || this.rawData === undefined) return this._data;

    let t = this._t;
    if (clazz && t && clazz !== t.ctor) {
      console.warn(`getData type not match, ${clazz.name} != ${t.name}`);
      t = { name: clazz.name, ctor: clazz };
    }

    this._data = this.rawData;
    return this._data;
  }

  toLogString() {
    const e = { ...this._raw };
    delete e.body;
    e.data = this.rawData;
    e.rtt = this.rtt;
    return JSON.stringify(e);
  }
}

/** —— 加解密器注册表 —— */
const registry = new Map();

/** lz4 + 头部掩码的 "lx" 方案 */
const lx = {
  encrypt: (buf) => {
    let e = lz4.compress(buf);
    const t = 2 + ~~(Math.random() * 248);
    for (let n = Math.min(e.length, 100); --n >= 0; ) e[n] ^= t;
    
    // 写入标识与混淆位
    e[0] = 112; e[1] = 108;
    e[2] = (e[2] & 0b10101010) | ((t >> 7 & 1) << 6) | ((t >> 6 & 1) << 4) | ((t >> 5 & 1) << 2) | (t >> 4 & 1);
    e[3] = (e[3] & 0b10101010) | ((t >> 3 & 1) << 6) | ((t >> 2 & 1) << 4) | ((t >> 1 & 1) << 2) | (t & 1);
    return e;
  },
  decrypt: (e) => {
    const t =
      ((e[2] >> 6 & 1) << 7) | ((e[2] >> 4 & 1) << 6) | ((e[2] >> 2 & 1) << 5) | ((e[2] & 1) << 4) |
      ((e[3] >> 6 & 1) << 3) | ((e[3] >> 4 & 1) << 2) | ((e[3] >> 2 & 1) << 1) | (e[3] & 1);
    for (let n = Math.min(100, e.length); --n >= 2; ) e[n] ^= t;
    e[0] = 4; e[1] = 34; e[2] = 77; e[3] = 24; // 还原头以便 lz4 解
    return lz4.decompress(e);
  }
};

/** 随机首 4 字节 + XOR 的 "x" 方案 */
const x = {
  encrypt: (e) => {
    const rnd = ~~(Math.random() * 0xFFFFFFFF) >>> 0;
    const n = new Uint8Array(e.length + 4);
    n[0] = rnd & 0xFF; n[1] = (rnd >>> 8) & 0xFF; n[2] = (rnd >>> 16) & 0xFF; n[3] = (rnd >>> 24) & 0xFF;
    n.set(e, 4);
    const r = 2 + ~~(Math.random() * 248);
    for (let i = n.length; --i >= 0; ) n[i] ^= r;
    n[0] = 112; n[1] = 120;
    n[2] = (n[2] & 0b10101010) | ((r >> 7 & 1) << 6) | ((r >> 6 & 1) << 4) | ((r >> 5 & 1) << 2) | (r >> 4 & 1);
    n[3] = (n[3] & 0b10101010) | ((r >> 3 & 1) << 6) | ((r >> 2 & 1) << 4) | ((r >> 1 & 1) << 2) | (r & 1);
    return n;
  },
  decrypt: (e) => {
    const t =
      ((e[2] >> 6 & 1) << 7) | ((e[2] >> 4 & 1) << 6) | ((e[2] >> 2 & 1) << 5) | ((e[2] & 1) << 4) |
      ((e[3] >> 6 & 1) << 3) | ((e[3] >> 4 & 1) << 2) | ((e[3] >> 2 & 1) << 1) | (e[3] & 1);
    for (let n = e.length; --n >= 4; ) e[n] ^= t;
    return e.subarray(4);
  }
};

/** 依赖 globalThis.XXTEA 的 "xtm" 方案 */
const xtm = {
  encrypt: (e) => globalThis.XXTEA ? globalThis.XXTEA.encryptMod({ data: e.buffer, length: e.length }) : e,
  decrypt: (e) => globalThis.XXTEA ? globalThis.XXTEA.decryptMod({ data: e.buffer, length: e.length }) : e,
};

/** 注册器 */
function register(name, impl) {
  registry.set(name, impl);
}

register('lx', lx);
register('x', x);
register('xtm', xtm);

/** 默认使用 x 加密（自动检测解密） */
const passthrough = {
  encrypt: (e) => getEnc('x').encrypt(e),
  decrypt: (e) => {
    if (e.length > 4 && e[0] === 112 && e[1] === 108)        e = getEnc('lx').decrypt(e);
    else if (e.length > 4 && e[0] === 112 && e[1] === 120)   e = getEnc('x').decrypt(e);
    else if (e.length > 3 && e[0] === 112 && e[1] === 116)   e = getEnc('xtm').decrypt(e);
    return e;
  }
};

/** 对外：按名称取加解密器；找不到则用默认 */
export function getEnc(name) {
  return registry.get(name) ?? passthrough;
}

/** 对外：encode（bon.encode → 加密） */
export function encode(obj, enc) {
  let bytes = bon.encode(obj, false);
  const out = enc.encrypt(bytes);
  return out.buffer.byteLength === out.length ? out.buffer : out.buffer.slice(0, out.length);
}

/** 对外：parse（解密 → bon.decode → ProtoMsg） */
export function parse(buf, enc) {
  const u8 = new Uint8Array(buf);
  const plain = enc.decrypt(u8);
  const raw = bon.decode(plain);
  return new ProtoMsg(raw);
}

// 游戏消息模板
export const GameMessages = {
  // 心跳消息
  heartBeat: (ack = 0, seq = 0) => ({
    ack,
    body: undefined,
    c: undefined,
    cmd: "_sys/ack",
    hint: undefined,
    seq,
    time: Date.now()
  }),

  // 获取角色信息
  getRoleInfo: (ack = 0, seq = 0, params = {}) => ({
    cmd: "role_getroleinfo",
    body: encode({
      clientVersion: "1.65.3-wx",
      inviteUid: 0,
      platform: "hortor",
      platformExt: "mix",
      scene: "",
      ...params
    }, getEnc('x')),
    ack: ack || 0,
    seq: seq || 0,
    time: Date.now()
  }),

  // 获取数据包版本
  getDataBundleVer: (ack = 0, seq = 0, params = {}) => ({
    cmd: "system_getdatabundlever",
    body: encode({
      isAudit: false,
      ...params
    }, getEnc('x')),
    ack: ack || 0,
    seq: seq || 0,
    time: Date.now()
  }),

  // 购买金币
  buyGold: (ack = 0, seq = 0, params = {}) => ({
    ack,
    body: encode({
      buyNum: 1,
      ...params
    }, getEnc('x')),
    cmd: "system_buygold",
    seq,
    time: Date.now()
  }),

  // 签到奖励
  signInReward: (ack = 0, seq = 0, params = {}) => ({
    ack,
    body: encode({
      ...params
    }, getEnc('x')),
    cmd: "system_signinreward",
    seq,
    time: Date.now()
  }),

  // 领取每日任务奖励
  claimDailyReward: (ack = 0, seq = 0, params = {}) => ({
    ack,
    body: encode({
      rewardId: 0,
      ...params
    }, getEnc('x')),
    cmd: "task_claimdailyreward",
    seq,
    time: Date.now()
  })
};

// 创建全局实例
export const g_utils = {
  getEnc,
  encode: (obj, encName = 'x') => encode(obj, getEnc(encName)),
  parse: (data, encName = 'auto') => parse(data, getEnc(encName)),
  bon // 添加BON编解码器
};

// 兼容性导出（保持旧的接口）
export const bonProtocol = {
  encode: bon.encode,
  decode: bon.decode,
  createMessage: (cmd, body = {}, ack = 0, seq = 0, options = {}) => ({
    cmd,
    body: bon.encode(body),
    ack: ack || 0,
    seq: seq || 0,
    time: Date.now(),
    ...options
  }),
  parseMessage: (messageData) => {
    try {
      let message;
      if (typeof messageData === 'string') {
        message = JSON.parse(messageData);
      } else {
        message = messageData;
      }
      if (message.body && (message.body instanceof ArrayBuffer || message.body instanceof Uint8Array)) {
        message.body = bon.decode(message.body);
      }
      return message;
    } catch (error) {
      console.error('消息解析失败:', error);
      return {
        error: true,
        message: '消息解析失败',
        originalData: messageData
      };
    }
  },
  generateSeq: () => Math.floor(Math.random() * 1000000),
  generateMessageId: () => 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
};

// 导出单独的加密器类以兼容测试文件
export const LXCrypto = lx;
export const XCrypto = x;
export const XTMCrypto = xtm;

export default { ProtoMsg, getEnc, encode, parse, GameMessages, g_utils, bon, bonProtocol };