#!/usr/bin/env python3
import json,re,sys,hashlib
from pathlib import Path
from collections import Counter,defaultdict

def norm(s):
 s=s.lower();s=re.sub(r'[^\wÀ-ỹĐđ ]+',' ',s);return re.sub(r'\s+',' ',s).strip()
def grams(s,n):
 w=norm(s).split();return {' '.join(w[i:i+n]) for i in range(max(0,len(w)-n+1))}
def main():
 p=Path(sys.argv[1]);d=json.loads(p.read_text());rows=[]
 for part in d.get('parts',[]):
  pn=part.get('part_number',part.get('part'))
  for i,q in enumerate(part.get('paragraphs',[]),1):rows.append((f'{int(pn):02d}.{i:02d}',q.get('text','') if isinstance(q,dict) else str(q)))
 issues=[]
 # Long shared phrases across paragraphs catch generated scaffolding while tolerating normal names/terms.
 idx=defaultdict(list)
 for pid,text in rows:
  for g in grams(text,12):idx[g].append(pid)
 for g,pids in idx.items():
  u=sorted(set(pids))
  if len(u)>=2:issues.append({'type':'shared_12gram','text':g,'paragraphs':u})
 # Detect highly similar paragraph pairs via 8-gram Jaccard.
 sets=[(pid,grams(text,8)) for pid,text in rows]
 for i,(a,x) in enumerate(sets):
  for b,y in sets[i+1:]:
   if not x or not y:continue
   score=len(x&y)/len(x|y)
   if score>=0.18:issues.append({'type':'paragraph_similarity','paragraphs':[a,b],'score':round(score,4)})
 out={'status':'passed' if not issues else 'failed','story_path':str(p),'story_sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'paragraphs':len(rows),'issue_count':len(issues),'issues':issues[:500]};print(json.dumps(out,ensure_ascii=False,indent=2));raise SystemExit(0 if not issues else 1)
if __name__=='__main__':main()
