#!/usr/bin/env python3
from __future__ import annotations
import asyncio, json, re, base64, mimetypes
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1]
OUT=ROOT/'qa_screenshots'; OUT.mkdir(exist_ok=True)

def read_asset(src):
    clean=src.split('?',1)[0].split('#',1)[0]
    return (ROOT/clean).read_text(encoding='utf-8')

def inline_preview():
    html=(ROOT/'preview.html').read_text(encoding='utf-8')
    qa=f"""<base href="file://{ROOT.as_posix()}/">
<script>
(function(){{
 const jsonMap={{
  'api/image-admin/templates.php': {json.dumps(json.load(open(ROOT/'assets/data/image-admin/athlete-templates.json')),ensure_ascii=False)},
  'api/image-admin/status.php': {json.dumps(json.load(open(ROOT/'assets/data/image-admin/status.json')),ensure_ascii=False)},
  'assets/data/image-admin/master-prompts.json': {json.dumps(json.load(open(ROOT/'assets/data/image-admin/master-prompts.json')),ensure_ascii=False)},
  'assets/data/image-admin/exercise-motion-prompts.json': {json.dumps(json.load(open(ROOT/'assets/data/image-admin/exercise-motion-prompts.json')),ensure_ascii=False)},
  'assets/data/image-admin/pose-sprite-library.json': {json.dumps(json.load(open(ROOT/'assets/data/image-admin/pose-sprite-library.json')),ensure_ascii=False)},
  'assets/data/image-admin/exercise-pose-sequences.json': {json.dumps(json.load(open(ROOT/'assets/data/image-admin/exercise-pose-sequences.json')),ensure_ascii=False)}
 }};
 const nativeFetch=window.fetch?window.fetch.bind(window):null;
 window.fetch=function(url,opts){{
   const s=(typeof url==='string')?url:((url&&url.url)?url.url:String(url||''));
   if(s.includes('api/image-admin/health.php')) return Promise.resolve({{ok:true,json:()=>Promise.resolve({{ok:true,php:'8.qa',curl:true}})}});
   for(const k in jsonMap){{if(s.includes(k)) return Promise.resolve({{ok:true,json:()=>Promise.resolve(jsonMap[k])}});}}
   if(s.includes('ota_youtube_proxy.php')||s.includes('video_backup_api.php')) return Promise.resolve({{ok:true,json:()=>Promise.resolve({{status:'missing'}})}});
   return nativeFetch?nativeFetch(url,opts):Promise.reject(new Error('fetch disabled '+s));
 }};
}})();
</script>"""
    html=html.replace('<head>', '<head>'+qa, 1)
    html=re.sub(r'<link\s+([^>]*rel=["\']stylesheet["\'][^>]*)>', lambda m: f"<style>\n{read_asset(re.search(r'href=[\"\']([^\"\']+)[\"\']',m.group(1)).group(1))}\n</style>", html)
    html=re.sub(r'<script\s+([^>]*src=["\'][^"\']+["\'][^>]*)>\s*</script>', lambda m: f"<script>\n{read_asset(re.search(r'src=[\"\']([^\"\']+)[\"\']',m.group(1)).group(1))}\n</script>", html)
    return html

def data_uri(src):
    if not src or src.startswith(('data:','http')): return None
    clean=src.split('?',1)[0].split('#',1)[0]
    p=Path(clean[7:]) if clean.startswith('file://') else ROOT/clean.lstrip('/')
    if not p.exists() or not p.is_file(): return None
    mime=mimetypes.guess_type(p.name)[0] or 'application/octet-stream'
    return clean, 'data:%s;base64,%s'%(mime,base64.b64encode(p.read_bytes()).decode('ascii'))

async def inline_images(page):
    imgs=await page.evaluate("() => Array.from(document.images).map((img,i)=>({i,src:img.getAttribute('src')||''}))")
    for im in imgs[:260]:
        d=data_uri(im['src'])
        if not d: continue
        clean,uri=d
        await page.evaluate("({i,clean,uri})=>{const img=document.images[i]; if(img){img.dataset.qaOriginalSrc=clean; img.src=uri;}}", {'i':im['i'],'clean':clean,'uri':uri})
    await page.wait_for_timeout(100)

async def main():
    from playwright.async_api import async_playwright
    html=inline_preview()
    async with async_playwright() as p:
        browser=await p.chromium.launch(executable_path='/usr/bin/chromium',headless=True,args=['--no-sandbox','--disable-dev-shm-usage','--disable-gpu','--disable-gpu-sandbox','--use-gl=swiftshader','--enable-unsafe-swiftshader'])
        page=await browser.new_page(viewport={'width':1440,'height':1024})
        await page.set_content(html,wait_until='domcontentloaded')
        await page.evaluate("() => { if(typeof showImageAdmin==='function') showImageAdmin(); }")
        await page.wait_for_timeout(700)
        await page.evaluate("() => { if(typeof imageAdminSelectExercise==='function') imageAdminSelectExercise('linear-pogos'); }")
        await page.wait_for_timeout(500)
        await page.evaluate("() => { if(typeof imageAdminAddPose==='function') imageAdminAddPose('broad-jump__pose05'); }")
        await page.wait_for_timeout(200)
        await inline_images(page)
        metrics=await page.evaluate("""() => {
          const stage=document.querySelector('.next-pose-preview-stage');
          const imgs=Array.from(document.querySelectorAll('.next-pose-preview-img'));
          const floor=document.querySelector('.next-pose-preview-floor');
          const srect=stage?.getBoundingClientRect?.();
          return {
            hasPage:!!document.querySelector('.next-image-admin-page'),
            hasPoseBuilder:!!document.querySelector('.next-pose-builder'),
            hasFloor:!!floor,
            hasSlotGuides:document.querySelectorAll('.next-pose-slot-guide').length,
            hasTransformInputs:document.querySelectorAll('.next-pose-transform-grid input').length,
            bankCards:document.querySelectorAll('.next-pose-bank-card').length,
            sequenceCards:document.querySelectorAll('.next-pose-sequence-card').length,
            previewImages:imgs.length,
            selected:document.querySelector('.next-image-admin-row.is-active')?.dataset.exerciseKey||'',
            floorY:getComputedStyle(stage).getPropertyValue('--pose-floor-y').trim(),
            poseBoxes:imgs.map(img=>{const r=img.getBoundingClientRect();return {key:img.dataset.poseKey,w:Math.round(r.width),h:Math.round(r.height),bottom:Math.round(r.bottom-(srect?.top||0)),top:Math.round(r.top-(srect?.top||0))};}),
            brokenImages:Array.from(document.images).filter(img=>img.naturalWidth===0&&!String(img.src||'').startsWith('http')).map(img=>img.dataset.qaOriginalSrc||img.getAttribute('src'))
          };
        }""")
        await page.screenshot(path=str(OUT/'rc13_pose_slot_scaling_desktop_1440x1024.png'),full_page=False)
        await browser.close()
    (OUT/'rc13_pose_slot_scaling_dom_metrics.json').write_text(json.dumps(metrics,indent=2,ensure_ascii=False),encoding='utf-8')
    errors=[]
    if not metrics.get('hasPoseBuilder'): errors.append('pose builder missing')
    if metrics.get('bankCards',0)<50: errors.append('too few bank cards')
    if not metrics.get('hasFloor'): errors.append('floor baseline missing')
    if metrics.get('hasSlotGuides',0)<5: errors.append('slot guides missing')
    if metrics.get('hasTransformInputs',0)<3: errors.append('pose transform inputs missing')
    if metrics.get('sequenceCards',0)<4: errors.append('sequence cards missing')
    if metrics.get('previewImages',0)<4: errors.append('preview images missing')
    if metrics.get('brokenImages'): errors.append('broken images '+str(metrics['brokenImages'][:5]))
    if errors: raise SystemExit('\n'.join(errors))
    print('RC13 pose slot scaling real-DOM QA OK')
if __name__=='__main__': asyncio.run(main())
