import protobuf from "protobufjs/minimal.js";

const { Reader, Writer } = protobuf;

// 比赛服 protobuf 协议适配层：
// 小程序环境不支持 protobufjs 反射模式里的动态 Function codegen，
// 所以这里使用 minimal Reader/Writer 做静态字段解码和客户端消息编码。
// <match-schema-generated>
// 此区块由 scripts/generate-match-schema.mjs 自动生成，请勿手动修改。
// 来源：src/utils/match.min.js（sha256: b4f272aad40fb951）
// 协议命名空间：rpc；消息数：12；字段数：137

export const ServerMessageType = {
  SERVER_MSG_UNKNOWN: 0,
  SERVER_MSG_MATCH_READY: 1,
  SERVER_MSG_MATCH_START: 2,
  SERVER_MSG_NOW_YOU: 3,
  SERVER_MSG_SHOT: 4,
  SERVER_MSG_NEW_ROUND: 5,
  SERVER_MSG_MATCH_END: 6,
  SERVER_MSG_TIMEOUT: 7,
  SERVER_MSG_CHECK: 8,
  SERVER_MSG_RAND: 9,
  SERVER_MSG_NOT_ENOUGH_DISTANCE: 10,
  SERVER_MSG_PLAYER_LEFT: 11,
  SERVER_MSG_HEARTBEAT: 12,
  SERVER_MSG_PRACTICE_END: 13,
  SERVER_MSG_SYNC_PRACTICE_INFO: 14,
  SERVER_MSG_SYNC_MATCH_INFO: 15,
};

export const ClientMessageType = {
  CLIENT_MSG_UNKNOWN: 0,
  CLIENT_MSG_HEARTBEAT_ACK: 1,
  CLIENT_MSG_SHOOT_DATA: 2,
  CLIENT_MSG_ACK: 3,
  CLIENT_MSG_LEAVE: 4,
  CLIENT_MSG_SYNC_PRACTICE_INFO: 5,
  CLIENT_MSG_SYNC_MATCH_INFO: 6,
};

const SCHEMAS = {
  MatchShoot: {
    1: { name: "player_id", kind: "int64" },
    2: { name: "status", kind: "int32" },
    3: { name: "x", kind: "float" },
    4: { name: "y", kind: "float" },
    5: { name: "ring", kind: "int32" },
    6: { name: "ring_x", kind: "bool" },
    7: { name: "angle", kind: "float" },
    8: { name: "distance", kind: "float" },
    9: { name: "three_consecutive_10_rings", kind: "bool" },
  },
  MatchShootList: {
    1: { name: "items", kind: "message", type: "MatchShoot", repeated: true },
  },
  RoundScore: {
    1: { name: "total_ring", kind: "int32" },
    2: { name: "score", kind: "int32" },
    3: { name: "if_win", kind: "bool" },
  },
  MatchRound: {
    1: {
      name: "shoots",
      kind: "map",
      keyKind: "int64",
      valueKind: "message",
      valueType: "MatchShootList",
    },
    2: {
      name: "scores",
      kind: "map",
      keyKind: "int32",
      valueKind: "message",
      valueType: "RoundScore",
    },
    3: { name: "round", kind: "int32" },
    4: { name: "if_gold", kind: "bool" },
    5: { name: "status", kind: "int32" },
    6: { name: "gold_round", kind: "int32" },
  },
  PlayerMatchResult: {
    1: { name: "total_ring", kind: "int32" },
    2: { name: "user_id", kind: "int64" },
    3: { name: "ten_ring_count", kind: "int32" },
    4: { name: "average_ring", kind: "float" },
  },
  PlayerFull: {
    1: { name: "id", kind: "int64" },
    2: { name: "name", kind: "string" },
    3: { name: "avatar", kind: "string" },
    4: { name: "exp", kind: "int32" },
    5: { name: "score", kind: "int32" },
    6: { name: "level", kind: "int32" },
    7: { name: "before_level", kind: "int32" },
    8: { name: "before_exp", kind: "int32" },
    9: { name: "current_exp", kind: "int32" },
    10: { name: "upgrade_exp", kind: "int32" },
    11: { name: "active", kind: "int32" },
    12: { name: "device_id", kind: "string" },
    13: { name: "s_vip", kind: "bool" },
    14: { name: "vip", kind: "bool" },
    15: { name: "player_match_result", kind: "message", type: "PlayerMatchResult" },
  },
  TeamInfo: {
    1: { name: "players", kind: "message", type: "PlayerFull", repeated: true },
    2: { name: "id", kind: "int32" },
    3: { name: "name", kind: "string" },
    4: { name: "score", kind: "int32" },
  },
  CurrentShoot: {
    1: { name: "round", kind: "int32" },
    2: { name: "round_id", kind: "int64" },
    3: { name: "index", kind: "int32" },
    4: { name: "start_time", kind: "int64" },
    5: { name: "player_id", kind: "int64" },
    6: { name: "gold_round", kind: "bool" },
    7: { name: "start_time_text", kind: "string" },
    8: { name: "my_index", kind: "int32" },
    9: {
      name: "index_map",
      kind: "map",
      keyKind: "int64",
      valueKind: "int32",
    },
    10: { name: "ack_time", kind: "int64" },
  },
  ShootData: {
    1: { name: "x", kind: "float" },
    2: { name: "y", kind: "float" },
    3: { name: "r", kind: "float" },
    4: { name: "dst", kind: "float" },
    5: { name: "m", kind: "string" },
    6: { name: "adc", kind: "float" },
    7: { name: "device_id", kind: "string" },
    8: { name: "shoot_id", kind: "string" },
    9: { name: "three_consecutive_10_rings", kind: "bool" },
  },
  PracticeInfo: {
    1: { name: "id", kind: "int64" },
    2: { name: "user_id", kind: "int64" },
    3: { name: "status", kind: "int32" },
    4: { name: "status_text", kind: "string" },
    5: { name: "start_time", kind: "int64" },
    6: { name: "target_type", kind: "int32" },
    7: { name: "vip", kind: "bool" },
    8: { name: "s_vip", kind: "bool" },
    9: { name: "device_id", kind: "string" },
    10: { name: "shoot_data", kind: "message", type: "MatchShoot" },
    11: { name: "details", kind: "message", type: "MatchShoot", repeated: true },
    12: { name: "training_type", kind: "string" },
    13: { name: "difficulty_level", kind: "int32" },
    14: { name: "hit_req", kind: "int32" },
    15: { name: "arrows_left", kind: "int32" },
    16: { name: "target_arrows", kind: "int32" },
    17: { name: "target_rings", kind: "int32" },
    18: { name: "current_arrows", kind: "int32" },
    19: { name: "current_rings", kind: "int32" },
    20: { name: "blocks", kind: "int32" },
    21: { name: "random_block", kind: "int32" },
    22: { name: "random_ring_area", kind: "int32" },
    23: { name: "time_limit", kind: "int32" },
    24: { name: "completed", kind: "bool" },
    25: { name: "total_arrows", kind: "int32" },
    26: { name: "duration", kind: "int32" },
    27: { name: "average_ring", kind: "float" },
    28: { name: "stability", kind: "float" },
    29: { name: "max_combo", kind: "int32" },
    30: { name: "total_hits", kind: "int32" },
    31: { name: "delta_total_hits", kind: "int32" },
    32: { name: "delta_duration", kind: "int32" },
    33: { name: "delta_max_combo", kind: "int32" },
    34: { name: "delta_total_rings", kind: "int32" },
    35: { name: "delta_total_arrows", kind: "int32" },
    36: { name: "delta_average_ring", kind: "float" },
    37: { name: "delta_stability", kind: "float" },
    38: { name: "before_exp", kind: "int32" },
    39: { name: "before_level", kind: "int32" },
    40: { name: "current_exp", kind: "int32" },
    41: { name: "level", kind: "int32" },
    42: { name: "upgrade_exp", kind: "int32" },
    43: { name: "calories", kind: "double" },
  },
  MatchInfo: {
    1: { name: "match_id", kind: "string" },
    2: { name: "create_time", kind: "int64" },
    3: { name: "start_time", kind: "int64" },
    4: { name: "server_time", kind: "int64" },
    5: { name: "shoot_time", kind: "int32" },
    6: { name: "shoot_number", kind: "int32" },
    7: { name: "ready_time", kind: "int32" },
    8: { name: "way", kind: "int32" },
    9: { name: "mode", kind: "int32" },
    10: { name: "status", kind: "int32" },
    11: { name: "status_text", kind: "string" },
    12: { name: "rounds", kind: "message", type: "MatchRound", repeated: true },
    13: {
      name: "teams",
      kind: "map",
      keyKind: "int32",
      valueKind: "message",
      valueType: "TeamInfo",
    },
    14: { name: "current", kind: "message", type: "CurrentShoot" },
    15: { name: "next", kind: "message", type: "CurrentShoot" },
    16: { name: "shoot_data", kind: "message", type: "MatchShoot" },
    17: { name: "win_team", kind: "int32" },
    18: { name: "mvp", kind: "message", type: "PlayerFull" },
    19: { name: "room_id", kind: "string" },
    20: { name: "result_list", kind: "message", type: "PlayerMatchResult", repeated: true },
    21: { name: "timeout_time", kind: "int64" },
    22: { name: "target_type", kind: "int32" },
    23: { name: "event_type", kind: "int32" },
    24: { name: "timeout", kind: "int32" },
    25: { name: "server_addr", kind: "string" },
    26: { name: "countdown_start_time", kind: "int64" },
  },
  ServerMessage: {
    1: { name: "type", kind: "int32" },
    2: { name: "match_id", kind: "string" },
    3: { name: "timestamp", kind: "int64" },
    4: { name: "match_info", kind: "message", type: "MatchInfo", oneof: "payload" },
    5: { name: "shoot_data", kind: "message", type: "ShootData", oneof: "payload" },
    6: { name: "practice_info", kind: "message", type: "PracticeInfo", oneof: "payload" },
    7: { name: "sequence", kind: "int64" },
  },
};
// </match-schema-generated>

// protobufjs 的 enum 默认是 name -> value，这里反转成 value -> name 用于日志打印。
const ServerMessageTypeNameByValue = Object.keys(ServerMessageType).reduce(
  (result, name) => {
    result[ServerMessageType[name]] = name;
    return result;
  },
  {}
);

// 小程序 websocket 收到的 data 可能是 ArrayBuffer、TypedArray 或字符串，这里统一成 Uint8Array。
function toUint8Array(data) {
  if (data instanceof Uint8Array) return data;
  if (data instanceof ArrayBuffer) return new Uint8Array(data);
  if (ArrayBuffer.isView(data)) {
    return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
  }
  if (typeof data === "string") {
    const bytes = new Uint8Array(data.length);
    for (let i = 0; i < data.length; i += 1) {
      bytes[i] = data.charCodeAt(i) & 0xff;
    }
    return bytes;
  }
  throw new TypeError("Unsupported match websocket message payload");
}

// uni.sendSocketMessage 发送二进制时使用 ArrayBuffer。
function toArrayBuffer(bytes) {
  const output = new Uint8Array(bytes.length);
  output.set(bytes);
  return output.buffer;
}

function readScalar(reader, kind) {
  switch (kind) {
    case "int32":
      return reader.int32();
    case "int64":
      // 统一把 int64 转成字符串，避免小程序环境下大整数精度丢失。
      return reader.int64().toString();
    case "float":
      return reader.float();
    case "double":
      return reader.double();
    case "bool":
      return reader.bool();
    case "string":
      return reader.string();
    case "bytes":
      return Array.from(reader.bytes());
    default:
      throw new Error(`Unsupported protobuf scalar kind: ${kind}`);
  }
}

function readMapEntry(reader, field) {
  const end = reader.uint32() + reader.pos;
  let key = "";
  let value;

  while (reader.pos < end) {
    const tag = reader.uint32();
    switch (tag >>> 3) {
      case 1:
        key = readScalar(reader, field.keyKind);
        break;
      case 2:
        value =
          field.valueKind === "message"
            ? decodeMessage(field.valueType, reader, reader.uint32())
            : readScalar(reader, field.valueKind);
        break;
      default:
        reader.skipType(tag & 7);
        break;
    }
  }

  return { key: String(key), value };
}

function readValue(reader, field) {
  if (field.kind === "message") {
    return decodeMessage(field.type, reader, reader.uint32());
  }
  return readScalar(reader, field.kind);
}

function decodeMessage(schemaName, readerOrData, length) {
  const schema = SCHEMAS[schemaName];
  const reader =
    readerOrData instanceof Reader ? readerOrData : Reader.create(readerOrData);
  const end = length === undefined ? reader.len : reader.pos + length;
  const message = {};

  while (reader.pos < end) {
    const tag = reader.uint32();
    const field = schema[tag >>> 3];

    if (!field) {
      reader.skipType(tag & 7);
      continue;
    }

    if (field.kind === "map") {
      const entry = readMapEntry(reader, field);
      const map = message[field.name] || {};
      map[entry.key] = entry.value;
      message[field.name] = map;
      continue;
    }

    const value = readValue(reader, field);

    if (field.repeated) {
      if (!message[field.name]) message[field.name] = [];
      message[field.name].push(value);
    } else {
      message[field.name] = value;
    }

    if (field.oneof) {
      message[field.oneof] = field.name;
    }
  }

  return message;
}

export function decodeServerMessage(data) {
  // 后端已确认比赛服 websocket 下发的是原始 protobuf frame，直接解 ServerMessage。
  return decodeMessage("ServerMessage", toUint8Array(data));
}

export function encodeClientMessage(payload) {
  // ClientMessage 字段名按 proto 定义写入，例如 match_id、user_id。
  const writer = Writer.create();
  const matchId = payload.match_id ?? payload.matchId;
  const userId = payload.user_id ?? payload.userId;

  if (payload.type !== undefined) writer.uint32(8).int32(payload.type);
  if (matchId !== undefined && matchId !== null) {
    writer.uint32(18).string(String(matchId));
  }
  if (userId !== undefined && userId !== null) writer.uint32(24).int64(userId);
  if (payload.sequence !== undefined && payload.sequence !== null) {
    writer.uint32(32).int64(payload.sequence);
  }
  if (payload.data !== undefined && payload.data !== null) {
    writer.uint32(42).bytes(toUint8Array(payload.data));
  }

  return toArrayBuffer(writer.finish());
}

export function getServerMessageTypeName(type) {
  return ServerMessageTypeNameByValue[type] || String(type);
}

export function createHeartbeatAckMessage() {
  // 心跳 ACK 按文档立即回，不等待语音播报。
  return encodeClientMessage({
    type: ClientMessageType.CLIENT_MSG_HEARTBEAT_ACK,
  });
}

export function createSyncPracticeInfoMessage({ matchId, userId }) {
  // 新版个人训练页连接成功后主动请求完整练习信息。
  return encodeClientMessage({
    type: ClientMessageType.CLIENT_MSG_SYNC_PRACTICE_INFO,
    match_id: matchId,
    user_id: userId,
  });
}

export function createAckMessage({ matchId, sequence }) {
  // 普通消息 ACK 仍携带 sequence，但前端不做 sequence 补发或排序。
  return encodeClientMessage({
    type: ClientMessageType.CLIENT_MSG_ACK,
    match_id: matchId,
    sequence,
  });
}

export function createLeaveMessage({ matchId, userId }) {
  // 比赛结束或页面离开时通知比赛服释放当前玩家连接。
  return encodeClientMessage({
    type: ClientMessageType.CLIENT_MSG_LEAVE,
    match_id: matchId,
    user_id: userId,
  });
}
