#!/usr/bin/env python3
import json, os, socketserver, threading, http.server, time, urllib.parse
from pathlib import Path
from playwright.sync_api import sync_playwright
ROOT=Path('/mnt/data/v319root/NEXT11_V319_ACTUAL_CALF_DRIVEN_PALM_SOLVE_ROOT'); OUT=ROOT/'diagnostics/v319'; OUT.mkdir(parents=True,exist_ok=True)
class Q(http.server.SimpleHTTPRequestHandler):
 def log_message(self,*a): pass
EVAL=r'''() => {
 const d=window.__NEXPROD01_DEBUG_SCENE||null;
 const r=window.__NEXPROD01_GRIP_SOLVER_V1||null;
 const s=window.__NEXPROD01_V319_ACTUAL_CALF_DRIVEN_PALM_SOLVE||null;
 const m=window.__NEXPROD01_V318_ACTUAL_CALF_PALM_MESH_TRUTH||null;
 const g=window.__NEXPROD01_V311_ACTUAL_MCP_PALM_PREFINGER_GATE||window.__NEXPROD01_V308_ACTUAL_MCP_PALM_PREFINGER_GATE||null;
 const f=window.__NEXPROD01_V301_FINGER_CHILD_LAYER_GATE||window.__NEXPROD01_V304_ZPLUS_SEQUENTIAL_FINGER_CONTACT||null;
 let boneCount=0,skinnedMeshCount=0;
 try{d?.model?.traverse?.(o=>{if(o?.isBone)boneCount++;if(o?.isSkinnedMesh)skinnedMeshCount++;});}catch(e){}
 return {hasModel:!!d?.model,runtimeVersion:r?.version||null,side:r?.side||null,gripPass:r?.pass,solve:s,mesh:m,prefinger:g?{schema:g.schema,pass:g.pass,status:g.status,orientationFail:g.orientationFail,wrongWay:g.wrongWay}:null,finger:f?{active:f.active,blocked:f.blocked,ready:f.ready,reason:f.reason}:null,boneCount,skinnedMeshCount};
}'''
def summary(v):
 v=v or {};s=v.get('solve') or {};m=v.get('mesh') or {};p=v.get('prefinger') or {};f=v.get('finger') or {};a=s.get('after') or {};bi=s.get('bestCandidate') or {}
 return {'hasModel':v.get('hasModel'),'runtimeVersion':v.get('runtimeVersion'),'side':v.get('side'),'boneCount':v.get('boneCount'),'skinnedMeshCount':v.get('skinnedMeshCount'),'solveApplied':s.get('applied'),'solvePass':s.get('pass'),'firstPalm':s.get('firstPalm'),'rollDeg':s.get('rollDeg'),'changedFingerLocals':len(s.get('changedFingerLocals') or []),'indexDistActualCalf':(a.get('index') or {}).get('distanceM'),'pinkyDistActualCalf':(a.get('pinky') or {}).get('distanceM'),'palmFacesCalf':a.get('palmFacesCalf'),'meshPass':m.get('pass'),'meshStatus':m.get('status'),'meshGapM':m.get('gapM'),'nearestPalmCalfM':m.get('nearestPalmCalfM'),'nearestPalmFootM':m.get('nearestPalmFootM'),'footCloser':m.get('footCloser'),'prefingerPass':p.get('pass'),'prefingerStatus':p.get('status'),'fingerBlocked':f.get('blocked'),'fingerActive':f.get('active')}
def main():
 os.chdir(ROOT);srv=socketserver.TCPServer(('127.0.0.1',0),Q);threading.Thread(target=srv.serve_forever,daemon=True).start();port=srv.server_address[1];rows=[]
 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']=''
   b=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','--remote-allow-origins=*'])
   c=b.new_context(viewport={'width':1400,'height':1000},device_scale_factor=1)
   for name,case,phase in [('right','v94_right_full','0.75')]:
    pg=c.new_page();qs=urllib.parse.urlencode({'exercise':'hurdle-stretch','caseId':case,'phase':phase,'play':'0','debug':'0','visualMode':'nex','nativeRuntimeParity':'1','nativeSkin':'1','skinPreset':'freeridehuman','safeBoot':'0','lowPower':'1','pixelRatioCap':'1.1','antialias':'1','maxFPS':'24','staticAfterReady':'0','idleSleepAfterReady':'0','readyTimeoutMs':'20000','build':'V319_ACTUAL_CALF_DRIVEN_PALM_SOLVE','v':'V319_'+name});url=f'http://127.0.0.1:{port}/assets/nexprod/nexprod01-player.html?{qs}'
    nav=None
    try:pg.goto(url,wait_until='domcontentloaded',timeout=18000)
    except Exception as e:nav=repr(e)
    val=None
    for i in range(40):
     pg.wait_for_timeout(600)
     try:val=pg.evaluate(EVAL)
     except Exception:continue
     if val.get('hasModel') and val.get('solve') is not None and val.get('mesh') is not None:break
    shot=OUT/f'V319_{name}_canvas.png'
    try:pg.locator('canvas').first.screenshot(path=str(shot),timeout=10000)
    except Exception:
     try:pg.screenshot(path=str(shot),full_page=False)
     except Exception:pass
    rows.append({'name':name,'navigationError':nav,'value':val,'summary':summary(val),'screenshot':str(shot)});pg.close()
   b.close()
 finally:srv.shutdown();srv.server_close()
 audit={'schema':'NEXT11_V319_BROWSER_ACTUAL_CALF_DRIVEN_PALM_SOLVE_AUDIT','generatedAt':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),'rows':rows,'summaries':[x['summary'] for x in rows]}
 audit['passBrowserRuntime']=all(x['summary']['hasModel'] and x['summary']['runtimeVersion']=='V319_ACTUAL_CALF_DRIVEN_PALM_SOLVE' for x in rows)
 audit['noFingerMutationInSolve']=all((x['summary']['changedFingerLocals'] in (0,None)) for x in rows)
 audit['fingersBlocked']=all(x['summary']['fingerBlocked'] is True and x['summary']['fingerActive'] is not True for x in rows)
 (OUT/'V319_BROWSER_AUDIT.json').write_text(json.dumps(audit,indent=2));print(json.dumps(audit,indent=2))
if __name__=='__main__':main()
