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

type P = { x: number; y: number };
type Item = P & { taken?: boolean; mesh?: THREE.Object3D };
type Enemy = P & { delay: number; last: number; mesh?: THREE.Object3D };
type State = "play" | "won" | "lost";

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

  render(<div style="min-height:100vh;background:radial-gradient(circle at top,#17344b,#050a12 70%);color:#eefaff;font-family:system-ui,sans-serif;display:grid;place-items:center;padding:12px;box-sizing:border-box">
    <main style="width:min(96vw,900px);text-align:center">
      <h1 style="margin:0;letter-spacing:.12em;font-size:clamp(25px,5vw,40px)">SHIFTING MAZE 3D</h1>
      <p style="margin:4px 0 9px;color:#9fc1d5">Escape the shifting labyrinth before its hunters find you.</p>
      <section style="display:flex;gap:9px;justify-content:space-between;align-items:center;flex-wrap:wrap;margin-bottom:8px;font-weight:800">
        <span id="level">Maze 1</span><span id="score">Score 0</span><span id="keys" style="color:#ffd166">Keys 0/0</span><span id="power" style="color:#74e8ff">Shield —</span>
        <button id="restart" style="border:0;border-radius:9px;background:#ef476f;color:white;padding:9px 14px;font-weight:900;cursor:pointer">Restart (R)</button>
      </section>
      <div style="position:relative;width:100%;aspect-ratio:19/13;overflow:hidden;background:#07111b;border:3px solid #315c79;border-radius:12px;box-sizing:border-box">
        <canvas id="game" style="display:block;width:100%;height:100%;touch-action:none"></canvas>
        <div id="banner" style="display:none;position:absolute;inset:0;background:#020813cc;place-items:center;pointer-events:none">
          <div><strong id="banner-title" style="display:block;font-size:clamp(30px,7vw,54px);color:#ef476f">RUN ENDED</strong><span id="banner-sub" style="font-weight:800">Press R or tap Retry</span></div>
        </div>
      </div>
      <div style="display:grid;grid-template-columns:repeat(3,58px);gap:6px;justify-content:center;margin:10px auto 5px;user-select:none">
        <i></i><button data-d="0,-1" aria-label="Move up" style="height:44px;font-size:21px;touch-action:none">▲</button><i></i>
        <button data-d="-1,0" aria-label="Move left" style="height:44px;font-size:21px;touch-action:none">◀</button><button data-d="0,1" aria-label="Move down" style="height:44px;font-size:21px;touch-action:none">▼</button><button data-d="1,0" aria-label="Move right" style="height:44px;font-size:21px;touch-action:none">▶</button>
      </div>
      <p id="message" style="min-height:22px;margin:3px;color:#a9c7d8">WASD / arrows / buttons · Collect all keys, then reach the green exit</p>
    </main>
  </div>, root);

  const canvas = root.querySelector("#game") as HTMLCanvasElement;
  const q = (s: string) => root.querySelector(s) as HTMLElement;
  const levelEl=q("#level"), scoreEl=q("#score"), keysEl=q("#keys"), powerEl=q("#power"), message=q("#message");
  const restart=q("#restart") as HTMLButtonElement, banner=q("#banner"), bannerTitle=q("#banner-title"), bannerSub=q("#banner-sub");
  const buttons=Array.from(root.querySelectorAll("[data-d]")) as HTMLButtonElement[];
  const renderer=new THREE.WebGLRenderer({canvas,antialias:true});
  renderer.setPixelRatio(Math.min(devicePixelRatio,2));
  renderer.shadowMap.enabled=true;
  renderer.shadowMap.type=THREE.PCFSoftShadowMap;
  renderer.outputColorSpace=THREE.SRGBColorSpace;
  const scene=new THREE.Scene();
  scene.background=new THREE.Color(0x06101a);
  scene.fog=new THREE.Fog(0x06101a,12,36);
  const camera=new THREE.PerspectiveCamera(48,19/13,.1,80);
  scene.add(new THREE.HemisphereLight(0x8ed8ff,0x071018,1.7));
  const sun=new THREE.DirectionalLight(0xffffff,2.2);sun.position.set(-8,16,8);sun.castShadow=true;sun.shadow.mapSize.set(1024,1024);scene.add(sun);
  const world=new THREE.Group();scene.add(world);
  const dirs:P[]=[{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
  let grid:number[][]=[],cols=15,rows=11;
  let player:P={x:1,y:1},exit:P={x:1,y:1};
  let keys:Item[]=[],traps:Item[]=[],shields:Item[]=[],doors:Item[]=[],enemies:Enemy[]=[];
  let playerMesh:THREE.Mesh,exitMesh:THREE.Group;
  let level=1,score=0,state:State="play",shield=0,nextAt=0,raf=0,lastFrame=0;
  let held:P|null=null,lastMove=0,audio:AudioContext|null=null,disposed=false;

  const mat=(color:number,emissive=0)=>new THREE.MeshStandardMaterial({color,emissive,emissiveIntensity:emissive?1.1:0,roughness:.58,metalness:.12});
  function mesh(geo:THREE.BufferGeometry,material:THREE.Material,x:number,z:number,y=0){const m=new THREE.Mesh(geo,material);m.position.set(x,y,z);m.castShadow=true;m.receiveShadow=true;world.add(m);return m;}
  function clearWorld(){world.traverse(o=>{const m=o as THREE.Mesh;if(m.geometry)m.geometry.dispose();const a=m.material as THREE.Material|THREE.Material[]|undefined;if(a)(Array.isArray(a)?a:[a]).forEach(v=>v.dispose());});world.clear();}
  function beep(f:number,d=.06){try{audio||=new AudioContext();const o=audio.createOscillator(),g=audio.createGain();o.frequency.value=f;g.gain.value=.045;g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+d);o.connect(g).connect(audio.destination);o.start();o.stop(audio.currentTime+d);}catch{}}
  const id=(p:P)=>`${p.x},${p.y}`;
  const same=(a:P,b:P)=>a.x===b.x&&a.y===b.y;
  const open=(x:number,y:number)=>x>0&&y>0&&x<cols-1&&y<rows-1&&grid[y][x]===0;
  const wx=(x:number)=>x-cols/2+.5, wz=(y:number)=>y-rows/2+.5;
  function shuffle<T>(a:T[]){for(let i=a.length-1;i;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];}return a;}

  function buildWorld(){
    clearWorld();
    const floor=mesh(new THREE.BoxGeometry(cols,1,rows),mat(0x102535),0,0,-.56);floor.receiveShadow=true;
    const wallGeo=new THREE.BoxGeometry(.94,1.55,.94),wallMat=mat(0x315f79);
    for(let y=0;y<rows;y++)for(let x=0;x<cols;x++)if(grid[y][x])mesh(wallGeo.clone(),wallMat.clone(),wx(x),wz(y),.22);
    exitMesh=new THREE.Group();
    const ring=new THREE.Mesh(new THREE.TorusGeometry(.34,.09,10,24),mat(0x06d6a0,0x024c3b));ring.rotation.x=Math.PI/2;ring.position.y=.12;exitMesh.add(ring);
    const beam=new THREE.Mesh(new THREE.CylinderGeometry(.28,.38,.08,16),mat(0x06d6a0,0x025f48));beam.position.y=.03;exitMesh.add(beam);exitMesh.position.set(wx(exit.x),0,wz(exit.y));world.add(exitMesh);
    for(const d of doors){d.mesh=mesh(new THREE.BoxGeometry(.86,1.15,.18),mat(0xa66b32),wx(d.x),wz(d.y),.08);}
    for(const k of keys){const g=new THREE.Group(),m=mat(0xffd166,0x765000);const loop=new THREE.Mesh(new THREE.TorusGeometry(.17,.06,8,18),m),shaft=new THREE.Mesh(new THREE.BoxGeometry(.4,.07,.07),m);loop.position.x=-.14;shaft.position.x=.15;g.add(loop,shaft);g.rotation.x=-Math.PI/2;g.position.set(wx(k.x),.35,wz(k.y));world.add(g);k.mesh=g;}
    for(const t of traps){const cone=new THREE.Mesh(new THREE.ConeGeometry(.28,.45,4),mat(0xbf4961,0x46101e));cone.position.set(wx(t.x),.2,wz(t.y));cone.rotation.y=Math.PI/4;world.add(cone);t.mesh=cone;}
    for(const s of shields){const orb=new THREE.Mesh(new THREE.IcosahedronGeometry(.27,1),new THREE.MeshStandardMaterial({color:0x74e8ff,emissive:0x176778,emissiveIntensity:1,transparent:true,opacity:.85}));orb.position.set(wx(s.x),.34,wz(s.y));world.add(orb);s.mesh=orb;}
    for(const e of enemies){const hunter=new THREE.Mesh(new THREE.OctahedronGeometry(.34),mat(0xef476f,0x7d1029));hunter.position.set(wx(e.x),.38,wz(e.y));world.add(hunter);e.mesh=hunter;}
    playerMesh=mesh(new THREE.CapsuleGeometry(.22,.32,5,10),mat(0x55c8ff,0x174d70),wx(player.x),wz(player.y),.35);
    const span=Math.max(cols,rows);camera.position.set(wx(player.x)+5,Math.min(13,7+span*.16),wz(player.y)+6);
  }

  function generate(){
    cols=Math.min(25,13+level*2);rows=Math.min(19,9+level*2);grid=Array.from({length:rows},()=>Array(cols).fill(1));
    const stack:P[]=[{x:1,y:1}];grid[1][1]=0;
    while(stack.length){const p=stack[stack.length-1],choices=shuffle(dirs.map(d=>({x:p.x+d.x*2,y:p.y+d.y*2})).filter(n=>n.x>0&&n.y>0&&n.x<cols-1&&n.y<rows-1&&grid[n.y][n.x]===1));if(!choices.length){stack.pop();continue;}const n=choices[0];grid[(p.y+n.y)/2][(p.x+n.x)/2]=0;grid[n.y][n.x]=0;stack.push(n);}
    const dist=new Map<string,number>([["1,1",0]]),queue:P[]=[{x:1,y:1}];
    while(queue.length){const p=queue.shift()!;for(const d of dirs){const n={x:p.x+d.x,y:p.y+d.y};if(open(n.x,n.y)&&!dist.has(id(n))){dist.set(id(n),dist.get(id(p))!+1);queue.push(n);}}}
    const cells=[...dist].map(([s,d])=>{const [x,y]=s.split(",").map(Number);return{x,y,d};}).sort((a,b)=>b.d-a.d);
    player={x:1,y:1};exit={x:cells[0].x,y:cells[0].y};
    const pool=shuffle(cells.filter(c=>c.d>5&&!same(c,exit)).slice(0,Math.max(10,Math.floor(cells.length*.7))));
    const take=():Item=>{const p=pool.pop()||cells[Math.floor(Math.random()*cells.length)];return{x:p.x,y:p.y};};
    keys=Array.from({length:Math.min(6,2+level)},take);traps=Array.from({length:Math.max(0,level-1)},take);shields=Array.from({length:level>1?1:0},take);doors=level>2?Array.from({length:Math.min(3,level-2)},take):[];
    enemies=Array.from({length:Math.min(4,level)},()=>{const p=take();return{x:p.x,y:p.y,delay:Math.max(250,700-level*55),last:performance.now()+Math.random()*500};});
    state="play";shield=0;held=null;banner.style.display="none";message.textContent=`Maze ${level}: collect every key and escape!`;buildWorld();updateHud();
  }
  function updateHud(){const got=keys.filter(k=>k.taken).length;levelEl.textContent=`Maze ${level}`;scoreEl.textContent=`Score ${score}`;keysEl.textContent=`Keys ${got}/${keys.length}`;powerEl.textContent=shield?`Shield ${shield}`:"Shield —";restart.textContent=state==="play"?"Restart (R)":"Retry (R)";}
  function blocked(x:number,y:number){if(!open(x,y))return true;const d=doors.find(v=>!v.taken&&v.x===x&&v.y===y);if(d&&keys.some(k=>!k.taken)){message.textContent="Locked passage: collect every key!";beep(150);return true;}if(d){d.taken=true;d.mesh!.visible=false;}return false;}
  function lose(text:string){if(shield){shield--;score=Math.max(0,score-40);message.textContent="Shield absorbed the danger!";beep(310,.12);updateHud();return;}state="lost";held=null;message.textContent=text;banner.style.display="grid";bannerTitle.textContent="RUN ENDED";bannerTitle.style.color="#ef476f";bannerSub.textContent="Press R or tap Retry";beep(100,.3);updateHud();}
  function inspect(){
    const k=keys.find(v=>!v.taken&&same(v,player));if(k){k.taken=true;k.mesh!.visible=false;score+=120+level*15;message.textContent="Key secured!";beep(820);}
    const s=shields.find(v=>!v.taken&&same(v,player));if(s){s.taken=true;s.mesh!.visible=false;shield=Math.min(2,shield+1);score+=75;message.textContent="Shield charged!";beep(1080);}
    const t=traps.find(v=>!v.taken&&same(v,player));if(t){t.taken=true;t.mesh!.visible=false;lose("A hidden trap ended the run!");}
    if(enemies.some(e=>same(e,player)))lose("A hunter caught you!");
    if(state==="play"&&same(player,exit)){if(keys.every(k=>k.taken)){state="won";held=null;score+=500+level*100;nextAt=performance.now()+1300;message.textContent="Maze cleared! The labyrinth is shifting…";banner.style.display="grid";bannerTitle.textContent="MAZE CLEARED!";bannerTitle.style.color="#06d6a0";bannerSub.textContent="Preparing a harder maze…";beep(1200,.2);}else message.textContent="Exit sealed — keys remain.";}updateHud();
  }
  function move(dx:number,dy:number){if(state!=="play")return;const nx=player.x+dx,ny=player.y+dy;if(blocked(nx,ny)){beep(120,.025);return;}player={x:nx,y:ny};inspect();}
  function chase(e:Enemy){const choices=dirs.map(d=>({x:e.x+d.x,y:e.y+d.y})).filter(n=>open(n.x,n.y)&&!doors.some(v=>!v.taken&&same(v,n)));choices.sort((a,b)=>(Math.abs(a.x-player.x)+Math.abs(a.y-player.y))-(Math.abs(b.x-player.x)+Math.abs(b.y-player.y)));const n=Math.random()<.82?choices[0]:choices[Math.floor(Math.random()*choices.length)];if(n){e.x=n.x;e.y=n.y;}if(same(e,player))lose("Relentless hunters cornered you!");}

  function frame(now:number){
    if(disposed)return;const dt=Math.min(.04,(now-lastFrame)/1000||.016);lastFrame=now;
    if(held&&state==="play"&&now-lastMove>115){move(held.x,held.y);lastMove=now;}
    if(state==="play")for(const e of enemies)if(now-e.last>e.delay){chase(e);e.last=now;}
    if(state==="won"&&now>nextAt){level++;generate();}
    const follow=(o:THREE.Object3D|undefined,p:P,y:number)=>{if(o){o.position.x=THREE.MathUtils.damp(o.position.x,wx(p.x),16,dt);o.position.z=THREE.MathUtils.damp(o.position.z,wz(p.y),16,dt);o.position.y=y;}};
    follow(playerMesh,player,.35);for(const e of enemies){follow(e.mesh,e,.38);if(e.mesh)e.mesh.rotation.y+=dt*3;}
    const target=new THREE.Vector3(wx(player.x),0,wz(player.y));camera.position.x=THREE.MathUtils.damp(camera.position.x,target.x+5,3.2,dt);camera.position.z=THREE.MathUtils.damp(camera.position.z,target.z+6,3.2,dt);camera.lookAt(target);exitMesh.rotation.y+=dt;for(const k of keys)if(k.mesh&&!k.taken)k.mesh.rotation.z+=dt*2;
    const rect=canvas.getBoundingClientRect(),w=Math.max(1,Math.floor(rect.width)),h=Math.max(1,Math.floor(rect.height));if(canvas.width!==Math.floor(w*renderer.getPixelRatio())||canvas.height!==Math.floor(h*renderer.getPixelRatio())){renderer.setSize(w,h,false);camera.aspect=w/h;camera.updateProjectionMatrix();}
    renderer.render(scene,camera);raf=requestAnimationFrame(frame);
  }

  const keyMap:Record<string,P>={ArrowUp:{x:0,y:-1},w:{x:0,y:-1},ArrowDown:{x:0,y:1},s:{x:0,y:1},ArrowLeft:{x:-1,y:0},a:{x:-1,y:0},ArrowRight:{x:1,y:0},d:{x:1,y:0}};
  const down=(e:KeyboardEvent)=>{if(e.key.toLowerCase()==="r"){generate();return;}const d=keyMap[e.key]||keyMap[e.key.toLowerCase()];if(d){e.preventDefault();if(!e.repeat)move(d.x,d.y);held=d;lastMove=performance.now();}};
  const up=(e:KeyboardEvent)=>{if(keyMap[e.key]||keyMap[e.key.toLowerCase()])held=null;};
  const blur=()=>held=null;
  const pointerHandlers=buttons.map(b=>{const d=b.dataset.d!.split(",").map(Number);const start=(e:PointerEvent)=>{e.preventDefault();held={x:d[0],y:d[1]};move(d[0],d[1]);lastMove=performance.now();b.setPointerCapture?.(e.pointerId);};const end=()=>held=null;b.addEventListener("pointerdown",start);b.addEventListener("pointerup",end);b.addEventListener("pointercancel",end);return{start,end};});
  const reset=()=>generate();window.addEventListener("keydown",down);window.addEventListener("keyup",up);window.addEventListener("blur",blur);restart.addEventListener("click",reset);generate();raf=requestAnimationFrame(frame);
  return()=>{disposed=true;cancelAnimationFrame(raf);window.removeEventListener("keydown",down);window.removeEventListener("keyup",up);window.removeEventListener("blur",blur);restart.removeEventListener("click",reset);buttons.forEach((b,i)=>{b.removeEventListener("pointerdown",pointerHandlers[i].start);b.removeEventListener("pointerup",pointerHandlers[i].end);b.removeEventListener("pointercancel",pointerHandlers[i].end);});clearWorld();renderer.dispose();audio?.close();root.replaceChildren();};
}