/**
 * Villager — a deterministic daily routine, so the village reads as
 * inhabited rather than merely built. See ROADMAP.md WP5.
 *
 * Home at night, the well by day, walking the ground between — keyed to
 * `world.timeInfo().phase`, the same shared clock every inhabitant sees.
 *
 * Deliberately NOT built on the inhabitant standing-intent stepper
 * (`World._stepIntent`): that method is shared, heavily-tested machinery for
 * humans and agents (pose/intent/perceive/act from the OUTSIDE), and a
 * villager has no outside — it's driven entirely by its own `update(world)`
 * tick, like Wanderer/Hopper/Prowler. So it gets its own minimal walker
 * instead, in the same shape as its siblings, and reuses `findPath` from
 * pathfind.js directly — the reuse WP8 left a note for.
 *
 * Reactions are deliberately minimal (per WP5): yield a step to an
 * inhabitant standing on top of it, glance toward someone who just spoke
 * nearby, and — the one that matters — flee to the nearest campfire the
 * moment a prowler is close at night. None of it interrupts the routine for
 * long; a villager always finds its way back to the schedule.
 */
'use strict';

const { findPath } = require('../pathfind');

const SPEED = 0.35; // per tick — a leisurely walking pace, slower than a forager's dash
const FLEE_SPEED = 0.85;
const ARRIVE_EPS = 0.3;
const PROWLER_SIGHT = 16; // notices a threat a bit closer than the prowler's own hunt radius
const CHAT_HEAR_RADIUS = 12;
const FACE_TICKS = 20; // ~2s at 10Hz — long enough to read as "looked over", not a stare
const YIELD_RADIUS = 1.0;

class Villager {
  /** @param {{id:string, home:{x,z}, work:{x,z}, x:number, z:number, seed:number, scale?:number}} o */
  constructor(o) {
    this.id = o.id;
    this.kind = 'villager';
    this.home = o.home;
    this.work = o.work;
    this.x = o.x;
    this.z = o.z;
    this.y = 0;
    this.rot = o.rot || 0;
    this.scale = o.scale != null ? o.scale : 1;
    this.stop = null; // 'home' | 'work' — null forces a fresh plan on the first tick
    this.route = null;
    this.routeIndex = 0;
    this.fleeing = false;
    this._faceTicks = 0;
    this._lastChatSeq = 0;
    this._rng = (o.seed >>> 0) || 1;
  }

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

  /** One step toward (tx,tz), sliding around solid props the way Wanderer/
   *  Hopper do. Returns true once close enough to call it arrived. */
  _stepToward(world, tx, tz, speed) {
    const dx = tx - this.x, dz = tz - this.z;
    if (Math.hypot(dx, dz) <= ARRIVE_EPS) return true;
    const heading = Math.atan2(this.x - tx, this.z - tz); // world.js's headingToward convention
    const tryAngle = (ang) => {
      const nx = this.x - Math.sin(ang) * speed, nz = this.z - Math.cos(ang) * speed;
      const m = world.materialAt(nx, nz);
      if (!m || m.liquid || !m.walkable) return null;
      if (world.blockedByProp(nx, nz, 0.35)) return null;
      return { nx, nz };
    };
    let dest = tryAngle(heading);
    if (!dest) {
      for (const d of [Math.PI / 4, -Math.PI / 4, Math.PI / 2, -Math.PI / 2]) {
        dest = tryAngle(heading + d);
        if (dest) break;
      }
    }
    if (dest) { this.x = dest.nx; this.z = dest.nz; this.rot = heading; }
    return false;
  }

  /** Glance toward whoever last spoke, if it was close and recent. Cheap: only
   *  looks at the newest chat line, never rescans the whole log. */
  _checkChat(world) {
    if (!world.chat.length) return;
    const last = world.chat[world.chat.length - 1];
    if (last.seq <= this._lastChatSeq) return;
    this._lastChatSeq = last.seq;
    const speaker = world.inhabitants.get(last.fromId);
    if (!speaker) return;
    if (Math.hypot(speaker.pose.x - this.x, speaker.pose.z - this.z) < CHAT_HEAR_RADIUS) {
      this.rot = Math.atan2(this.x - speaker.pose.x, this.z - speaker.pose.z);
      this._faceTicks = FACE_TICKS;
    }
  }

  update(world) {
    const phase = world.timeInfo().phase;

    // A prowler close by at night overrides everything — run for the fire.
    if (phase === 'night') {
      const threat = world.nearestEntity('prowler', this.x, this.z, PROWLER_SIGHT);
      if (threat) {
        this.fleeing = true;
        this.route = null; // abandon the routine's route; fleeing takes over
        const fire = world.nearestEntity('campfire', this.x, this.z, 300); // the nearest fire it could plausibly know about
        const tx = fire ? fire.x : this.home.x, tz = fire ? fire.z : this.home.z;
        this._stepToward(world, tx, tz, FLEE_SPEED);
        this.y = world.heightAt(this.x, this.z);
        return;
      }
    }
    if (this.fleeing) { this.fleeing = false; this.stop = null; } // clear — replan the routine fresh

    this._checkChat(world);
    if (this._faceTicks > 0) { this._faceTicks--; this.y = world.heightAt(this.x, this.z); return; }

    // Step aside for an inhabitant standing right on top of us — a courtesy,
    // not a collision (villagers don't block movement either way).
    let nearInh = null, nd = YIELD_RADIUS * YIELD_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 < nd) { nd = d; nearInh = inh; }
    }
    if (nearInh) {
      const away = Math.atan2(this.x - nearInh.pose.x, this.z - nearInh.pose.z) + Math.PI / 2;
      this._stepToward(world, this.x - Math.sin(away) * 2, this.z - Math.cos(away) * 2, SPEED * 0.6);
      this.y = world.heightAt(this.x, this.z);
      return;
    }

    // The schedule: home at night, the workplace the rest of the day.
    // Re-plan only when the desired stop actually changes.
    const wantHome = phase === 'night';
    const stop = wantHome ? 'home' : 'work';
    if (stop !== this.stop) {
      this.stop = stop;
      const target = stop === 'home' ? this.home : this.work;
      this.route = findPath(world, this.x, this.z, target.x, target.z); // may be null — falls back below
      this.routeIndex = 0;
    }
    const target = this.stop === 'home' ? this.home : this.work;

    if (this.route && this.routeIndex < this.route.length) {
      const wp = this.route[this.routeIndex];
      if (this._stepToward(world, wp.x, wp.z, SPEED)) {
        this.routeIndex++;
        if (this.routeIndex >= this.route.length) this.route = null; // the last waypoint IS the target
      }
    } else {
      // Already there (or routing found nothing) — a direct step is a no-op
      // once within ARRIVE_EPS, so this is safe as the resting state too.
      const arrived = this._stepToward(world, target.x, target.z, SPEED);
      if (arrived && this._rand() < 0.01) {
        // The odd small shuffle near the stop so it doesn't read as a statue.
        const a = this._rand() * Math.PI * 2;
        this._stepToward(world, target.x + Math.cos(a) * 1.2, target.z + Math.sin(a) * 1.2, SPEED * 0.4);
      }
    }
    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, rot: this.rot },
      scale: this.scale,
      home: this.home,
      work: this.work,
      stop: this.stop, // so a save/load resumes the routine instead of replanning from scratch
      activity: this.fleeing ? 'fleeing' : (this.stop || 'settling in'), // what it's doing, not just where
    };
  }
}

module.exports = { Villager };
