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

const COLS=30, ROWS=22, STEP=.19;
type Cell={x:number;y:number};
type Hazard={mesh:THREE.Mesh;vx:number;vz:number;r:number};
const TIERS=[
  {name:"CALM CIRCUIT",at:0,step:.19,hazards:1,color:0x65ffd0},
  {name:"RUSH ZONE",at:100,step:.165,hazards:3,color:0xffe35e},
  {name:"DANGER GRID",at:250,step:.14,hazards:5,color:0xff9f55},
  {name:"VOID STORM",at:500,step:.115,hazards:7,color:0xff5d91}
];

export default function mount(){
  const root=document.querySelector("#game-root") as HTMLElement;
  render(<div className="game"><style>{`
    .game{min-height:100%;display:grid;place-items:center;padding:14px;box-sizing:border-box;color:#ecffff;background:radial-gradient(circle at 50% 8%,#173c55,#06121d 58%,#02060a);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;user-select:none}.wrap{width:min(96vw,920px)}
    .top{display:flex;justify-content:space-between;align-items:end;gap:12px;margin-bottom:8px}h1{margin:0;color:#76ffd2;font-size:clamp(22px,4vw,36px);letter-spacing:.09em}.sub{margin:3px 0 0;color:#829eae;font-size:10px}.stats{display:flex;gap:14px;text-align:right}.stat span{display:block;color:#7895a4;font-size:8px;letter-spacing:.14em}.stat strong{font-size:18px}.combo{color:#ffe35e}
    .mission{display:flex;align-items:center;gap:9px;min-height:26px;color:#b9d3df;font-size:10px}.bar{flex:1;height:7px;overflow:hidden;border:1px solid #294e61;border-radius:10px;background:#071019}.fill{height:100%;background:#65ffd0;box-shadow:0 0 12px currentColor;transition:width .2s}.stage{position:relative;overflow:hidden;aspect-ratio:16/10;border:2px solid #2b6c89;border-radius:12px;background:#050d14;box-shadow:0 18px 55px #000b}.view,.view canvas{width:100%;height:100%;display:block;touch-action:none}
    .overlay{position:absolute;inset:0;display:none;place-items:center;text-align:center;background:#03101be8;backdrop-filter:blur(3px);z-index:4}.overlay.show{display:grid}.card h2{margin:0;color:#ff668f;font-size:clamp(29px,6vw,50px)}.card p{color:#bfd1dc;line-height:1.6}.button{border:1px solid #70f5cc;border-radius:7px;padding:9px 17px;color:#031510;background:#70f5cc;font:bold 12px inherit;cursor:pointer;box-shadow:0 0 18px #70f5cc55}.toast{position:absolute;left:50%;top:13px;z-index:3;transform:translateX(-50%);opacity:0;padding:7px 13px;border:1px solid currentColor;border-radius:999px;background:#06121dee;font:bold 11px inherit;pointer-events:none;transition:opacity .15s}.toast.on{opacity:1}
    .legend{display:flex;flex-wrap:wrap;gap:12px;margin:7px 0;color:#91adba;font-size:9px}.footer{display:flex;align-items:center;justify-content:space-between;gap:10px;color:#8aa9ba;font-size:10px}.keys{display:grid;grid-template-columns:repeat(3,38px);gap:3px}.key{height:31px;border:1px solid #31566a;border-radius:6px;color:#d5fbff;background:#102635;font:bold 14px inherit;cursor:pointer;touch-action:manipulation}.key[data-dir="up"]{grid-column:2}.key[data-dir="left"]{grid-column:1;grid-row:2}.key[data-dir="down"]{grid-column:2;grid-row:2}.key[data-dir="right"]{grid-column:3;grid-row:2}@media(max-width:600px){.stats{gap:7px}.stat strong{font-size:14px}.footer>span,.sub{display:none}}
  `}</style><main className="wrap">
    <header className="top"><div><h1>SUPER SNAKE 3D</h1><p className="sub">WEBGL ARENA · ESCALATING HAZARDS · COMBO ENERGY</p></div><div className="stats"><div className="stat"><span>SCORE</span><strong id="score">0</strong></div><div className="stat"><span>COMBO</span><strong id="combo" className="combo">×1</strong></div><div className="stat"><span>TIER</span><strong id="tier">1</strong></div><div className="stat"><span>BEST</span><strong id="best">0</strong></div></div></header>
    <div className="mission"><b id="tierName">CALM CIRCUIT</b><div className="bar"><div id="fill" className="fill"/></div><span id="objective">Next tier: 100</span></div>
    <section className="stage"><div id="view" className="view"/><div id="toast" className="toast"/><div id="overlay" className="overlay"><div className="card"><h2>RUN ENDED</h2><p id="result"/><button id="retry" className="button">RETRY RUN</button></div></div></section>
    <div className="legend"><span>● gold energy</span><span>◆ cyan shield core</span><span>● red moving hazard</span><span>Shield absorbs one impact</span></div>
    <footer className="footer"><span>Arrows/WASD · Swipe · Collect energy · Survive</span><div className="keys"><button className="key" data-dir="up">▲</button><button className="key" data-dir="left">◀</button><button className="key" data-dir="down">▼</button><button className="key" data-dir="right">▶</button></div><button id="restart" className="button">RESTART</button></footer>
  </main></div>,root);

  const get=(s:string)=>root.querySelector(s) as HTMLElement;
  const view=get("#view"),overlay=get("#overlay"),toast=get("#toast"),result=get("#result");
  const renderer=new THREE.WebGLRenderer({antialias:true});renderer.setPixelRatio(Math.min(devicePixelRatio,2));renderer.shadowMap.enabled=true;renderer.outputColorSpace=THREE.SRGBColorSpace;view.appendChild(renderer.domElement);
  const scene=new THREE.Scene();scene.background=new THREE.Color(0x050d14);scene.fog=new THREE.Fog(0x050d14,24,47);
  const camera=new THREE.PerspectiveCamera(47,1,.1,100);camera.position.set(0,25,22);camera.lookAt(0,0,0);
  scene.add(new THREE.HemisphereLight(0x9defff,0x081018,1.5));const sun=new THREE.DirectionalLight(0xffffff,2);sun.position.set(-8,18,10);sun.castShadow=true;scene.add(sun);
  const floor=new THREE.Mesh(new THREE.PlaneGeometry(COLS,ROWS),new THREE.MeshStandardMaterial({color:0x081923,roughness:.75,metalness:.2}));floor.rotation.x=-Math.PI/2;floor.receiveShadow=true;scene.add(floor);
  const grid=new THREE.GridHelper(Math.max(COLS,ROWS),Math.max(COLS,ROWS),0x277a88,0x163946);grid.position.y=.012;grid.scale.z=ROWS/COLS;scene.add(grid);
  const wallMat=new THREE.MeshStandardMaterial({color:0x17485d,emissive:0x062b3d,metalness:.5});
  [[0,-ROWS/2-.3,COLS+.8,.5],[0,ROWS/2+.3,COLS+.8,.5],[-COLS/2-.3,0,.5,ROWS+.8],[COLS/2+.3,0,.5,ROWS+.8]].forEach(([x,z,w,d])=>{const m=new THREE.Mesh(new THREE.BoxGeometry(w,.7,d),wallMat);m.position.set(x,.35,z);scene.add(m)});
  const snakeGroup=new THREE.Group(),hazardGroup=new THREE.Group(),pickupGroup=new THREE.Group();scene.add(snakeGroup,hazardGroup,pickupGroup);
  const bodyGeo=new THREE.BoxGeometry(.82,.72,.82),headGeo=new THREE.BoxGeometry(.94,.9,.94),hazGeo=new THREE.IcosahedronGeometry(.48,1),energyGeo=new THREE.OctahedronGeometry(.42),bonusGeo=new THREE.OctahedronGeometry(.55);
  const bodyMat=new THREE.MeshStandardMaterial({color:0x36c993,emissive:0x073c2e,roughness:.32}),headMat=new THREE.MeshStandardMaterial({color:0xdffff5,emissive:0x299d78}),hazMat=new THREE.MeshStandardMaterial({color:0xff385e,emissive:0x8a0927}),energyMat=new THREE.MeshStandardMaterial({color:0xffeb62,emissive:0x8b6400}),bonusMat=new THREE.MeshStandardMaterial({color:0x73e7ff,emissive:0x086d8d});
  const energyMesh=new THREE.Mesh(energyGeo,energyMat),bonusMesh=new THREE.Mesh(bonusGeo,bonusMat);energyMesh.castShadow=bonusMesh.castShadow=true;pickupGroup.add(energyMesh,bonusMesh);
  let snake:Cell[]=[],energy:Cell={x:0,y:0},bonus:Cell|null=null,hazards:Hazard[]=[];
  let dir:Cell={x:1,y:0},queued:Cell={x:1,y:0},score=0,best=0,combo=1,tier=0,shield=0,alive=true,acc=0,last=performance.now(),raf=0,touch:Cell|null=null,comboUntil=0,bonusUntil=0,toastUntil=0,audio:AudioContext|null=null;
  const pos=(p:Cell)=>({x:p.x-COLS/2+.5,z:p.y-ROWS/2+.5});
  function tone(f:number,d=.08){try{audio||=new AudioContext();const o=audio.createOscillator(),g=audio.createGain();o.frequency.value=f;g.gain.setValueAtTime(.06,audio.currentTime);g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+d);o.connect(g);g.connect(audio.destination);o.start();o.stop(audio.currentTime+d)}catch{}}
  function occupied(p:Cell){const q=pos(p);return snake.some(s=>s.x===p.x&&s.y===p.y)||hazards.some(h=>Math.hypot(h.mesh.position.x-q.x,h.mesh.position.z-q.z)<1.2)}
  function empty(){let p:Cell;do p={x:Math.floor(Math.random()*COLS),y:Math.floor(Math.random()*ROWS)};while(occupied(p)||(p.x===energy.x&&p.y===energy.y));return p}
  function addHazard(){const mesh=new THREE.Mesh(hazGeo,hazMat);mesh.castShadow=true;mesh.position.set((Math.random()-.5)*(COLS-4),.6,(Math.random()-.5)*(ROWS-4));hazardGroup.add(mesh);const pace=2.1+tier*.45;hazards.push({mesh,vx:(Math.random()<.5?-1:1)*pace,vz:(Math.random()<.5?-1:1)*pace,r:.62})}
  function announce(s:string,c:string,now:number){toast.textContent=s;toast.style.color=c;toast.classList.add("on");toastUntil=now+1500}
  function updateUI(){get("#score").textContent=String(score);get("#combo").textContent=`×${combo}`;get("#tier").textContent=String(tier+1);get("#best").textContent=String(best);get("#tierName").textContent=TIERS[tier].name;const next=TIERS[tier+1],fill=get("#fill");fill.style.background=`#${TIERS[tier].color.toString(16).padStart(6,"0")}`;fill.style.width=next?`${Math.min(100,(score-TIERS[tier].at)/(next.at-TIERS[tier].at)*100)}%`:"100%";get("#objective").textContent=next?`Next tier: ${next.at}`:"FINAL TIER · SURVIVE!"}
  function syncSnake(){while(snakeGroup.children.length>snake.length)snakeGroup.remove(snakeGroup.children.at(-1)!);while(snakeGroup.children.length<snake.length){const i=snakeGroup.children.length,m=new THREE.Mesh(i?bodyGeo:headGeo,i?bodyMat:headMat);m.castShadow=true;snakeGroup.add(m)}snake.forEach((p,i)=>{const q=pos(p),m=snakeGroup.children[i] as THREE.Mesh;m.position.set(q.x,.5,q.z);m.scale.setScalar(Math.max(.68,1-i*.012))})}
  function reset(){snake=Array.from({length:5},(_,i)=>({x:15-i,y:11}));dir=queued={x:1,y:0};score=combo=tier=0;combo=1;shield=0;alive=true;acc=0;last=performance.now();bonus=null;bonusMesh.visible=false;hazards.forEach(h=>hazardGroup.remove(h.mesh));hazards=[];addHazard();energy=empty();overlay.classList.remove("show");toast.classList.remove("on");syncSnake();updateUI()}
  function steer(d:Cell){if(alive&&!(d.x===-dir.x&&d.y===-dir.y))queued=d}
  function finish(){alive=false;best=Math.max(best,score);result.textContent=`Score ${score} · Length ${snake.length} · Tier ${tier+1}`;overlay.classList.add("show");tone(90,.4);updateUI()}
  function step(now:number){dir=queued;const h=snake[0],n={x:h.x+dir.x,y:h.y+dir.y},q=pos(n),eat=n.x===energy.x&&n.y===energy.y;const crash=n.x<0||n.x>=COLS||n.y<0||n.y>=ROWS||(eat?snake:snake.slice(0,-1)).some(p=>p.x===n.x&&p.y===n.y)||hazards.some(z=>Math.hypot(z.mesh.position.x-q.x,z.mesh.position.z-q.z)<1.05);if(crash){if(shield){shield--;dir=queued={x:-dir.x,y:-dir.y};announce("SHIELD ABSORBED IMPACT","#69dcff",now);return}finish();return}snake.unshift(n);if(eat){combo=now<comboUntil?Math.min(5,combo+1):1;comboUntil=now+2600;score+=10*combo;energy=empty();if(!bonus&&score>=60&&Math.random()<.4){bonus=empty();bonusUntil=now+6500;bonusMesh.visible=true}tone(600+combo*70)}else snake.pop();if(bonus&&n.x===bonus.x&&n.y===bonus.y){score+=35*combo;shield++;bonus=null;bonusMesh.visible=false;announce("CYAN CORE · SHIELD EARNED","#69dcff",now);tone(1050,.18)}const old=tier;while(tier<TIERS.length-1&&score>=TIERS[tier+1].at)tier++;if(tier!==old){shield++;while(hazards.length<TIERS[tier].hazards)addHazard();announce(`${TIERS[tier].name} · SHIELD REWARD`,`#${TIERS[tier].color.toString(16)}`,now)}best=Math.max(best,score);syncSnake();updateUI()}
  function resize(){const r=view.getBoundingClientRect();renderer.setSize(r.width,r.height,false);camera.aspect=r.width/r.height;camera.updateProjectionMatrix()}
  function loop(now:number){const dt=Math.min(.05,(now-last)/1000);last=now;if(alive){acc+=dt;hazards.forEach(h=>{h.mesh.position.x+=h.vx*dt;h.mesh.position.z+=h.vz*dt;if(Math.abs(h.mesh.position.x)>COLS/2-1){h.vx*=-1;h.mesh.position.x=Math.sign(h.mesh.position.x)*(COLS/2-1)}if(Math.abs(h.mesh.position.z)>ROWS/2-1){h.vz*=-1;h.mesh.position.z=Math.sign(h.mesh.position.z)*(ROWS/2-1)}h.mesh.rotation.x+=dt*2;h.mesh.rotation.y+=dt*3});while(acc>=TIERS[tier].step&&alive){step(now);acc-=TIERS[tier].step}if(bonus&&now>bonusUntil){bonus=null;bonusMesh.visible=false;combo=1;updateUI()}if(combo>1&&now>comboUntil){combo=1;updateUI()}}const ep=pos(energy);energyMesh.position.set(ep.x,.65+Math.sin(now*.006)*.12,ep.z);energyMesh.rotation.y+=dt*2;if(bonus){const bp=pos(bonus);bonusMesh.position.set(bp.x,.8+Math.sin(now*.008)*.15,bp.z);bonusMesh.rotation.y-=dt*3}headMat.emissive.setHex(shield?0x168fb5:0x299d78);if(now>toastUntil)toast.classList.remove("on");renderer.render(scene,camera);raf=requestAnimationFrame(loop)}
  const dirs:Record<string,Cell>={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}};
  const onKey=(e:KeyboardEvent)=>{if(dirs[e.key]){e.preventDefault();steer(dirs[e.key])}if(!alive&&(e.key==="Enter"||e.key===" "))reset()};
  const down=(e:PointerEvent)=>{touch={x:e.clientX,y:e.clientY};renderer.domElement.setPointerCapture(e.pointerId)},up=(e:PointerEvent)=>{if(!touch)return;const dx=e.clientX-touch.x,dy=e.clientY-touch.y;touch=null;if(Math.max(Math.abs(dx),Math.abs(dy))>15)steer(Math.abs(dx)>Math.abs(dy)?{x:Math.sign(dx),y:0}:{x:0,y:Math.sign(dy)})};
  const handlers=Array.from(root.querySelectorAll("[data-dir]")).map(el=>{const b=el as HTMLButtonElement,map:Record<string,Cell>={up:{x:0,y:-1},down:{x:0,y:1},left:{x:-1,y:0},right:{x:1,y:0}},h=(e:Event)=>{e.preventDefault();steer(map[b.dataset.dir!])};b.addEventListener("pointerdown",h);return{b,h}});const restart=get("#restart"),retry=get("#retry");
  window.addEventListener("keydown",onKey,{passive:false});window.addEventListener("resize",resize);renderer.domElement.addEventListener("pointerdown",down);renderer.domElement.addEventListener("pointerup",up);restart.addEventListener("click",reset);retry.addEventListener("click",reset);resize();reset();raf=requestAnimationFrame(loop);
  return()=>{cancelAnimationFrame(raf);window.removeEventListener("keydown",onKey);window.removeEventListener("resize",resize);renderer.domElement.removeEventListener("pointerdown",down);renderer.domElement.removeEventListener("pointerup",up);restart.removeEventListener("click",reset);retry.removeEventListener("click",reset);handlers.forEach(({b,h})=>b.removeEventListener("pointerdown",h));scene.traverse(o=>{const m=o as THREE.Mesh;m.geometry?.dispose();const mat=m.material as THREE.Material|THREE.Material[];if(Array.isArray(mat))mat.forEach(x=>x.dispose());else mat?.dispose()});renderer.dispose();renderer.domElement.remove();audio?.close();render(null,root)};
}