/**
 * explorer_bot.js — Claude explores the world through a mineflayer bot's eyes.
 *
 * Connects to the server, reports what it sees, and accepts movement commands
 * via stdin. Outputs structured JSON observations.
 *
 * Usage: node explorer_bot.js [host] [port]
 */

const mineflayer = require('mineflayer');
const { Vec3 } = require('vec3');
const readline = require('readline');

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

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

// ── Observation functions ──

function observe() {
  if (!bot.entity) return '{"error": "not spawned"}';

  const pos = bot.entity.position;
  const obs = {
    position: { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) },
    health: bot.health,
    food: bot.food,
    gamemode: bot.game.gameMode,
    time: bot.time.timeOfDay,
    isRaining: bot.isRaining,
  };

  // Block below and around
  obs.blockBelow = blockName(pos.offset(0, -1, 0));
  obs.blockAt = blockName(pos);
  obs.blockAbove = blockName(pos.offset(0, 2, 0));

  // Look in all 4 directions for interesting blocks
  obs.surroundings = {};
  const dirs = { north: [0, 0, -1], south: [0, 0, 1], east: [1, 0, 0], west: [-1, 0, 0] };
  for (const [name, [dx, dy, dz]] of Object.entries(dirs)) {
    const blocks = [];
    for (let dist = 1; dist <= 8; dist++) {
      const b = blockName(pos.offset(dx * dist, 0, dz * dist));
      if (b !== 'air') blocks.push({ dist, block: b });
    }
    if (blocks.length) obs.surroundings[name] = blocks;
  }

  // Nearby entities
  obs.entities = [];
  for (const entity of Object.values(bot.entities)) {
    if (entity === bot.entity) continue;
    const dist = entity.position.distanceTo(pos);
    if (dist <= 20) {
      obs.entities.push({
        type: entity.type === 'player' ? 'player' : (entity.name || entity.type),
        name: entity.username || null,
        distance: Math.round(dist),
        position: {
          x: Math.floor(entity.position.x),
          y: Math.floor(entity.position.y),
          z: Math.floor(entity.position.z),
        },
      });
    }
  }

  // Scan a 5x5 area at foot level for terrain overview
  obs.terrain = [];
  for (let dx = -5; dx <= 5; dx++) {
    for (let dz = -5; dz <= 5; dz++) {
      // Find surface block (scan down from y+3)
      for (let dy = 3; dy >= -3; dy--) {
        const b = bot.blockAt(pos.offset(dx, dy, dz));
        if (b && b.name !== 'air') {
          obs.terrain.push({ dx, dz, y: Math.floor(pos.y) + dy, block: b.name });
          break;
        }
      }
    }
  }

  return JSON.stringify(obs);
}

function blockName(vec) {
  const b = bot.blockAt(vec);
  return b ? b.name : 'unloaded';
}

// ── Movement functions ──

async function walkTo(x, y, z) {
  const goal = new Vec3(x, y, z);
  console.log(JSON.stringify({ action: 'walking', target: { x, y, z } }));

  // Simple movement: look at target and walk forward
  await bot.lookAt(goal);
  bot.setControlState('forward', true);

  // Walk until close or timeout
  return new Promise((resolve) => {
    const check = setInterval(() => {
      const dist = bot.entity.position.distanceTo(goal);
      if (dist < 2) {
        bot.setControlState('forward', false);
        clearInterval(check);
        resolve(true);
      }
    }, 200);

    setTimeout(() => {
      bot.setControlState('forward', false);
      clearInterval(check);
      resolve(false);
    }, 10000); // 10s max walk
  });
}

async function lookAround() {
  const snapshots = [];
  const yaws = [0, Math.PI / 2, Math.PI, -Math.PI / 2]; // N, E, S, W
  const names = ['north', 'east', 'south', 'west'];

  for (let i = 0; i < 4; i++) {
    await bot.look(yaws[i], 0, true);
    await new Promise(r => setTimeout(r, 500));

    // What do I see in this direction?
    const pos = bot.entity.position;
    const dx = Math.round(Math.sin(yaws[i]));
    const dz = Math.round(-Math.cos(yaws[i]));

    const visible = [];
    for (let dist = 1; dist <= 16; dist++) {
      const b = bot.blockAt(pos.offset(dx * dist, 0, dz * dist));
      const bUp = bot.blockAt(pos.offset(dx * dist, 1, dz * dist));
      const bDown = bot.blockAt(pos.offset(dx * dist, -1, dz * dist));
      if (b && b.name !== 'air') visible.push({ dist, block: b.name, level: 'eye' });
      if (bDown && bDown.name !== 'air' && bDown.name !== (b && b.name)) visible.push({ dist, block: bDown.name, level: 'ground' });
    }
    snapshots.push({ direction: names[i], blocks: visible.slice(0, 8) });
  }
  return snapshots;
}

// ── Event handlers ──

bot.on('login', () => {
  console.log(JSON.stringify({ event: 'login', username: bot.username }));
});

bot.on('spawn', async () => {
  console.log(JSON.stringify({ event: 'spawn', ...JSON.parse(observe()) }));

  // Initial look around
  const views = await lookAround();
  console.log(JSON.stringify({ event: 'look_around', views }));
});

bot.on('chat', (username, message) => {
  if (username === bot.username) return;
  console.log(JSON.stringify({ event: 'chat', from: username, message }));
});

bot.on('health', () => {
  console.log(JSON.stringify({ event: 'health', health: bot.health, food: bot.food }));
});

bot.on('death', () => {
  console.log(JSON.stringify({ event: 'death' }));
});

bot.on('kicked', (reason) => {
  console.log(JSON.stringify({ event: 'kicked', reason: typeof reason === 'object' ? JSON.stringify(reason) : reason }));
});

bot.on('error', (err) => {
  console.error(JSON.stringify({ event: 'error', message: err.message }));
});

// ── Command interface (stdin) ──

const rl = readline.createInterface({ input: process.stdin });

rl.on('line', async (line) => {
  const cmd = line.trim();

  if (cmd === 'observe' || cmd === 'look') {
    console.log(observe());
  }
  else if (cmd === 'around') {
    const views = await lookAround();
    console.log(JSON.stringify({ action: 'look_around', views }));
  }
  else if (cmd.startsWith('walk ')) {
    const parts = cmd.split(' ');
    const x = parseInt(parts[1]), y = parseInt(parts[2]), z = parseInt(parts[3]);
    const arrived = await walkTo(x, y, z);
    console.log(JSON.stringify({ action: 'walk_result', arrived, ...JSON.parse(observe()) }));
  }
  else if (cmd === 'forward') {
    bot.setControlState('forward', true);
    setTimeout(() => {
      bot.setControlState('forward', false);
      console.log(observe());
    }, 3000);
  }
  else if (cmd === 'jump') {
    bot.setControlState('jump', true);
    setTimeout(() => bot.setControlState('jump', false), 500);
    setTimeout(() => console.log(observe()), 1000);
  }
  else if (cmd.startsWith('say ')) {
    bot.chat(cmd.slice(4));
  }
  else if (cmd === 'quit') {
    bot.end();
    process.exit(0);
  }
  else {
    console.log(JSON.stringify({ error: 'unknown command', commands: ['observe', 'around', 'walk X Y Z', 'forward', 'jump', 'say MSG', 'quit'] }));
  }
});
