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

type Kind = "chaser" | "runner" | "tank" | "shooter" | "boss";
type Enemy = { mesh:THREE.Mesh; kind:Kind; hp:number; max:number; speed:number; radius:number; cool:number; phase:number; hit:number };
type Shot = { mesh:THREE.Mesh; velocity:THREE.Vector3; life:number; damage:number; pierce:number; enemy:boolean };
type Spark = { mesh:THREE.Mesh; velocity:THREE.Vector3; life:number; max:number };
type Upgrade = "damage" | "rapid" | "spread" | "pierce" | "speed" | "vitality";

export default function mount() {
  const root = document.querySelector("#game-root") as HTMLElement;
  render(
    <div class="arena3d">
      <style>{`
        .arena3d{position:relative;width:100%;height:100%;min-height:520px;overflow:hidden;background:#02050d;color:#eef8ff;font-family:ui-monospace,monospace;user-select:none}
        .arena3d canvas{display:block;width:100%;height:100%;cursor:crosshair;touch-action:none}
        .hud{position:absolute;inset:0;pointer-events:none;padding:16px;box-sizing:border-box;text-shadow:0 2px 6px #000}
        .top{display:flex;justify-content:space-between;gap:16px;font-size:15px;font-weight:900}.build{margin-top:8px;color:#75efff;font-size:12px;letter-spacing:.04em}
        .help{position:absolute;left:16px;bottom:16px;color:#b4c6d9;font-size:12px}.restart{position:absolute;right:16px;bottom:14px}
        .arena3d button{pointer-events:auto;border:1px solid #5eeeff;background:#102d43;color:#fff;padding:10px 17px;font:700 14px ui-monospace,monospace;cursor:pointer}.arena3d button:hover{background:#19506d}
        .overlay{position:absolute;inset:0;display:none;place-items:center;background:#01040bdc;pointer-events:auto;text-align:center;padding:20px}.overlay.show{display:grid}
        .card{max-width:760px;border:1px solid #59eaff88;background:#081326f2;padding:26px;box-shadow:0 0 70px #00d9ff2b}.card h1{margin:0 0 8px;color:#68f2ff}.card p{color:#c7d6e7}
        .choices{display:grid;grid-template-columns:repeat(3,minmax(145px,1fr));gap:12px;margin-top:18px}.choice{text-align:left;min-height:112px}.choice strong{display:block;color:#75f5ff;margin-bottom:7px}.choice small{color:#c7d6e7;line-height:1.4}
        @media(max-width:620px){.choices{grid-template-columns:1fr}.choice{min-height:70px}.help{max-width:65%}}
      `}</style>
      <div class="hud"><div class="top"><span class="stats"/><span class="wave"/></div><div class="build"/><div class="help">MOVE: WASD / ARROWS · AIM: POINTER · FIRE: CLICK / SPACE · RESTART: R</div><button class="restart">Restart</button></div>
      <div class="upgrade overlay"><div class="card"><h1>WAVE CLEARED</h1><p>Choose one upgrade for the next assault</p><div class="choices"/></div></div>
      <div class="message overlay"><div class="card"><h1 class="result"/><p class="summary"/><button class="retry">Play Again</button></div></div>
    </div>, root
  );

  const shell=root.querySelector(".arena3d") as HTMLElement;
  const stats=shell.querySelector(".stats") as HTMLElement, waveLabel=shell.querySelector(".wave") as HTMLElement, build=shell.querySelector(".build") as HTMLElement;
  const upgradePanel=shell.querySelector(".upgrade") as HTMLElement, choices=shell.querySelector(".choices") as HTMLElement, message=shell.querySelector(".message") as HTMLElement;
  const result=shell.querySelector(".result") as HTMLElement, summary=shell.querySelector(".summary") as HTMLElement;
  const restart=shell.querySelector(".restart") as HTMLButtonElement, retry=shell.querySelector(".retry") as HTMLButtonElement;

  const renderer=new THREE.WebGLRenderer({antialias:true}); renderer.setPixelRatio(Math.min(devicePixelRatio||1,2)); renderer.shadowMap.enabled=true; renderer.outputColorSpace=THREE.SRGBColorSpace; shell.prepend(renderer.domElement);
  const scene=new THREE.Scene(); scene.background=new THREE.Color(0x030711); scene.fog=new THREE.FogExp2(0x030711,.018);
  const camera=new THREE.PerspectiveCamera(52,1,.1,150); camera.position.set(0,35,30); camera.lookAt(0,0,0);
  scene.add(new THREE.HemisphereLight(0x74cfff,0x07101d,1.25));
  const sun=new THREE.DirectionalLight(0xb8edff,2.1); sun.position.set(-12,24,10); sun.castShadow=true; sun.shadow.mapSize.set(1024,1024); scene.add(sun);
  const floor=new THREE.Mesh(new THREE.PlaneGeometry(42,28,14,10),new THREE.MeshStandardMaterial({color:0x091526,roughness:.82,metalness:.25,wireframe:false})); floor.rotation.x=-Math.PI/2; floor.receiveShadow=true; scene.add(floor);
  const grid=new THREE.GridHelper(42,21,0x1e7690,0x14334c); grid.position.y=.012; grid.scale.z=28/42; scene.add(grid);
  const wallMat=new THREE.MeshStandardMaterial({color:0x12344b,emissive:0x071c2c,metalness:.7,roughness:.35});
  for(const [x,z,sx,sz] of [[0,-14,42,.6],[0,14,42,.6],[-21,0,.6,28],[21,0,.6,28]] as number[][]){const w=new THREE.Mesh(new THREE.BoxGeometry(sx,1.2,sz),wallMat);w.position.set(x,.55,z);w.castShadow=true;scene.add(w)}
  const ring=new THREE.Mesh(new THREE.TorusGeometry(4.2,.05,6,64),new THREE.MeshBasicMaterial({color:0x155b75}));ring.rotation.x=Math.PI/2;ring.position.y=.04;scene.add(ring);

  const playerMat=new THREE.MeshStandardMaterial({color:0x41e7ff,emissive:0x126578,emissiveIntensity:1.2,metalness:.65,roughness:.22});
  const player=new THREE.Mesh(new THREE.CylinderGeometry(.65,.85,.8,8),playerMat); player.position.y=.45; player.castShadow=true; scene.add(player);
  const gun=new THREE.Mesh(new THREE.BoxGeometry(.25,.25,1.35),new THREE.MeshStandardMaterial({color:0xeaffff,emissive:0x4cb9c9,emissiveIntensity:1}));gun.position.set(0,.35,-.8);player.add(gun);
  const aimPoint=new THREE.Vector3(), raycaster=new THREE.Raycaster(), pointerNdc=new THREE.Vector2();
  const keys=new Set<string>(); let pointerDown=false,raf=0,last=performance.now(),audio:AudioContext|null=null;
  let enemies:Enemy[]=[],shots:Shot[]=[],sparks:Spark[]=[];
  let hp=100,maxHp=100,damage=1,fireRate=1,moveSpeed=12,shotCount=1,pierce=1,cooldown=0,invuln=0;
  let score=0,wave=1,spawned=0,spawnTimer=.5,state:"play"|"upgrade"|"won"|"lost"="play",shake=0,flash=0;
  const totalWaves=7, quota=(n:number)=>5+n*3;
  const levels:Record<Upgrade,number>={damage:0,rapid:0,spread:0,pierce:0,speed:0,vitality:0};
  const info:Record<Upgrade,{name:string,desc:string}>={damage:{name:"Overcharged Rounds",desc:"Projectiles deal +1 damage."},rapid:{name:"Accelerated Core",desc:"Fire 22% faster."},spread:{name:"Split Chamber",desc:"Add two angled projectiles."},pierce:{name:"Phase Ammunition",desc:"Pierce one more hostile."},speed:{name:"Kinetic Boots",desc:"Move 18% faster."},vitality:{name:"Nanite Plating",desc:"Gain 25 max HP and heal 35."}};

  function sound(f:number,d=.05,type:OscillatorType="square",v=.025){try{audio||=new AudioContext();const o=audio.createOscillator(),g=audio.createGain();o.type=type;o.frequency.value=f;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 disposeMesh(m:THREE.Mesh){scene.remove(m);m.geometry.dispose();const material=m.material as THREE.Material|THREE.Material[];(Array.isArray(material)?material:[material]).forEach(x=>x.dispose())}
  function clearActors(){enemies.forEach(e=>disposeMesh(e.mesh));shots.forEach(s=>disposeMesh(s.mesh));sparks.forEach(s=>disposeMesh(s.mesh));enemies=[];shots=[];sparks=[]}
  function burst(pos:THREE.Vector3,color:number,count:number,power=7){for(let i=0;i<count;i++){const mesh=new THREE.Mesh(new THREE.BoxGeometry(.09,.09,.09),new THREE.MeshBasicMaterial({color}));mesh.position.copy(pos);scene.add(mesh);const life=.2+Math.random()*.3,s=2+Math.random()*power,a=Math.random()*Math.PI*2;sparks.push({mesh,velocity:new THREE.Vector3(Math.cos(a)*s,2+Math.random()*4,Math.sin(a)*s),life,max:life})}}
  function resize(){const w=Math.max(1,shell.clientWidth),h=Math.max(1,shell.clientHeight);renderer.setSize(w,h,false);camera.aspect=w/h;camera.updateProjectionMatrix()}
  function reset(){clearActors();hp=maxHp=100;damage=fireRate=pierce=1;moveSpeed=12;shotCount=1;cooldown=invuln=0;score=0;wave=1;spawned=0;spawnTimer=.5;state="play";shake=flash=0;pointerDown=false;player.position.set(0,.45,0);for(const k in levels)levels[k as Upgrade]=0;upgradePanel.classList.remove("show");message.classList.remove("show");last=performance.now()}
  function enemyColor(k:Kind){return k==="boss"?0xa84cdf:k==="tank"?0xd2493d:k==="runner"?0xffc738:k==="shooter"?0xff873b:0xec315b}
  function spawnEnemy(){const side=Math.floor(Math.random()*4),p=new THREE.Vector3();if(side<2)p.set(side?-20:20,.6,(Math.random()-.5)*25);else p.set((Math.random()-.5)*38,.6,side===2?-13:13);const boss=(wave===4||wave===7)&&spawned===quota(wave)-1;const r=Math.random();let kind:Kind=boss?"boss":wave>=4&&r<.18?"shooter":wave>=3&&r<.38?"tank":wave>=2&&r<.62?"runner":"chaser";let radius=.68,h=1+Math.floor(wave/3),speed=3.1+wave*.22,geo:THREE.BufferGeometry=new THREE.IcosahedronGeometry(radius,1);if(kind==="runner"){radius=.48;h=Math.max(1,h-1);speed=6.1+wave*.3;geo=new THREE.ConeGeometry(radius,.9,5)}if(kind==="tank"){radius=1.05;h=4+wave;speed=2.15+wave*.12;geo=new THREE.BoxGeometry(1.7,1.25,1.7)}if(kind==="shooter"){radius=.72;h=2+Math.floor(wave/2);speed=2.4;geo=new THREE.OctahedronGeometry(.85)}if(kind==="boss"){radius=1.8;h=28+wave*7;speed=2.1+wave*.12;geo=new THREE.DodecahedronGeometry(1.75)}const mat=new THREE.MeshStandardMaterial({color:enemyColor(kind),emissive:enemyColor(kind),emissiveIntensity:.28,metalness:.35,roughness:.4});const mesh=new THREE.Mesh(geo,mat);mesh.position.copy(p);mesh.castShadow=true;scene.add(mesh);enemies.push({mesh,kind,hp:h,max:h,speed,radius,cool:1+Math.random(),phase:Math.random()*6,hit:0});spawned++;if(boss)sound(65,.5,"sawtooth",.07)}
  function addShot(origin:THREE.Vector3,angle:number,enemy=false,speed=29,dmg=damage){const color=enemy?0xff8147:pierce>1?0xb29aff:0xdfffff,mesh=new THREE.Mesh(new THREE.SphereGeometry(enemy?.18:.14,7,5),new THREE.MeshBasicMaterial({color}));mesh.position.copy(origin);mesh.position.y=.55;scene.add(mesh);shots.push({mesh,velocity:new THREE.Vector3(Math.sin(angle)*speed,0,Math.cos(angle)*speed),life:enemy?4:1.45,damage:dmg,pierce:enemy?1:pierce,enemy})}
  function shoot(){if(cooldown>0||state!=="play")return;const dir=aimPoint.clone().sub(player.position);const angle=Math.atan2(dir.x,dir.z);if(shotCount===1)addShot(player.position,angle);else for(let i=0;i<shotCount;i++)addShot(player.position,angle+(i-(shotCount-1)/2)*.13);cooldown=.17/fireRate;shake=Math.max(shake,.18);burst(player.position.clone().add(new THREE.Vector3(Math.sin(angle),0,Math.cos(angle))),0xdfffff,3,3);sound(245,.04)}
  function enemyShot(e:Enemy,offset=0,speed=12){const d=player.position.clone().sub(e.mesh.position),a=Math.atan2(d.x,d.z)+offset;addShot(e.mesh.position,a,true,speed,e.kind==="boss"?13:9)}
  function hurt(n:number){if(invuln>0)return;hp-=n;invuln=.5;shake=.75;flash=.3;burst(player.position,0xff345c,18,10);sound(70,.16,"sawtooth",.07)}
  function finish(next:"won"|"lost"){state=next;result.textContent=next==="won"?"ARENA CONQUERED":"SYSTEM DOWN";summary.textContent=`Score ${score} · Reached wave ${wave} · ${Object.values(levels).reduce((a,b)=>a+b,0)} upgrades installed`;message.classList.add("show");sound(next==="won"?680:65,.5,next==="won"?"sine":"sawtooth",.07)}
  function present(){state="upgrade";hp=Math.min(maxHp,hp+15);const pool=(Object.keys(info) as Upgrade[]).filter(k=>k!=="spread"||levels[k]<2).sort(()=>Math.random()-.5);choices.replaceChildren();for(const kind of pool.slice(0,3)){const b=document.createElement("button");b.className="choice";b.innerHTML=`<strong>${info[kind].name}</strong><small>${info[kind].desc}<br>Current level: ${levels[kind]}</small>`;b.onclick=()=>choose(kind);choices.appendChild(b)}upgradePanel.classList.add("show");sound(560,.25,"sine",.05)}
  function choose(k:Upgrade){levels[k]++;if(k==="damage")damage++;if(k==="rapid")fireRate*=1.22;if(k==="spread")shotCount+=2;if(k==="pierce")pierce++;if(k==="speed")moveSpeed*=1.18;if(k==="vitality"){maxHp+=25;hp=Math.min(maxHp,hp+35)}wave++;spawned=0;spawnTimer=1;shots.forEach(s=>disposeMesh(s.mesh));shots=[];state="play";upgradePanel.classList.remove("show");sound(760,.16,"sine",.06)}
  function update(dt:number){
    shake=Math.max(0,shake-dt*2.5);flash=Math.max(0,flash-dt);cooldown-=dt;invuln-=dt;
    for(const p of sparks){p.mesh.position.addScaledVector(p.velocity,dt);p.velocity.y-=12*dt;p.life-=dt;p.mesh.scale.setScalar(Math.max(.05,p.life/p.max))}for(const p of sparks.filter(p=>p.life<=0))disposeMesh(p.mesh);sparks=sparks.filter(p=>p.life>0);
    if(state!=="play")return;
    let dx=+(keys.has("d")||keys.has("arrowright"))-+(keys.has("a")||keys.has("arrowleft")),dz=+(keys.has("s")||keys.has("arrowdown"))-+(keys.has("w")||keys.has("arrowup"));const len=Math.hypot(dx,dz)||1;player.position.x+=dx/len*moveSpeed*dt;player.position.z+=dz/len*moveSpeed*dt;player.position.x=THREE.MathUtils.clamp(player.position.x,-19.7,19.7);player.position.z=THREE.MathUtils.clamp(player.position.z,-12.7,12.7);player.rotation.y=Math.atan2(aimPoint.x-player.position.x,aimPoint.z-player.position.z);if(pointerDown||keys.has(" "))shoot();
    spawnTimer-=dt;if(spawned<quota(wave)&&spawnTimer<=0){spawnEnemy();spawnTimer=Math.max(.18,.78-wave*.055)}
    for(const s of shots){s.mesh.position.addScaledVector(s.velocity,dt);s.life-=dt;s.mesh.rotation.x+=dt*8}
    for(const e of enemies){e.cool-=dt;e.hit-=dt;e.phase+=dt;e.mesh.rotation.y+=dt*(e.kind==="boss"?.7:1.8);const d=player.position.clone().sub(e.mesh.position),dist=Math.hypot(d.x,d.z)||1;if(e.kind==="shooter"){if(dist>9)e.mesh.position.addScaledVector(d.normalize(),e.speed*dt);else if(dist<6)e.mesh.position.addScaledVector(d.normalize(),-e.speed*.7*dt);if(e.cool<=0){enemyShot(e);e.cool=Math.max(.7,1.45-wave*.06)}}else{let a=Math.atan2(d.x,d.z)+(e.kind==="runner"?Math.sin(e.phase*8)*.5:0);e.mesh.position.x+=Math.sin(a)*e.speed*dt;e.mesh.position.z+=Math.cos(a)*e.speed*dt}if(e.kind==="boss"&&e.cool<=0){for(const o of [-.42,-.21,0,.21,.42])enemyShot(e,o,10.5);e.cool=1.35;shake=.3;sound(105,.1,"sawtooth",.04)}if(dist<e.radius+.65){hurt(e.kind==="boss"?20:e.kind==="tank"?15:10);e.mesh.position.addScaledVector(d.normalize(),-1)}}
    for(const s of shots){if(s.life<=0)continue;if(s.enemy){if(s.mesh.position.distanceTo(player.position)<.82){s.life=0;hurt(s.damage)}continue}for(const e of enemies){if(e.hp<=0||e.hit>0)continue;if(s.mesh.position.distanceTo(e.mesh.position)<e.radius+.22){e.hp-=s.damage;e.hit=.03;s.pierce--;if(s.pierce<=0)s.life=0;(e.mesh.material as THREE.MeshStandardMaterial).emissiveIntensity=2;setTimeout(()=>{if(e.mesh.parent)(e.mesh.material as THREE.MeshStandardMaterial).emissiveIntensity=.28},55);burst(s.mesh.position,enemyColor(e.kind),e.hp<=0?15:4,e.hp<=0?10:4);if(e.hp<=0){score+=e.kind==="boss"?1500:e.kind==="tank"?250:e.kind==="shooter"?220:e.kind==="runner"?140:100;sound(110,.06,"sawtooth",.035)}break}}}
    for(const e of enemies.filter(e=>e.hp<=0))disposeMesh(e.mesh);enemies=enemies.filter(e=>e.hp>0);for(const s of shots.filter(s=>s.life<=0||Math.abs(s.mesh.position.x)>24||Math.abs(s.mesh.position.z)>18))disposeMesh(s.mesh);shots=shots.filter(s=>s.life>0&&Math.abs(s.mesh.position.x)<=24&&Math.abs(s.mesh.position.z)<=18);
    if(hp<=0)finish("lost");else if(spawned===quota(wave)&&enemies.length===0){if(wave===totalWaves)finish("won");else present()}
  }
  function frame(now:number){const dt=Math.min(.033,(now-last)/1000);last=now;raycaster.setFromCamera(pointerNdc,camera);raycaster.ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0,1,0),0),aimPoint);update(dt);const target=new THREE.Vector3(player.position.x,0,player.position.z);camera.position.x=THREE.MathUtils.lerp(camera.position.x,target.x*.18+(Math.random()-.5)*shake,.08);camera.position.z=THREE.MathUtils.lerp(camera.position.z,30+target.z*.18+(Math.random()-.5)*shake,.08);camera.lookAt(target);player.visible=!(invuln>0&&Math.floor(invuln*18)%2===0);renderer.domElement.style.filter=flash>0?`brightness(${1+flash}) saturate(${1+flash*2})`:"";renderer.render(scene,camera);stats.textContent=`HP ${Math.max(0,Math.ceil(hp))}/${maxHp}  ·  SCORE ${score}`;waveLabel.textContent=`WAVE ${wave}/${totalWaves}  ·  HOSTILES ${enemies.length+Math.max(0,quota(wave)-spawned)}`;const tags=(Object.keys(levels) as Upgrade[]).filter(k=>levels[k]).map(k=>`${info[k].name.split(" ")[0]} ${levels[k]}`);build.textContent=tags.length?`BUILD: ${tags.join(" · ")}`:"BUILD: BASE PULSE CANNON";raf=requestAnimationFrame(frame)}
  const keyDown=(e:KeyboardEvent)=>{const k=e.key.toLowerCase();keys.add(k);if([" ","arrowup","arrowdown","arrowleft","arrowright"].includes(k))e.preventDefault();if(k==="r")reset()},keyUp=(e:KeyboardEvent)=>keys.delete(e.key.toLowerCase());
  const move=(e:PointerEvent)=>{const r=renderer.domElement.getBoundingClientRect();pointerNdc.set((e.clientX-r.left)/r.width*2-1,-((e.clientY-r.top)/r.height)*2+1)},down=(e:PointerEvent)=>{move(e);pointerDown=true;renderer.domElement.setPointerCapture?.(e.pointerId);sound(1,.01,"sine",.0001)},up=()=>pointerDown=false;
  window.addEventListener("resize",resize);window.addEventListener("keydown",keyDown);window.addEventListener("keyup",keyUp);renderer.domElement.addEventListener("pointermove",move);renderer.domElement.addEventListener("pointerdown",down);window.addEventListener("pointerup",up);restart.addEventListener("click",reset);retry.addEventListener("click",reset);resize();reset();raf=requestAnimationFrame(frame);
  return()=>{cancelAnimationFrame(raf);window.removeEventListener("resize",resize);window.removeEventListener("keydown",keyDown);window.removeEventListener("keyup",keyUp);renderer.domElement.removeEventListener("pointermove",move);renderer.domElement.removeEventListener("pointerdown",down);window.removeEventListener("pointerup",up);restart.removeEventListener("click",reset);retry.removeEventListener("click",reset);clearActors();scene.traverse(o=>{if(o instanceof THREE.Mesh){o.geometry.dispose();const m=o.material as THREE.Material|THREE.Material[];(Array.isArray(m)?m:[m]).forEach(x=>x.dispose())}});renderer.dispose();audio?.close();root.replaceChildren()};
}