import {LibretroHost} from './runtime-src/src/libretro-host';
import {AudioEngine} from './runtime-src/src/audio';
const $=(id:string)=>document.getElementById(id) as any;
const audio=new AudioEngine();let host:LibretroHost|null=null,running=false,paused=false,muted=false,volume=.7,frames=0,last=0,acc=0;
const keyJoy=new Set<string>(),touch=new Set<number>();let padKeys=new Set<number>();
const keys:Record<string,number>={ArrowUp:4,ArrowDown:5,ArrowLeft:6,ArrowRight:7,KeyZ:8,KeyX:0,Space:8,Enter:8};
const retrok:Record<string,number>={Escape:27,Enter:13,Space:32,Digit1:49,Digit2:50,F1:282};
const status=(s:string)=>{$('status').textContent=s;};
const identity='buster-px68k-c7f831f-v1';
const diagnostics=document.createElement('script');diagnostics.type='application/json';diagnostics.id='diagnostics';document.body.append(diagnostics);
let audioPeak=0,audioSamples=0;
setInterval(()=>{diagnostics.textContent=JSON.stringify({frames,paused,audio:audio.audioEnv(),audioPeak,audioSamples,av:host?.avInfo,controller:$('controller').textContent,joystick:(host as any)?.mod?(host as any).mod._webx68k_joystick_read(0):null});},1000);
let mapped:number[]=JSON.parse(localStorage.getItem(identity+'-mapping')||'[0,1,9]');
function release(){keyJoy.clear();touch.clear();host?.setJoyState(0,0);for(const k of [...Object.values(retrok),...padKeys])host?.setKey(k,false);padKeys.clear();}
function poll(){
 let bits=0;for(const c of keyJoy)bits|=1<<keys[c];for(const n of touch)bits|=1<<n;
 const p=Array.from(navigator.getGamepads?.()||[]).find(x=>x?.connected);
 $('controller').textContent=p?`Connected: ${p.id}`:'No controller detected. Connect it and press a button.';
 const pressed=(i:number)=>!!p?.buttons[i]?.pressed;
 if(p){if(pressed(12)||(p.axes[1]||0)<-.35)bits|=1<<4;if(pressed(13)||(p.axes[1]||0)>.35)bits|=1<<5;if(pressed(14)||(p.axes[0]||0)<-.35)bits|=1<<6;if(pressed(15)||(p.axes[0]||0)>.35)bits|=1<<7;if(pressed(mapped[0])||pressed(mapped[2]))bits|=1<<8;if(pressed(mapped[1]))bits|=1;}
 const next=new Set<number>();if(p&&pressed(mapped[2])){next.add(13);next.add(32);}for(const k of new Set([...padKeys,...next]))host?.setKey(k,next.has(k)||keyJoy.has(k===13?'Enter':'Space'));padKeys=next;
 host?.setJoyState(0,bits);return bits;
}
function tick(t:number){requestAnimationFrame(tick);if(!running||paused){last=t;return;}acc+=Math.min(t-last,80);last=t;const dt=1000/(host?.avInfo?.fps||55.46);let count=0;while(acc>=dt&&count++<6){poll();host!.runFrame();frames++;acc-=dt;}}
requestAnimationFrame(tick);
async function bytes(url:string){const r=await fetch(url);if(!r.ok)throw Error(`Cannot load ${url}: ${r.status}`);return new Uint8Array(await r.arrayBuffer());}
$('start').onclick=async()=>{if(running)return;try{$('start').disabled=true;status('Loading original disk…');await audio.start();
 const [ipl,cg,disk]=await Promise.all([bytes('bios/iplrom.dat'),bytes('bios/cgrom.dat'),bytes('../original/buster.xdf')]);
 host=new LibretroHost($('screen'),s=>{for(const n of s)audioPeak=Math.max(audioPeak,Math.abs(n));audioSamples+=s.length;if(muted)return;const a=new Float32Array(s.length);for(let n=0;n<s.length;n++)a[n]=s[n]*volume;audio.push(a,true);});
 for(const [k,v] of Object.entries({px68k_ramsize:'2MB',px68k_cpuspeed:'10Mhz',px68k_joytype1:'Default (2 Buttons)',px68k_joy_mouse:'Joystick',px68k_frameskip:'Full Frame',px68k_no_wait_mode:'disabled',px68k_adjust_frame_rates:'enabled'}))host.setCoreOption(k,v);
 await host.init(ipl,cg,undefined,new URL('core/',location.href).href);const p=host.writeDiskImage('buster.xdf',disk);if(!host.loadGame(p))throw Error('The original disk could not be mounted.');host.fetchAvInfo();running=true;last=performance.now();$('start').hidden=true;$('screen').focus();for(const b of document.querySelectorAll('[data-running]'))(b as HTMLButtonElement).disabled=false;status('Running original X68000 edition. Allow the floppy disk to boot.');
 }catch(e){status(String(e));$('start').disabled=false;}};
function pause(p:boolean){if(!running)return;paused=p;release();audio.flush();p?audio.suspend():audio.resume();$('pause').textContent=p?'Resume':'Pause';status(p?'Paused.':'Running original X68000 edition.');}
$('pause').onclick=()=>pause(!paused);
$('mute').onclick=()=>{muted=!muted;audio.flush();$('mute').textContent=muted?'Unmute':'Mute';};
$('volume').oninput=(e:any)=>volume=Number(e.target.value)/100;
$('fullscreen').onclick=()=>$('stage').requestFullscreen();
$('recheck').onclick=()=>{poll();};
for(let i=0;i<3;i++){const sel=$('map'+i);for(let n=0;n<16;n++){const op=document.createElement('option');op.value=String(n);op.textContent=`Button ${n+1}`;sel.append(op);}sel.value=String(mapped[i]);sel.onchange=()=>{mapped[i]=Number(sel.value);localStorage.setItem(identity+'-mapping',JSON.stringify(mapped));release();};}
window.addEventListener('gamepaddisconnected',release);window.addEventListener('gamepadconnected',()=>poll());
document.addEventListener('keydown',e=>{if(!running||paused||['INPUT','SELECT','BUTTON'].includes((e.target as HTMLElement).tagName))return;if(e.code in keys){e.preventDefault();keyJoy.add(e.code);}if(e.code in retrok){e.preventDefault();host?.setKey(retrok[e.code],true);}});
document.addEventListener('keyup',e=>{keyJoy.delete(e.code);if(e.code in retrok)host?.setKey(retrok[e.code],false);});
window.addEventListener('blur',()=>{release();if(running&&!paused)pause(true);});
document.addEventListener('visibilitychange',()=>{if(document.hidden&&running&&!paused)pause(true);});
for(const el of document.querySelectorAll<HTMLElement>('[data-bit]')){el.onpointerdown=e=>{e.preventDefault();el.setPointerCapture(e.pointerId);touch.add(Number(el.dataset.bit));};el.onpointerup=el.onpointercancel=()=>touch.delete(Number(el.dataset.bit));}
async function store(mode:IDBTransactionMode,fn:(s:IDBObjectStore)=>IDBRequest){return new Promise<any>((resolve,reject)=>{const q=indexedDB.open(identity,1);q.onupgradeneeded=()=>q.result.createObjectStore('saves');q.onerror=()=>reject(q.error);q.onsuccess=()=>{const db=q.result;const tr=db.transaction('saves',mode),r=fn(tr.objectStore('saves'));tr.oncomplete=()=>{resolve(r.result);db.close();};tr.onerror=()=>{reject(tr.error);db.close();};};});}
function snapshot(){const b=host?.serialize();if(!b)throw Error('Save failed.');return b;}
async function restore(b:Uint8Array){if(!host?.unserialize(b))throw Error('Incompatible or damaged Buster browser state.');release();audio.flush();status('Browser checkpoint loaded.');}
$('save').onclick=async()=>{try{await store('readwrite',s=>s.put(snapshot(),'checkpoint'));status('Browser checkpoint saved on this device.');}catch(e){status(String(e));}};
$('load').onclick=async()=>{try{const b=await store('readonly',s=>s.get('checkpoint'));if(!b)throw Error('No saved checkpoint on this browser.');await restore(new Uint8Array(b));}catch(e){status(String(e));}};
function download(b:Blob,name:string){const u=URL.createObjectURL(b),a=document.createElement('a');a.href=u;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(u),1000);}
$('export').onclick=()=>{try{const meta=new TextEncoder().encode(identity+'\n');download(new Blob([meta,snapshot()]),'buster-browser-v1.state');}catch(e){status(String(e));}};
$('import').onchange=async(e:any)=>{try{const f=e.target.files[0];if(!f)return;const b=new Uint8Array(await f.arrayBuffer()),h=new TextEncoder().encode(identity+'\n');if(h.some((v,i)=>b[i]!==v))throw Error('Choose a Buster browser v1 checkpoint. Native .x68 states use a different format.');await restore(b.subarray(h.length));}catch(e){status(String(e));}e.target.value='';};
// Passive diagnostics for acceptance tests; no guest RAM writes or simulated gamepads.
(window as any).buster={get host(){return host;},get frames(){return frames;},get paused(){return paused;},get audio(){return audio.audioEnv();},get mapping(){return mapped;}};
