#!/usr/bin/env python3
"""Create chapter PDFs, a merged report, and explicitly labelled PDF preview images.
Requires: weasyprint, PyMuPDF, Pillow. No web fetches and no browser-policy changes.
"""
from pathlib import Path
import base64,hashlib,io,json,logging,re,sys
from urllib.parse import urlparse,unquote
from weasyprint import HTML,default_url_fetcher
import fitz
from PIL import Image,ImageDraw,ImageFont
R=Path(__file__).resolve().parents[1]
PDFS=json.loads((R/'data/pdf-index.json').read_text());NAV=json.loads((R/'data/navigation.json').read_text())
LOG=[]
class Capture(logging.Handler):
 def emit(self,record):LOG.append(self.format(record))
handler=Capture();logging.getLogger('weasyprint').addHandler(handler);logging.getLogger('weasyprint').setLevel(logging.ERROR)
def local_fetch(url,*a,**kw):
 parsed=urlparse(url)
 if parsed.scheme=='data':return default_url_fetcher(url,*a,**kw)
 if parsed.scheme not in ('','file'):raise ValueError('Network resources are not allowed in report export.')
 p=Path(unquote(parsed.path)).resolve()
 if not p.is_relative_to(R):raise ValueError('Resource outside report folder refused.')
 return default_url_fetcher(url,*a,**kw)
def scrub_pdf(data,title):
 doc=fitz.open(stream=data,filetype='pdf');doc.set_metadata({'title':title+' | Lunis ist Zeit','author':'Lunis ist Zeit','subject':'Entwicklungsbericht – statische Lesefassung; keine Browserabnahme','keywords':'Lunis, Entwicklungsstand, Quellen, Pilot','creator':'Lunis Report Export'})
 for page in doc:
  for l in page.get_links():
   if l.get('uri','').startswith(('http://','https://')):continue
   if l.get('kind')==fitz.LINK_GOTO:continue
   page.delete_link(l)
 out=doc.tobytes(garbage=4,deflate=True);doc.close();return out

def run():
 export=R/'exports';proof=R/'proof/pdf';export.mkdir(exist_ok=True);proof.mkdir(parents=True,exist_ok=True)
 output=[];media=json.loads((R/'data/media.json').read_text());media=[m for m in media if m.get('typ')!='Aktuelle PDF-Ansicht']
 names={slug:title for slug,title,num in NAV}
 for slug,name in PDFS.items():
  print('PDF',slug,flush=True)
  data=HTML(filename=str(R/(slug+'.html')),url_fetcher=local_fetch,media_type='print').write_pdf()
  file=export/(name+'.pdf');file.write_bytes(scrub_pdf(data,names[slug]))
  doc=fitz.open(file)
  pix=doc[0].get_pixmap(matrix=fitz.Matrix(1.5,1.5),alpha=False)
  raw=Image.open(io.BytesIO(pix.tobytes('png'))).convert('RGB');preview=Image.new('RGB',(raw.width,raw.height+38),'white');preview.paste(raw,(0,38))
  draw=ImageDraw.Draw(preview);draw.rectangle((0,0,preview.width,38),fill='#091a40')
  font=ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',12) if Path('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf').exists() else ImageFont.load_default()
  draw.text((16,12),'PDF-LESEFASSUNG · '+names[slug].upper()+' · KEINE BROWSER-AUFNAHME',fill='white',font=font)
  png=proof/(slug+'.png');preview.save(png)
  image=R/'assets/media'/('pdf-'+slug+'.webp');preview.save(image,'WEBP',quality=89)
  media.append({'id':'pdf-'+slug,'datei':str(image.relative_to(R)),'typ':'Aktuelle PDF-Ansicht','titel':names[slug],'pruefung':'Aus dem generierten Kapitel-PDF gerendert; kein Browser-Screenshot','bearbeitung':'Transparente Kennzeichnung als PDF-Ansicht','sha256':hashlib.sha256(image.read_bytes()).hexdigest()})
  text='\n'.join(p.get_text() for p in doc)
  output.append({'chapter':slug,'file':str(file.relative_to(R)),'pages':len(doc),'bytes':file.stat().st_size,'sha256':hashlib.sha256(file.read_bytes()).hexdigest(),'text_characters':len(text),'preview':str(png.relative_to(R))})
  doc.close()
 combined=fitz.open();toc=[]
 for row in output:
  doc=fitz.open(R/row['file']);toc.append([1,names[row['chapter']],len(combined)+1]);combined.insert_pdf(doc);doc.close()
 combined.set_toc(toc);combined.set_metadata({'title':'Lunis ist Zeit | Entwicklungsbericht','author':'Lunis ist Zeit','subject':'Zwölf Kapitel mit Quellen und offenen Punkten','creator':'Lunis Report Export'})
 for i,p in enumerate(combined):
  box=fitz.Rect(p.rect.width-94,p.rect.height-43,p.rect.width-24,p.rect.height-18)
  p.draw_rect(box,color=None,fill=(1,1,1));p.insert_textbox(box,f'{i+1} / {len(combined)}',fontname='helv',fontsize=8,color=(.035,.10,.25),align=2)
 combined.save(export/'Lunis-Endbericht.pdf',garbage=4,deflate=True);total=len(combined);combined.close()
 (R/'data/media.json').write_text(json.dumps(media,ensure_ascii=False,indent=2))
 (R/'proof/pdf-export.json').write_text(json.dumps({'status':'GENERATED','renderer':'WeasyPrint → PyMuPDF','browser_claim':False,'chapters':output,'total_pages':total,'render_errors':LOG,'limits':'Static PDFs do not execute JavaScript or prove browser interactions.'},ensure_ascii=False,indent=2))
 # Contact sheet for visual review of the first page of every chapter.
 cols=3;thumb_w=400;thumb_h=580;sheet=Image.new('RGB',(cols*thumb_w,((len(output)+cols-1)//cols)*thumb_h),'#e9edf5')
 for i,row in enumerate(output):
  im=Image.open(R/row['preview']);im.thumbnail((thumb_w-18,thumb_h-18));sheet.paste(im,((i%cols)*thumb_w+9,(i//cols)*thumb_h+9))
 sheet.save(R/'proof/pdf-contact-sheet.jpg',quality=92)
 # All-page contact sheets and physical-page text bounds; these are not browser proofs.
 doc=fitz.open(export/'Lunis-Endbericht.pdf');font=ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',13)
 for start in range(0,len(doc),24):
  sheet=Image.new('RGB',(1400,3060),'#e6eaf1');draw=ImageDraw.Draw(sheet)
  for n in range(start,min(start+24,len(doc))):
   pix=doc[n].get_pixmap(matrix=fitz.Matrix(.54,.54),alpha=False);im=Image.open(io.BytesIO(pix.tobytes('png')));im.thumbnail((330,476));x=(n-start)%4*350+10;y=(n-start)//4*510+27;sheet.paste(im,(x,y));draw.text((x,y-20),f'PDF · Seite {n+1}',font=font,fill='#091a40')
  sheet.save(R/'proof'/f'pdf-pages-{start+1:02d}.jpg',quality=92)
 bounds=[]
 for n,page in enumerate(doc):
  for block in page.get_text('dict')['blocks']:
   for line in block.get('lines',[]):
    for span in line['spans']:
     x0,y0,x1,y1=span['bbox']
     if x0< -1 or y0< -1 or x1>page.rect.width+1 or y1>page.rect.height+1:bounds.append({'page':n+1,'kind':'glyph_outside_page'})
 (R/'proof/pdf-text-checks.json').write_text(json.dumps({'pages':len(doc),'outline_chapters':len(doc.get_toc()),'glyph_bounds_errors':bounds,'scope':'PDF text boxes against physical page, not browser layout'},indent=2));doc.close()
 for name,page,label in [('02-funktionsmatrix.pdf',1,'matrix-detail'),('08-markt.pdf',3,'markt-detail')]:
  d=fitz.open(export/name);pix=d[min(page,len(d)-1)].get_pixmap(matrix=fitz.Matrix(1.8,1.8),alpha=False);pix.save(str(R/'proof'/f'pdf-{label}.png'));d.close()
 if bounds:raise SystemExit('PDF glyphs outside physical page; inspect PDF before packaging.')
 print(json.dumps({'chapter_pdfs':len(output),'total_pages':total,'render_errors':len(LOG)},ensure_ascii=False))
 if LOG:raise SystemExit('PDF rendering errors. Review proof/pdf-export.json before packaging.')
if __name__=='__main__':run()
