'use strict';

const { encodeVarInt, parseVarInt, readString, encodeString } = require('./packets');

const EAGLER_PKT_CLIENT_VERSION = 0x01;
const EAGLER_PKT_SERVER_VERSION = 0x02;
const EAGLER_PKT_CLIENT_REQUEST_LOGIN = 0x04;
const EAGLER_PKT_SERVER_ALLOW_LOGIN = 0x05;
const EAGLER_PKT_CLIENT_PROFILE_DATA = 0x07;
const EAGLER_PKT_CLIENT_FINISH_LOGIN = 0x08;
const EAGLER_PKT_SERVER_FINISH_LOGIN = 0x09;

const PACKET_KEEPALIVE = 0x1F;
const PACKET_KEEPALIVE_REPLY = 0x0B;
const PACKET_JOIN_GAME = 0x23;
const PACKET_CHAT = 0x0F;
const PACKET_POSITION_LOOK = 0x2F;
const PACKET_CHUNK_DATA = 0x20;
const PACKET_PLAYER_ABILITIES = 0x2C;
const PACKET_DISCONNECT = 0x1A;
const PACKET_PLUGIN_MESSAGE = 0x09;
const PACKET_SET_COMPRESSION = 0x03;

function buildEaglerVersion(brand, version, mcProtocol, username) {
  const usernameBuf = Buffer.from(username, 'ascii');
  const buf = Buffer.alloc(512);
  let o = 0;
  buf[o++] = 0x01;
  buf[o++] = 0x02;
  buf.writeUInt16BE(3, o); o += 2;
  buf.writeUInt16BE(2, o); o += 2;
  buf.writeUInt16BE(3, o); o += 2;
  buf.writeUInt16BE(4, o); o += 2;
  buf.writeUInt16BE(1, o); o += 2;
  buf.writeUInt16BE(mcProtocol, o); o += 2;
  buf[o++] = brand.length;
  o += buf.write(brand, o, 'ascii');
  buf[o++] = version.length;
  o += buf.write(version, o, 'ascii');
  buf[o++] = 0x00;
  buf[o++] = usernameBuf.length & 0xFF;
  usernameBuf.copy(buf, o); o += usernameBuf.length;
  return buf.slice(0, o);
}

function buildEaglerLoginRequest(username, serverName, password) {
  const usernameBuf = Buffer.from(username, 'ascii');
  const serverBuf = Buffer.from(serverName, 'ascii');
  const passBuf = Buffer.from(password, 'ascii');
  const buf = Buffer.alloc(512);
  let o = 0;
  buf[o++] = EAGLER_PKT_CLIENT_REQUEST_LOGIN;
  buf[o++] = usernameBuf.length & 0xFF;
  usernameBuf.copy(buf, o); o += usernameBuf.length;
  buf[o++] = serverBuf.length & 0xFF;
  serverBuf.copy(buf, o); o += serverBuf.length;
  buf[o++] = passBuf.length & 0xFF;
  passBuf.copy(buf, o); o += passBuf.length;
  buf[o++] = 0x01;
  buf[o++] = 0x00;
  return buf.slice(0, o);
}

function buildEaglerProfileData(entries = ['skin_v2', 'brand_uuid_v1', 'cape_v1']) {
  const parts = [Buffer.from([EAGLER_PKT_CLIENT_PROFILE_DATA, entries.length & 0xFF])];
  for (const name of entries) {
    const nameBuf = Buffer.from(name, 'ascii');
    parts.push(Buffer.from([nameBuf.length & 0xFF]));
    parts.push(nameBuf);
    parts.push(Buffer.from([0x00, 0x00]));
  }
  return Buffer.concat(parts);
}

function buildEaglerFinishLogin() {
  return Buffer.from([EAGLER_PKT_CLIENT_FINISH_LOGIN]);
}

function buildClientSettings(locale = 'en_US', viewDist = 8, chatMode = 0, chatColors = true, skinParts = 0x7F, mainHand = 1) {
  return Buffer.concat([
    encodeString(locale),
    Buffer.from([viewDist & 0xFF]),
    encodeVarInt(chatMode),
    Buffer.from([chatColors ? 1 : 0]),
    Buffer.from([skinParts & 0xFF]),
    encodeVarInt(mainHand),
  ]);
}

function buildBrandPluginMessage(brand = 'vanilla') {
  const brandStr = Buffer.from(brand, 'utf8');
  return Buffer.concat([
    encodeString('minecraft:brand'),
    encodeVarInt(brandStr.length),
    brandStr,
  ]);
}

function buildPositionAndLook(x, y, z, yaw, pitch, onGround) {
  const buf = Buffer.alloc(33);
  buf.writeDoubleLE(x, 0);
  buf.writeDoubleLE(y, 8);
  buf.writeDoubleLE(z, 16);
  buf.writeFloatLE(yaw, 24);
  buf.writeFloatLE(pitch, 28);
  buf[32] = onGround ? 1 : 0;
  return buf;
}

function buildTeleportConfirm(teleportId) {
  return encodeVarInt(teleportId);
}

function buildKeepAliveReply(payload) {
  return payload;
}

function buildKeepAliveReplyLong(longBuf) {
  return longBuf;
}

function buildKeepAliveReplyVarInt(id) {
  return encodeVarInt(id);
}

function buildChat(message) {
  return encodeString(message);
}

function parsePositionAndLook(data) {
  if (data.length < 33) return null;
  const x = data.readDoubleLE(0);
  const y = data.readDoubleLE(8);
  const z = data.readDoubleLE(16);
  const yaw = data.readFloatLE(24);
  const pitch = data.readFloatLE(28);
  const flags = data[32];
  const bitmask = flags & 0xFF;
  let off = 33;
  const hasX = (bitmask & 0x01) !== 0;
  const hasY = (bitmask & 0x02) !== 0;
  const hasZ = (bitmask & 0x04) !== 0;
  const hasYaw = (bitmask & 0x08) !== 0;
  const hasPitch = (bitmask & 0x10) !== 0;
  let teleportId = 0;
  if (hasX && hasY && hasZ && hasYaw && hasPitch) {
    if (data.length < off + 1) return null;
    off += 1;
  }
  if (data.length >= off) {
    const { value, bytesRead } = parseVarInt(data, off);
    if (bytesRead > 0) teleportId = value;
  }
  return { x, y, z, yaw, pitch, flags: bitmask, teleportId };
}

function parseJoinGame(data) {
  const off = 0;
  const { value: entityId, bytesRead: eBytes } = parseVarInt(data, off);
  if (eBytes === 0) return null;
  let p = off + eBytes;
  if (data.length < p + 1) return null;
  const gamemode = data.readUInt8(p); p += 1;
  if (data.length < p + 4) return null;
  const dimension = data.readInt32BE(p); p += 4;
  if (data.length < p + 1) return null;
  const difficulty = data.readUInt8(p); p += 1;
  if (data.length < p + 1) return null;
  const maxPlayers = data.readUInt8(p); p += 1;
  if (data.length < p + 2) return null;
  const levelType = readString(data, p).value;
  return { entityId, gamemode, dimension, difficulty, maxPlayers, levelType };
}

function extractText(obj) {
  if (typeof obj === 'string') return obj;
  if (!obj || typeof obj !== 'object') return '';
  let t = '';
  if (typeof obj.text === 'string') t += obj.text;
  if (typeof obj.translate === 'string') t += obj.translate;
  if (Array.isArray(obj.extra)) {
    for (const p of obj.extra) t += extractText(p);
  }
  if (Array.isArray(obj.with)) {
    for (const p of obj.with) t += extractText(p);
  }
  return t;
}

function parseChat(data) {
  const { value: jsonStr, bytesRead } = readString(data, 0);
  if (bytesRead === 0) return null;
  let text = '';
  let translate = null;
  try {
    const obj = JSON.parse(jsonStr);
    text = extractText(obj);
    if (obj && typeof obj.translate === 'string') translate = obj.translate;
  } catch (_) {
    text = jsonStr;
  }
  return { text: text.replace(/§[0-9a-fk-or]/gi, '').trim(), translate };
}

function parsePluginMessage(data) {
  const { value: channel, bytesRead: chBytes } = readString(data, 0);
  if (chBytes === 0) return null;
  const rest = data.slice(chBytes);
  let body = '';
  if (rest.length > 0) {
    const { value, bytesRead } = readString(rest, 0);
    if (bytesRead > 0) body = value;
  }
  return { channel, body };
}

function handleEaglerPacket(buf, bot) {
  if (buf.length < 1) return;
  const pktId = buf[0];
  const data = buf.slice(1);
  switch (pktId) {
    case EAGLER_PKT_SERVER_VERSION: {
      if (data.length < 4) return;
      const eaglerProto = data.readUInt16BE(0);
      const mcProto = data.readUInt16BE(2);
      bot.log('info', `[${bot.id}] Eagler proto=${eaglerProto}, MC proto=${mcProto}`);
      bot.onEaglerServerVersion(eaglerProto, mcProto);
      return;
    }
    case EAGLER_PKT_SERVER_ALLOW_LOGIN:
      bot.log('info', `[${bot.id}] Server allowed login`);
      bot.onEaglerAllowLogin();
      return;
    case EAGLER_PKT_SERVER_FINISH_LOGIN:
      bot.onEaglerFinishLogin();
      return;
    case 0x06: {
      const reason = data.length > 1 ? data.slice(1).toString('utf8') : 'Denied';
      bot.fail(`Login denied: ${reason}`);
      return;
    }
    case 0xFF: {
      const reason = data.length > 1 ? data.slice(1).toString('utf8') : 'Error';
      bot.fail(`Server error: ${reason}`);
      return;
    }
  }
}

function handleMCPacket(packetId, data, bot) {
  if (packetId === PACKET_SET_COMPRESSION) {
    const { value, bytesRead } = parseVarInt(data, 0);
    if (bytesRead > 0) {
      bot.mcStream.setThreshold(value);
      bot.log('info', `[${bot.id}] Compression threshold set to ${value}`);
    }
    return;
  }
  if (packetId === PACKET_KEEPALIVE) {
    let reply;
    if (data.length === 8) {
      reply = buildKeepAliveReplyLong(data);
    } else {
      const { value, bytesRead } = parseVarInt(data, 0);
      reply = bytesRead > 0 ? buildKeepAliveReplyVarInt(value) : buildKeepAliveReply(data);
    }
    bot.onKeepAlive(reply);
    return;
  }
  if (packetId === PACKET_JOIN_GAME) {
    const info = parseJoinGame(data);
    bot.log('info', `[${bot.id}] Joined game (entity=${info?.entityId}, dim=${info?.dimension})`);
    bot.onJoinGame(info);
    return;
  }
  if (packetId === PACKET_CHAT) {
    const parsed = parseChat(data);
    if (parsed) bot.onChat(parsed);
    return;
  }
  if (packetId === PACKET_POSITION_LOOK) {
    const info = parsePositionAndLook(data);
    if (info) bot.onPositionLook(info);
    return;
  }
  if (packetId === PACKET_CHUNK_DATA) {
    bot.onChunkData();
    return;
  }
  if (packetId === PACKET_PLAYER_ABILITIES) {
    bot.onPlayerAbilities();
    return;
  }
  if (packetId === PACKET_PLUGIN_MESSAGE) {
    const pm = parsePluginMessage(data);
    if (pm) bot.onPluginMessage(pm);
    return;
  }
  if (packetId === PACKET_DISCONNECT) {
    const { value } = readString(data, 0);
    bot.log('warn', `[${bot.id}] Kicked: ${value || 'Unknown'}`);
    bot.stop('kicked');
    return;
  }
}

module.exports = {
  buildEaglerVersion,
  buildEaglerLoginRequest,
  buildEaglerProfileData,
  buildEaglerFinishLogin,
  buildClientSettings,
  buildBrandPluginMessage,
  buildPositionAndLook,
  buildTeleportConfirm,
  buildKeepAliveReply,
  buildKeepAliveReplyLong,
  buildKeepAliveReplyVarInt,
  buildChat,
  parsePositionAndLook,
  parseJoinGame,
  parseChat,
  parsePluginMessage,
  extractText,
  handleEaglerPacket,
  handleMCPacket,
  EAGLER_PKT_CLIENT_VERSION,
  EAGLER_PKT_SERVER_VERSION,
  EAGLER_PKT_CLIENT_REQUEST_LOGIN,
  EAGLER_PKT_SERVER_ALLOW_LOGIN,
  EAGLER_PKT_CLIENT_PROFILE_DATA,
  EAGLER_PKT_CLIENT_FINISH_LOGIN,
  EAGLER_PKT_SERVER_FINISH_LOGIN,
  PACKET_KEEPALIVE,
  PACKET_KEEPALIVE_REPLY,
  PACKET_JOIN_GAME,
  PACKET_CHAT,
  PACKET_POSITION_LOOK,
  PACKET_CHUNK_DATA,
  PACKET_PLAYER_ABILITIES,
  PACKET_DISCONNECT,
  PACKET_PLUGIN_MESSAGE,
  PACKET_SET_COMPRESSION,
};
