#!/usr/bin/env python3
from pathlib import Path
import json
import subprocess
from PIL import Image, ImageDraw, ImageFont

ROOT=Path(__file__).resolve().parents[1]
QA=ROOT/'qa_screenshots'; QA.mkdir(exist_ok=True)
REPORT=ROOT/'assets/data/image-admin/rc40_mediapipe_patch_merge_report.json'
VIEWS={'mobile':(390,844),'ipad':(820,1180),'desktop':(1440,900)}


def font(size,bold=False):
    candidates=[
        '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf' if bold else '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
        '/usr/share/fonts/truetype/liberation2/LiberationSans-Bold.ttf' if bold else '/usr/share/fonts/truetype/liberation2/LiberationSans-Regular.ttf',
    ]
    for c in candidates:
        try: return ImageFont.truetype(c,size)
        except Exception: pass
    return ImageFont.load_default()


def draw_flow():
    W,H=1600,900
    img=Image.new('RGB',(W,H),(4,10,19))
    d=ImageDraw.Draw(img)
    # background glow
    for r,alpha in [(520,32),(360,24),(220,18)]:
        d.ellipse((-180,-170,r,r),fill=(8+alpha//4,55+alpha,82+alpha//2))
    title_f=font(48,True); h2=font(29,True); body=font(21); small=font(17)
    d.text((70,54),'RC40 · MediaPipe patch merge flow',font=title_f,fill=(238,252,255))
    d.text((72,115),'Safe bridge: raw MediaPipe is stored separately, runtime landmarks stay visual-left/right and limbIntent-first.',font=body,fill=(185,215,226))
    steps=[
        ('1','Browser extraction','MediaPipe IMAGE mode\nexports rc40 patch JSON\nno app data is changed'),
        ('2','Dry-run validation','poseKey + athleteId match\nsprite filename check\n33 landmark + visibility checks'),
        ('3','Visual remap','MediaPipe anatomical labels\nare sorted to visual left/right\nfor feet and hands'),
        ('4','Candidate merge','store under\nruntimeLandmarksMediapipeCandidate\npreserve alpha fallback'),
        ('5','Activate after QA','optional --activate-runtime\nbacks up old atlas\nsequence limbIntent untouched'),
    ]
    x0,y0=70,190; w,h=280,470; gap=24
    for i,(num,head,txt) in enumerate(steps):
        x=x0+i*(w+gap)
        d.rounded_rectangle((x,y0,x+w,y0+h),radius=28,fill=(8,25,42),outline=(65,193,224),width=2)
        d.ellipse((x+22,y0+24,x+78,y0+80),fill=(113,232,255))
        d.text((x+42,y0+34),num,font=font(29,True),fill=(4,16,27),anchor='mm')
        d.text((x+24,y0+105),head,font=h2,fill=(238,252,255))
        yy=y0+162
        for line in txt.split('\n'):
            d.text((x+24,yy),line,font=body,fill=(194,225,235))
            yy+=34
        if i<4:
            ax=x+w+5; ay=y0+h//2
            d.line((ax,ay,ax+18,ay),fill=(113,232,255),width=5)
            d.polygon([(ax+22,ay),(ax+6,ay-10),(ax+6,ay+10)],fill=(113,232,255))
    counts={}
    if REPORT.exists():
        rep=json.loads(REPORT.read_text())
        counts=rep.get('counts',{})
    d.rounded_rectangle((70,710,1530,835),radius=24,fill=(7,21,35),outline=(38,111,139),width=1)
    d.text((100,735),'Self-test dry run',font=h2,fill=(238,252,255))
    msg=f"accepted {counts.get('accepted',0)} / {counts.get('patchItems',0)} · skipped {counts.get('skipped',0)} · runtimeActivated {counts.get('runtimeActivated',0)} · candidateStored {counts.get('candidateStored',0)}"
    d.text((100,785),msg,font=body,fill=(181,245,255))
    d.text((1080,785),'Production atlas not overwritten by self-test',font=body,fill=(185,215,226))
    out=QA/'rc40_mediapipe_patch_merge_flow.jpg'
    img.save(out,quality=92)
    return out

FLOW=draw_flow()


def html_for(name,w,h):
    is_mobile=name=='mobile'
    stage_h=int(h*.34 if is_mobile else h*.58)
    cards='grid-template-columns:1fr;' if is_mobile else 'grid-template-columns:1.15fr .85fr;'
    return f'''<!doctype html><html><head><meta charset="utf-8"><style>
@page {{ size:{w}px {h}px; margin:0; }}
*{{box-sizing:border-box}} body{{margin:0;width:{w}px;height:{h}px;background:radial-gradient(circle at 8% 0%,#154963,#071220 54%,#040811);font-family:Inter,Arial,sans-serif;color:#effbff;overflow:hidden}}
.shell{{height:100%;padding:{16 if is_mobile else 28}px;display:flex;flex-direction:column;gap:{12 if is_mobile else 18}px}}
.top{{display:flex;align-items:center;justify-content:space-between;gap:14px}} .brand{{font-weight:950;letter-spacing:.09em;font-size:{17 if is_mobile else 24}px}} .pill{{padding:8px 12px;border:1px solid rgba(120,233,255,.42);border-radius:999px;background:rgba(3,22,36,.72);color:#aaf4ff;font-weight:900;font-size:{10 if is_mobile else 13}px}}
.grid{{display:grid;{cards}gap:{12 if is_mobile else 18}px;min-height:0;flex:1}}
.stage{{position:relative;min-height:{stage_h}px;border:1px solid rgba(120,233,255,.22);border-radius:{20 if is_mobile else 30}px;background:linear-gradient(180deg,rgba(10,30,48,.72),rgba(2,8,18,.92));overflow:hidden;box-shadow:0 22px 70px rgba(0,0,0,.42)}}
.stage img{{position:absolute;left:0;top:0;width:100%;height:100%;object-fit:contain;padding:{8 if is_mobile else 16}px}}
.panel{{border:1px solid rgba(120,233,255,.18);border-radius:{18 if is_mobile else 25}px;background:rgba(5,15,29,.78);padding:{15 if is_mobile else 24}px;box-shadow:0 18px 55px rgba(0,0,0,.30);display:flex;flex-direction:column;justify-content:space-between;gap:14px}}
h1{{margin:0;font-size:{24 if is_mobile else 40}px;line-height:1.0;letter-spacing:-.04em}} p{{margin:10px 0 0;color:#b9cfdb;font-size:{12 if is_mobile else 16}px;line-height:1.42}}
.check{{display:flex;gap:10px;margin-top:12px;color:#e7fcff;font-size:{12 if is_mobile else 15}px;line-height:1.32}} .check b{{color:#75eaff}}
.code{{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;margin-top:12px;border-radius:14px;padding:10px;background:rgba(0,0,0,.28);font-size:{9 if is_mobile else 12}px;color:#d6fbff;line-height:1.35;white-space:pre-wrap}}
.badges{{display:flex;flex-wrap:wrap;gap:7px;margin-top:14px}} .badge{{border-radius:999px;padding:7px 9px;background:rgba(120,233,255,.10);border:1px solid rgba(120,233,255,.18);font-size:{10 if is_mobile else 12}px;color:#c6f8ff;font-weight:800}}
.footer{{display:{'none' if is_mobile else 'flex'};justify-content:space-between;gap:8px;color:#88a4b4;font-size:{9 if is_mobile else 12}px}}
</style></head><body><main class="shell"><div class="top"><div class="brand">NEXT ATHLETE OS</div><div class="pill">RC40 · {name} QA</div></div><section class="grid"><div class="stage"><img src="qa_screenshots/rc40_mediapipe_patch_merge_flow.jpg"></div><aside class="panel"><div><h1>MediaPipe merge utan att sabba bindningarna</h1><p>RC40 bygger bron mellan browser-extraktor och atlas-data. Rå MediaPipe sparas separat och runtime aktiveras först efter QA.</p><div class="check"><b>✓</b><span>Ny merge tool med dry-run, report, backup och optional runtime activation.</span></div><div class="check"><b>✓</b><span>Fötter/händer sorteras till <b>visual-left/right</b> för appens skelettlogik.</span></div><div class="check"><b>✓</b><span>Alpha runtimeLandmarks sparas som fallback innan aktivering.</span></div><div class="code">python3 tools/pose_atlas_rc40_merge_mediapipe_patch.py --patch rc40_mediapipe_pose_patch.json --apply</div><div class="badges"><span class="badge">candidate-first</span><span class="badge">limbIntent preserved</span><span class="badge">rollback-safe</span></div></div></aside></section><div class="footer"><span>Static fallback render: WeasyPrint → PNG</span><span>Chromium/Playwright unavailable in sandbox</span></div></main></body></html>'''

for name,(w,h) in VIEWS.items():
    html=ROOT/f'qa_rc40_{name}.html'; pdf=QA/f'rc40_{name}_static_qa.pdf'; prefix=QA/f'rc40_{name}_static_qa'
    html.write_text(html_for(name,w,h),encoding='utf-8')
    subprocess.run(['weasyprint',str(html),str(pdf)],check=True,cwd=ROOT)
    subprocess.run(['pdftoppm','-png','-singlefile','-r','96',str(pdf),str(prefix)],check=True)
    print(QA/f'rc40_{name}_static_qa.png')
print(FLOW)
