# 클릭 인터랙션 실전 확인: 실제 부재를 화면에서 클릭해 고정점·하중점을 찍고 즉시 재계산
import asyncio, time
from playwright.async_api import async_playwright

URL = "http://127.0.0.1:8099/fab.html"
SHOT = r"C:\Users\user\AppData\Local\Temp\dzw_fea\fea_click.png"


async def main():
    errs = []
    async with async_playwright() as p:
        br = await p.chromium.launch()
        pg = await br.new_page(viewport={'width': 1600, 'height': 950})
        pg.on('pageerror', lambda e: errs.append(str(e)))
        await pg.goto(URL, timeout=30000)
        await pg.wait_for_timeout(3000)

        # 실제 제품 부재 1개 로드 + 격리해서 단품 선택 상태로
        await pg.evaluate("""async () => {
            const m = await (await fetch('fab_models/400q/manifest_400q.json')).json();
            const parts = m.items.slice(0, 1).map((it, i) => ({name: it.name, file: it.file, path: 'G01/P' + i}));
            await window.FAB.loadBlock('CLICKTEST', parts);
        }""")
        await pg.evaluate("() => window.FAB.selectMode()")
        b = await pg.locator('#fab-canvas').bounding_box()
        cx, cy = b['x'] + b['width'] * 0.5, b['y'] + b['height'] * 0.5
        await pg.mouse.click(cx, cy)
        await pg.wait_for_timeout(500)
        sel = await pg.evaluate("() => { const f = window.FAB_FEA.findSelected(); return f ? f.name : null; }")
        print('1) 부재 선택      :', sel)

        await pg.locator('#fab-fea-btn').click()
        await pg.wait_for_timeout(500)
        print('2) 재질 목록      :', await pg.evaluate(
            "() => Array.from(document.querySelectorAll('#fab-fea-panel select')[0].options).map(o=>o.value).join(', ')"))

        # ── 고정점 찍기 ──
        await pg.evaluate("() => window.FAB_FEA.setMode('fix')")
        for dx in (0.36, 0.40):
            await pg.mouse.click(b['x'] + b['width'] * dx, b['y'] + b['height'] * 0.5)
            await pg.wait_for_timeout(250)
        nfix = await pg.evaluate("() => window.FAB_FEA.state.fixPoints.length")
        print('3) 고정점 클릭    : %d곳' % nfix)

        # ── 하중점 찍기 (자동 재계산 켜짐 → 찍는 순간 해석) ──
        await pg.evaluate("() => { window.FAB_FEA.state.lastResult = null; }")
        await pg.evaluate("() => window.FAB_FEA.setMode('load')")
        t0 = time.time()
        for dx, dy in ((0.60, 0.5), (0.58, 0.48), (0.55, 0.52), (0.62, 0.45), (0.50, 0.50)):
            await pg.mouse.click(b['x'] + b['width'] * dx, b['y'] + b['height'] * dy)
            await pg.wait_for_timeout(300)
            if await pg.evaluate("() => window.FAB_FEA.state.loadPoints.length"):
                break
        # 결과가 화면에 뜰 때까지 대기
        for _ in range(120):
            done = await pg.evaluate("() => !!(window.FAB_FEA.state.lastResult)")
            if done:
                break
            await pg.wait_for_timeout(500)
        wall = time.time() - t0
        nload = await pg.evaluate("() => window.FAB_FEA.state.loadPoints.length")
        print('4) 하중점 클릭    : %d곳 → 자동 재계산 응답 %.1f초' % (nload, wall))

        r = await pg.evaluate("() => window.FAB_FEA.state.lastResult")
        if not r:
            txt = await pg.locator('#fab-fea-panel > div:last-child').inner_text()
            print('   [실패] 패널 내용:', ' | '.join(txt[:400].split(chr(10))))
            await br.close()
            return
        print('5) 해석 결과      : %s' % r['method_kr'])
        print('   메시           : %s %d개 / 절점 %d (%s)'
              % (r['mesh']['type'], r['mesh']['elements'], r['mesh']['nodes'], r['mesh'].get('quality', '')))
        print('   경계조건       : 고정절점 %d / 하중절점 %d (mode=%s)'
              % (r['bc']['fix_nodes'], r['bc']['load_nodes'], r['bc']['mode']))
        print('   최대응력       : %.1f MPa (항복대비 %.0f%%)'
              % (r['result']['max_vonmises_MPa'], r['result']['yield_ratio_pct']))
        print('   최대변위       : %.3f mm' % r['result']['max_disp_mm'])
        print('   취약점 상위3   : %s' % ', '.join('%.0fMPa' % h['MPa'] for h in r['result']['hotspots']))
        print('   ccx 계산       : %.1f초' % r['solve_sec'])

        # ── 조건 바꿔 재계산 (하중 5배) ──
        await pg.evaluate("() => { window.FAB_FEA.state.mag = 5000; }")
        await pg.evaluate("() => { window.FAB_FEA.state.loadPoints.forEach(l => l.N = 5000); }")
        t1 = time.time()
        await pg.evaluate("() => window.FAB_FEA.run()")
        prev = r['result']['max_vonmises_MPa']
        for _ in range(120):
            v = await pg.evaluate("() => window.FAB_FEA.state.lastResult.result.max_vonmises_MPa")
            if abs(v - prev) > 1e-6:
                break
            await pg.wait_for_timeout(500)
        w2 = time.time() - t1
        r2 = await pg.evaluate("() => window.FAB_FEA.state.lastResult")
        print('6) 하중 1000→5000N 재계산 : %.1f초, 최대응력 %.1f → %.1f MPa (5배면 선형 정상)'
              % (w2, prev, r2['result']['max_vonmises_MPa']))

        print('7) 오버레이       :', await pg.evaluate("""() => {
            let tri=0, mk=0;
            window.FAB.scene.traverse(o=>{ if(o.userData && o.userData.__feaOverlay)
              o.traverse(c=>{ if(c.isMesh){ if(c.geometry.index) tri+=c.geometry.index.count/3;
                if(c.material.color && c.material.color.getHex()===0xff2d3d) mk++; }}); });
            return {삼각형:tri, 취약점마커:mk};
        }"""))
        print('   원본 부재 색   :', await pg.evaluate(
            "() => { let c=null; window.FAB.scene.traverse(o=>{ if(o.isMesh&&o.name&&o.name.indexOf('400Q')>=0) c=o.material.color.getHexString(); }); return c; }"))

        # 결과가 보이도록 카메라 맞추기
        await pg.evaluate("""() => {
            const T=window.FAB.THREE, cam=window.FAB.camera; let g=null;
            window.FAB.scene.traverse(o=>{ if(o.userData && o.userData.__feaOverlay && o.name==='__FEA_RESULT__') g=o; });
            const box=new T.Box3().setFromObject(g), ctr=box.getCenter(new T.Vector3()), sz=box.getSize(new T.Vector3());
            const rr=sz.length()*1.0;
            cam.position.set(ctr.x+rr*0.5, ctr.y+rr*0.55, ctr.z+rr*0.6); cam.lookAt(ctr);
            cam.near=1; cam.updateProjectionMatrix();
        }""")
        await pg.wait_for_timeout(800)
        await pg.screenshot(path=SHOT)
        await br.close()
    print('페이지 오류:', errs if errs else '없음')
    print('캡처:', SHOT)

asyncio.run(main())
