"""Render with an explicit local CJK font config and validate this deck."""
from pathlib import Path
from zipfile import ZipFile
from lxml import etree as E
from PIL import Image,ImageDraw
import fitz,json,re,hashlib,posixpath,csv,subprocess,os,io
ROOT=Path(__file__).resolve().parents[1]
KIND='reference' if '--reference' in __import__('sys').argv else 'main'
EXPECTED=7 if KIND=='reference' else 10
STEM='nutrition-settlement-v3-'+KIND
PREVIEW=ROOT/'preview'/('v3-'+KIND);PREVIEW.mkdir(exist_ok=True)
FONTDIR=ROOT/'fonts';FONTDIR.mkdir(exist_ok=True)
fontconf=FONTDIR/'fonts.conf'
fontconf.write_text(f'''<?xml version="1.0"?><!DOCTYPE fontconfig SYSTEM "fonts.dtd"><fontconfig><dir>{Path.home()}/Library/Fonts</dir><dir>/System/Library/Fonts</dir><dir>/System/Library/Fonts/Supplemental</dir><cachedir>{FONTDIR}/cache</cachedir></fontconfig>''')
if '--render' in __import__('sys').argv:
 env=os.environ.copy();env['FONTCONFIG_FILE']=str(fontconf)
 subprocess.run(['soffice','--headless','--convert-to','pdf','--outdir',str(ROOT/'exports'),str(ROOT/'exports'/(STEM+'.pptx'))],check=True,env=env)
PDF=ROOT/'exports'/(STEM+'.pdf');PPTX=ROOT/'exports'/(STEM+'.pptx')
doc=fitz.open(PDF);assert len(doc)==EXPECTED
alltext=[]
for i,page in enumerate(doc):
 text=page.get_text();assert len(re.findall(r'[\u4e00-\u9fff]',text))>30,(i,'CJK text missing')
 alltext.append(text)
 page.get_pixmap(matrix=fitz.Matrix(1.6,1.6)).save(PREVIEW/f'slide-{i+1:02d}.png')
(ROOT/'preview/rendered-text.txt').write_text('\n\n'.join(alltext))
# Preview is a montage, not a modified deliverable image.
for start in range(0,len(doc),9):
 sheet=Image.new('RGB',(1500,910),'#e8e8e8');dr=ImageDraw.Draw(sheet)
 for j in range(min(9,len(doc)-start)):
  im=Image.open(PREVIEW/f'slide-{start+j+1:02d}.png');im.thumbnail((490,276));x=(j%3)*500+5;y=(j//3)*303+22;sheet.paste(im,(x,y));dr.text((x,y-17),f'{start+j+1:02d}',fill='black')
 sheet.save(PREVIEW/f'overview-{start//9+1}.jpg',quality=92)
ns={'a':'http://schemas.openxmlformats.org/drawingml/2006/main','p':'http://schemas.openxmlformats.org/presentationml/2006/main','r':'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}
issues=[];stats=[]
with ZipFile(PPTX) as z:
 slides=sorted([n for n in z.namelist() if re.fullmatch(r'ppt/slides/slide\d+\.xml',n)],key=lambda s:int(re.search(r'slide(\d+)',s).group(1)))
 assert len(slides)==EXPECTED
 notes=[n for n in z.namelist() if re.fullmatch(r'ppt/notesSlides/notesSlide\d+\.xml',n)];assert len(notes)==EXPECTED
 for i,n in enumerate(slides,1):
  root=E.fromstring(z.read(n));rels={r.get('Id'):posixpath.normpath(posixpath.join('ppt/slides',r.get('Target'))) for r in E.fromstring(z.read(f'ppt/slides/_rels/slide{i}.xml.rels'))}
  text=''.join(root.xpath('//a:t/text()',namespaces=ns));assert len(text)>100
  assert not any(v in text for v in ['待补图','Lorem ipsum','TODO','占位符'])
  stats.append({'page':i,'editable_text_nodes':len(root.findall('.//a:t',ns)),'pictures':len(root.findall('.//p:pic',ns)),'native_tables':len(root.findall('.//a:tbl',ns))})
  for pic in root.findall('.//p:pic',ns):
   blip=pic.find('.//a:blip',ns);ext=pic.find('./p:spPr/a:xfrm/a:ext',ns)
   if blip is None or ext is None:continue
   target=rels[blip.get('{'+ns['r']+'}embed')]
   im=Image.open(io.BytesIO(z.read(target)));crop=pic.find('.//a:srcRect',ns)
   c={k:int(crop.get(k,'0'))/100000 for k in ['l','r','t','b']} if crop is not None else dict(l=0,r=0,t=0,b=0)
   ratio=im.width*(1-c['l']-c['r'])/(im.height*(1-c['t']-c['b']))
   displayed=int(ext.get('cx'))/int(ext.get('cy'))
   if abs(ratio/displayed-1)>.006:issues.append({'page':i,'image':target,'ratio_error':ratio/displayed-1})
 # Validate package relationship targets; no external image dependencies.
 for name in z.namelist():
  if not name.endswith('.rels'):continue
  base=posixpath.dirname(name).replace('/_rels','')
  for rel in E.fromstring(z.read(name)):
   if rel.get('TargetMode')=='External':continue
   target=posixpath.normpath(posixpath.join(base,rel.get('Target'))).lstrip('/')
   if name=='_rels/.rels':target=rel.get('Target').lstrip('/')
   assert target in z.namelist(),(name,target)
assert not issues,issues
# Ensure all authored native boxes remain on the slide.
layout=json.loads((ROOT/(STEM+'-layout.json')).read_text())
for r in layout:
 assert r['x']>=0 and r['y']>=0 and r['x']+r['w']<=13.334 and r['y']+r['h']<=7.501,r
result={'pages':EXPECTED,'notes_pages':EXPECTED,'native_text_nodes':sum(r['editable_text_nodes'] for r in stats),'native_tables':sum(r['native_tables'] for r in stats),'cjk_rendering':'passed all pages','image_aspect_ratio':'passed all images within 0.6%','relationships':'all internal targets resolve','native_box_bounds':'passed','slides':stats,'viewer_validation':'LibreOffice import/export and all-page rendering; WPS GUI not exercised (node_repl unavailable)'}
(ROOT/(STEM+'-validation.json')).write_text(json.dumps(result,ensure_ascii=False,indent=2))
assets=[]
for f in [PPTX,PDF]:
 assets.append({'path':str(f.relative_to(ROOT)),'bytes':f.stat().st_size,'sha256':hashlib.sha256(f.read_bytes()).hexdigest(),'storage':'local ignored binary; not publicly published'})
(ROOT/(STEM+'-delivery.json')).write_text(json.dumps(assets,ensure_ascii=False,indent=2))
print(json.dumps({k:v for k,v in result.items() if k!='slides'},ensure_ascii=False))
