# _dxf_multi_test.py — DXF 저장하기(요구1) + 다중업로드(요구2) 기능 게이트 [CEO 2026-07-11]
# window.__dzw 메서드 + localStorage 를 직접 구동해 실제 앱 로직을 검증. PASS/FAIL, 실패 시 비정상종료.
# 사용: python 05_BROWSER_TESTS/_dxf_multi_test.py  (서버 8090 필요)
import asyncio, sys
from playwright.async_api import async_playwright

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

# 두 개의 간단한 사각형 도면(segs)
SEG_A='[[[0,0],[100,0],[100,80],[0,80],[0,0]]]'
SEG_B='[[[0,0],[60,0],[60,60],[0,60],[0,0]]]'

async def ready(pg):
    for _ in range(90):
        await pg.wait_for_timeout(1000)
        if await pg.evaluate("!!(window.__dzw&&window.__dzw.three&&window.__dzw._grp&&window.__dzw._T)"):
            return True
    return False

async def main():
    fails=[]; 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)
        if not await ready(pg): print('== FAIL: 앱 로딩 실패 =='); sys.exit(2)
        await pg.wait_for_timeout(2000)
        # 깨끗한 상태로 시작
        await pg.evaluate("localStorage.removeItem('dzw_dxf_saved_layout');localStorage.removeItem('dzw_dxf_tf_A.json');localStorage.removeItem('dzw_dxf_tf_B.json')")

        # ── PHASE A: 단일 도면 배치 → 저장 → 새로고침 → 복원 ──
        await pg.evaluate(f"""(()=>{{ const a=window.__dzw;
            a._dxfData={{name:'A.json', segs:{SEG_A}}};
            a._dxfTf={{s:1,rot:0.25,tx:123,ty:-45}}; a._dxfLocked=true; a._dxfMatched=true; a._dxfSelected=true;
            a._dxfDraw(); a._dxfSaveLayout();
        }})()""")
        saved=await pg.evaluate("localStorage.getItem('dzw_dxf_saved_layout')")
        okA1 = saved and '"A.json"' in saved and 'content' in saved
        print(('PASS' if okA1 else 'FAIL'), 'A1 저장 — dzw_dxf_saved_layout 에 A.json+content 기록')
        if not okA1: fails.append('A1')

        await pg.reload();
        if not await ready(pg): print('== FAIL: 재로딩 실패 =='); sys.exit(2)
        await pg.wait_for_timeout(2500)  # 400ms 복원 + 여유
        rst=await pg.evaluate("""(()=>{ const a=window.__dzw;
            return {has:!!a._dxfData, name:a._dxfData&&a._dxfData.name, tf:a._dxfTf, locked:!!a._dxfLocked,
                    grp:!!a._dxfLayoutGrp}; })()""")
        okA2 = rst['has'] and rst['name']=='A.json' and rst['grp'] and abs(rst['tf']['tx']-123)<0.01 and rst['locked']
        print(('PASS' if okA2 else 'FAIL'), 'A2 복원 — 새로고침 후 A.json 이 tf(tx=123)·locked·layoutGrp 로 자동복원', rst)
        if not okA2: fails.append('A2')

        # ── PHASE B: 2개 이상 공존 + 개별 조작 독립성 ──
        has_b=await pg.evaluate("typeof window.__dzw._dxfAddDoc==='function'")
        if not has_b:
            print('SKIP Phase B — _dxfAddDoc 미구현(Phase A 커밋 단계)')
            await br.close()
            real_errs=[e for e in errs if 'favicon' not in e and 'Failed to load resource' not in e]
            print('콘솔 에러:', len(real_errs))
            for e in real_errs[:5]: print('  -', e)
            if fails or real_errs:
                print('== FAIL:', fails, '(console errs:', len(real_errs), ') =='); sys.exit(1)
            print('== PASS: DXF 저장(요구1) 통과 (Phase B 대기) =='); return
        await pg.evaluate("localStorage.removeItem('dzw_dxf_saved_layout')")
        await pg.evaluate(f"""(()=>{{ const a=window.__dzw;
            if(a._dxfDelete) a._dxfDelete();
            a._dxfAddDoc({{name:'A.json', segs:{SEG_A}}}, {{s:1,rot:0,tx:0,ty:0}});
            a._dxfAddDoc({{name:'B.json', segs:{SEG_B}}}, {{s:1,rot:0,tx:400,ty:0}});
        }})()""")
        cnt=await pg.evaluate("(()=>{ const a=window.__dzw; return {docs:a._dxfDocs?a._dxfDocs.length:-1, groups:a._grp.children.filter(c=>c.name==='dxfLayout').length}; })()")
        okB1 = cnt['docs']>=2 and cnt['groups']>=2
        print(('PASS' if okB1 else 'FAIL'), 'B1 다중 — 2개 도면 공존(_dxfDocs>=2, layoutGrp>=2)', cnt)
        if not okB1: fails.append('B1')

        # 클릭 히트테스트(정반평면 로컬점) — A 위치→A, B 위치→B 로 정확히 해석되는지(핵심 신규로직)
        h=await pg.evaluate("""(()=>{ const a=window.__dzw;
            const hA=a._dxfHitDoc({x:50,y:2});     // A(0..100) 아랫변 위 — 미선택 도면은 선 근처 클릭
            const hB=a._dxfHitDoc({x:430,y:30});   // B(400..460) — 선택(활성)이라 박스 내부 OK
            return {A:hA&&hA.doc.name, B:hB&&hB.doc.name}; })()""")
        okBH = h['A']=='A.json' and h['B']=='B.json'
        print(('PASS' if okBH else 'FAIL'), 'BH 히트 — A 위치점→A.json, B 위치점→B.json (다중 라이캐스트)', h)
        if not okBH: fails.append('BH')

        # 활성=B, B만 이동/잠금 → A 불변 확인
        move=await pg.evaluate("""(()=>{ const a=window.__dzw;
            const A=a._dxfDocs.find(d=>d.name==='A.json'), B=a._dxfDocs.find(d=>d.name==='B.json');
            const axPre=A.tf.tx;
            a._dxfActivate(B);           // B 활성
            a._dxfLocked=true;           // 활성(B) 잠금 → 프록시가 B.locked 로
            a._dxfTf={...a._dxfTf, tx:B.tf.tx+50};  // B 이동
            a._dxfDraw();
            return {A_tx_pre:axPre, A_tx_post:A.tf.tx, A_locked:A.locked, B_tx:B.tf.tx, B_locked:B.locked}; })()""")
        okB2 = abs(move['A_tx_post']-move['A_tx_pre'])<0.01 and (move['A_locked'] in (False,None)) and move['B_locked']==True
        print(('PASS' if okB2 else 'FAIL'), 'B2 독립 — B만 이동/잠금해도 A 불변(A.tx·A.locked 유지)', move)
        if not okB2: fails.append('B2')

        # 활성(B) 삭제 → A 남고 활성 전환
        de=await pg.evaluate("""(()=>{ const a=window.__dzw;
            const B=a._dxfDocs.find(d=>d.name==='B.json'); a._dxfActivate(B); a._dxfDelete();
            return {docs:a._dxfDocs.length, names:a._dxfDocs.map(d=>d.name), active:a._dxfActive&&a._dxfActive.name,
                    groups:a._grp.children.filter(c=>c.name==='dxfLayout').length}; })()""")
        okB3 = de['docs']==1 and de['names']==['A.json'] and de['active']=='A.json' and de['groups']==1
        print(('PASS' if okB3 else 'FAIL'), 'B3 삭제 — 활성(B)만 삭제, A 남고 활성 전환', de)
        if not okB3: fails.append('B3')

        # 단일 흐름 유지 — 남은 A 저장/복원 여전히 동작
        await pg.evaluate("(()=>{ const a=window.__dzw; a._dxfTf={...a._dxfTf,tx:77}; a._dxfDraw(); a._dxfSaveLayout(); })()")
        s2=await pg.evaluate("localStorage.getItem('dzw_dxf_saved_layout')")
        okB4 = s2 and '"A.json"' in s2 and '77' in s2
        print(('PASS' if okB4 else 'FAIL'), 'B4 단일흐름 — 도면 1개 남은 상태 저장 정상')
        if not okB4: fails.append('B4')

        await br.close()

    real_errs=[e for e in errs if 'favicon' not in e and 'Failed to load resource' not in e]
    print('콘솔 에러:', len(real_errs))
    for e in real_errs[:5]: print('  -', e)
    if fails or real_errs:
        print('== FAIL:', fails, '(console errs:', len(real_errs), ') ==')
        sys.exit(1)
    print('== PASS: DXF 저장+다중 전 항목 통과 ==')

asyncio.run(main())
