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

type Point = { x: number; y: number };
type Direction = "up" | "down" | "left" | "right";
type Power = "shrink" | "freeze" | "phase";

export default function mount() {
  const root = document.querySelector<HTMLElement>("#game-root");
  if (!root) throw new Error("Missing #game-root");

  const dispose = render(
    <main class="game-shell">
      <style>{`
        * { box-sizing:border-box }
        .game-shell { min-height:100vh; display:grid; place-items:center; padding:14px; color:#eefcff; background:radial-gradient(circle at top,#18394e,#050b11 72%); font-family:ui-monospace,SFMono-Regular,Menlo,monospace }
        .card { width:min(96vw,760px); padding:14px; border:1px solid #315c70; border-radius:18px; background:#061018f2; box-shadow:0 24px 70px #000a }
        .top { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:10px }
        h1 { margin:0; color:#7fffd4; font-size:clamp(17px,4vw,25px); letter-spacing:.05em }
        .stats { display:flex; gap:11px; color:#9ebfcb; font-size:12px; white-space:nowrap }.stats b{color:white}
        button { border:1px solid #4c7d8e; border-radius:9px; color:#eefcff; background:#173241; font:inherit; cursor:pointer }
        button:hover,button:focus-visible { background:#245066; outline:2px solid #7fffd4; outline-offset:2px }
        #restart { padding:8px 12px }
        .stage { position:relative; aspect-ratio:1; overflow:hidden; border-radius:12px; background:#03090d }
        #arena { position:absolute; inset:0; width:100%; height:100%; touch-action:none; outline:none }
        #arena canvas { display:block; width:100%; height:100% }
        .hudline { position:absolute; left:10px; right:10px; top:9px; display:flex; justify-content:space-between; pointer-events:none; font-size:clamp(10px,2vw,13px); font-weight:bold; text-shadow:0 2px 5px #000 }
        #pressure { color:#ff91a5 } #phaseState { color:#d49cff }
        #banner { position:absolute; top:17%; left:0; right:0; text-align:center; color:white; font-size:clamp(17px,4vw,28px); font-weight:bold; text-shadow:0 3px 8px #000; pointer-events:none; opacity:0 }
        .message { position:absolute; inset:0; display:none; place-items:center; padding:25px; text-align:center; background:#02070bec }.message.show{display:grid}
        .message h2 { margin:0 0 8px; color:#ff6680; font-size:29px }.message p{margin:0 0 18px;color:#c5dce5;line-height:1.5}#retry{padding:11px 24px;background:#137055;border-color:#3de0ac}
        .tools { display:grid; grid-template-columns:repeat(3,1fr); gap:7px; margin-top:10px }.tool{min-height:48px;padding:6px;font-size:11px;color:#9ebfcb}.tool b{display:block;color:white;font-size:13px}.tool[data-power="shrink"] b{color:#a8ff78}.tool[data-power="freeze"] b{color:#62dfff}.tool[data-power="phase"] b{color:#d49cff}.tool:disabled{opacity:.4;cursor:not-allowed}
        .below { display:grid; grid-template-columns:1fr auto; align-items:center; gap:12px; margin-top:10px }.help{color:#8daebb;font-size:11px;line-height:1.55}.pad{display:grid;grid-template-columns:repeat(3,40px);grid-template-rows:repeat(2,35px);gap:4px}.pad button{padding:0;font-size:17px;touch-action:manipulation}.pad [data-dir="up"]{grid-column:2}.pad [data-dir="left"]{grid-column:1;grid-row:2}.pad [data-dir="down"]{grid-column:2;grid-row:2}.pad [data-dir="right"]{grid-column:3;grid-row:2}
        @media(max-width:540px){.card{padding:9px}.stats{gap:6px;font-size:10px}.below{grid-template-columns:1fr}.pad{justify-self:center}.tool{font-size:9px}}
      `}</style>
      <section class="card">
        <div class="top"><h1>PRESSURE SNAKE 3D</h1><div class="stats"><span>Score <b id="score">0</b></span><span>Wave <b id="wave">1/4</b></span><span>Time <b id="time">25</b></span></div><button id="restart" type="button">Restart</button></div>
        <div class="stage">
          <div id="arena" tabindex="0" role="application" aria-label="Pressure Snake 3D game"></div>
          <div class="hudline"><span id="pressure"></span><span id="phaseState"></span></div><div id="banner"></div>
          <div id="message" class="message" role="dialog" aria-modal="true"><div><h2 id="result">CRUSHED</h2><p id="summary"></p><button id="retry" type="button">Retry</button></div></div>
        </div>
        <div id="tools" class="tools">
          <button class="tool" data-power="shrink" type="button"><b>[1] SHRINK ×<span id="shrink">0</span></b>Remove 4 tail segments</button>
          <button class="tool" data-power="freeze" type="button"><b>[2] FREEZE ×<span id="freeze">0</span></b>Stop walls for 6 seconds</button>
          <button class="tool" data-power="phase" type="button"><b>[3] PHASE ×<span id="phase">0</span></b>Ignore collisions for 4 seconds</button>
        </div>
        <div class="below"><div class="help">Steer with arrows/WASD, swipe, or pad. Collect glowing diamonds, then spend them with 1–3.<br/>Survive four escalating waves as the raised walls close inward.</div><div id="pad" class="pad"><button data-dir="up">↑</button><button data-dir="left">←</button><button data-dir="down">↓</button><button data-dir="right">→</button></div></div>
      </section>
    </main>, root
  );

  const arena = root.querySelector<HTMLElement>("#arena")!;
  const scoreEl = root.querySelector<HTMLElement>("#score")!, waveEl = root.querySelector<HTMLElement>("#wave")!, timeEl = root.querySelector<HTMLElement>("#time")!;
  const pressureEl = root.querySelector<HTMLElement>("#pressure")!, phaseEl = root.querySelector<HTMLElement>("#phaseState")!, bannerEl = root.querySelector<HTMLElement>("#banner")!;
  const messageEl = root.querySelector<HTMLElement>("#message")!, resultEl = root.querySelector<HTMLElement>("#result")!, summaryEl = root.querySelector<HTMLElement>("#summary")!;
  const toolsEl = root.querySelector<HTMLElement>("#tools")!, padEl = root.querySelector<HTMLElement>("#pad")!;

  const renderer = new THREE.WebGLRenderer({ antialias:true, alpha:false });
  renderer.setPixelRatio(Math.min(devicePixelRatio || 1, 2));
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  renderer.outputColorSpace = THREE.SRGBColorSpace;
  arena.appendChild(renderer.domElement);
  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x03090d);
  scene.fog = new THREE.Fog(0x03090d, 34, 52);
  const camera = new THREE.PerspectiveCamera(42, 1, .1, 100);
  camera.position.set(0, 30, 24); camera.lookAt(0, 0, 0);
  scene.add(new THREE.HemisphereLight(0x86dfff, 0x081018, 1.15));
  const key = new THREE.DirectionalLight(0xffffff, 2.1); key.position.set(-9, 22, 12); key.castShadow=true; key.shadow.mapSize.set(1024,1024); scene.add(key);
  const floor = new THREE.Mesh(new THREE.BoxGeometry(30, .5, 30), new THREE.MeshStandardMaterial({color:0x07151c,roughness:.82,metalness:.12})); floor.position.y=-.35; floor.receiveShadow=true; scene.add(floor);
  const grid = new THREE.GridHelper(30,30,0x24505c,0x102832); grid.position.y=-.08; scene.add(grid);
  const gameGroup = new THREE.Group(), wallGroup = new THREE.Group(), pickupGroup = new THREE.Group(); scene.add(gameGroup,wallGroup,pickupGroup);
  const foodGeo=new THREE.SphereGeometry(.34,18,12), segmentGeo=new THREE.BoxGeometry(.82,.72,.82), headGeo=new THREE.BoxGeometry(.9,.82,.9), pickupGeo=new THREE.OctahedronGeometry(.45);
  const foodMat=new THREE.MeshStandardMaterial({color:0xffd166,emissive:0xff9d20,emissiveIntensity:1.7});
  const foodMesh=new THREE.Mesh(foodGeo,foodMat); foodMesh.castShadow=true; pickupGroup.add(foodMesh);
  const wallMat=new THREE.MeshStandardMaterial({color:0x8d1830,emissive:0x4d0717,emissiveIntensity:.7,roughness:.48});
  const cells=30;
  const delta:Record<Direction,Point>={up:{x:0,y:-1},down:{x:0,y:1},left:{x:-1,y:0},right:{x:1,y:0}};
  const opposite:Record<Direction,Direction>={up:"down",down:"up",left:"right",right:"left"};
  const powerColors:Record<Power,number>={shrink:0xa8ff78,freeze:0x62dfff,phase:0xd49cff};
  let snake:Point[]=[], food:Point={x:0,y:0}, pickup:(Point&{type:Power})|null=null, snakeMeshes:THREE.Mesh[]=[];
  let pickupMesh:THREE.Mesh|null=null, dir:Direction="right", queue:Direction[]=[], inventory:Record<Power,number>={shrink:0,freeze:0,phase:0};
  let score=0,wall=0,wave=1,waveMs=0,wallMs=0,freezeMs=0,phaseMs=0,spawnMs=0,alive=true,acc=0,previous=performance.now(),frame=0,swipe:Point|null=null,flash=0,banner="",bannerMs=0;
  let audio:AudioContext|null=null;

  const same=(a:Point,b:Point)=>a.x===b.x&&a.y===b.y;
  const inside=(p:Point)=>p.x>=wall&&p.y>=wall&&p.x<cells-wall&&p.y<cells-wall;
  const world=(p:Point)=>({x:p.x-cells/2+.5,z:p.y-cells/2+.5});
  function tone(f:number,d=.08,v=.03,slide=0){try{audio||=new AudioContext();if(audio.state==="suspended")audio.resume();const o=audio.createOscillator(),g=audio.createGain();o.type="square";o.frequency.setValueAtTime(f,audio.currentTime);o.frequency.linearRampToValueAtTime(f+slide,audio.currentTime+d);g.gain.setValueAtTime(v,audio.currentTime);g.gain.exponentialRampToValueAtTime(.0001,audio.currentTime+d);o.connect(g).connect(audio.destination);o.start();o.stop(audio.currentTime+d)}catch{}}
  function resize(){const w=arena.clientWidth,h=arena.clientHeight;renderer.setSize(w,h,false);camera.aspect=w/h;camera.updateProjectionMatrix()}
  function randomOpen(){const open:Point[]=[];for(let y=wall;y<cells-wall;y++)for(let x=wall;x<cells-wall;x++){const p={x,y};if(!snake.some(s=>same(s,p))&&!same(food,p)&&(!pickup||!same(pickup,p)))open.push(p)}return open[Math.floor(Math.random()*open.length)]}
  function placeFood(){const p=randomOpen();if(!p){end(true);return}food=p}
  function spawnPower(){if(pickup)return;const p=randomOpen();if(p)pickup={...p,type:(['shrink','freeze','phase'] as Power[])[Math.floor(Math.random()*3)]}}
  function refresh(){scoreEl.textContent=String(score);waveEl.textContent=`${wave}/4`;timeEl.textContent=String(Math.max(0,Math.ceil((25000-waveMs)/1000)));(Object.keys(inventory) as Power[]).forEach(k=>{root.querySelector(`#${k}`)!.textContent=String(inventory[k]);root.querySelector<HTMLButtonElement>(`[data-power="${k}"]`)!.disabled=!alive||inventory[k]<1})}
  function clearGroup(group:THREE.Group){while(group.children.length){const child=group.children.pop()!;group.remove(child)}}
  function rebuildWalls(){clearGroup(wallGroup);if(wall<=0)return;const open=cells-wall*2, center=0, height=1.15+wall*.06;const pieces=[[open+wall*2,height,wall,center,wall/2-cells/2],[open+wall*2,height,wall,center,cells/2-wall/2],[wall,height,open,-cells/2+wall/2,0],[wall,height,open,cells/2-wall/2,0]];pieces.forEach(([w,h,d,x,z])=>{const m=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),wallMat);m.position.set(x,h/2-.05,z);m.castShadow=m.receiveShadow=true;wallGroup.add(m)})}
  function reset(){snake=[{x:15,y:15},{x:14,y:15},{x:13,y:15}];dir="right";queue=[];inventory={shrink:0,freeze:0,phase:0};score=wall=waveMs=wallMs=freezeMs=phaseMs=spawnMs=acc=0;wave=1;alive=true;pickup=null;flash=0;banner="WAVE 1 · CALM";bannerMs=1800;previous=performance.now();messageEl.classList.remove("show");rebuildWalls();placeFood();refresh();arena.focus();tone(330,.08,.025,130)}
  function end(won=false){if(!alive)return;alive=false;refresh();resultEl.textContent=won?"PRESSURE MASTERED":"CRUSHED";summaryEl.textContent=`Score ${score} · Length ${snake.length} · Reached wave ${wave}.`;messageEl.classList.add("show");tone(won?520:140,.4,.05,won?400:-90)}
  function steer(n:Direction){if(!alive)return;const last=queue.length?queue[queue.length-1]:dir;if(n!==last&&n!==opposite[last]&&queue.length<2)queue.push(n)}
  function usePower(type:Power){if(!alive||inventory[type]<1)return;inventory[type]--;if(type==="shrink"){snake.splice(Math.max(3,snake.length-4));banner="TAIL SHED";bannerMs=900}if(type==="freeze"){freezeMs=6000;banner="WALLS FROZEN";bannerMs=1000}if(type==="phase"){phaseMs=4000;banner="PHASE ACTIVE";bannerMs=1000}tone(type==="phase"?760:560,.15,.04,180);refresh()}
  function pressure(){wall++;flash=1;rebuildWalls();tone(120+wave*35,.16,.05,-40);if(wall>=13){end(true);return}if(phaseMs<=0&&snake.some(p=>!inside(p))){end();return}if(!inside(food))placeFood();if(pickup&&!inside(pickup))pickup=null}
  function tick(dt:number){if(!alive)return;waveMs+=dt;spawnMs+=dt;freezeMs=Math.max(0,freezeMs-dt);phaseMs=Math.max(0,phaseMs-dt);bannerMs=Math.max(0,bannerMs-dt);if(spawnMs>7000-wave*500){spawnMs=0;spawnPower()}if(waveMs>=25000){if(wave===4){end(true);return}wave++;waveMs=0;banner=`WAVE ${wave} · ${['','TIGHTENING','SURGE','PANIC'][wave-1]}`;bannerMs=2200;tone(360,.3,.05,360)}if(freezeMs<=0){wallMs+=dt;const interval=7800-wave*1050;if(wallMs>=interval){wallMs=0;pressure();if(!alive)return}}if(queue.length)dir=queue.shift()!;const d=delta[dir];let head={x:snake[0].x+d.x,y:snake[0].y+d.y};if(phaseMs>0){const lo=wall,hi=cells-wall;head={x:head.x<lo?hi-1:head.x>=hi?lo:head.x,y:head.y<lo?hi-1:head.y>=hi?lo:head.y}}const eating=same(head,food),body=eating?snake:snake.slice(0,-1);if((!inside(head)||body.some(p=>same(p,head)))&&phaseMs<=0){end();return}snake.unshift(head);if(eating){score++;banner="GROW +1";bannerMs=500;tone(600,.08,.035,160);placeFood()}else snake.pop();if(pickup&&same(head,pickup)){inventory[pickup.type]=Math.min(2,inventory[pickup.type]+1);banner=`${pickup.type.toUpperCase()} CHARGE`;bannerMs=1000;tone(780,.12,.04,240);pickup=null}refresh()}
  function syncScene(now:number){flash*=.9;wallMat.color.setHex(freezeMs>0?0x176a80:flash>.05?0xe93659:0x8d1830);wallMat.emissive.setHex(freezeMs>0?0x0b526c:0x4d0717);const fw=world(food);foodMesh.position.set(fw.x,.42+Math.sin(now/170)*.1,fw.z);foodMesh.rotation.y=now/500;while(snakeMeshes.length<snake.length){const i=snakeMeshes.length,m=new THREE.Mesh(i===0?headGeo:segmentGeo,new THREE.MeshStandardMaterial({color:i===0?0xeaffaa:0x38ce82,emissive:i===0?0x516e20:0x073c27,emissiveIntensity:.7,roughness:.34}));m.castShadow=true;m.receiveShadow=true;snakeMeshes.push(m);gameGroup.add(m)}while(snakeMeshes.length>snake.length){const m=snakeMeshes.pop()!;gameGroup.remove(m);(m.material as THREE.Material).dispose()}snakeMeshes.forEach((m,i)=>{const p=world(snake[i]);m.position.set(p.x,.42,p.z);m.rotation.y=i===0?({up:0,down:Math.PI,left:Math.PI/2,right:-Math.PI/2} as Record<Direction,number>)[dir]:0;m.scale.y=i===0?1+Math.sin(now/100)*.04:1;m.material.opacity=phaseMs>0?.52:1;m.material.transparent=phaseMs>0});if(pickup){if(!pickupMesh){pickupMesh=new THREE.Mesh(pickupGeo,new THREE.MeshStandardMaterial());pickupMesh.castShadow=true;pickupGroup.add(pickupMesh)}const p=world(pickup);pickupMesh.position.set(p.x,.65+Math.sin(now/130)*.12,p.z);pickupMesh.rotation.set(now/700,now/450,0);const mat=pickupMesh.material as THREE.MeshStandardMaterial;mat.color.setHex(powerColors[pickup.type]);mat.emissive.setHex(powerColors[pickup.type]);mat.emissiveIntensity=1.25}else if(pickupMesh){pickupGroup.remove(pickupMesh);(pickupMesh.material as THREE.Material).dispose();pickupMesh=null}pressureEl.textContent=freezeMs>0?`FROZEN ${(freezeMs/1000).toFixed(1)}s`:`NEXT PRESSURE ${Math.max(0,Math.ceil((7800-wave*1050-wallMs)/1000))}s`;pressureEl.style.color=freezeMs>0?"#62dfff":"#ff91a5";phaseEl.textContent=phaseMs>0?`PHASE ${(phaseMs/1000).toFixed(1)}s`:"";bannerEl.textContent=banner;bannerEl.style.opacity=bannerMs>0?String(Math.min(1,bannerMs/350)):"0"}
  function loop(now:number){const dt=Math.min(100,now-previous);previous=now;acc+=dt;const step=Math.max(72,145-wave*8-score);while(acc>=step){tick(step);acc-=step}syncScene(now);renderer.render(scene,camera);frame=requestAnimationFrame(loop)}
  function onKey(e:KeyboardEvent){const map:Record<string,Direction>={ArrowUp:"up",w:"up",W:"up",ArrowDown:"down",s:"down",S:"down",ArrowLeft:"left",a:"left",A:"left",ArrowRight:"right",d:"right",D:"right"};if(map[e.key]){e.preventDefault();steer(map[e.key])}if(e.key==="1")usePower("shrink");if(e.key==="2")usePower("freeze");if(e.key==="3")usePower("phase");if(!alive&&(e.key==="Enter"||e.key===" "))reset()}
  function pointerDown(e:PointerEvent){swipe={x:e.clientX,y:e.clientY};arena.setPointerCapture(e.pointerId)}
  function pointerUp(e:PointerEvent){if(!swipe)return;const dx=e.clientX-swipe.x,dy=e.clientY-swipe.y;swipe=null;if(Math.max(Math.abs(dx),Math.abs(dy))>16)steer(Math.abs(dx)>Math.abs(dy)?(dx>0?"right":"left"):(dy>0?"down":"up"))}
  function pad(e:PointerEvent){const b=(e.target as HTMLElement).closest<HTMLButtonElement>("[data-dir]");if(b){e.preventDefault();steer(b.dataset.dir as Direction)}}
  function tool(e:PointerEvent){const b=(e.target as HTMLElement).closest<HTMLButtonElement>("[data-power]");if(b)usePower(b.dataset.power as Power)}
  const restart=()=>reset();
  window.addEventListener("resize",resize);window.addEventListener("keydown",onKey,{passive:false});arena.addEventListener("pointerdown",pointerDown);arena.addEventListener("pointerup",pointerUp);padEl.addEventListener("pointerdown",pad);toolsEl.addEventListener("pointerdown",tool);root.querySelector("#restart")!.addEventListener("click",restart);root.querySelector("#retry")!.addEventListener("click",restart);
  resize();reset();frame=requestAnimationFrame(loop);
  return()=>{cancelAnimationFrame(frame);window.removeEventListener("resize",resize);window.removeEventListener("keydown",onKey);arena.removeEventListener("pointerdown",pointerDown);arena.removeEventListener("pointerup",pointerUp);padEl.removeEventListener("pointerdown",pad);toolsEl.removeEventListener("pointerdown",tool);root.querySelector("#restart")?.removeEventListener("click",restart);root.querySelector("#retry")?.removeEventListener("click",restart);scene.traverse(o=>{if(o instanceof THREE.Mesh){o.geometry.dispose();const mats=Array.isArray(o.material)?o.material:[o.material];mats.forEach(m=>m.dispose())}});renderer.dispose();renderer.domElement.remove();if(audio)audio.close();if(typeof dispose==="function")dispose()};
}