#!/usr/bin/env python3
import hashlib,json,re,sys
from pathlib import Path
PROJECT='gacmai_20260716_155848'
OUTLINE=Path('/home/hermes/gacmai_20260716_155848_locked_outline_canonical.json')
WINDOWS={(1,4):(4650,5050),(5,8):(4650,5050),(9,12):(4650,5050)}

def wc(s): return len(re.findall(r'\b\w+\b',s,re.UNICODE))
def fail(es): print(json.dumps({'status':'failed','errors':es},ensure_ascii=False));raise SystemExit(1)
def main(raw):
 p=Path(raw);d=json.loads(p.read_text());o=json.loads(OUTLINE.read_text());osh=hashlib.sha256(OUTLINE.read_bytes()).hexdigest();es=[]
 if d.get('project_id')!=PROJECT:es.append('project mismatch')
 if d.get('title')!=o['title']:es.append('title mismatch')
 if d.get('outline_sha256')!=osh:es.append('outline SHA mismatch')
 rr=tuple(d.get('part_range',[]));parts=d.get('parts',[])
 if rr not in WINDOWS:es.append('invalid part_range')
 elif [x.get('part') for x in parts]!=list(range(rr[0],rr[1]+1)):es.append('part order mismatch')
 text=[]
 for x in parts:
  ps=x.get('paragraphs')
  if not isinstance(ps,list) or len(ps)!=12 or any(not isinstance(y,str) or not y.strip() for y in ps):es.append(f"part {x.get('part')} paragraphs")
  else:text.extend(ps)
 joined='\n\n'.join(text);n=wc(joined)
 if rr in WINDOWS and not WINDOWS[rr][0]<=n<=WINDOWS[rr][1]:es.append(f'words {n} outside {WINDOWS[rr]}')
 for name in o['cast']:
  surname=name.split()[0];scrub=joined.replace(name,'')
  if re.search(rf'(?<!\w){re.escape(surname)}(?!\w)',scrub):es.append(f'bare surname {surname}')
 # Named-character inventory in every batch is exact, even when some are offstage.
 if d.get('named_characters')!=o['cast']:es.append('named_characters mismatch')
 if not isinstance(d.get('continuity_ledger'),dict):es.append('continuity_ledger missing')
 if not isinstance(d.get('evidence_ledger'),dict):es.append('evidence_ledger missing')
 if es:fail(es)
 print(json.dumps({'status':'passed','sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'part_range':list(rr),'parts':len(parts),'paragraphs':len(text),'words':n},ensure_ascii=False))
if __name__=='__main__':
 if len(sys.argv)!=2:fail(['usage validator BATCH.json'])
 main(sys.argv[1])
