/* ============================================================
   Wildlife Kingdom — js/entities.js
   Animals · Guests · Keepers · Vets · Particles
   All coordinates are float tile-space; drawing projects them.
   ============================================================ */
(function(){
'use strict';
const WK = window.WK;
const D = WK.Data;
const HW = WK.HW, HH = WK.HH;

WK.ANIMAL_SIZE = {
  deer:.95, zebra:.95, monkey:.78, flamingo:.78, penguin:.68, crocodile:.9,
  giraffe:1.12, lion:1, hippo:1.08, tiger:.98, rhino:1.05, elephant:1.22,
  gorilla:.92, polarbear:1.08, panda:.88,
};

let NEXT_ID = 1;
const R = Math.random;

/* ============================================================
   ANIMAL
   ============================================================ */
class Animal{
  constructor(speciesId, x, y, regionId){
    const sp = D.SPECIES[speciesId];
    this.id = NEXT_ID++;
    this.kind='animal';
    this.species = speciesId;
    this.sp = sp;
    this.name = D.ANIMAL_NAMES[(R()*D.ANIMAL_NAMES.length)|0];
    this.x=x; this.y=y;
    this.face = R()<.5?-1:1;
    this.state='idle';
    this.stateT = 1+R()*4;
    this.phase = R()*10;
    this.tOff = R()*100;
    this.hunger = 20+R()*20;
    this.happiness = 62;
    this.happyT = 0;      // remaining happy-burst time
    this.eatT = 0;
    this.sick = false;
    this.regionId = regionId;
    this.wp=null;
  }
  get size(){ return WK.ANIMAL_SIZE[this.species]||1; }

  startEating(){
    this.eatT = 3;
    this.hunger = 4;
    this.happyT = 3.5;
    this._taken = false;
  }
  makeHappy(){ this.happyT = Math.max(this.happyT, 2.6); }

  update(sdt, game){
    const sp=this.sp;
    // needs drift
    this.hunger = WK.clamp(this.hunger + sdt*0.19, 0, 100);
    let target = game.habitatScore(this);
    if(this.hunger>55) target -= (this.hunger-55)*0.85;
    if(this.sick) target -= 38;
    target = WK.clamp(target,5,98);
    this.happiness += (target-this.happiness)*Math.min(1,sdt*0.07);

    if(this.happyT>0) this.happyT-=sdt;
    if(this.eatT>0){ this.eatT-=sdt; this.state='eat'; return; }
    if(this.happyT>0 && this.state!=='walk'){ this.state='happy'; this.stateT-=sdt; if(this.stateT<=0)this.state='idle'; return; }

    this.stateT -= sdt;
    if(this.state==='idle'){
      if(this.stateT<=0){
        // choose a stroll
        const dest = game.world.randomRegionTile(this.regionId, sp, R);
        if(dest){
          const path = game.world.findPathAnimal(sp, this.x, this.y, dest[0], dest[1], this.regionId);
          if(path&&path.length){ this.wp=path; this.state='walk'; }
        }
        this.stateT = 3.5+R()*7;
      }
    }else if(this.state==='walk'){
      if(!this.wp||!this.wp.length){ this.state='idle'; this.stateT=2+R()*4; return; }
      const [tx,ty]=this.wp[0];
      const dx=tx-this.x, dy=ty-this.y;
      const d=Math.hypot(dx,dy);
      const step=sp.speed*sdt;
      if(d<=step){ this.x=tx; this.y=ty; this.wp.shift(); if(!this.wp.length){this.state='idle'; this.stateT=2.5+R()*6;} }
      else{ this.x+=dx/d*step; this.y+=dy/d*step; }
      const screenDx=(tx-this.x)-(ty-this.y);
      if(Math.abs(screenDx)>0.05) this.face = screenDx>0?1:-1;
      this.phase += step*5.2;
    }
  }

  draw(ctx, cam, time, groundId){
    const p = cam.project(this.x,this.y);
    if(!cam.isVisible(p.x,p.y,80)) return;
    const s=this.size;
    const g = WK.Data.GROUND[groundId||'grass'];
    const swimming = g.water && this.sp.swim;
    ctx.save();
    ctx.translate(p.x,p.y);
    // shadow (not for swimmers)
    if(!swimming){
      ctx.fillStyle='rgba(50,40,15,.26)';
      ctx.beginPath(); ctx.ellipse(0,1,15*s,5.5*s,0,0,WK.TAU); ctx.fill();
    }
    ctx.scale(this.face*s, s);
    if(swimming){
      ctx.save();
      ctx.beginPath(); ctx.rect(-48,-72,96,64); ctx.clip(); // hide feet under waterline
      WK.Sprites.drawAnimal(ctx, this.species, {
        state:this.state==='walk'?'walk':this.state==='eat'?'eat':this.state==='happy'?'happy':'idle',
        ph:this.phase, t:time+this.tOff,
      });
      ctx.restore();
      // ripples at waterline
      ctx.strokeStyle='rgba(255,255,255,.55)'; ctx.lineWidth=1.6;
      const rr=(14+Math.sin(time*3+this.tOff)*2)*s;
      ctx.beginPath(); ctx.ellipse(0,-6*s,rr,rr*0.36,0,0,WK.TAU); ctx.stroke();
      ctx.beginPath(); ctx.ellipse(0,-6*s,rr*.6,rr*.2,0,0,WK.TAU); ctx.stroke();
    }else{
      WK.Sprites.drawAnimal(ctx, this.species, {
        state:this.state==='walk'?'walk':this.state==='eat'?'eat':this.state==='happy'?'happy':'idle',
        ph:this.phase, t:time+this.tOff,
      });
    }
    ctx.restore();
    // status markers
    if(this.sick || this.hunger>82){
      ctx.font='bold 13px sans-serif'; ctx.textAlign='center';
      ctx.fillText(this.sick?'🤒':'🍖', p.x, p.y-46*s + Math.sin(time*4)*1.5);
    }
  }
}

WK.Animal = Animal;

/* ============================================================
   GUEST
   ============================================================ */
const SKINS=['#f5d5b8','#eec39a','#d9a06b','#a9744f','#8a5a3a'];
const SHIRTS=['#e8624e','#ffc93c','#63b34c','#69c3ef','#b78ae8','#ff8aa8','#ff9d47','#5fb8a8'];
const PANTS=['#4a6a8a','#6b4a8a','#7a6a55','#43362a','#5a7a4a'];
const HAIRS=['#5a3c22','#2e2a26','#a9743f','#d9b97e','#8a3a2a','#e8e2d4'];

class Guest{
  constructor(game, ex, ey){
    this.id=NEXT_ID++;
    this.kind='guest';
    this.game=game;
    this.x=ex; this.y=ey;
    this.skin=SKINS[(R()*SKINS.length)|0];
    this.shirt=SHIRTS[(R()*SHIRTS.length)|0];
    this.pants=PANTS[(R()*PANTS.length)|0];
    this.hair=HAIRS[(R()*HAIRS.length)|0];
    this.kid=R()<0.3;
    this.balloon=R()<0.22?(SHIRTS[(R()*SHIRTS.length)|0]):null;
    this.joy=58+R()*10;
    this.speed=(this.kid?1.25:1.6)*(0.9+R()*0.25);
    this.lane=(R()-0.5)*0.24;
    this.state='walk';
    this.phase=R()*10;
    this.itin=[];
    const stops=2+(R()*4|0);
    for(let i=0;i<stops;i++) this.itin.push('poi');
    this.leaving=false;
    this.wantsRestroom=false;
    this.restroomWarned=false;
    this.dwellT=0;
    this.poi=null;
    this.wp=null;
    this.waitT=0.5+R();
  }
  setPathTo(tx,ty){
    const w=this.game.world;
    this.wp=w.findPathVisitor(Math.round(this.x),Math.round(this.y),tx,ty);
    if(this.wp&&this.wp.length===0) this.wp=[[tx,ty]];
    return !!this.wp;
  }
  pickNext(){
    const pois=this.game.pois;
    if(!pois||!pois.length){ this.leave(); return; }
    let pool=[];
    if(this.wantsRestroom){
      const rr=pois.filter(p=>p.type==='restroom');
      if(rr.length) pool=rr.map(p=>({p,w:50}));
    }
    if(!pool.length){
      for(const p of pois){
        let wgt=0;
        if(p.type==='view') wgt=6+p.appeal*2.2;
        else if(p.type==='shop') wgt=3+p.income*0.5+p.joy;
        else if(p.type==='visit') wgt=1.5+p.joy*0.5;
        else if(p.type==='bench') wgt=2;
        else if(p.type==='restroom') wgt=this.wantsRestroom?40:1.2;
        pool.push({p,w:wgt||0.5});
      }
    }
    const pick=WK.weightedPick(pool,R).p;
    this.poi=pick;
    if(this.setPathTo(pick.x,pick.y)){
      this.state='walk';
    }else{
      // unreachable: skip stop
      if(this.itin.length)this.itin.pop();
      if(!this.itin.length) this.leave();
      else this.pickNext();
    }
  }
  leave(){
    this.leaving=true; this.poi=null;
    const e=this.game.exitTile;
    if(e) this.setPathTo(e[0],e[1]);
  }
  arriveAtPoi(){
    const p=this.poi;
    this.dwellT=2.2+R()*3.5;
    this.state='dwell';
    this.act='stand';
    if(R()<0.3) this.act='photo';
    if(!p) return;
    const g=this.game;
    if(p.type==='view'){
      this.joy=WK.clamp(this.joy+3.5+p.appeal*0.85*(p.quality||1),0,100);
      this.act=R()<0.45?'photo':'point';
      if(R()<0.3){ g.puffHeart(this.x,this.y); }
    }else if(p.type==='shop'){
      const def=D.BUILDING[p.btype];
      this.joy=WK.clamp(this.joy+def.joy*0.65,0,100);
      const spend=Math.round(def.income*(0.8+R()*0.4));
      g.earnMoney(spend, 'shop:'+p.btype);
      g.coinBurst(p.x,p.y);
      if(['burger','icecream','restaurant'].includes(p.btype)) this.wantsRestroom=true;
    }else if(p.type==='visit'){
      const def=D.BUILDING[p.btype];
      this.joy=WK.clamp(this.joy+def.joy*0.4,0,100);
    }else if(p.type==='restroom'){
      this.wantsRestroom=false;
      this.joy=WK.clamp(this.joy+3,0,100);
    }else if(p.type==='bench'){
      this.joy=WK.clamp(this.joy+2.5,0,100);
      this.act='sit';
      this.dwellT=3.5+R()*3;
    }
  }
  update(sdt){
    if(this.waitT>0){ this.waitT-=sdt; return; }
    // slow boredom / crowd stress
    this.joy=WK.clamp(this.joy - sdt*0.16 - (this.game.crowdStress||0)*sdt, 0, 100);
    if(this.state==='walk'){
      if(!this.wp){ 
        if(this.leaving){ this.done=true; return; }
        this.pickNext(); if(!this.wp) return;
      }
      if(!this.wp.length){
        if(this.leaving){ this.done=true; return; }
        this.arriveAtPoi();
        return;
      }
      const [tx,ty]=this.wp[0];
      const dx=tx-this.x, dy=ty-this.y;
      const d=Math.hypot(dx,dy);
      const step=this.speed*sdt;
      if(d<=step){ this.x=tx; this.y=ty; this.wp.shift(); }
      else{
        // gentle lane offset for organic crowds
        const px=-dy/d*this.lane, py=dx/d*this.lane;
        this.x+=(dx/d)*step+px*sdt*0.6;
        this.y+=(dy/d)*step+py*sdt*0.6;
        const sdx=dx-dy;
        if(Math.abs(sdx)>0.05) this.faceDir=sdx>0?1:-1;
      }
      this.phase+=step*5.5;
      this.act='walk';
    }else if(this.state==='dwell'){
      this.dwellT-=sdt;
      this.phase+=sdt*1.4;
      if(this.dwellT<=0){
        this.poi=null;
        if(this.itin.length){ this.itin.pop(); this.pickNext(); }
        else this.leave();
        this.state='walk';
      }
    }
    if(this.leaving && (!this.wp || !this.wp.length) && this.state!=='dwell'){
      this.done=true;
    }
  }
  draw(ctx,cam,time){
    const p=cam.project(this.x,this.y);
    if(!cam.isVisible(p.x,p.y,40)) return;
    ctx.save();
    ctx.translate(p.x,p.y);
    let act=this.act||'stand';
    if(this.state==='walk') act='walk';
    if(act==='sit') ctx.translate(0,-6);
    WK.Sprites.drawPerson(ctx,{
      phase:this.phase, state:act,
      shirt:this.shirt, pants:this.pants, skin:this.skin, hair:this.hair,
      kid:this.kid, balloon:this.balloon, face:this.faceDir||1,
      smile:this.joy>45,
    });
    if(this.joy<25 && R()<0.02){
      ctx.fillStyle='rgba(90,110,220,.8)';
      ctx.font='bold 10px sans-serif'; ctx.textAlign='center';
      ctx.fillText('💧',8,-34);
    }
    ctx.restore();
  }
}
WK.Guest = Guest;

/* ============================================================
   STAFF (keeper / vet)
   ============================================================ */
class Staff{
  constructor(role,x,y){
    this.id=NEXT_ID++;
    this.kind='staff'; this.role=role; // 'keeper'|'vet'
    this.x=x; this.y=y;
    this.speed=role==='keeper'?2.1:2.3;
    this.state='idle';
    this.job=null;
    this.wp=null;
    this.workT=0;
    this.phase=R()*10;
    this.shirt=role==='keeper'?'#5fb868':'#f5f5f5';
    this.pants='#43362a';
    this.skin=SKINS[(R()*SKINS.length)|0];
    this.hair=HAIRS[(R()*HAIRS.length)|0];
    this.face=1;
    this.idleT=1+R()*2;
  }
  update(sdt,game){
    if(this.workT>0){
      this.workT-=sdt;
      if(this.workT<=0){
        if(this.role==='keeper'&&this.job&&this.job.alive){
          this.job.startEating();
          game.puffHeart(this.job.x,this.job.y);
          this.job.makeHappy();
        }
        if(this.role==='vet'&&this.job&&this.job.alive){
          this.job.sick=false;
          this.job.makeHappy();
          game.puffHeart(this.job.x,this.job.y);
          game.notify(this.job.name+' the '+D.SPECIES[this.job.species].name+' was healed!','good');
        }
        this.job=null;
        this.state='idle';
        this.idleT=0.5;
      }
      return;
    }
    if(!this.job){
      this.idleT-=sdt;
      // ask dispatcher
      const j=game.requestJob(this.role,this);
      if(j){
        this.job=j;
        const w=game.world;
        this.wp=w.findPathKeeper(Math.round(this.x),Math.round(this.y),Math.round(j.x),Math.round(j.y));
        if(!this.wp){ game.abandonJob(this.role,j); this.job=null; this.idleT=2; }
        else { this.state='walk'; }
        return;
      }
      if(this.idleT<=0){
        // casual wander
        const w=game.world;
        const b=w.buildings.find(bb=>bb.type===(this.role==='keeper'?'hut':'vet'))||w.buildings[0];
        let tx,ty;
        if(b){ const d=w.doorTile(b); tx=d[0]+((R()*7)|0)-3; ty=d[1]+((R()*7)|0)-3; }
        else { tx=(R()*w.cols)|0; ty=(R()*w.rows)|0; }
        tx=WK.clamp(tx,1,w.cols-2); ty=WK.clamp(ty,1,w.rows-2);
        if(w.path[w.idx(tx,ty)]||D.GROUND[w.gid(tx,ty)].walk){
          this.wp=w.findPathKeeper(Math.round(this.x),Math.round(this.y),tx,ty);
          if(this.wp&&this.wp.length) this.state='walk';
        }
        this.idleT=4+R()*5;
      }
      if(this.state==='idle') return;
    }
    if(this.state==='walk'){
      if(!this.wp||!this.wp.length){
        if(this.job){ this.state='work'; this.workT=this.role==='keeper'?2.4:3; this.face=this.job.x>=this.x?1:-1; }
        else { this.state='idle'; }
        return;
      }
      const [tx,ty]=this.wp[0];
      const dx=tx-this.x, dy=ty-this.y;
      const d=Math.hypot(dx,dy);
      const step=this.speed*sdt;
      if(d<=step){ this.x=tx; this.y=ty; this.wp.shift(); }
      else{ this.x+=dx/d*step; this.y+=dy/d*step; }
      const sdx=(tx-this.x)-(ty-this.y);
      if(Math.abs(sdx)>0.05) this.face=sdx>0?1:-1;
      this.phase+=step*5.5;
    }
  }
  draw(ctx,cam,time){
    const p=cam.project(this.x,this.y);
    if(!cam.isVisible(p.x,p.y,40)) return;
    ctx.save();
    ctx.translate(p.x,p.y);
    const act=this.workT>0?'feed':(this.state==='walk'?'walk':'stand');
    WK.Sprites.drawPerson(ctx,{
      phase:this.phase,state:act,shirt:this.shirt,pants:this.pants,
      skin:this.skin,hair:this.hair,kid:false,balloon:null,face:this.face,smile:true,
    });
    if(this.workT>0){
      // work bubble
      ctx.font='bold 12px sans-serif'; ctx.textAlign='center';
      ctx.fillText(this.role==='keeper'?'🍎':'🩺',0,-36+Math.sin(time*6));
    }
    // role cap
    ctx.fillStyle=this.role==='keeper'?'#3e8a33':'#e86a6a';
    ctx.beginPath(); ctx.ellipse(0,-29,4.6,2,0,0,WK.TAU); ctx.fill();
    ctx.restore();
  }
}
WK.Staff = Staff;

/* ============================================================
   PARTICLES
   ============================================================ */
class Particles{
  constructor(){ this.list=[]; }
  add(o){ o.age=0; this.list.push(o); if(this.list.length>240) this.list.splice(0,this.list.length-240); }
  coins(x,y){ for(let i=0;i<3;i++) this.add({t:'coin',x:x+(R()-.5)*.4,y,vz:0,z:14+R()*8,vx:(R()-.5)*0.8,vy:(R()-.5)*0.4,dur:0.9}); }
  hearts(x,y){ for(let i=0;i<2;i++) this.add({t:'heart',x:x+(R()-.5)*.5,y:y-0.2,z:20,vx:(R()-.5)*0.3,vy:-0.5-R()*0.4,dur:1.2}); }
  puff(x,y,c){ this.add({t:'puff',x,y,z:4,vx:0,vy:0,dur:0.5,c:c||'rgba(180,160,130,'}); }
  splash(x,y){ this.add({t:'splash',x,y,z:2,vx:0,vy:0,dur:0.5}); }
  note(x,y){ this.add({t:'note',x,y,z:18,vx:0.2,vy:-0.6,dur:1}); }
  update(sdt){
    const L=this.list;
    for(let i=L.length-1;i>=0;i--){
      const p=L[i];
      p.age+=sdt;
      if(p.age>=p.dur){ L.splice(i,1); continue; }
      p.x+=(p.vx||0)*sdt; p.y+=(p.vy||0)*sdt;
    }
  }
  draw(ctx,cam,time){
    for(const p of this.list){
      const k=p.age/p.dur;
      const pr=cam.project(p.x,p.y);
      if(!cam.isVisible(pr.x,pr.y,30)) continue;
      const z=p.z*(1-k)+ (p.z||0)*0;
      ctx.save();
      ctx.translate(pr.x,pr.y-(p.z||0)*(0.4+k*1.4)*16);
      ctx.globalAlpha=1-k*k;
      if(p.t==='coin'){
        ctx.fillStyle='#ffc93c';
        ctx.beginPath(); ctx.ellipse(0,0,4.4*Math.abs(Math.cos(k*9)),4.4,0,0,WK.TAU); ctx.fill();
        ctx.strokeStyle='#d99a1e'; ctx.lineWidth=1.2; ctx.stroke();
      }else if(p.t==='heart'){
        ctx.fillStyle='#ff6b81';
        const s=3.2+k*2;
        ctx.beginPath();
        ctx.moveTo(0,s*0.9); ctx.bezierCurveTo(-s*1.6,-s*0.3,-s*0.9,-s*1.4,0,-s*0.4);
        ctx.bezierCurveTo(s*0.9,-s*1.4,s*1.6,-s*0.3,0,s*0.9);
        ctx.fill();
      }else if(p.t==='puff'){
        ctx.fillStyle=p.c+(0.5*(1-k))+')';
        ctx.beginPath(); ctx.arc(0,0,4+k*14,0,WK.TAU); ctx.fill();
      }else if(p.t==='splash'){
        ctx.strokeStyle='rgba(200,235,250,'+(0.8*(1-k))+')';
        ctx.lineWidth=1.6;
        ctx.beginPath(); ctx.arc(0,0,3+k*13,0,WK.TAU); ctx.stroke();
      }else if(p.t==='note'){
        ctx.fillStyle='rgba(90,60,120,'+(1-k)+')';
        ctx.font='bold 12px sans-serif'; ctx.textAlign='center';
        ctx.fillText(Math.sin(p.x*7)>0?'♪':'♫',0,0);
      }
      ctx.restore();
    }
    ctx.globalAlpha=1;
  }
}
WK.Particles = Particles;

})();
