#!/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("""() => ({
          hasPage:!!document.querySelector('.next-image-admin-page'),
          hasPoseBuilder:!!document.querySelector('.next-pose-builder'),
          bankCards:document.querySelectorAll('.next-pose-bank-card').length,
          sequenceCards:document.querySelectorAll('.next-pose-sequence-card').length,
          previewImages:document.querySelectorAll('.next-pose-preview-row img').length,
          selected:document.querySelector('.next-image-admin-row.is-active')?.dataset.exerciseKey||'',
          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/'rc12_pose_sequence_builder_desktop_1440x1024.png'),full_page=False)
        await browser.close()
    (OUT/'rc12_pose_sequence_builder_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 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('RC12 pose sequence real-DOM QA OK')
if __name__=='__main__': asyncio.run(main())
