# Reads what gapcheck.js measured on screen plus the printed PDF, and prints the pass or fail report for check.sh.
# Usage: python3 checkreport.py <dumped dom.html> <printed pdf>
import re, html, json, sys, fitz

s = open(sys.argv[1]).read(); pdf = fitz.open(sys.argv[2]); MM = 72 / 25.4

def rep(a):
    m = re.search(a + r'="([^"]*)"', s)
    return json.loads(html.unescape(m.group(1))) if m else None

fit = rep('data-fit-report') or []; gap = rep('data-gap-report')
bad = 0
print("\nAUTO FIT BOXES")
for b in fit:
    if 'page' in b:
        print(f"  FAIL page {b['page']} overflows by {b['overflow']}px"); bad += 1
    else:
        ok = 0 <= b['gap'] < 0.05 or abs(b['gap']) < 0.002
        print(f"  {'ok  ' if ok else 'FAIL'} {b['box']}: scale {b['scale']}, empty {b['gap']*100:.1f}%"); bad += not ok
if gap is None:
    print("\nEMPTY SPACE: NO REPORT (checker did not run)"); sys.exit(2)

# the printed PDF is what Jo gets, so overlaps are read from it, and its layout must match the screen the gaps were measured on
print("\nPRINTED PDF")
for pi, page in enumerate(pdf):
    lay = gap['layout'][pi] if pi < len(gap['layout']) else {'texts': [], 'overlay_text': [], 'overlay_pics': []}
    lines = []
    for bi, b in enumerate(page.get_text('dict')['blocks']):
        for li, l in enumerate(b.get('lines', [])):
            whole = ''.join(sp['text'] for sp in l['spans']).strip()
            for sp in l['spans']:  # runs, not whole lines: a line can join a tall date and a wrapped title
                t = sp['text'].strip()
                if not t: continue
                x0, y0, x1, y1 = sp['bbox']
                lines.append((bi, li, fitz.Rect(x0 + .5, y0, x1 - .5, y1), t, whole))
    hits = []
    for i in range(len(lines)):
        for j in range(i + 1, len(lines)):
            a, b = lines[i], lines[j]
            if a[0] == b[0] and abs(a[1] - b[1]) <= 1: continue
            r = a[2] & b[2]
            # tightly set neighbours share a little of their line boxes; a real collision covers much more
            if r.width > 1 and r.height > .4 * min(a[2].height, b[2].height):
                hits.append(f"\"{a[3][:40]}\" over \"{b[3][:40]}\", {a[2].y0/MM:.0f}mm down")
    allowed = [fitz.Rect(*[v * MM for v in q]) for q in lay['overlay_pics']]
    for im in page.get_image_info():
        ir = fitz.Rect(im['bbox'])
        if ir.width < 20 or ir.height < 20 or any(abs(ir.x0 - q.x0) < 6 and abs(ir.y0 - q.y0) < 6 for q in allowed): continue
        for _, _, lr, _, t in lines:
            r = lr & ir
            tl = t.lower()  # overlays are often text-transform:uppercase in print
            if r.width > 1 and r.height > .5 and not any(tl.startswith(o.lower()[:12]) or o.lower().startswith(tl[:12]) for o in lay['overlay_text']):
                hits.append(f"\"{t[:40]}\" printed on a picture, {lr.y0/MM:.0f}mm down")
    drift = []
    for x in lay['texts']:
        rs = page.search_for(x['t'])
        if rs:
            d = min(abs(r.y0 / MM - x['top']) for r in rs)
            if d > 2: drift.append((d, x['t']))
    for h in hits[:12]: print(f"  FAIL p{pi+1} overlap: {h}")
    if len(hits) > 12: print(f"  FAIL p{pi+1} ...and {len(hits)-12} more overlaps")
    if len(drift) < 3: drift = []  # one or two moved matches are repeated or letter spaced text, not a layout change
    if drift:
        drift.sort(reverse=True)
        print(f"  FAIL p{pi+1} prints differently from screen: {len(drift)} text blocks moved, worst {drift[0][0]:.0f}mm "
              f"(\"{drift[0][1]}\"); the empty space numbers for this page are not reliable")
    bad += len(hits) + (1 if drift else 0)
    if not hits and not drift: print(f"  ok   p{pi+1}: nothing overlaps, prints as on screen")

cl = gap.get('clipped', [])
print(f"\nCUT OFF TEXT: {len(cl)} pieces of text hidden by the edge of their box")
for c in cl: print(f"  FAIL p{c['page']} \"{c['text']}\" ({c['lines_cut']} of {c['of']} lines hidden) in {c['box']}")
bad += len(cl)

print(f"\nEMPTY SPACE: {gap['checked']} columns, cells and boxes checked, {len(gap['failed'])} over 5%")
for f in gap['failed']:
    print(f"  FAIL {f['box']}\n       {f['why']}: bottom {f['empty_bottom']*100:.1f}% ({f['empty_bottom_mm']}mm), "
          f"biggest blank band {f['blank_band']*100:.1f}% ({f['blank_band_mm']}mm, {f['band_from_top_mm']}mm down), box {f['height_mm']}mm tall")
bad += len(gap['failed'])
print("\nRESULT:", "PASS" if not bad else f"FAIL ({bad})", "\nNow look at every page PNG before sending.")
sys.exit(1 if bad else 0)
