# verify_app_health.py — 앱 건강검진 (블럭화 분할 전후 비교용) [CEO 2026-07-04]
# 헤드리스 브라우저로 뷰어를 열어 핵심 객체·기능 존재를 전수 확인. PASS/FAIL 판정.
# 사용: python 04_VERIFICATION/verify_app_health.py  (서버 8090 필요)
import asyncio, json, sys
from playwright.async_api import async_playwright

URL='http://localhost:8090/DOZIKWORKS_OS_v5_locked.dc.html?health=1'

CHECKS = """(()=>{
  const a=window.__dzw, r={};
  r['app(__dzw)']              = !!a;
  r['three.scene']             = !!(a&&a.three&&a.three.scene);
  r['scene 객체수>=100(재귀)']  = (()=>{try{let c=0;a.three.scene.traverse(()=>c++);return c>=100;}catch(e){return false;}})();
  r['로봇 그룹(_robotGrp)']     = !!(a&&a._robotGrp);
  r['TCP축(_tcpAxes)']          = !!(a&&a._tcpAxes);
  r['orbit 카메라']             = !!(a&&a.orbit);
  r['관절 상태(state.joints)']  = !!(a&&a.state&&a.state.joints&&'S' in a.state.joints);
  r['FK 함수(fk)']              = !!(a&&typeof a.fk==='function');
  r['IK 엔진(DZW5_ENGINE)']     = !!(window.DZW5_ENGINE&&DZW5_ENGINE.solveIK);
  r['엣지 모듈(DZW5_EDGEPATH)'] = !!window.DZW5_EDGEPATH;
  r['티칭 블럭(DZW5_TEACH)']    = !!window.DZW5_TEACH;
  r['저장 블럭(DZW5_PROJECT)']  = !!window.DZW5_PROJECT;
  r['상단바 블럭(DZW5_TOPBAR)'] = !!window.DZW5_TOPBAR;
  r['뷰도구 블럭(DZW5_VIEWTOOLS)']=!!window.DZW5_VIEWTOOLS;
  r['undo 시스템(_undoPop)']    = !!(a&&typeof a._undoPop==='function');
  r['redo(_genRedo)']           = !!(a&&typeof a._genRedo==='function');
  r['패널(dzw-xform-panel)']    = !!document.getElementById('dzw-xform-panel');
  r['캡처 안전망(dzwSaveShot)'] = !!window.dzwSaveShot;
  // FK 실계산 스팟체크 — 홈자세 fk가 숫자 반환
  try{ const f=a.fk({S:0,L:0,U:0,R:0,B:0,T:0}); r['FK 홈자세 계산']=Number.isFinite(f.x); }catch(e){ r['FK 홈자세 계산']=false; }
  return r;
})()"""

async def main():
    errs=[]
    async with async_playwright() as p:
        br=await p.chromium.launch(channel='msedge', headless=True)
        pg=await br.new_page(viewport={'width':1536,'height':864})
        pg.on('console', lambda m: errs.append(m.text[:200]) if m.type=='error' else None)
        pg.on('pageerror', lambda e: errs.append('PAGEERROR: '+str(e)[:200]))
        await pg.goto(URL)
        # 앱 준비 폴링 (최장 90초)
        ready=False
        for _ in range(90):
            await pg.wait_for_timeout(1000)
            ok=await pg.evaluate("!!(window.__dzw&&window.__dzw.three&&window.__dzw._robotGrp)")
            if ok: ready=True; break
        await pg.wait_for_timeout(3000)  # 블럭 자가장착 대기
        res=await pg.evaluate(CHECKS)
        await br.close()

    fails=[k for k,v in res.items() if not v]
    real_errs=[e for e in errs if 'favicon' not in e and 'Failed to load resource' not in e]  # 리소스 404는 response 추적으로 별도 (일시성 노이즈 제외)
    print(json.dumps(res, ensure_ascii=False, indent=1))
    print('콘솔 에러:', len(real_errs))
    for e in real_errs[:5]: print('  -', e)
    if not ready: print('== FAIL: 앱 로딩 실패 =='); sys.exit(2)
    if fails or real_errs:
        print('== FAIL:', fails, '=='); sys.exit(1)
    print('== PASS: 전 항목 통과 ==')

asyncio.run(main())
