#!/usr/bin/env python3
import hashlib,json,re,sys
from pathlib import Path
PROJECT='gacmai_20260717_104203'
EXPECTED_CAST=['Lê An Nhiên','Trần Khải Minh','Vũ Thanh Hà','Phạm Đức Long','Đỗ Mai Sương','Hoàng Nhật Quang']
FORBIDDEN=[r'\b(bệnh viện|phẫu thuật|ecg)\b',r'\b(kho lạnh|thủy sản|container lạnh)\b',r'\b(ảo thuật|hậu đài)\b',r'\b(cites|lâm sản|đồi trà|pháo hoa)\b',r'\b(xăng dầu|đo lường pháp định|lưu lượng kế)\b']
def fail(errors):print(json.dumps({'status':'failed','errors':errors},ensure_ascii=False));raise SystemExit(1)
def main(raw):
 p=Path(raw);d=json.loads(p.read_text());e=[]
 if d.get('project_id')!=PROJECT:e.append('project')
 if d.get('target_words')!=14580:e.append('target_words')
 if d.get('exact_cast')!=6 or d.get('cast')!=EXPECTED_CAST:e.append('cast')
 bible=d.get('character_bible',[]);aliases=d.get('alias_map',{})
 if [x.get('name') for x in bible]!=EXPECTED_CAST or list(aliases)!=EXPECTED_CAST:e.append('character_bible_or_aliases')
 pov=d.get('pov_plan',{})
 if pov.get('mode')!='limited_single_pov' or pov.get('primary_pov')!='Lê An Nhiên' or pov.get('allowed_pov')!=['Lê An Nhiên']:e.append('pov')
 auth=d.get('authorities',[])
 if [x.get('authority_id') for x in auth]!=[f'A{i:02d}' for i in range(1,7)] or any(not x.get('scope') or not x.get('cannot_do') for x in auth):e.append('authorities')
 ev=d.get('evidence',[])
 if [x.get('evidence_id') for x in ev]!=[f'E{i:02d}' for i in range(1,10)]:e.append('evidence_ids')
 req={'name','aliases','setup','preservation','payoff','cannot_support','lawful_authority'}
 if any(not req.issubset(x) or not all(x.get(k) not in (None,'',[]) for k in req) or x.get('lawful_authority') not in {f'A{i:02d}' for i in range(1,7)} for x in ev):e.append('evidence_fields')
 parts=d.get('parts',[])
 if [x.get('part_id') for x in parts]!=[f'P{i}' for i in range(1,13)]:e.append('parts')
 beats=[]
 for i,x in enumerate(parts,1):
  bs=x.get('paragraph_beats',[]);beats+=bs
  if len(bs)!=12 or not x.get('title') or not x.get('purpose'):e.append(f'P{i}_shape')
  if sum(int(b.get('target_words',0)) for b in bs)!=int(x.get('target_words',0)):e.append(f'P{i}_words')
  for b in bs:
   if b.get('pov')!='Lê An Nhiên' or not b.get('scene_goal') or not b.get('transition'):e.append(f"{b.get('beat_id')}_core")
   if any(z not in {f'E{j:02d}' for j in range(1,10)} for z in b.get('evidence_actions',[])):e.append(f"{b.get('beat_id')}_evidence")
   if any(z not in {f'A{j:02d}' for j in range(1,7)} for z in b.get('authority_constraints',[])):e.append(f"{b.get('beat_id')}_authority")
 if len(beats)!=144 or len({b.get('beat_id') for b in beats})!=144:e.append('beat_ids')
 if sum(x.get('target_words',0) for x in parts)!=14580 or sum(b.get('target_words',0) for b in beats)!=14580:e.append('global_words')
 text=json.dumps({'title':d.get('title'),'domain':d.get('domain'),'parts':parts},ensure_ascii=False).lower()
 hits=[pat for pat in FORBIDDEN if re.search(pat,text,re.I)]
 if hits:e.append('forbidden_domain')
 guards=d.get('content_guards',{})
 if not all(guards.get(k) for k in ('named_entities','community_consent_privacy','sacred_audio','safety','ending')):e.append('guards')
 if e:fail(sorted(set(e)))
 print(json.dumps({'status':'passed','sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'title':d['title'],'cast':d['cast'],'parts':12,'paragraphs':144,'target_words':14580,'evidence_threads':9,'authority_scopes':6},ensure_ascii=False))
if __name__=='__main__':
 if len(sys.argv)!=2:fail(['usage validator OUTLINE.json'])
 main(sys.argv[1])
