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

type Rect={x:number;y:number;w:number;h:number};
type Platform=Rect&{axis?:"x"|"y";range?:number;speed?:number;ox?:number;oy?:number;phase?:number;dx?:number;dy?:number;mesh?:THREE.Mesh};
type Enemy=Rect&{min:number;max:number;vx:number;alive:boolean;mesh?:THREE.Mesh};
type Pickup=Rect&{kind:"coin"|"star";taken:boolean;mesh?:THREE.Mesh};
type Stage={name:string;width:number;spawn:[number,number];goal:Rect;platforms:Platform[];hazards:Rect[];enemies:Enemy[];pickups:Pickup[];checks:number[]};
const R=(x:number,y:number,w:number,h:number):Rect=>({x,y,w,h});
const hit=(a:Rect,b:Rect)=>a.x<b.x+b.w&&a.x+a.w>b.x&&a.y<b.y+b.h&&a.y+a.h>b.y;

export default function mount(){
 const root=document.querySelector("#game-root") as HTMLElement|null;if(!root)return()=>{};
 render(<div className="game"><style>{`
 .game{position:relative;width:100%;height:100%;min-height:540px;overflow:hidden;background:#10182d;color:white;font-family:system-ui,sans-serif;user-select:none;touch-action:none}.view{position:absolute;inset:0}.view canvas{display:block;width:100%;height:100%}.hud{position:absolute;z-index:2;left:12px;right:12px;top:10px;display:flex;align-items:center;gap:10px;font-weight:900;text-shadow:0 2px 4px #000;pointer-events:none}.stats{min-width:260px}.title{flex:1;text-align:center;color:#fff4a8}.hud button,.ctrl,.card{pointer-events:auto;border:2px solid white;border-radius:10px;color:white;font-weight:900;cursor:pointer}.hud button{padding:8px 12px;background:#cf4455;box-shadow:0 3px #792736}.hud .stages{background:#315da7;box-shadow:0 3px #1c3768}.help{position:absolute;z-index:2;bottom:10px;left:50%;transform:translateX(-50%);padding:6px 10px;border-radius:8px;background:#091127bb;font-size:12px;white-space:nowrap}.touch{position:absolute;z-index:3;left:15px;right:15px;bottom:14px;display:flex;justify-content:space-between;pointer-events:none}.pad{display:flex;gap:9px}.ctrl{width:62px;height:56px;background:#091127bb;font-size:23px}.jump{width:90px;background:#c97728cc;font-size:13px}.menu{position:absolute;z-index:5;inset:0;display:flex;align-items:center;justify-content:center;background:#081027e8;padding:18px}.panel{width:min(760px,95%);padding:20px;border:3px solid #72d9ff;border-radius:18px;background:#182747;box-shadow:0 18px 50px #0009}.panel h1,.panel p{text-align:center;margin:0 0 8px}.panel h1{color:#ffe46d}.panel p{color:#bde9ff;margin-bottom:16px}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.card{min-height:140px;padding:14px;text-align:left;background:#29416f}.card:hover{background:#375893;transform:translateY(-2px)}.card b{display:block;color:#ffe46d;font-size:18px}.card small{display:block;margin-top:9px;line-height:1.45;color:#d7ebff}.banner{position:absolute;z-index:4;inset:0;display:none;align-items:center;justify-content:center;text-align:center;background:#081027bd}.banner div{font-size:42px;font-weight:1000;text-shadow:0 4px #000}.banner small{display:block;margin-top:8px;font-size:17px}.banner button{margin-top:18px;padding:10px 18px;border:2px solid white;border-radius:10px;background:#cf4455;color:white;font-weight:900;cursor:pointer}@media(max-width:650px){.cards{grid-template-columns:1fr}.card{min-height:85px}.panel{max-height:90%;overflow:auto}.stats{min-width:0;font-size:11px}.title{font-size:12px}.help{display:none}.hud button{padding:7px;font-size:11px}}
 `}</style><div className="view"/><div className="hud"><span className="stats">Coins 0 · Stars 0/3 · 0 pts</span><span className="title">Choose a world</span><button className="stages">Stages</button><button className="restart">Restart</button></div><div className="touch"><div className="pad"><button className="ctrl left">◀</button><button className="ctrl right">▶</button></div><button className="ctrl jump">JUMP ▲</button></div><div className="help">A/D or arrows move · W/↑/Space jump · R restart · Esc stages</div><div className="menu"><div className="panel"><h1>STARBOUND TRAILS 3D</h1><p>Cross moving bridges, stomp enemies, gather stars, and reach each beacon.</p><div className="cards"/></div></div><div className="banner"><div><span/><small/><button>Retry</button></div></div></div>,root);
 const host=root.querySelector(".game") as HTMLElement,view=host.querySelector(".view") as HTMLElement,stats=host.querySelector(".stats") as HTMLElement,title=host.querySelector(".title") as HTMLElement,menu=host.querySelector(".menu") as HTMLElement,cards=host.querySelector(".cards") as HTMLElement,banner=host.querySelector(".banner") as HTMLElement,big=banner.querySelector("span")!,detail=banner.querySelector("small")!,retry=banner.querySelector("button") as HTMLButtonElement;
 const renderer=new THREE.WebGLRenderer({antialias:true});renderer.setPixelRatio(Math.min(2,devicePixelRatio||1));renderer.shadowMap.enabled=true;renderer.shadowMap.type=THREE.PCFSoftShadowMap;renderer.outputColorSpace=THREE.SRGBColorSpace;view.appendChild(renderer.domElement);
 const scene=new THREE.Scene();scene.background=new THREE.Color(0x74c9ee);scene.fog=new THREE.Fog(0x74c9ee,900,1900);const camera=new THREE.PerspectiveCamera(42,1,1,3000);camera.position.set(250,220,720);
 scene.add(new THREE.HemisphereLight(0xcceeff,0x49362c,2.2));const sun=new THREE.DirectionalLight(0xfff1cf,2.8);sun.position.set(-300,600,500);sun.castShadow=true;sun.shadow.mapSize.set(1024,1024);sun.shadow.camera.left=-700;sun.shadow.camera.right=700;sun.shadow.camera.top=600;sun.shadow.camera.bottom=-600;scene.add(sun);
 const world=new THREE.Group();scene.add(world);const mats:THREE.Material[]=[];const mat=(c:number)=>{const m=new THREE.MeshStandardMaterial({color:c,roughness:.72,metalness:.04});mats.push(m);return m};const box=(r:Rect,z:number,depth:number,m:THREE.Material)=>{const g=new THREE.BoxGeometry(r.w,r.h,depth),o=new THREE.Mesh(g,m);o.position.set(r.x+r.w/2,600-r.y-r.h/2,z);o.castShadow=o.receiveShadow=true;world.add(o);return o};
 const coinPts=(kind:"coin"|"star",a:number[][])=>a.map(([x,y])=>({x,y,w:kind==="coin"?22:28,h:kind==="coin"?22:28,kind,taken:false}));
 const build=(n:string,width:number,grounds:number[][],ledges:number[][],haz:number[][],enemy:number[][],coins:number[][],stars:number[][],checks:number[],moving:number[][]):Stage=>({name:n,width,spawn:[45,450],goal:R(width-90,300,55,220),platforms:[...grounds.map(a=>R(a[0],a[1],a[2],a[3])),...ledges.map(a=>R(a[0],a[1],a[2],a[3])),...moving.map((a,i)=>({...R(a[0],a[1],a[2],a[3]),axis:i%2?"x":"y",range:a[4],speed:a[5],phase:i}))],hazards:haz.map(a=>R(a[0],a[1],a[2],a[3])),enemies:enemy.map(a=>({...R(a[0],a[1],36,38),min:a[2],max:a[3],vx:a[4],alive:true})),pickups:[...coinPts("coin",coins),...coinPts("star",stars)],checks});
 const stages:Stage[]=[
 build("1 · Breezy Bluffs",2850,[[0,520,430,80],[510,520,390,80],[1000,520,330,80],[1430,520,370,80],[1900,520,340,80],[2340,520,510,80]],[[220,405,150,20],[560,390,135,20],[1080,400,140,20],[1260,310,120,20],[1750,300,120,20],[1990,390,130,20],[2200,300,120,20],[2440,400,135,20],[2530,230,180,20]],[[430,550,80,50],[900,550,100,50],[1330,550,100,50],[2240,550,100,50]],[[590,482,525,870,70],[1090,362,1080,1220,75],[1940,482,1910,2220,90]],[[270,360],[610,345],[830,270],[1120,355],[1300,265],[1570,340],[1790,255],[2040,345],[2240,255],[2490,355],[2610,185]],[[680,470],[1350,250],[2600,175]],[1030,1930],[[790,330,125,20,75,1.5],[1510,390,140,20,110,1.2]]),
 build("2 · Clockwork Canyon",3300,[[0,520,350,80],[460,520,300,80],[900,520,310,80],[1360,520,270,80],[1780,520,330,80],[2250,520,260,80],[2660,520,640,80]],[[170,390,115,20],[620,310,125,20],[980,365,130,20],[1190,270,125,20],[1660,300,100,20],[2130,275,115,20],[2320,390,120,20],[2780,390,130,20],[2980,300,140,20]],[[350,545,110,55],[760,545,140,55],[1210,545,150,55],[1630,545,150,55],[2110,545,140,55],[2510,545,150,55]],[[510,482,480,745,95],[1010,327,980,1110,105],[1830,482,1800,2080,110],[2770,482,2690,3150,120]],[[210,345],[440,350],[665,265],[835,345],[1030,320],[1230,225],[1510,330],[1690,255],[1960,330],[2160,230],[2360,345],[2580,275],[2840,345],[3040,255]],[[735,465],[1720,245],[2400,465]],[920,1810,2690],[[390,410,110,20,120,2],[780,400,110,20,130,1.8],[1460,390,120,20,150,1.7],[1900,385,125,20,150,2.1],[2530,330,115,20,120,2.3]]),
 build("3 · Ember Citadel",3650,[[0,520,310,80],[430,520,250,80],[810,520,250,80],[1190,520,230,80],[1550,520,250,80],[1930,520,240,80],[2310,520,230,80],[2700,520,250,80],[3100,520,550,80]],[[150,395,115,20],[560,300,105,20],[900,330,110,20],[1270,275,110,20],[1450,365,100,20],[1840,400,90,20],[2040,300,110,20],[2400,280,100,20],[2800,300,110,20],[3000,400,100,20],[3220,320,110,20],[3400,240,130,20]],[[310,535,120,65],[680,535,130,65],[1060,535,130,65],[1420,535,130,65],[1800,535,130,65],[2170,535,140,65],[2540,535,160,65],[2950,535,150,65]],[[465,482,440,660,110],[920,292,900,1010,110],[1580,482,1570,1780,125],[2340,482,2320,2510,135],[3160,482,3120,3520,145]],[[190,350],[375,330],[600,255],[750,340],[950,285],[1130,325],[1310,230],[1490,320],[1720,255],[1870,350],[2090,255],[2240,330],[2440,235],[2630,340],[2850,255],[3040,355],[3270,275],[3460,195]],[[645,455],[2075,245],[3470,175]],[830,1580,2340,3130],[[330,390,100,20,140,2.2],[700,390,100,20,120,2.2],[1080,385,100,20,150,2.4],[1670,300,100,20,130,2.5],[2190,390,105,20,160,2.6],[2570,390,110,20,145,2.5]])];
 const player={x:0,y:0,w:32,h:44,vx:0,vy:0,ground:false,face:1,mesh:null as THREE.Group|null},keys={left:false,right:false,jump:false};let stage:Stage|null=null,index=0,state:"menu"|"play"|"dead"|"win"="menu",coins=0,stars=0,score=0,time=0,checkpoint=-1,spawn:[number,number]=[0,0],jumpBuffer=0,coyote=0,raf=0,last=0,camX=250,deathTimer=0;
 function clear(){while(world.children.length){const o=world.children.pop()!;o.traverse(q=>{const m=q as THREE.Mesh;m.geometry?.dispose()})}player.mesh=null;}
 function makePlayer(){const g=new THREE.Group(),body=box(R(0,0,28,30),0,30,mat(0x2854c4));world.remove(body);body.position.set(0,-5,0);g.add(body);const head=new THREE.Mesh(new THREE.SphereGeometry(14,18,12),mat(0xf0bd88));head.position.y=18;head.castShadow=true;g.add(head);const cap=new THREE.Mesh(new THREE.BoxGeometry(29,8,32),mat(0xe7444d));cap.position.y=29;g.add(cap);world.add(g);player.mesh=g;}
 function load(i:number){index=i;stage=JSON.parse(JSON.stringify(stages[i]));clear();const s=stage!,groundMat=mat(i===0?0x765038:i===1?0x596a78:0x56333d),topMat=mat(i===2?0xd86542:i===1?0xd2a340:0x56b956),danger=mat(i===2?0xff3e20:0xdce7ed);for(const p of s.platforms){p.ox=p.x;p.oy=p.y;p.dx=p.dy=0;p.mesh=box(p,0,110,groundMat);const lip=box(R(p.x,p.y,p.w,8),0,114,topMat);p.mesh.add(lip);lip.position.set(0,p.h/2-4,0)}for(const h of s.hazards)box(h,0,105,danger);for(const e of s.enemies){e.mesh=box(e,0,42,mat(i===2?0x64283e:0x9e4048))}const cg=new THREE.CylinderGeometry(10,10,5,18),sg=new THREE.OctahedronGeometry(14);for(const p of s.pickups){p.mesh=new THREE.Mesh(p.kind==="coin"?cg:sg,mat(p.kind==="coin"?0xffd42f:0xfff08a));p.mesh.rotation.x=Math.PI/2;p.mesh.castShadow=true;world.add(p.mesh)}box(s.goal,-5,30,mat(0xffee99));for(let x=0;x<s.width;x+=350){const hill=new THREE.Mesh(new THREE.ConeGeometry(180,360,5),mat(i===2?0x5c3043:0x5a9c68));hill.position.set(x,0,-350);world.add(hill)}makePlayer();Object.assign(player,{x:s.spawn[0],y:s.spawn[1],vx:0,vy:0,ground:false});spawn=[...s.spawn];checkpoint=-1;coins=stars=score=time=0;state="play";menu.style.display="none";banner.style.display="none";title.textContent=s.name;updateHud();}
 function updateHud(){stats.textContent=`Coins ${coins} · Stars ${stars}/3 · ${score} pts · ${(time/1000).toFixed(1)}s`;}
 function fail(){if(state!=="play")return;state="dead";deathTimer=.75;big.textContent="CHECKPOINT RECALL";detail.textContent="Returning to solid ground…";banner.style.display="flex";retry.style.display="none";}
 function respawn(){Object.assign(player,{x:spawn[0],y:spawn[1],vx:0,vy:0});state="play";banner.style.display="none";}
 function win(){if(state!=="play")return;state="win";score+=5000+stars*1000+coins*100-Math.min(4000,Math.floor(time/25));updateHud();big.textContent="WORLD CONQUERED!";detail.textContent=`${score} points · ★ ${stars}/3 · ${(time/1000).toFixed(1)} seconds`;retry.textContent="Play Again";retry.style.display="inline-block";banner.style.display="flex";}
 function showMenu(){state="menu";menu.style.display="flex";banner.style.display="none";keys.left=keys.right=keys.jump=false;title.textContent="Choose a world";}
 function update(dt:number){if(state==="dead"){deathTimer-=dt;if(deathTimer<=0)respawn();return}if(!stage||state!=="play")return;time+=dt*1000;const dir=(keys.right?1:0)-(keys.left?1:0);if(dir){player.vx+=dir*(player.ground?1900:1050)*dt;player.face=dir}else player.vx*=Math.pow(.001,dt);player.vx=Math.max(-310,Math.min(310,player.vx));jumpBuffer=Math.max(0,jumpBuffer-dt);coyote=player.ground?.12:Math.max(0,coyote-dt);if(jumpBuffer&&coyote){player.vy=-610;player.ground=false;jumpBuffer=coyote=0}if(!keys.jump&&player.vy<-220)player.vy+=1200*dt;player.vy=Math.min(900,player.vy+1550*dt);for(const p of stage.platforms){const x=p.x,y=p.y;if(p.axis==="x")p.x=p.ox!+Math.sin(time/1000*p.speed!+p.phase!)*p.range!;if(p.axis==="y")p.y=p.oy!+Math.sin(time/1000*p.speed!+p.phase!)*p.range!;p.dx=p.x-x;p.dy=p.y-y;p.mesh!.position.set(p.x+p.w/2,600-p.y-p.h/2,0)}player.x=Math.max(0,Math.min(stage.width-player.w,player.x+player.vx*dt));const oldBottom=player.y+player.h;player.y+=player.vy*dt;player.ground=false;if(player.vy>=0)for(const p of stage.platforms)if(player.x+player.w>p.x+3&&player.x<p.x+p.w-3&&oldBottom<=p.y+8&&player.y+player.h>=p.y){player.y=p.y-player.h;player.vy=0;player.ground=true;player.x+=p.dx||0;break}if(player.y>680||stage.hazards.some(h=>hit(player,h)))fail();for(const e of stage.enemies){if(!e.alive)continue;e.x+=e.vx*dt;if(e.x<e.min||e.x+e.w>e.max){e.vx*=-1;e.x=Math.max(e.min,Math.min(e.max-e.w,e.x))}e.mesh!.position.x=e.x+e.w/2;if(hit(player,e)){if(player.vy>80&&oldBottom<e.y+16){e.alive=false;e.mesh!.visible=false;player.vy=-390;score+=250}else fail()}}for(const p of stage.pickups){if(p.taken)continue;p.mesh!.position.set(p.x+p.w/2,600-p.y-p.h/2,0);p.mesh!.rotation.y+=dt*3;if(hit(player,p)){p.taken=true;p.mesh!.visible=false;p.kind==="coin"?(coins++,score+=100):(stars++,score+=1000);updateHud()}}stage.checks.forEach((x,i)=>{if(player.x>x&&i>checkpoint){checkpoint=i;spawn=[x+15,430];score+=300;updateHud()}});if(hit(player,stage.goal))win();if(player.mesh){player.mesh.position.set(player.x+16,600-player.y-25,0);player.mesh.scale.x=player.face}camX+=(Math.max(250,Math.min(stage.width-250,player.x+100))-camX)*Math.min(1,dt*5);camera.position.x=camX;camera.lookAt(camX,240,0);}
 function draw(t:number){if(stage)for(const p of stage.pickups)if(!p.taken&&p.mesh)p.mesh.position.y+=Math.sin(t*.004+p.x)*.08;renderer.render(scene,camera)}
 function frame(t:number){const dt=Math.min(.033,(t-last)/1000||0);last=t;update(dt);draw(t);raf=requestAnimationFrame(frame)}
 function resize(){const r=view.getBoundingClientRect(),w=Math.max(320,r.width),h=Math.max(440,r.height);renderer.setSize(w,h,false);camera.aspect=w/h;camera.updateProjectionMatrix()}
 stages.forEach((s,i)=>{const b=document.createElement("button");b.className="card";b.innerHTML=`<b>${s.name}</b><small>${i===0?"Moving bridges above green valleys":i===1?"Precision lifts across a steel canyon":"Rising danger in the volcanic keep"}<br>${s.pickups.filter(p=>p.kind==="coin").length} coins · 3 stars</small>`;b.onclick=()=>load(i);cards.appendChild(b)});
 function key(e:KeyboardEvent,on:boolean){const k=e.key.toLowerCase();if(["arrowleft","arrowright","arrowup"," ","a","d","w"].includes(k))e.preventDefault();if(k==="arrowleft"||k==="a")keys.left=on;if(k==="arrowright"||k==="d")keys.right=on;if(k==="arrowup"||k==="w"||k===" "){if(on&&!keys.jump)jumpBuffer=.14;keys.jump=on}if(on&&k==="r"&&stage)load(index);if(on&&k==="escape")showMenu()}
 const kd=(e:KeyboardEvent)=>key(e,true),ku=(e:KeyboardEvent)=>key(e,false);window.addEventListener("keydown",kd);window.addEventListener("keyup",ku);window.addEventListener("resize",resize);(host.querySelector(".restart") as HTMLButtonElement).onclick=()=>stage?load(index):showMenu();(host.querySelector(".stages") as HTMLButtonElement).onclick=showMenu;retry.onclick=()=>load(index);
 const binds:Array<()=>void>=[];for(const [q,n] of [[".left","left"],[".right","right"],[".jump","jump"]] as const){const b=host.querySelector(q) as HTMLElement,on=(e:PointerEvent)=>{e.preventDefault();if(n==="jump"&&!keys.jump)jumpBuffer=.14;keys[n]=true;b.setPointerCapture?.(e.pointerId)},off=(e:PointerEvent)=>{e.preventDefault();keys[n]=false};b.addEventListener("pointerdown",on);b.addEventListener("pointerup",off);b.addEventListener("pointercancel",off);binds.push(()=>{b.removeEventListener("pointerdown",on);b.removeEventListener("pointerup",off);b.removeEventListener("pointercancel",off)})}
 resize();raf=requestAnimationFrame(frame);return()=>{cancelAnimationFrame(raf);window.removeEventListener("keydown",kd);window.removeEventListener("keyup",ku);window.removeEventListener("resize",resize);binds.forEach(f=>f());clear();mats.forEach(m=>m.dispose());renderer.dispose();renderer.domElement.remove();render(null,root)};
}