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

type Mark = "X" | "O" | null;
type Power = "block" | "move" | "surge" | null;

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

  const unrender = render(
    <main class="shell"><section class="card">
      <header><div><p class="eyebrow">WEBGL TACTICAL ARENA</p><h1>Power Grid</h1></div><button id="restart" type="button">Restart</button></header>
      <div class="setup"><button class="size active" data-size="3" type="button">3×3</button><button class="size" data-size="4" type="button">4×4</button><label><input id="powers" type="checkbox" checked /> Tactical powers</label></div>
      <div class="hud"><div><span>Score</span><b id="score">0</b></div><div><span>Charge</span><b id="charge">0 / 3</b></div><div><span>Rival</span><b id="rival">0</b></div></div>
      <div id="status" role="status">Your turn — connect 3</div>
      <div class="powerbar" id="powerbar">
        <button data-power="block" type="button">▣ Block <small>1</small></button>
        <button data-power="move" type="button">↔ Move <small>2</small></button>
        <button data-power="surge" type="button">⚡ Surge <small>3</small></button>
      </div>
      <div id="arena" tabindex="0" aria-label="3D tactical tic-tac-toe board"></div>
      <p class="help">Click a power, then a cell · Move: select your mark, then an empty cell · Arrows + Enter · R restart</p>
    </section></main>, root
  );

  const style = document.createElement("style");
  style.textContent = `
    *{box-sizing:border-box}body{margin:0;background:#030912;color:#eaf8ff;font-family:ui-rounded,system-ui,sans-serif}.shell{min-height:100vh;display:grid;place-items:center;padding:12px;background:radial-gradient(circle at 50% 0,#17465f,#030912 68%)}.card{width:min(96vw,700px);padding:17px;border:1px solid #285b73;border-radius:23px;background:#081722f3;box-shadow:0 25px 70px #000b}header{display:flex;align-items:center;justify-content:space-between;gap:12px}h1{margin:0;font-size:clamp(29px,7vw,44px);line-height:1}.eyebrow{margin:0 0 5px;color:#65f1cf;font-size:10px;font-weight:900;letter-spacing:.18em}button{border:0;border-radius:10px;padding:9px 13px;background:#65f1cf;color:#062019;font:inherit;font-weight:900;cursor:pointer;box-shadow:0 4px 0 #238975}button:active{transform:translateY(2px);box-shadow:0 2px 0 #238975}.setup{display:flex;gap:8px;align-items:center;margin:14px 0 9px}.setup button{padding:7px 12px;background:#173648;color:#9bb8c6;box-shadow:none}.setup button.active{background:#ffbd62;color:#2d1900}.setup label{margin-left:auto;color:#a9c7d5;font-size:12px;font-weight:800}.setup input{accent-color:#65f1cf}.hud{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.hud div{text-align:center;padding:7px;border-radius:11px;background:#102b3c}.hud span{display:block;color:#7f9fad;font-size:9px;text-transform:uppercase;letter-spacing:.14em}.hud b{font-size:20px}.hud div:nth-child(2) b{color:#ffda6f}#status{text-align:center;min-height:35px;padding:9px 3px;color:#d8f3ff;font-weight:800}.powerbar{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin-bottom:10px}.powerbar button{padding:8px 4px;background:#17384b;color:#a8c3cf;box-shadow:none;font-size:12px}.powerbar button.ready{background:#743fc5;color:white;box-shadow:0 3px 0 #3a1e66}.powerbar button.selected{outline:3px solid #ffdb69;background:#a35ceb}.powerbar button:disabled{opacity:.35;cursor:not-allowed}.powerbar small{color:#ffdc74}#arena{width:100%;aspect-ratio:1.25;overflow:hidden;border-radius:17px;background:#06121d;outline:none;touch-action:none}#arena canvas{display:block;width:100%;height:100%}#arena:focus{box-shadow:0 0 0 3px #65f1cf}.help{text-align:center;margin:9px 0 0;color:#7696a5;font-size:10px}@media(max-width:520px){.card{padding:12px}.setup{flex-wrap:wrap}.setup label{width:100%;margin:0}.powerbar button{font-size:10px}}
  `;
  document.head.appendChild(style);

  const arena = root.querySelector<HTMLElement>("#arena")!;
  const status = root.querySelector<HTMLElement>("#status")!;
  const scoreEl = root.querySelector<HTMLElement>("#score")!;
  const chargeEl = root.querySelector<HTMLElement>("#charge")!;
  const rivalEl = root.querySelector<HTMLElement>("#rival")!;
  const restart = root.querySelector<HTMLButtonElement>("#restart")!;
  const toggle = root.querySelector<HTMLInputElement>("#powers")!;
  const sizeButtons = [...root.querySelectorAll<HTMLButtonElement>(".size")];
  const powerButtons = [...root.querySelectorAll<HTMLButtonElement>("[data-power]")];

  const renderer = new THREE.WebGLRenderer({ antialias:true });
  renderer.setPixelRatio(Math.min(devicePixelRatio,2)); renderer.setClearColor(0x06121d); renderer.shadowMap.enabled=true; arena.appendChild(renderer.domElement);
  const scene = new THREE.Scene(); scene.fog = new THREE.FogExp2(0x06121d,.04);
  const camera = new THREE.PerspectiveCamera(43,1,.1,80);
  const raycaster = new THREE.Raycaster(), mouse = new THREE.Vector2();
  const boardGroup = new THREE.Group(), pieceGroup = new THREE.Group(); scene.add(boardGroup,pieceGroup);
  scene.add(new THREE.HemisphereLight(0xa5e9ff,0x071019,1.8));
  const light = new THREE.DirectionalLight(0xffffff,2.2); light.position.set(-4,9,7); light.castShadow=true; scene.add(light);
  const floor = new THREE.Mesh(new THREE.CircleGeometry(12,64),new THREE.MeshStandardMaterial({color:0x071722,roughness:.8,metalness:.3})); floor.rotation.x=-Math.PI/2; floor.position.y=-.5; scene.add(floor);

  let size=3, win=3, board:Mark[]=[], blocked:boolean[]=[], cells:THREE.Mesh[]=[], pieces:(THREE.Object3D|null)[]=[];
  let selected=0, hovered=-1, phase="playing", cpu=false, cpuTimer=0, raf=0, score=0, rival=0, charge=0, turns=0;
  let activePower:Power=null, moveFrom=-1, surge=false, winner:number[]|null=null, powersOn=true;
  const spent = {block:false,move:false,surge:false};
  let audio:AudioContext|null=null;

  function tone(f:number,d=.07,type:OscillatorType="sine") { try { audio ||= new AudioContext(); const o=audio.createOscillator(),g=audio.createGain(); o.type=type;o.frequency.value=f;g.gain.setValueAtTime(.04,audio.currentTime);g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+d);o.connect(g).connect(audio.destination);o.start();o.stop(audio.currentTime+d); } catch{} }
  function dispose(object:THREE.Object3D){object.traverse(c=>{const m=c as THREE.Mesh;if(m.geometry)m.geometry.dispose();if(m.material)(Array.isArray(m.material)?m.material:[m.material]).forEach(x=>x.dispose());});}
  function clear(group:THREE.Group){while(group.children.length){const child=group.children.pop()!;dispose(child);}}
  function pos(i:number){const gap=2.05,h=(size-1)/2;return new THREE.Vector3((i%size-h)*gap,0,(Math.floor(i/size)-h)*gap);}
  function allLines(){const out:number[][]=[];for(let r=0;r<size;r++)for(let c=0;c<size;c++)for(const [dx,dy] of [[1,0],[0,1],[1,1],[-1,1]]){const x=c+dx*(win-1),y=r+dy*(win-1);if(x>=0&&x<size&&y>=0&&y<size)out.push(Array.from({length:win},(_,k)=>(r+dy*k)*size+c+dx*k));}return out;}
  function line(mark:Mark){return allLines().find(a=>a.every(i=>board[i]===mark))||null;}
  function open(){return board.map((m,i)=>!m&&!blocked[i]?i:-1).filter(i=>i>=0);}
  function immediate(mark:"X"|"O"){for(const i of open()){board[i]=mark;const yes=!!line(mark);board[i]=null;if(yes)return i;}return -1;}
  function build(){clear(boardGroup);clear(pieceGroup);cells=[];pieces=Array(size*size).fill(null);const extent=(size-1)*2.05+2;const base=new THREE.Mesh(new THREE.BoxGeometry(extent,.4,extent),new THREE.MeshStandardMaterial({color:0x0d2939,roughness:.4,metalness:.55}));base.position.y=-.3;boardGroup.add(base);for(let i=0;i<size*size;i++){const cell=new THREE.Mesh(new THREE.BoxGeometry(1.82,.2,1.82),new THREE.MeshStandardMaterial({color:0x14384b,emissive:0x06121d,roughness:.35,metalness:.5}));cell.position.copy(pos(i));cell.userData.index=i;cell.castShadow=true;boardGroup.add(cell);cells.push(cell);}const d=size*2.5+3;camera.position.set(0,d*.76,d*.88);camera.lookAt(0,0,0);}
  function makePiece(i:number,mark:"X"|"O") {const g=new THREE.Group(),mat=new THREE.MeshStandardMaterial({color:mark==="X"?0x65f1cf:0xffb65f,emissive:mark==="X"?0x125d50:0x672e08,emissiveIntensity:.8,roughness:.25,metalness:.5});if(mark==="X")for(const a of [Math.PI/4,-Math.PI/4]){const bar=new THREE.Mesh(new THREE.BoxGeometry(1.45,.32,.32),mat);bar.rotation.y=a;g.add(bar);}else{const ring=new THREE.Mesh(new THREE.TorusGeometry(.62,.19,14,36),mat);ring.rotation.x=Math.PI/2;g.add(ring);}g.position.copy(pos(i));g.position.y=.38;g.scale.setScalar(.01);g.userData.born=performance.now();pieceGroup.add(g);pieces[i]=g;}
  function removePiece(i:number){const p=pieces[i];if(p){pieceGroup.remove(p);dispose(p);pieces[i]=null;}}
  function addBlock(i:number){blocked[i]=true;const crystal=new THREE.Mesh(new THREE.OctahedronGeometry(.55),new THREE.MeshStandardMaterial({color:0x9b67ff,emissive:0x3e187b,emissiveIntensity:1,roughness:.2,metalness:.5}));crystal.position.copy(pos(i));crystal.position.y=.55;crystal.userData.block=i;boardGroup.add(crystal);}
  function syncHud(){scoreEl.textContent=String(score);rivalEl.textContent=String(rival);chargeEl.textContent=powersOn?`${charge} / 3`:"OFF";powerButtons.forEach(b=>{const p=b.dataset.power as keyof typeof spent,cost=p==="block"?1:p==="move"?2:3;b.disabled=!powersOn||spent[p]||charge<cost||phase!=="playing"||cpu;b.classList.toggle("ready",!b.disabled);b.classList.toggle("selected",activePower===p);});}
  function cancelPower(){activePower=null;moveFrom=-1;syncHud();}
  function finish(mark:"X"|"O"){const found=line(mark);if(found){phase="over";winner=found;if(mark==="X"){score++;status.textContent="Tactical victory! Restart for another round.";tone(820,.25,"triangle");}else{rival++;status.textContent="The rival completed its line — retry!";tone(135,.3,"sawtooth");}syncHud();return true;}if(!open().length){phase="over";status.textContent="Grid locked — restart for a rematch.";syncHud();return true;}return false;}
  function cpuChoice(){const w=immediate("O");if(w>=0)return w;const stop=immediate("X");if(stop>=0)return stop;return open().sort((a,b)=>{const h=(size-1)/2,sa=5-Math.abs(a%size-h)-Math.abs(Math.floor(a/size)-h),sb=5-Math.abs(b%size-h)-Math.abs(Math.floor(b/size)-h);return sb-sa+Math.random()-.5;})[0];}
  function cpuTurn(){if(phase!=="playing")return;let choices=open();if(!choices.length){finish("O");return;}const move=cpuChoice();board[move]="O";makePiece(move,"O");tone(220,.1,"square");cpu=false;if(!finish("O")){status.textContent=`Your turn — connect ${win}`;syncHud();}}
  function queueCpu(){cpu=true;status.textContent="Rival is calculating counterplay…";syncHud();clearTimeout(cpuTimer);cpuTimer=window.setTimeout(cpuTurn,360);}
  function normalPlay(i:number){if(board[i]||blocked[i]){tone(100,.06,"square");return;}board[i]="X";makePiece(i,"X");turns++;charge=Math.min(3,charge+1);tone(540,.08,"triangle");if(finish("X"))return;if(surge){surge=false;status.textContent="Surge turn: place one guarded extra mark!";syncHud();}else queueCpu();}
  function usePower(i:number){if(activePower==="block"){if(board[i]||blocked[i])return tone(100);charge--;spent.block=true;addBlock(i);cancelPower();status.textContent="Cell sealed. The rival must route around it.";tone(680,.14,"triangle");queueCpu();return;}if(activePower==="move"){if(moveFrom<0){if(board[i]!=="X")return tone(100);moveFrom=i;status.textContent="Now choose an empty destination.";return;}if(board[i]||blocked[i]||i===moveFrom)return tone(100);board[moveFrom]=null;removePiece(moveFrom);board[i]="X";makePiece(i,"X");charge-=2;spent.move=true;cancelPower();tone(610,.13,"triangle");if(!finish("X"))queueCpu();return;}if(activePower==="surge"){charge-=3;spent.surge=true;surge=true;cancelPower();status.textContent="Surge armed — place your first mark.";tone(900,.14,"triangle");normalPlay(i);}}
  function play(i:number){if(phase!=="playing"||cpu)return;if(activePower)usePower(i);else normalPlay(i);selected=i;syncHud();}
  function reset(){clearTimeout(cpuTimer);board=Array(size*size).fill(null);blocked=Array(size*size).fill(false);phase="playing";cpu=false;selected=Math.floor(size*size/2);hovered=-1;charge=0;turns=0;activePower=null;moveFrom=-1;surge=false;winner=null;spent.block=spent.move=spent.surge=false;status.textContent=`Your turn — connect ${win}. Earn charge by placing marks.`;build();syncHud();arena.focus();}
  function choosePower(e:Event){const b=e.currentTarget as HTMLButtonElement,p=b.dataset.power as Power;if(b.disabled)return;activePower=activePower===p?null:p;moveFrom=-1;status.textContent=activePower==="move"?"Select one of your marks to relocate.":activePower==="block"?"Choose an empty cell to seal.":activePower==="surge"?"Choose the first cell for your guarded extra turn.":`Your turn — connect ${win}`;syncHud();}
  function pick(e:PointerEvent,go:boolean){const r=renderer.domElement.getBoundingClientRect();mouse.set((e.clientX-r.left)/r.width*2-1,-(e.clientY-r.top)/r.height*2+1);raycaster.setFromCamera(mouse,camera);const hit=raycaster.intersectObjects(cells,false)[0];hovered=hit?hit.object.userData.index:-1;if(hit){selected=hovered;if(go)play(selected);}if(go)arena.focus();}
  function down(e:PointerEvent){pick(e,true)} function move(e:PointerEvent){pick(e,false)} function leave(){hovered=-1;}
  function key(e:KeyboardEvent){const r=Math.floor(selected/size),c=selected%size;if(e.key.startsWith("Arrow")){e.preventDefault();if(e.key==="ArrowLeft")selected=r*size+(c+size-1)%size;if(e.key==="ArrowRight")selected=r*size+(c+1)%size;if(e.key==="ArrowUp")selected=((r+size-1)%size)*size+c;if(e.key==="ArrowDown")selected=((r+1)%size)*size+c;}else if(e.key==="Enter"||e.key===" "){e.preventDefault();play(selected);}else if(e.key.toLowerCase()==="r")reset();else if(e.key==="Escape")cancelPower();}
  function resize(){const w=arena.clientWidth,h=arena.clientHeight;renderer.setSize(w,h,false);camera.aspect=w/h;camera.updateProjectionMatrix();}
  function animate(now:number){cells.forEach((cell,i)=>{const mat=cell.material as THREE.MeshStandardMaterial,hot=phase==="playing"&&(i===selected||i===hovered);mat.color.setHex(blocked[i]?0x2b1744:hot?0x1c665e:0x14384b);mat.emissive.setHex(winner?.includes(i)?0x9b6410:hot?0x0b5549:0x06121d);mat.emissiveIntensity=winner?.includes(i)?2.2:1;cell.position.y=hot?.08:0;});pieces.forEach(p=>{if(!p)return;const t=Math.min(1,(now-p.userData.born)/220);p.scale.setScalar(1-Math.pow(1-t,3));p.rotation.y+=.007;});boardGroup.children.forEach(o=>{if(o.userData.block!==undefined)o.rotation.y+=.012;});camera.position.x=Math.sin(now*.00018)*.2;camera.lookAt(0,0,0);renderer.render(scene,camera);raf=requestAnimationFrame(animate);}

  const sizeClick=(e:Event)=>{const b=e.currentTarget as HTMLButtonElement;size=Number(b.dataset.size);win=size===3?3:3;sizeButtons.forEach(x=>x.classList.toggle("active",x===b));reset();};
  const togglePowers=()=>{powersOn=toggle.checked;cancelPower();status.textContent=powersOn?"Tactical mode enabled — earn charge by placing marks.":"Classic mode — no tactical powers.";syncHud();};
  renderer.domElement.addEventListener("pointerdown",down);renderer.domElement.addEventListener("pointermove",move);renderer.domElement.addEventListener("pointerleave",leave);arena.addEventListener("keydown",key);restart.addEventListener("click",reset);toggle.addEventListener("change",togglePowers);sizeButtons.forEach(b=>b.addEventListener("click",sizeClick));powerButtons.forEach(b=>b.addEventListener("click",choosePower));window.addEventListener("resize",resize);
  reset();resize();raf=requestAnimationFrame(animate);

  return ()=>{cancelAnimationFrame(raf);clearTimeout(cpuTimer);renderer.domElement.removeEventListener("pointerdown",down);renderer.domElement.removeEventListener("pointermove",move);renderer.domElement.removeEventListener("pointerleave",leave);arena.removeEventListener("keydown",key);restart.removeEventListener("click",reset);toggle.removeEventListener("change",togglePowers);sizeButtons.forEach(b=>b.removeEventListener("click",sizeClick));powerButtons.forEach(b=>b.removeEventListener("click",choosePower));window.removeEventListener("resize",resize);clear(boardGroup);clear(pieceGroup);dispose(floor);renderer.dispose();renderer.domElement.remove();audio?.close();style.remove();if(typeof unrender==="function")unrender();};
}