/**
 * Hopper — small skittish prey. The second bundled creature, and the other half
 * of making the world feel *inhabited*: wanderers plod and forage, hoppers
 * nibble in the meadows and bolt the moment anything larger comes near. Chasing
 * one (and watching it outrun you) is the cheapest fun in the world.
 *
 * Same contract as Wanderer: deterministic-ish (seeded), terrain-aware, no
 * graphics. The viewport gives it a hop animation client-side by kind.
 */
'use strict';

const NIBBLE_SPEED = 0.18;
const FLEE_SPEED = 0.95;
const FLEE_RADIUS = 7; // starts running when something this close approaches
const CALM_RADIUS = 12; // keeps running until the threat is this far away
const TURN = Math.PI * 0.8;

class Hopper {
  constructor(o) {
    this.id = o.id;
    this.kind = 'hopper';
    this.x = o.x;
    this.z = o.z;
    this.y = 0;
    this.heading = (o.seed % 628) / 100;
    this.scale = o.scale != null ? o.scale : 1;
    this.fleeing = false;
    this._rng = (o.seed >>> 0) || 1;
  }

  _rand() {
    this._rng = (Math.imul(this._rng, 1664525) + 1013904223) >>> 0;
    return this._rng / 4294967296;
  }

  _step(world, speed) {
    const nx = this.x + Math.cos(this.heading) * speed;
    const nz = this.z + Math.sin(this.heading) * speed;
    const mat = world.materialAt(nx, nz);
    if (!mat || mat.liquid || !mat.walkable || world.blockedByProp(nx, nz, 0.3)) {
      this.heading += TURN * (this._rand() < 0.5 ? 1 : -1); // blocked — dodge
    } else {
      this.x = nx;
      this.z = nz;
    }
  }

  /** Nearest thing a hopper is afraid of: any inhabitant, or a wanderer.
   *  Inhabitants are few (loop them); wanderers come from the world's
   *  chunk-bucket index so a big active world stays cheap. */
  _nearestThreat(world, radius) {
    let best = null, bd = radius * radius;
    for (const inh of world.inhabitants.values()) {
      const dx = inh.pose.x - this.x, dz = inh.pose.z - this.z, d = dx * dx + dz * dz;
      if (d < bd) { bd = d; best = { x: inh.pose.x, z: inh.pose.z }; }
    }
    const w = world.nearestEntity('wanderer', this.x, this.z, radius);
    if (w) {
      const d = (w.x - this.x) ** 2 + (w.z - this.z) ** 2;
      if (d < bd) best = { x: w.x, z: w.z };
    }
    return best;
  }

  update(world) {
    const threat = this._nearestThreat(world, this.fleeing ? CALM_RADIUS : FLEE_RADIUS);
    this.fleeing = Boolean(threat);
    if (threat) {
      // Run directly away, with a touch of jink so it isn't a straight rail.
      this.heading = Math.atan2(this.z - threat.z, this.x - threat.x) + (this._rand() - 0.5) * 0.5;
      this._step(world, FLEE_SPEED);
    } else if (this._rand() < 0.35) {
      // Mostly stands and nibbles; occasionally shuffles a few paces.
      if (this._rand() < 0.2) this.heading += (this._rand() - 0.5) * TURN;
      this._step(world, NIBBLE_SPEED);
    }
    this.y = world.heightAt(this.x, this.z);
  }

  serialize() {
    return {
      id: this.id,
      kind: this.kind,
      pose: { x: this.x, y: this.y, z: this.z, heading: this.heading, rot: this.heading },
      scale: this.scale,
      fleeing: this.fleeing, // renderer stretches the hop when it's bolting
    };
  }
}

module.exports = { Hopper };
