"""Split a text PDF into review batches and preserve original-page provenance.
No inference calls; no claim that a file size or character budget is a token budget.
"""
import argparse, csv, hashlib, json
from pathlib import Path
import fitz

def split(source, out, max_chars=12000, max_pages=6):
    source=Path(source);out=Path(out)
    if max_chars<1 or max_pages<1:raise ValueError('positive budgets required')
    out.mkdir(parents=True,exist_ok=False)
    doc=fitz.open(source);digest=hashlib.sha256(source.read_bytes()).hexdigest()
    batches=[];current=[];size=0;coverage=[]
    for number,page in enumerate(doc,1):
        text=page.get_text(sort=True).strip()
        status='text-extracted' if text else 'needs-visual-review-or-OCR'
        entry={'source_file':source.name,'source_sha256':digest,'page':number,'status':status,'text':text}
        rendered=f'\n[SOURCE {source.name} | ORIGINAL PAGE {number}]\n{text}\n'
        if current and (size+len(rendered)>max_chars or len(current)>=max_pages):batches.append(current);current=[];size=0
        current.append(entry);size+=len(rendered)
        if len(rendered)>max_chars:entry['oversized_page']=True
        coverage.append({'page':number,'status':status,'characters':len(text)})
    if current:batches.append(current)
    manifest=[]
    for n,batch in enumerate(batches,1):
        name=f'batch-{n:02d}.txt'
        (out/name).write_text(''.join(f"\n[SOURCE {source.name} | ORIGINAL PAGE {e['page']}]\n{e['text'] or '[NO TEXT: inspect original page; do not mark read]'}\n" for e in batch),encoding='utf-8')
        sub=fitz.open()
        for e in batch:sub.insert_pdf(doc,from_page=e['page']-1,to_page=e['page']-1)
        sub.save(out/f'batch-{n:02d}.pdf')
        manifest.append({'batch':n,'text_file':name,'pdf_file':f'batch-{n:02d}.pdf','original_pages':[e['page'] for e in batch],'oversized_pages':[e['page'] for e in batch if e.get('oversized_page')]})
    result={'source_file':source.name,'source_sha256':digest,'page_count':len(doc),'max_characters':max_chars,'max_pages':max_pages,'note':'Character and page limits are workflow controls, not model token limits. Extraction is not proof of model reading.','batches':manifest,'coverage':coverage}
    (out/'manifest.json').write_text(json.dumps(result,indent=2),encoding='utf-8')
    assert [p for b in manifest for p in b['original_pages']]==list(range(1,len(doc)+1))
    print(f'{len(doc)} pages -> {len(batches)} batches. Inspect empty text and oversized pages before inference.')
    return result

if __name__=='__main__':
    p=argparse.ArgumentParser();p.add_argument('source');p.add_argument('out');p.add_argument('--max-chars',type=int,default=12000);p.add_argument('--max-pages',type=int,default=6)
    a=p.parse_args();split(a.source,a.out,a.max_chars,a.max_pages)
