/* ============================================================
   Wildlife Kingdom — js/render.js
   Isometric camera · layered renderer · cached terrain ·
   water sparkle · day/night light · ghost previews · hero scene
   ============================================================ */
(function(){
'use strict';
const WK = window.WK;
const D = WK.Data;
const HW = WK.HW, HH = WK.HH, TAU = WK.TAU;

/* ---------------- Camera ---------------- */
class Camera{
  constructor(){ this.x=0; this.y=0; this.zoom=1; this.w=800; this.h=600; }
  resize(w,h){ this.w=w; this.h=h; }
  centerOnTile(gx,gy){
    const wx=(gx-gy)*HW, wy=(gx+gy)*HH;
    this.x=wx; this.y=wy;
  }
  /* tile coords → screen px */
  project(gx,gy){
    const wx=(gx-gy)*HW, wy=(gx+gy)*HH;
    return {
      x:(wx-this.x)*this.zoom + this.w/2,
      y:(wy-this.y)*this.zoom + this.h/2,
    };
  }
  worldToScreen(wx,wy){
    return { x:(wx-this.x)*this.zoom+this.w/2, y:(wy-this.y)*this.zoom+this.h/2 };
  }
  screenToWorld(sx,sy){
    return {
      x:(sx-this.w/2)/this.zoom + this.x,
      y:(sy-this.h/2)/this.zoom + this.y,
    };
  }
  screenToTile(sx,sy){
    const w=this.screenToWorld(sx,sy);
    return {
      x:Math.floor((w.x/HW + w.y/HH)/2),
      y:Math.floor((w.y/HH - w.x/HW)/2),
    };
  }
  panBy(dx,dy){ this.x+=dx/this.zoom; this.y+=dy/this.zoom; }
  zoomAt(sx,sy,factor){
    const before=this.screenToWorld(sx,sy);
    this.zoom=WK.clamp(this.zoom*factor,0.45,2.4);
    const after=this.screenToWorld(sx,sy);
    this.x+=before.x-after.x; this.y+=before.y-after.y;
  }
  clampToWorld(world){
    const maxX=((world.cols))*HW, maxY=((world.cols+world.rows))*HH;
    this.x=WK.clamp(this.x,-maxX,maxX);
    this.y=WK.clamp(this.y,-200,maxY+200);
  }
  isVisible(sx,sy,margin){
    const m=(margin||40)+60;
    return sx>-m && sx<this.w+m && sy>-m && sy<this.h+m;
  }
}
WK.Camera = Camera;

/* ---------------- Renderer ---------------- */
class Renderer{
  constructor(canvas){
    this.canvas=canvas;
    this.ctx=canvas.getContext('2d');
    this.dpr=Math.min(2,window.devicePixelRatio||1);
    this.resize();
    this._terrain=document.createElement('canvas');
    this._terrainW=0; this._terrainH=0; this._terrainOX=0; this._terrainOY=0;
    this._lampCacheVer=-1; this._lamps=[];
  }
  resize(){
    this.dpr=Math.min(2,window.devicePixelRatio||1);
    const r=this.canvas.getBoundingClientRect();
    this.cssW=Math.max(320,r.width); this.cssH=Math.max(240,r.height);
    this.canvas.width=Math.round(this.cssW*this.dpr);
    this.canvas.height=Math.round(this.cssH*this.dpr);
  }

  /* ---- static terrain layer ---- */
  rebuildTerrain(world){
    const W=(world.cols+world.rows)*HW+4, H=(world.cols+world.rows)*HH+80;
    if(this._terrainW!==W||this._terrainH!==H){
      this._terrain.width=W; this._terrain.height=H;
      this._terrainW=W; this._terrainH=H;
      this._terrainOX=-(world.rows-1)*HW-2;
      this._terrainOY=HH-30;
    }
    const c=this._terrain.getContext('2d');
    c.clearRect(0,0,W,H);
    for(let y=0;y<world.rows;y++)for(let x=0;x<world.cols;x++){
      const wx=(x-y)*HW-this._terrainOX, wy=(x+y)*HH-this._terrainOY;
      WK.Sprites.paintGround(c,wx,wy,world.gid(x,y),WK.hash2(x,y,17));
    }
    for(let y=0;y<world.rows;y++)for(let x=0;x<world.cols;x++){
      const pid=world.pid(x,y);
      if(!pid) continue;
      const wx=(x-y)*HW-this._terrainOX, wy=(x+y)*HH-this._terrainOY;
      WK.Sprites.paintPath(c,wx,wy,pid,WK.hash2(x,y,31));
    }
    world.terrainDirty=false;
    this._lampCacheVer=-1;
  }
  repaintTile(world,x,y){
    const c=this._terrain.getContext('2d');
    const wx=(x-y)*HW-this._terrainOX, wy=(x+y)*HH-this._terrainOY;
    c.save();
    WK.Sprites.diamondPath(c,wx,wy,0); c.clip();
    c.clearRect(wx-HW-2,wy-HH-2,HW*2+4,HH*2+4);
    WK.Sprites.paintGround(c,wx,wy,world.gid(x,y),WK.hash2(x,y,17));
    const pid=world.pid(x,y);
    if(pid) WK.Sprites.paintPath(c,wx,wy,pid,WK.hash2(x,y,31));
    c.restore();
    world.terrainDirty=false;
    this._lampCacheVer=-1;
  }

  lampsFor(world){
    const ver=world.ver||0;
    if(this._lampCacheVer===ver) return this._lamps;
    this._lamps=[];
    for(let y=0;y<world.rows;y++)for(let x=0;x<world.cols;x++){
      if(world.oid(x,y)==='lamp') this._lamps.push([x,y]);
    }
    this._lampCacheVer=ver;
    return this._lamps;
  }

  /* ---- main frame ---- */
  frame(game,cam,opt){
    opt=opt||{};
    const ctx=this.ctx;
    ctx.setTransform(this.dpr,0,0,this.dpr,0,0);
    const world=game.world;

    // sky / backdrop follows daylight
    const dl=this.daylight(game.minutes);
    const top=WK.mix('#2c3e66','#7ec8f5',dl), bot=WK.mix('#4a5f8a','#cdeef9',dl);
    const grd=ctx.createLinearGradient(0,0,0,this.cssH);
    grd.addColorStop(0,top); grd.addColorStop(1,bot);
    ctx.fillStyle=grd; ctx.fillRect(0,0,this.cssW,this.cssH);

    if(world.terrainDirty) this.rebuildTerrain(world);

    // terrain layer
    const tl=cam.worldToScreen(this._terrainOX,this._terrainOY);
    ctx.imageSmoothingEnabled=false;
    ctx.drawImage(this._terrain,
      tl.x,tl.y,
      this._terrainW*cam.zoom, this._terrainH*cam.zoom);
    ctx.imageSmoothingEnabled=true;

    // visible tile range
    const corners=[cam.screenToTile(0,0),cam.screenToTile(this.cssW,0),cam.screenToTile(0,this.cssH),cam.screenToTile(this.cssW,this.cssH)];
    let minX=1e9,maxX=-1e9,minY=1e9,maxY=-1e9;
    for(const c2 of corners){
      minX=Math.min(minX,c2.x); maxX=Math.max(maxX,c2.x);
      minY=Math.min(minY,c2.y); maxY=Math.max(maxY,c2.y);
    }
    minX=Math.max(0,minX-2); maxX=Math.min(world.cols-1,maxX+2);
    minY=Math.max(0,minY-2); maxY=Math.min(world.rows-1,maxY+2);

    // water shimmer (under everything dynamic)
    ctx.save(); ctx.strokeStyle='rgba(255,255,255,.4)'; ctx.lineWidth=1.4;
    const t=game.timeAbs;
    for(let y=minY;y<=maxY;y++)for(let x=minX;x<=maxX;x++){
      if(!D.GROUND[world.gid(x,y)].water) continue;
      const h=WK.hash2(x,y,7);
      const p=cam.project(x,y);
      const ph=(t*(0.6+h)+h*9)%1;
      const a=Math.sin(ph*Math.PI);
      ctx.globalAlpha=a*0.5;
      const ox=(h-0.5)*20;
      ctx.beginPath();
      ctx.moveTo(p.x-10+ox,p.y+(h-0.5)*8);
      ctx.quadraticCurveTo(p.x+ox,p.y+(h-0.5)*8-3,p.x+10+ox,p.y+(h-0.5)*8);
      ctx.stroke();
    }
    ctx.restore();

    // build-mode grid
    if(opt.grid){
      ctx.save(); ctx.strokeStyle='rgba(50,40,20,.14)'; ctx.lineWidth=1;
      for(let y=minY;y<=maxY;y++)for(let x=minX;x<=maxX;x++){
        const p=cam.project(x,y);
        WK.Sprites.diamondPath(ctx,p.x,p.y,0.5); ctx.stroke();
      }
      ctx.restore();
    }

    // region tint overlay
    if(opt.tintRegionId){
      const reg=world.regions.find(r=>r.id===opt.tintRegionId);
      if(reg){
        ctx.fillStyle='rgba(120,220,140,.16)';
        for(const k of reg.tiles){
          const x=k%world.cols, y=(k/world.cols)|0;
          if(x<minX||x>maxX||y<minY||y>maxY) continue;
          const p=cam.project(x,y);
          WK.Sprites.diamondPath(ctx,p.x,p.y,0.5); ctx.fill();
        }
      }
    }

    /* ---------- collect drawables (painter's algorithm) ---------- */
    const draws=[];
    const z=cam.zoom;
    const push=(d,fn)=>draws.push({d,fn});
    for(let y=minY;y<=maxY;y++)for(let x=minX;x<=maxX;x++){
      const k=world.idx(x,y);
      const p=cam.project(x,y);
      // fences — exact diamond-corner geometry:
      //   W corner = C+(-HW,0) · S corner = C+(0,+HH) · E corner = C+(+HW,0)
      // so consecutive collinear edges share endpoints perfectly.
      const fs=world.fenceS[k], fe=world.fenceE[k];
      if(fs){
        push(x+y+0.32,()=>{ WK.Sprites.drawFenceEdge(ctx,p.x-HW*z,p.y,p.x,p.y+HH*z,fs,t); });
      }
      if(fe){
        push(x+y+0.34,()=>{ WK.Sprites.drawFenceEdge(ctx,p.x,p.y+HH*z,p.x+HW*z,p.y,fe,t); });
      }
      // nature objects
      const oid=world.oid(x,y);
      if(oid){
        const spr=WK.Sprites.getObject(oid,world.objVar[k]);
        const scale=(WK.Sprites.objectRealSize[oid]||1);
        push(x+y+0.5+(x-y)*0.001,()=>{
          ctx.save();
          ctx.translate(p.x,p.y);
          const sway=Math.sin(t*1.1+x*1.7+y)*0.02;
          if(scale!==1) ctx.scale(scale,scale);
          ctx.rotate(sway);
          ctx.drawImage(spr.cv,-spr.ax,-spr.ay);
          ctx.restore();
        });
      }
      // buildings
      const b=world.buildingAt(x,y);
      if(b && b.x===x && b.y===y){ // draw once at NW corner tile
        const spr=WK.Sprites.getBuilding(b.type);
        const def=D.BUILDING[b.type];
        const cxT=b.x+def.w/2, cyT=b.y+def.h/2;
        const pc=cam.project(cxT,cyT);
        push(b.x+b.y+def.w+def.h-1.2,()=>{
          ctx.drawImage(spr.cv,pc.x-spr.ax,pc.y-spr.ay);
        });
      }
    }
    // entities
    for(const a of game.animals){
      push(a.x+a.y+0.55,()=>a.draw(ctx,cam,t,game.world.gid(Math.round(a.x),Math.round(a.y))));
    }
    for(const g of game.guests){
      push(g.x+g.y+0.56,()=>g.draw(ctx,cam,t));
    }
    for(const s of game.staff){
      push(s.x+s.y+0.57,()=>s.draw(ctx,cam,t));
    }
    draws.sort((A,B)=>A.d-B.d);
    for(const it of draws) it.fn();

    // particles above world
    game.particles.draw(ctx,cam,t);

    // ghost preview & hover handled by input via callbacks
    if(opt.overlay) opt.overlay(ctx,cam,{minX,maxX,minY,maxY});

    // selection highlight
    if(game.selected&&game.selected.alive!==false&&game.selected.kind==='animal'){
      const s=game.selected;
      const p=cam.project(s.x,s.y);
      const rr=(18*s.size)*(1+Math.sin(t*5)*0.08);
      ctx.strokeStyle='rgba(255,201,60,.95)'; ctx.lineWidth=2.5;
      ctx.beginPath(); ctx.ellipse(p.x,p.y+1,rr,rr*0.42,0,0,TAU); ctx.stroke();
    }

    // lamps glow + night tint
    const dark=1-dl;
    if(dark>0.05){
      const lamps=this.lampsFor(world);
      ctx.save();
      ctx.globalCompositeOperation='screen';
      for(const [lx,ly] of lamps){
        const p=cam.project(lx,ly);
        if(!cam.isVisible(p.x,p.y,80)) continue;
        const rr=46*cam.zoom;
        const gg=ctx.createRadialGradient(p.x,p.y-52*cam.zoom,2,p.x,p.y-52*cam.zoom,rr);
        gg.addColorStop(0,'rgba(255,220,130,'+(0.5*dark)+')');
        gg.addColorStop(1,'rgba(255,220,130,0)');
        ctx.fillStyle=gg;
        ctx.beginPath(); ctx.arc(p.x,p.y-52*cam.zoom,rr,0,TAU); ctx.fill();
      }
      ctx.restore();
      ctx.fillStyle='rgba(24,34,72,'+(dark*0.42)+')';
      ctx.fillRect(0,0,this.cssW,this.cssH);
    }
    // gentle vignette
    const vg=ctx.createRadialGradient(this.cssW/2,this.cssH/2,Math.min(this.cssW,this.cssH)*0.62,this.cssW/2,this.cssH/2,Math.max(this.cssW,this.cssH)*0.78);
    vg.addColorStop(0,'rgba(30,25,10,0)'); vg.addColorStop(1,'rgba(30,25,10,.18)');
    ctx.fillStyle=vg; ctx.fillRect(0,0,this.cssW,this.cssH);
  }

  daylight(minutes){
    // 0 at midnight → 1 at noon, warm golden hours
    const m=minutes/1440;
    const sun=Math.sin((m-0.25)*TAU)*0.5+0.5; // peak at noon
    return Math.pow(WK.clamp(sun*1.15,0,1),0.8);
  }

  /* ---- ghost preview ---- */
  drawGhost(ctx,cam,g){
    if(!g||g.x==null) return;
    const ok=g.valid;
    const col= ok?'rgba(110,220,120,.85)':'rgba(230,80,70,.9)';
    const fill= ok?'rgba(140,230,150,.28)':'rgba(230,90,80,.3)';
    if(g.mode==='tile'){
      const p=cam.project(g.x,g.y);
      WK.Sprites.diamondPath(ctx,p.x,p.y,1);
      ctx.fillStyle=fill; ctx.fill();
      ctx.strokeStyle=col; ctx.lineWidth=2; ctx.stroke();
      if(g.sprite){
        ctx.save(); ctx.globalAlpha=0.75;
        ctx.drawImage(g.sprite.cv,p.x-g.sprite.ax,p.y-g.sprite.ay);
        ctx.restore();
      }
    }else if(g.mode==='foot'){
      const def=D.BUILDING[g.item];
      for(let j=0;j<def.h;j++)for(let i=0;i<def.w;i++){
        const p=cam.project(g.x+i,g.y+j);
        WK.Sprites.diamondPath(ctx,p.x,p.y,1);
        ctx.fillStyle=fill; ctx.fill();
        ctx.strokeStyle=col; ctx.lineWidth=1.6; ctx.stroke();
      }
      const spr=WK.Sprites.getBuilding(g.item);
      const pc=cam.project(g.x+def.w/2,g.y+def.h/2);
      ctx.save(); ctx.globalAlpha=0.75;
      ctx.drawImage(spr.cv,pc.x-spr.ax,pc.y-spr.ay);
      ctx.restore();
    }else if(g.mode==='edge'){
      const p=cam.project(g.x,g.y);
      const gz=cam.zoom;
      let ax,ay,bx,by;
      if(g.edge==='S'){ ax=p.x-HW*gz; ay=p.y; bx=p.x; by=p.y+HH*gz; }
      else{ ax=p.x; ay=p.y+HH*gz; bx=p.x+HW*gz; by=p.y; }
      ctx.strokeStyle=col; ctx.lineWidth=6; ctx.lineCap='round';
      ctx.beginPath(); ctx.moveTo(ax,ay-6); ctx.lineTo(bx,by-6); ctx.stroke();
      ctx.lineWidth=2; ctx.beginPath(); ctx.moveTo(ax,ay-12); ctx.lineTo(bx,by-12); ctx.stroke();
    }
  }
}
WK.Renderer = Renderer;
})();
