import { render } from "tradjs/client";
import * as THREE from "three";

export default function mount() {
  const root = document.querySelector("#game-root");
  if (!root) return () => {};

  const shell = document.createElement("div");
  shell.style.cssText = "min-height:100vh;background:#050d0b;color:#eafff3;font-family:ui-monospace,monospace;display:grid;place-items:center;padding:16px;box-sizing:border-box";
  render(
    <main style={{ width: "min(94vw,800px)", textAlign: "center" }}>
      <h1 style={{ margin: "0 0 4px", color: "#72f5a5", letterSpacing: "3px" }}>CLOSING COIL 3D</h1>
      <p style={{ margin: "0 0 10px", color: "#9fc5b0" }}>Dodge patrols. Risk powerups. Outlive the closing walls.</p>
      <section style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: "6px", marginBottom: "8px", fontWeight: "bold" }}>
        <span>Score <b id="score">0</b></span><span>Combo <b id="combo">—</b></span>
        <span>Arena <b id="walls">100%</b></span><span>Best <b id="best">0</b></span>
      </section>
      <div id="status" style={{ minHeight: "22px", marginBottom: "6px", color: "#9fc5b0", fontWeight: "bold" }}>No power active</div>
      <div id="stage" style={{ position: "relative", aspectRatio: "4/3", overflow: "hidden", background: "#07110e", border: "2px solid #37664d", boxShadow: "0 0 30px rgba(50,220,130,.15)", touchAction: "none" }}>
        <div id="banner" style={{ position: "absolute", zIndex: "2", top: "8%", left: "50%", transform: "translateX(-50%)", fontWeight: "bold", fontSize: "clamp(14px,3vw,22px)", textShadow: "0 2px 8px #000", pointerEvents: "none", opacity: "0", whiteSpace: "nowrap" }} />
        <div id="message" style={{ position: "absolute", zIndex: "3", inset: "0", display: "none", placeItems: "center", background: "rgba(2,8,6,.78)", fontSize: "clamp(20px,5vw,36px)", fontWeight: "bold", whiteSpace: "pre-line" }} />
      </div>
      <div style={{ display: "flex", justifyContent: "center", gap: "10px", marginTop: "12px", flexWrap: "wrap" }}>
        <button id="restart" style={{ padding: "10px 20px", border: "0", borderRadius: "6px", background: "#72f5a5", color: "#082016", fontWeight: "bold", cursor: "pointer" }}>Restart / Retry</button>
        <span style={{ alignSelf: "center", color: "#9fc5b0" }}>Arrows / WASD · Swipe · R restarts</span>
      </div>
      <p style={{ color: "#789d89", fontSize: "12px" }}>◆ Shield blocks one hit · » Speed scores faster · ❄ Freeze delays the walls</p>
    </main>, shell
  );
  root.replaceChildren(shell);

  const stage = shell.querySelector("#stage") as HTMLElement;
  const get = (id: string) => shell.querySelector(id) as HTMLElement;
  const scoreEl=get("#score"), comboEl=get("#combo"), wallsEl=get("#walls"), bestEl=get("#best");
  const status=get("#status"), banner=get("#banner"), message=get("#message");
  const restart=get("#restart") as HTMLButtonElement;
  const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
  renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
  renderer.setClearColor(0x050d0b);
  renderer.domElement.style.cssText = "width:100%;height:100%;display:block;touch-action:none";
  stage.prepend(renderer.domElement);

  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x050d0b);
  scene.fog = new THREE.Fog(0x050d0b, 30, 54);
  const camera = new THREE.PerspectiveCamera(48, 4/3, .1, 100);
  camera.position.set(0, 30, 31);
  camera.lookAt(0, 0, 0);
  scene.add(new THREE.HemisphereLight(0xb8ffe0, 0x06100c, 1.5));
  const key = new THREE.DirectionalLight(0xffffff, 1.5); key.position.set(-12, 24, 18); scene.add(key);

  const COLS=36, ROWS=27, CELL=.72;
  type Point={x:number;y:number};
  type Hazard=Point&{dx:number;dy:number;phase:number};
  type Power=Point&{kind:"shield"|"speed"|"freeze";life:number};
  type Spark={mesh:THREE.Mesh;vx:number;vy:number;vz:number;life:number};
  const worldX=(x:number)=>(x-(COLS-1)/2)*CELL;
  const worldZ=(y:number)=>(y-(ROWS-1)/2)*CELL;
  const floorMat=new THREE.MeshStandardMaterial({color:0x10231c,roughness:.86,metalness:.05});
  const floor=new THREE.Mesh(new THREE.BoxGeometry(COLS*CELL,.24,ROWS*CELL),floorMat); floor.position.y=-.2; scene.add(floor);
  const grid=new THREE.GridHelper(Math.max(COLS,ROWS)*CELL,COLS,0x285441,0x17352b); grid.position.y=-.065; scene.add(grid);
  const wallMat=new THREE.MeshStandardMaterial({color:0xff4d42,emissive:0x651009,emissiveIntensity:.5,roughness:.38});
  const walls=[0,1,2,3].map(()=>{const m=new THREE.Mesh(new THREE.BoxGeometry(1,1,1),wallMat);scene.add(m);return m;});
  const foodMat=new THREE.MeshStandardMaterial({color:0xff745f,emissive:0xff2418,emissiveIntensity:1.5});
  const foodMesh=new THREE.Mesh(new THREE.IcosahedronGeometry(.3,1),foodMat); scene.add(foodMesh);
  const snakeGeo=new THREE.BoxGeometry(CELL*.78,.5,CELL*.78);
  const headMat=new THREE.MeshStandardMaterial({color:0xeafff3,emissive:0x319b61,emissiveIntensity:.55});
  const bodyMat=new THREE.MeshStandardMaterial({color:0x43d77b,emissive:0x0d562c,emissiveIntensity:.45});
  const hazardGeo=new THREE.OctahedronGeometry(.4);
  const hazardMat=new THREE.MeshStandardMaterial({color:0xff405c,emissive:0xff102d,emissiveIntensity:1.25,metalness:.3});
  const powerGeo=new THREE.TorusKnotGeometry(.25,.08,32,6);
  const powerMats={shield:new THREE.MeshStandardMaterial({color:0x68d9ff,emissive:0x168bb6,emissiveIntensity:1.3}),speed:new THREE.MeshStandardMaterial({color:0xffe36e,emissive:0x9e7410,emissiveIntensity:1.3}),freeze:new THREE.MeshStandardMaterial({color:0xb999ff,emissive:0x5932aa,emissiveIntensity:1.3})};
  const snakeMeshes:THREE.Mesh[]=[], hazardMeshes:THREE.Mesh[]=[];
  let powerMesh:THREE.Mesh|null=null, shieldRing:THREE.Mesh|null=null, sparks:Spark[]=[];
  let snake:Point[]=[], food:Point={x:0,y:0}, hazards:Hazard[]=[], power:Power|null=null;
  let dir:Point={x:1,y:0}, queued:Point={x:1,y:0}, pointerStart:Point|null=null;
  let score=0,best=0,combo=0,comboTime=0,inset=0,elapsed=0,stepClock=0,hazardClock=0;
  let last=performance.now(),running=true,raf=0,warning=0,shake=0,bannerTime=0;
  let shield=0,speedTime=0,freezeTime=0,nextPower=6500,nextHazard=4500;

  const same=(a:Point,b:Point)=>a.x===b.x&&a.y===b.y;
  const blocked=(x:number,y:number)=>x<inset||x>=COLS-inset||y<inset||y>=ROWS-inset;
  const occupied=(p:Point)=>snake.some(s=>same(s,p))||hazards.some(h=>same(h,p))||same(food,p);
  function randomOpen(){const open:Point[]=[];for(let y=inset;y<ROWS-inset;y++)for(let x=inset;x<COLS-inset;x++){const p={x,y};if(!occupied(p)&&(!power||!same(power,p)))open.push(p);}return open.length?open[Math.floor(Math.random()*open.length)]:null;}
  function placeFood(){const p=randomOpen();if(p){food=p;foodMesh.position.set(worldX(p.x),.35,worldZ(p.y));}else finish("ARENA MASTERED!");}
  function announce(text:string,color="#ffcb6b"){banner.textContent=text;banner.style.color=color;bannerTime=1200;}
  function updateWalls(){
    const left=worldX(inset)-CELL/2,right=worldX(COLS-1-inset)+CELL/2,top=worldZ(inset)-CELL/2,bottom=worldZ(ROWS-1-inset)+CELL/2;
    walls[0].position.set((left+right)/2,.48,top);walls[0].scale.set(right-left+.3,1.1,.28);
    walls[1].position.set((left+right)/2,.48,bottom);walls[1].scale.set(right-left+.3,1.1,.28);
    walls[2].position.set(left,.48,(top+bottom)/2);walls[2].scale.set(.28,1.1,bottom-top+.3);
    walls[3].position.set(right,.48,(top+bottom)/2);walls[3].scale.set(.28,1.1,bottom-top+.3);
  }
  function syncSnake(){while(snakeMeshes.length<snake.length){const m=new THREE.Mesh(snakeGeo,bodyMat);scene.add(m);snakeMeshes.push(m);}while(snakeMeshes.length>snake.length)scene.remove(snakeMeshes.pop()!);snakeMeshes.forEach((m,i)=>{m.material=i===0?headMat:bodyMat;m.position.set(worldX(snake[i].x),.27,worldZ(snake[i].y));m.rotation.y=i*.08;});}
  function syncHazards(){while(hazardMeshes.length<hazards.length){const m=new THREE.Mesh(hazardGeo,hazardMat);scene.add(m);hazardMeshes.push(m);}while(hazardMeshes.length>hazards.length)scene.remove(hazardMeshes.pop()!);hazardMeshes.forEach((m,i)=>m.position.set(worldX(hazards[i].x),.43,worldZ(hazards[i].y)));}
  function burst(p:Point,color:number){for(let i=0;i<12;i++){const mat=new THREE.MeshBasicMaterial({color});const mesh=new THREE.Mesh(new THREE.BoxGeometry(.09,.09,.09),mat);mesh.position.set(worldX(p.x),.45,worldZ(p.y));scene.add(mesh);const a=Math.random()*Math.PI*2,s=1.5+Math.random()*3;sparks.push({mesh,vx:Math.cos(a)*s,vy:2+Math.random()*3,vz:Math.sin(a)*s,life:450+Math.random()*300});}}
  function spawnHazard(){const p=randomOpen();if(!p||hazards.length>=7)return;const h=Math.random()<.5;hazards.push({...p,dx:h?(Math.random()<.5?-1:1):0,dy:h?0:(Math.random()<.5?-1:1),phase:Math.random()*6});syncHazards();announce("PATROL ENTERING","#ff8e78");}
  function spawnPower(){const p=randomOpen();if(!p)return;const kinds:Power["kind"][]=["shield","speed","freeze"];const kind=kinds[Math.floor(Math.random()*3)];power={...p,kind,life:7000};powerMesh=new THREE.Mesh(powerGeo,powerMats[kind]);powerMesh.position.set(worldX(p.x),.5,worldZ(p.y));scene.add(powerMesh);announce("RISKY POWERUP!","#8edbff");}
  function clearPower(){if(powerMesh){scene.remove(powerMesh);powerMesh=null;}power=null;}
  function finish(title:string){if(!running)return;running=false;shake=500;best=Math.max(best,score);bestEl.textContent=String(best);message.textContent=`${title}\nScore ${score}\nPress R or Retry`;message.style.display="grid";}
  function hit(reason:string){if(shield){shield=0;shake=280;announce("SHIELD SHATTERED!","#70dcff");return true;}finish(reason);return false;}
  function collectPower(){if(!power)return;burst(power,power.kind==="shield"?0x68d9ff:power.kind==="speed"?0xffe36e:0xb999ff);if(power.kind==="shield")shield=1;if(power.kind==="speed")speedTime=6500;if(power.kind==="freeze")freezeTime=7000;score+=25;scoreEl.textContent=String(score);announce(`${power.kind.toUpperCase()} ACTIVE +25`,"#aeeaff");clearPower();}
  function updateSnake(){dir=queued;const head={x:snake[0].x+dir.x,y:snake[0].y+dir.y};const eating=same(head,food),body=eating?snake:snake.slice(0,-1);const danger=blocked(head.x,head.y)||body.some(p=>same(p,head))||hazards.some(h=>same(h,head));if(danger){if(hit("CRUSHED!")){queued=dir={x:-dir.x,y:-dir.y};}return;}snake.unshift(head);if(power&&same(head,power))collectPower();if(eating){burst(food,0xff745f);combo=comboTime>0?combo+1:1;comboTime=3600;const gained=10*combo*(speedTime>0?2:1);score+=gained;scoreEl.textContent=String(score);comboEl.textContent=`x${combo}`;announce(`CRUNCH x${combo} +${gained}`,combo>2?"#ffe36e":"#ff9b7d");placeFood();}else snake.pop();syncSnake();}
  function moveHazards(){hazards.forEach(h=>{let nx=h.x+h.dx,ny=h.y+h.dy;if(blocked(nx,ny)){h.dx=-h.dx;h.dy=-h.dy;nx=h.x+h.dx;ny=h.y+h.dy;}if(!blocked(nx,ny)){h.x=nx;h.y=ny;}if(snake.some(s=>same(s,h)))hit("PATROL COLLISION!");});syncHazards();}
  function contract(){inset++;warning=0;shake=260;wallsEl.textContent=`${Math.round(((COLS-inset*2)/COLS)*100)}%`;announce("WALLS CLOSING!","#ff665c");hazards=hazards.filter(h=>!blocked(h.x,h.y));syncHazards();if(power&&blocked(power.x,power.y))clearPower();updateWalls();if(snake.some(p=>blocked(p.x,p.y)))hit("THE WALLS GOT YOU!");else if(blocked(food.x,food.y))placeFood();}
  function reset(){snake=[{x:10,y:13},{x:9,y:13},{x:8,y:13}];dir=queued={x:1,y:0};score=combo=comboTime=inset=elapsed=stepClock=hazardClock=warning=shake=bannerTime=0;shield=speedTime=freezeTime=0;nextPower=6500;nextHazard=4500;hazards=[];clearPower();sparks.forEach(s=>{scene.remove(s.mesh);s.mesh.geometry.dispose();(s.mesh.material as THREE.Material).dispose();});sparks=[];running=true;last=performance.now();message.style.display="none";scoreEl.textContent="0";comboEl.textContent="—";wallsEl.textContent="100%";status.textContent="No power active";syncSnake();syncHazards();updateWalls();placeFood();}
  function steer(next:Point){if(next.x!==-dir.x||next.y!==-dir.y)queued=next;}
  function resize(){const w=Math.max(1,stage.clientWidth),h=Math.max(1,stage.clientHeight);renderer.setSize(w,h,false);camera.aspect=w/h;camera.updateProjectionMatrix();}
  function frame(now:number){const dt=Math.min(50,now-last);last=now;if(running){elapsed+=dt;stepClock+=dt;hazardClock+=dt;comboTime=Math.max(0,comboTime-dt);speedTime=Math.max(0,speedTime-dt);freezeTime=Math.max(0,freezeTime-dt);if(power){power.life-=dt;if(power.life<=0)clearPower();}if(!comboTime&&combo){combo=0;comboEl.textContent="—";}nextPower-=dt;nextHazard-=dt;if(nextPower<=0&&!power){spawnPower();nextPower=9000+Math.random()*5000;}if(nextHazard<=0){spawnHazard();nextHazard=7500;}const wallElapsed=Math.max(0,elapsed-freezeTime),cycle=wallElapsed%12000;warning=inset<10&&freezeTime<=0&&cycle>=9500?12000-cycle:0;if(inset<10&&freezeTime<=0&&wallElapsed>=(inset+1)*12000)contract();const moveSpeed=Math.max(58,132-Math.floor(score/50)*3-inset*3)*(speedTime>0?.72:1);while(running&&stepClock>=moveSpeed){stepClock-=moveSpeed;updateSnake();}while(running&&hazardClock>=260){hazardClock-=260;moveHazards();}status.textContent=shield?"◆ SHIELD READY":speedTime>0?`» SPEED ${Math.ceil(speedTime/1000)}s`:freezeTime>0?`❄ WALL FREEZE ${Math.ceil(freezeTime/1000)}s`:warning>0?`CONTRACTION IN ${(warning/1000).toFixed(1)}s`:"No power active";status.style.color=shield?"#68d9ff":speedTime>0?"#ffe36e":freezeTime>0?"#b999ff":warning>0?"#ff8e78":"#9fc5b0";}bannerTime=Math.max(0,bannerTime-dt);banner.style.opacity=bannerTime>0?String(Math.min(1,bannerTime/180)):"0";foodMesh.rotation.x+=dt*.002;foodMesh.rotation.y+=dt*.004;foodMesh.scale.setScalar(1+Math.sin(elapsed/100)*.12);hazardMeshes.forEach((m,i)=>{m.rotation.x+=dt*.005;m.rotation.y+=dt*.007;m.position.y=.43+Math.sin(elapsed/150+hazards[i].phase)*.1;});if(powerMesh){powerMesh.rotation.x+=dt*.003;powerMesh.rotation.y+=dt*.006;powerMesh.position.y=.5+Math.sin(elapsed/120)*.12;}if(shield){if(!shieldRing){shieldRing=new THREE.Mesh(new THREE.TorusGeometry(.65,.045,8,32),new THREE.MeshBasicMaterial({color:0x68d9ff}));shieldRing.rotation.x=Math.PI/2;scene.add(shieldRing);}shieldRing.visible=true;shieldRing.position.copy(snakeMeshes[0].position);shieldRing.rotation.z+=dt*.004;}else if(shieldRing)shieldRing.visible=false;sparks.forEach(s=>{s.mesh.position.x+=s.vx*dt/1000;s.mesh.position.y+=s.vy*dt/1000;s.mesh.position.z+=s.vz*dt/1000;s.vy-=7*dt/1000;s.life-=dt;});sparks=sparks.filter(s=>{if(s.life>0)return true;scene.remove(s.mesh);s.mesh.geometry.dispose();(s.mesh.material as THREE.Material).dispose();return false;});const j=shake>0?(Math.random()-.5)*.18:0;camera.position.x=j;camera.position.y=30+j;shake=Math.max(0,shake-dt);wallMat.emissiveIntensity=warning>0?.9+Math.sin(elapsed/70)*.5:freezeTime>0?.2:.5;renderer.render(scene,camera);raf=requestAnimationFrame(frame);}
  function keydown(e:KeyboardEvent){const keys:Record<string,Point>={ArrowUp:{x:0,y:-1},w:{x:0,y:-1},W:{x:0,y:-1},ArrowDown:{x:0,y:1},s:{x:0,y:1},S:{x:0,y:1},ArrowLeft:{x:-1,y:0},a:{x:-1,y:0},A:{x:-1,y:0},ArrowRight:{x:1,y:0},d:{x:1,y:0},D:{x:1,y:0}};if(keys[e.key]){e.preventDefault();steer(keys[e.key]);}if(e.key==="r"||e.key==="R")reset();}
  function pointerDown(e:PointerEvent){pointerStart={x:e.clientX,y:e.clientY};renderer.domElement.setPointerCapture(e.pointerId);}
  function pointerUp(e:PointerEvent){if(!pointerStart)return;const dx=e.clientX-pointerStart.x,dy=e.clientY-pointerStart.y;if(Math.max(Math.abs(dx),Math.abs(dy))>16)steer(Math.abs(dx)>Math.abs(dy)?{x:Math.sign(dx),y:0}:{x:0,y:Math.sign(dy)});pointerStart=null;}
  window.addEventListener("keydown",keydown);window.addEventListener("resize",resize);renderer.domElement.addEventListener("pointerdown",pointerDown);renderer.domElement.addEventListener("pointerup",pointerUp);restart.addEventListener("click",reset);
  resize();reset();raf=requestAnimationFrame(frame);
  return()=>{cancelAnimationFrame(raf);window.removeEventListener("keydown",keydown);window.removeEventListener("resize",resize);renderer.domElement.removeEventListener("pointerdown",pointerDown);renderer.domElement.removeEventListener("pointerup",pointerUp);restart.removeEventListener("click",reset);scene.traverse(o=>{const m=o as THREE.Mesh;if(m.geometry)m.geometry.dispose();if(m.material){const mats=Array.isArray(m.material)?m.material:[m.material];mats.forEach(x=>x.dispose());}});renderer.dispose();renderer.domElement.remove();render(null,shell);shell.remove();};
}