#!/usr/bin/env python3
import os, json, time, socketserver, threading, http.server, urllib.parse
from pathlib import Path
from playwright.sync_api import sync_playwright
ROOT=Path('/mnt/data/nex_v313_restart_after_wrist/NEXT11_V313_WRIST_RESTART_CHAIN_ROOT')
OUT=ROOT/'diagnostics/v313_quick'; OUT.mkdir(parents=True, exist_ok=True)
class Quiet(http.server.SimpleHTTPRequestHandler):
    def log_message(self,*args): pass
EVAL="""() => {
 const debug=window.__NEXPROD01_DEBUG_SCENE||null;
 const model=debug?.model||null;
 let boneCount=null, skinnedMeshCount=0;
 try{ if(model){ boneCount=0; model.traverse(o=>{ if(o?.isBone) boneCount++; if(o?.isSkinnedMesh) skinnedMeshCount++; }); }}catch(e){}
 const grip=window.__NEXPROD01_GRIP_SOLVER_V1||null;
 const final=window.__NEXPROD01_V313_FINAL_PREFINGER_TRUTH||window.__NEXPROD01_V312_FINAL_PREFINGER_TRUTH||null;
 const gate=window.__NEXPROD01_V313_ACTUAL_MCP_PALM_PREFINGER_GATE||window.__NEXPROD01_V311_ACTUAL_MCP_PALM_PREFINGER_GATE||window.__NEXPROD01_V307_ACTUAL_MCP_PALM_PREFINGER_GATE||null;
 const restart=window.__NEXPROD01_V313_WRIST_RESTART_CHAIN||null;
 const reset=window.__NEXPROD01_V312_WRIST_NODE_180_RESTART||null;
 const finger=window.__NEXPROD01_V301_FINGER_CHILD_LAYER_GATE||window.__NEXPROD01_V304_ZPLUS_SEQUENTIAL_FINGER_CONTACT||null;
 const metrics=(gate?.v307ActualMcpPalmGate?.metrics)||{};
 return {ready:document.readyState, hasDebug:!!debug, hasModel:!!model, boneCount, skinnedMeshCount, grip, final, gate, restart, reset, finger, metrics, text:(document.body?.innerText||'').slice(0,500), keys:Object.keys(window).filter(k=>k.startsWith('__NEXPROD01_V31')||k==='__NEXPROD01_GRIP_SOLVER_V1').sort()};
}"""
def summary(v):
    g=v.get('grip') or {}; gate=v.get('gate') or {}; m=v.get('metrics') or {}; r=v.get('restart') or {}; reset=v.get('reset') or {}; finger=v.get('finger') or {}
    return dict(hasModel=v.get('hasModel'), boneCount=v.get('boneCount'), skinnedMeshCount=v.get('skinnedMeshCount'), version=g.get('version'), side=g.get('side'), gripPass=g.get('pass'), gripStatus=g.get('status'), restartApplied=r.get('applied'), restartPass=r.get('pass'), restartFailedCount=r.get('failedCount'), resetApplied=reset.get('applied'), resetDeg=reset.get('rotationDeg'), gatePass=gate.get('pass'), gateStatus=gate.get('status'), wrongWay=gate.get('wrongWay'), orientationFail=gate.get('orientationFail'), indexPen=(m.get('index') or {}).get('penetrationM'), pinkyPen=(m.get('pinky') or {}).get('penetrationM'), indexDist=(m.get('index') or {}).get('distanceToSurfaceM'), pinkyDist=(m.get('pinky') or {}).get('distanceToSurfaceM'), fingerBlocked=finger.get('blocked'), fingerActive=finger.get('active'))

def main():
    os.chdir(ROOT)
    srv=socketserver.TCPServer(('127.0.0.1',0),Quiet)
    threading.Thread(target=srv.serve_forever,daemon=True).start(); port=srv.server_address[1]
    results=[]
    try:
      with sync_playwright() as p:
        env=os.environ.copy(); env['LIBGL_ALWAYS_SOFTWARE']='1'; env['VK_ICD_FILENAMES']='/usr/lib/chromium/vk_swiftshader_icd.json'; env['DISPLAY']=''
        browser=p.chromium.launch(executable_path='/usr/bin/chromium',headless=True,env=env,args=['--no-sandbox','--disable-dev-shm-usage','--ignore-gpu-blocklist','--enable-webgl','--enable-webgl2','--enable-unsafe-swiftshader','--disable-gpu-sandbox','--ozone-platform=headless','--use-gl=angle','--use-angle=swiftshader-webgl'])
        ctx=browser.new_context(viewport={'width':1200,'height':900},device_scale_factor=1)
        for name,caseId,phase in [('right','v94_right_full','0.75'),('left','v94_left_full','0.25')]:
          page=ctx.new_page(); messages=[]; page.on('console', lambda msg: messages.append({'type':msg.type,'text':msg.text[:500]})); page.on('pageerror', lambda err: messages.append({'type':'pageerror','text':str(err)[:500]}))
          url=f'http://127.0.0.1:{port}/assets/nexprod/nexprod01-player.html?'+urllib.parse.urlencode({'exercise':'hurdle-stretch','caseId':caseId,'phase':phase,'play':'0','debug':'0','visualMode':'nex','nativeRuntimeParity':'1','nativeSkin':'1','skinPreset':'freeridehuman','readyTimeoutMs':'12000','hostLifecycle':'v313','safeBoot':'0','lowPower':'1','pixelRatioCap':'1.15','v':'V313_'+name})
          try: page.goto(url,wait_until='domcontentloaded',timeout=15000)
          except Exception as e: messages.append({'type':'goto_error','text':repr(e)})
          val=None
          for i in range(25):
            page.wait_for_timeout(500)
            try: val=page.evaluate(EVAL)
            except Exception as e: messages.append({'type':'eval_error','text':repr(e)}); continue
            if val.get('hasModel') and val.get('grip'):
              break
          canvas=str(OUT/f'V313_{name}_canvas.png')
          try: page.locator('canvas').first.screenshot(path=canvas,timeout=5000)
          except Exception as e: messages.append({'type':'shot_error','text':repr(e)}); canvas=None
          results.append({'name':name,'caseId':caseId,'phase':phase,'summary':summary(val or {}),'final':val,'messages':messages[-20:],'screenshot':canvas})
          page.close()
        browser.close()
    finally:
      srv.shutdown(); srv.server_close()
    audit={'schema':'NEXT11_V313_QUICK_BROWSER_AUDIT','generatedAt':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),'results':results,'summaries':[r['summary'] for r in results]}
    (OUT/'V313_QUICK_BROWSER_AUDIT.json').write_text(json.dumps(audit,indent=2),encoding='utf-8')
    print(json.dumps({'audit':str(OUT/'V313_QUICK_BROWSER_AUDIT.json'),'summaries':audit['summaries']},indent=2))
if __name__=='__main__': main()
