/**
 * rl_bot.js — Minimal mineflayer bot for RL training.
 *
 * Communicates via stdin (actions) / stdout (observations) as JSON.
 * Designed to be spawned by minecraft_env.py as a subprocess.
 *
 * Usage: node rl_bot.js [host] [port] [username]
 */

const mineflayer = require('mineflayer');
const pvp = require('mineflayer-pvp').plugin;
const readline = require('readline');

const host = process.argv[2] || '192.168.0.244';
const port = parseInt(process.argv[3] || '25568', 10);
const username = process.argv[4] || 'RLBot';

const HOSTILE = new Set([
  'zombie', 'husk', 'skeleton', 'creeper', 'spider', 'cave_spider',
  'witch', 'enderman', 'drowned', 'stray', 'phantom', 'parched',
  'camel_husk', 'slime', 'magma_cube',
]);

const bot = mineflayer.createBot({ host, port, username, auth: 'offline', version: '1.21.11' });
bot.loadPlugin(pvp);

let alive = false;
let kills = 0;
let deaths = 0;
let died = false;

function out(obj) {
  try { process.stdout.write(JSON.stringify(obj) + '\n'); } catch (e) {}
}

function observe() {
  if (!bot.entity) return { hp: 0, died: true };
  const pos = bot.entity.position;
  const mobs = [];

  for (const e of Object.values(bot.entities)) {
    if (e === bot.entity) continue;
    const d = Math.round(e.position.distanceTo(pos));
    if (d > 24) continue;
    const name = (e.name || e.type || '').toLowerCase();
    const hostile = HOSTILE.has(name);
    if (e.type === 'player') continue; // ignore players
    if (hostile || d < 8) {
      mobs.push({ type: name, dist: d, hostile });
    }
  }

  return {
    pos: { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) },
    hp: Math.round(bot.health * 10) / 10,
    food: bot.food,
    day: (bot.time?.timeOfDay || 0) < 13000,
    below: bot.blockAt(pos.offset(0, -1, 0))?.name || '?',
    inv: bot.inventory.items().map(i => `${i.name}x${i.count}`).join(', ') || 'empty',
    armor: [5, 6, 7, 8].map(s => bot.inventory.slots[s]?.name).filter(Boolean).join(', ') || 'none',
    mobs: mobs.sort((a, b) => a.dist - b.dist).slice(0, 8),
    kills,
    deaths,
    died,
  };
}

function nearestHostile(maxDist) {
  if (!bot.entity) return null;
  let nearest = null;
  let nd = maxDist || 16;
  for (const e of Object.values(bot.entities)) {
    if (e === bot.entity || e.type === 'player') continue;
    if (!HOSTILE.has((e.name || '').toLowerCase())) continue;
    const d = e.position.distanceTo(bot.entity.position);
    if (d < nd) { nd = d; nearest = e; }
  }
  return nearest;
}

function stopMoving() {
  ['forward', 'back', 'left', 'right', 'sprint', 'jump'].forEach(s => bot.setControlState(s, false));
}

// ── Events ──
bot.once('spawn', () => {
  alive = true;
  died = false;
  out({ event: 'ready', ...observe() });
});

bot.on('spawn', () => { alive = true; died = false; });
bot.on('death', () => {
  alive = false;
  died = true;
  deaths++;
  out({ event: 'death', ...observe() });
  setTimeout(() => { try { bot.respawn(); } catch (e) {} }, 2000);
});

bot.on('entityDead', (e) => {
  if (HOSTILE.has((e.name || '').toLowerCase())) kills++;
});

bot.on('kicked', () => process.exit(1));
bot.on('error', () => {});

// ── Actions (from stdin) ──
const rl = readline.createInterface({ input: process.stdin });
rl.on('line', (line) => {
  const cmd = line.trim();
  died = false; // reset per-tick death flag

  if (cmd === 'observe') {
    out(observe());
  }
  else if (cmd === 'forward') {
    stopMoving();
    bot.setControlState('forward', true);
    setTimeout(() => { bot.setControlState('forward', false); out(observe()); }, 500);
  }
  else if (cmd === 'fight') {
    const target = nearestHostile(6);
    if (target) {
      const weapon = bot.inventory.items().find(i => i.name.includes('sword'));
      if (weapon) bot.equip(weapon, 'hand').catch(() => {});
      bot.lookAt(target.position.offset(0, 1, 0));
      bot.attack(target);
    }
    out(observe());
  }
  else if (cmd === 'flee') {
    const target = nearestHostile(16);
    if (target) {
      const pos = bot.entity.position;
      const dx = pos.x - target.position.x;
      const dz = pos.z - target.position.z;
      const len = Math.sqrt(dx * dx + dz * dz) || 1;
      bot.lookAt(pos.offset((dx / len) * 10, 0, (dz / len) * 10));
    }
    stopMoving();
    bot.setControlState('forward', true);
    bot.setControlState('sprint', true);
    bot.setControlState('jump', true);
    setTimeout(() => {
      bot.setControlState('jump', false);
      setTimeout(() => { stopMoving(); out(observe()); }, 400);
    }, 200);
  }
  else if (cmd === 'eat') {
    const food = bot.inventory.items().find(i =>
      ['cooked_beef', 'bread', 'cooked_porkchop', 'golden_apple',
       'cooked_chicken', 'apple', 'baked_potato', 'carrot'].includes(i.name)
    );
    if (food) {
      bot.equip(food, 'hand').then(() => {
        bot.activateItem();
        setTimeout(() => { bot.deactivateItem(); out(observe()); }, 1600);
      }).catch(() => out(observe()));
    } else {
      out(observe());
    }
  }
  else if (cmd === 'sprint') {
    stopMoving();
    bot.setControlState('forward', true);
    bot.setControlState('sprint', true);
    setTimeout(() => { stopMoving(); out(observe()); }, 500);
  }
  else if (cmd === 'idle') {
    stopMoving();
    out(observe());
  }
  else if (cmd === 'quit') {
    bot.end();
    process.exit(0);
  }
});
