"""Text / timing level comparison: English Targ vs Ukrainian baseline (7-8) vs Ukrainian new (8-9).
Uses word-level timings: English faster-whisper ASR; Ukrainian performed passports (master clock)."""
import json,re,statistics as st,sys
from pathlib import Path
ROOT=Path('/workspaces/UGS/AdFactorySongFormat/trees/c06fb16085'); S=Path(sys.argv[1]); OUT=S/'analysis'
UK_V='аеєиіїоуюя'; EN_V='aeiouy'
def uk_syl(w): return sum(1 for c in w.lower() if c in UK_V)
def en_syl(w):
    w=re.sub(r"[^a-z]","",w.lower())
    if not w: return 0
    groups=len(re.findall(r'[aeiouy]+',w))
    if w.endswith('e') and not w.endswith(('le','ee','ye')) and groups>1: groups-=1
    if w.endswith('ed') and not w.endswith(('ted','ded')) and groups>1: groups-=1
    return max(1,groups)
def strip_stress(w): return w.replace('́','')
def clean_uk(w): return re.sub(r"[^а-яіїєґ'’]","",strip_stress(w).lower())
EN_FUNC=set("i me my mine you your she her hers he him his it its we us our they them their that this these those the a an and or but so then because like as at in on of to for from with by into over up down out off about before after there here was were is are be been am had has have do did does not no yes just very really too also all every each any some one two of's".split())
UK_FUNC=set("я ти він вона ми ви вони мене мені її їй його йому нас вам їх ним ній це той та те ті цей ця ці у в на з із до за про по при від без для над під між через щоб що як коли де і й та а але ні не так ще вже теж вже наче ніби мов немов якщо то би б же ж ось бо чи ну от лише тільки навіть хоч сам сама своє свою свій себе тобі ти".split())
def is_func(w,lang): return (clean_uk(w) in UK_FUNC) if lang=='uk' else (re.sub(r"[^a-z']","",w.lower()) in EN_FUNC)
# ---- English words (ASR) ----
en=json.load(open(S/'asr/targ.asr.json'))
en_words=[]
for seg in en['segments']:
    for w in seg['words']:
        t=w['word'].strip()
        if not re.search('[a-zA-Z]',t): continue
        en_words.append({'text':t,'start':w['start'],'end':w['end'],'seg':seg['start']})
en_lines=[{'text':seg['text'].strip(),'start':seg['start'],'end':seg['end'],'words':[w['word'].strip() for w in seg['words']]} for seg in en['segments']]
# ---- Ukrainian words (passports) ----
def uk_words(passport):
    p=json.load(open(passport)); ws=[]
    for w in p['wordOccurrences']:
        ws.append({'text':strip_stress(w['text']),'start':w['time'][0],'end':w['time'][1]})
    return ws,p
new_words,new_p=uk_words(ROOT/'runs/targ-refined-ce99390b3b-v01/song/performed-song-passport-v01.json')
base_words,base_p=uk_words(ROOT/'runs/targ-improved-b614aa3b3e-v01/song/performed-song-passport-v01.json')
# lyric lines -> phrases (split on sentence punctuation) and map words sequentially
def uk_phrases(lyrics_path,words):
    text=Path(lyrics_path).read_text()
    lines=[l for l in text.splitlines() if l.strip() and not l.startswith('[')]
    phrases=[]
    for l in lines:
        for ph in re.split(r'(?<=[.!?…:])\s+|\s—\s(?=[«А-ЯІЇЄҐ])',l):
            toks=[t for t in re.findall(r"[А-Яа-яІіЇїЄєҐґ'’́]+",ph)]
            if toks: phrases.append({'text':ph.strip(),'ntok':len(toks),'line':l})
    i=0; out=[]
    for ph in phrases:
        ws=words[i:i+ph['ntok']]; i+=ph['ntok']
        if not ws: break
        out.append({**ph,'start':ws[0]['start'],'end':ws[-1]['end'],'words':[w['text'] for w in ws]})
    assert i==len(words), (i,len(words))
    return out
new_ph=uk_phrases(ROOT/'runs/targ-refined-ce99390b3b-v01/song/lyrics-plain-v02.txt',new_words)
base_ph=uk_phrases(ROOT/'runs/targ-improved-b614aa3b3e-v01/song/lyrics-plain-v07.txt',base_words)
# English phrases = ASR segments split on punctuation too
en_ph=[]
for seg in en['segments']:
    ws=[w for w in seg['words'] if re.search('[a-zA-Z]',w['word'])]
    cur=[]
    for w in ws:
        cur.append(w)
        if re.search(r'[.!?,]$',w['word'].strip()):
            en_ph.append({'text':' '.join(x['word'].strip() for x in cur),'start':cur[0]['start'],'end':cur[-1]['end'],'words':[x['word'].strip() for x in cur]}); cur=[]
    if cur: en_ph.append({'text':' '.join(x['word'].strip() for x in cur),'start':cur[0]['start'],'end':cur[-1]['end'],'words':[x['word'].strip() for x in cur]})
def stats(name,lang,words,phrases,duration):
    syl=(uk_syl if lang=='uk' else en_syl)
    ws=[{**w,'syl':syl(w['text']),'dur':w['end']-w['start'],'func':is_func(w['text'],lang)} for w in words]
    ws=[w for w in ws if w['syl']>0]
    nsyl=sum(w['syl'] for w in ws)
    sung_time=sum(max(0.0,p['end']-p['start']) for p in phrases)
    mono=sum(1 for w in ws if w['syl']==1)/len(ws)
    poly3=sum(1 for w in ws if w['syl']>=3)/len(ws)
    poly4=sum(1 for w in ws if w['syl']>=4)/len(ws)
    func=sum(1 for w in ws if w['func'])/len(ws)
    # phrase-final endings
    def final_type(w):
        c=(clean_uk(w) if lang=='uk' else re.sub(r"[^a-z]","",w.lower()))
        if not c: return 'none'
        last=c[-1]
        if lang=='uk': return 'vowel' if last in UK_V else ('plosive' if last in 'птксдгбґчцш' else 'consonant')
        # english rough: word-final letter classes; silent e handled roughly
        if c.endswith('e') and len(c)>2 and c[-2] not in 'aeiou': return 'consonant_silent_e'
        return 'vowel' if last in 'aeiou' else ('plosive' if last in 'ptkdgbc' else 'consonant')
    finals=[final_type(p['words'][-1]) for p in phrases if p['words']]
    fin={k:round(finals.count(k)/len(finals),3) for k in set(finals)}
    # syllable rate per phrase
    rates=[]
    for p in phrases:
        d=p['end']-p['start']; s=sum(syl(w) for w in p['words'])
        if d>0.3 and s>=2: rates.append(s/d)
    # elongation: seconds per syllable per word
    el=[(w['dur']/w['syl'],w) for w in ws if w['dur']>0]
    el.sort(key=lambda x:-x[0])
    top=[{'word':w['text'],'secPerSyl':round(v,2),'dur':round(w['dur'],2),'syl':w['syl'],'at':round(w['start'],2),'func':w['func']} for v,w in el[:25]]
    long_words=[w for w in ws if w['dur']>=0.9]
    long_func=sum(1 for w in long_words if w['func'])
    # gaps between phrases
    gaps=[]
    for a,b in zip(phrases,phrases[1:]):
        g=b['start']-a['end']
        if g>0: gaps.append(g)
    gaps_ge04=[g for g in gaps if g>=0.4]; gaps_ge1=[g for g in gaps if g>=1.0]
    # repetition: repeated word bigrams (content)
    toks=[(clean_uk(w['text']) if lang=='uk' else re.sub(r"[^a-z']","",w['text'].lower())) for w in ws]
    from collections import Counter
    bi=Counter(zip(toks,toks[1:])); tri=Counter(zip(toks,toks[1:],toks[2:]))
    rep_bi=[(' '.join(k),v) for k,v in bi.most_common(12) if v>=2]
    rep_tri=[(' '.join(k),v) for k,v in tri.most_common(8) if v>=2]
    return {'id':name,'lang':lang,'duration':duration,'words':len(ws),'syllables':nsyl,'phrases':len(phrases),
        'wordsPerMinute':round(len(ws)/duration*60,1),'syllablesPerSecondOverall':round(nsyl/duration,3),'syllablesPerSungSecond':round(nsyl/sung_time,3),'sungTimeShare':round(sung_time/duration,3),
        'meanSyllablesPerWord':round(nsyl/len(ws),3),'monosyllableShare':round(mono,3),'poly3Share':round(poly3,3),'poly4Share':round(poly4,3),'functionWordShare':round(func,3),
        'meanWordsPerPhrase':round(len(ws)/len(phrases),2),'medianPhraseSeconds':round(st.median([p['end']-p['start'] for p in phrases]),2),
        'phraseFinalEnding':fin,'phraseSylRateMean':round(st.mean(rates),2),'phraseSylRateSd':round(st.pstdev(rates),2),'phraseSylRateCV':round(st.pstdev(rates)/st.mean(rates),3),'phraseSylRateP10P90':[round(x,2) for x in (sorted(rates)[int(.1*len(rates))],sorted(rates)[int(.9*len(rates))])],
        'wordsHeld≥0.9s':len(long_words),'heldWordsFunctionShare':round(long_func/max(1,len(long_words)),3),'heldWords':[{'w':w['text'],'dur':round(w['dur'],2),'at':round(w['start'],1)} for w in long_words],
        'topElongated':top,'phraseGaps≥0.4':len(gaps_ge04),'phraseGaps≥1.0':len(gaps_ge1),'phraseGapTotalSeconds':round(sum(gaps),1),'gapList':[round(g,2) for g in sorted(gaps,reverse=True)[:15]],
        'repeatedBigrams':rep_bi,'repeatedTrigrams':rep_tri,
        'questions':sum(1 for p in phrases if '?' in p['text']),'quotes':sum(1 for p in phrases if ('«' in p['text'] or '"' in p['text'] or 'said' in p['text'].lower() or 'Said' in p['text'])),
        'negations':sum(1 for p in phrases if re.search(r"\b(не|ні|not|no|wasn't|isn't|didn't|hadn't|wouldn't|never)\b",p['text'].lower())),
        'phrasesList':[{'t':round(p['start'],2),'e':round(p['end'],2),'syl':sum(syl(w) for w in p['words']),'text':p['text']} for p in phrases]}
res=[stats('targ','en',en_words,en_ph,194.3756),stats('baseline','uk',base_words,base_ph,197.726),stats('new','uk',new_words,new_ph,203.241)]
json.dump(res,open(OUT/'text-level.json','w'),ensure_ascii=False,indent=1)
keys=['duration','words','syllables','phrases','wordsPerMinute','syllablesPerSecondOverall','syllablesPerSungSecond','sungTimeShare','meanSyllablesPerWord','monosyllableShare','poly3Share','poly4Share','functionWordShare','meanWordsPerPhrase','medianPhraseSeconds','phraseSylRateMean','phraseSylRateSd','phraseSylRateCV','phraseSylRateP10P90','wordsHeld≥0.9s','heldWordsFunctionShare','phraseGaps≥0.4','phraseGaps≥1.0','phraseGapTotalSeconds','questions','quotes','negations','phraseFinalEnding']
print(f"{'metric':32s} {'targ':>22s} {'baseline':>22s} {'new':>22s}")
for k in keys: print(f"{k:32s} "+' '.join(f"{str(r[k]):>22s}" for r in res))
for r in res:
    print('\n==',r['id'],'held words ≥0.9s:',[(h['w'],h['dur']) for h in r['heldWords']][:30])
    print('   top elongated:',[(t['word'],t['secPerSyl']) for t in r['topElongated'][:12]])
    print('   gaps:',r['gapList'][:12]); print('   rep bigrams:',r['repeatedBigrams'][:8]); print('   rep trigrams:',r['repeatedTrigrams'][:6])
