# _teach_ui_regression_test.py — 티칭 UI 회귀 4종 영구 방지 [CEO 재발금지 지시 2026-07-20]
#   배경(오늘 터진 증상): 배타 모드전환이 부품선택(tcSel)을 꺼서 ①티칭 패널(#dzw-xform-panel)이
#     판A 전환 순간 사라지고 ②판A가 판 클릭마다 꺼지던 사고. + 자동티칭보정 1클릭 오진입.
#   판정 4항목(8090 실로드):
#     1. 부품선택→판A 전환→판 3연속 클릭 동안 티칭 패널 계속 표시 + faceMode 유지
#     2. 판A 5연속 클릭 시 [모드전환] 부품선택 이탈(끄기) 0회 + tcSelected 유지
#     3. 화면 콘솔 오버레이(#dzw-console-wrap.open)가 판 클릭 후에도 유지
#     4. openAutoTeach 1회 호출로는 autoTeach=false 유지(실수진입 차단)
import asyncio
from playwright.async_api import async_playwright

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

CEN = """(fi)=>{ const a=window.__dzw, T=window.THREE, km=a._knuckleMesh, g=km.geometry;
    const dom=a.three.rnd.domElement, r=dom.getBoundingClientRect();
    const ia = g.index ? [g.index.getX(fi*3),g.index.getX(fi*3+1),g.index.getX(fi*3+2)]
                       : [fi*3, fi*3+1, fi*3+2];
    const p=new T.Vector3();
    for(const i of ia) p.add(new T.Vector3().fromBufferAttribute(g.attributes.position,i));
    p.divideScalar(3).applyMatrix4(km.matrixWorld);
    const s=p.clone().project(a.three.cam);
    return [r.left+(s.x+1)/2*r.width, r.top+(-s.y+1)/2*r.height]; }"""
PANEL = "()=>{ const p=document.getElementById('dzw-xform-panel'); return p?getComputedStyle(p).display:'no-panel'; }"
STATE = "()=>({fm: window.__dzw._faceMode, tc: !!window.__dzw._tcSelected})"
RDY = "()=>!!(window.__dzw&&window.__dzw._knuckleMesh&&window.__dzw._knuckleMesh.geometry&&window.__dzw._knuckleMesh.geometry.attributes&&window.__dzw._knuckleMesh.geometry.attributes.position)"

async def main():
    fails = []
    async with async_playwright() as p:
        br = await p.chromium.launch(headless=True)
        pg = await (await br.new_context(viewport={'width':1600,'height':900})).new_page()
        logs = []
        pg.on('console', lambda m: logs.append(m.text))
        await pg.goto(URL)
        await pg.wait_for_timeout(13000)
        has = await pg.evaluate(RDY)
        for _ in range(14):
            if has: break
            await pg.wait_for_timeout(1000); has = await pg.evaluate(RDY)
        if not has:
            print('결과: FAIL — _knuckleMesh 미준비'); await br.close(); return 1

        pt = await pg.evaluate(f"({CEN})(7288)")

        # ── 항목1: 부품선택→판A→판 3연속, 패널 유지 + faceMode 유지 ──
        await pg.evaluate("()=>window.__dzw.selectTool('select')"); await pg.wait_for_timeout(200)
        await pg.mouse.click(pt[0], pt[1]); await pg.wait_for_timeout(500)
        p1 = await pg.evaluate(PANEL); s1 = await pg.evaluate(STATE)
        await pg.evaluate("()=>DZW5_EDGEPATH.startCurveMode(window.__dzw)"); await pg.wait_for_timeout(400)
        p2 = await pg.evaluate(PANEL); s2 = await pg.evaluate(STATE)
        panels = []
        for _ in range(3):
            await pg.mouse.click(pt[0], pt[1]); await pg.wait_for_timeout(400)
            panels.append(await pg.evaluate(PANEL))
        s3 = await pg.evaluate(STATE)
        c1 = (p1=='block' and s1['tc'] and p2=='block' and s2['tc'] and s2['fm']>0
              and all(x=='block' for x in panels) and s3['fm']>0 and s3['tc'])
        print(f"항목1 패널유지: sel={p1}/{s1} 판A={p2}/{s2} 판3={panels} 최종={s3} -> {'OK' if c1 else 'FAIL'}")
        if not c1: fails.append('1_패널유지')

        # ── 항목2: 판A 5연속 클릭, 부품선택 이탈(끄기) 0회 ──
        n_before = len(logs)
        for _ in range(5):
            await pg.mouse.click(pt[0], pt[1]); await pg.wait_for_timeout(350)
        s4 = await pg.evaluate(STATE)
        leaks = sum(1 for l in logs[n_before:] if '[모드전환] 부품선택 →' in l)
        c2 = (leaks == 0 and s4['tc'] == True)
        print(f"항목2 판A5연타: 부품선택끄기={leaks}회 tc={s4['tc']} -> {'OK' if c2 else 'FAIL'}")
        if not c2: fails.append('2_판A이탈')

        # ── 항목3: 콘솔 오버레이가 판 클릭 후에도 유지 ──
        await pg.evaluate("()=>document.getElementById('dzw-console-wrap').classList.add('open')")
        await pg.wait_for_timeout(200)
        open_b = await pg.evaluate("()=>getComputedStyle(document.getElementById('dzw-console-wrap')).display")
        await pg.mouse.click(pt[0], pt[1]); await pg.wait_for_timeout(350)
        open_a = await pg.evaluate("()=>({disp:getComputedStyle(document.getElementById('dzw-console-wrap')).display, open:document.getElementById('dzw-console-wrap').classList.contains('open')})")
        c3 = (open_b != 'none' and open_a['disp'] != 'none' and open_a['open'] == True)
        print(f"항목3 콘솔오버레이: 전={open_b} 후={open_a} -> {'OK' if c3 else 'FAIL'}")
        if not c3: fails.append('3_콘솔오버레이')

        # ── 항목4: openAutoTeach 1회로는 autoTeach=false 유지 ──
        at0 = await pg.evaluate("()=>window.__dzw.state.autoTeach")
        await pg.evaluate("()=>window.__dzw.openAutoTeach()"); await pg.wait_for_timeout(500)
        at1 = await pg.evaluate("()=>window.__dzw.state.autoTeach")
        c4 = (at0 == False and at1 == False)
        print(f"항목4 자동티칭 1클릭차단: 전={at0} 후={at1} -> {'OK' if c4 else 'FAIL'}")
        if not c4: fails.append('4_자동티칭진입')

        await br.close()

    print()
    print('결과:', 'PASS' if not fails else 'FAIL' + ' — ' + ', '.join(fails))
    return 0 if not fails else 1

raise SystemExit(asyncio.run(main()))
