"""Session 52: structural position-state and singleton capacity audit.
No key fitting, language scoring, or new translation.
"""
from pathlib import Path
from collections import Counter,defaultdict
import hashlib,json
import numpy as np
from lp_core import R,lp
from lexical_words import tokenize_words
ROOT=Path(__file__).resolve().parent
SOURCE='%'.join(lp[:56]);WORDS=tokenize_words(SOURCE)
RESET={'global':'','section':'$','page':'%','paragraph':'&§$','stop':'.&§$','line':'/%$'}

def counters(source):
    clocks={k:0 for k in RESET};rows=[]
    for c in source:
        if c in R:
            rows.append(dict(clocks))
            for k in clocks:clocks[k]+=1
        else:
            for k,chars in RESET.items():
                if c in chars:clocks[k]=0
    return rows
ROWS=counters(SOURCE)
for w in WORDS:
    for j in range(len(w['values'])):ROWS[w['rune_offset']+j]['word']=j
SINGLE=[w for w in WORDS if len(w['values'])==1]
ANCHORS=[]
for a,b in zip(WORDS,WORDS[1:]):
    if a['values']==[23,11,1] and b['values']==[17,18,10] and a['paragraph']==b['paragraph'] and a['section']==b['section']:
        ANCHORS.append(a['rune_offset'])
assert ANCHORS==[6555,12950]

def state(i,family,period):
    n=ROWS[i][family]
    return n if period is None else n%period

def capacity(items,family,period,k,split=False):
    groups=defaultdict(Counter)
    for w in items:
        key=state(w['rune_offset'],family,period)
        if split:
            # Only sections 6 and 8 share a map; all other sections may differ.
            key=(('anchor' if w['section'] in (6,8) else w['section']),key)
        groups[key][w['values'][0]]+=1
    required=sum(sum(c.values())-sum(n for _,n in c.most_common(k)) for c in groups.values())
    return {'minimum_exception_tokens':required,'state_classes':len(groups),'max_distinct_cipher_runes_in_class':max(map(len,groups.values())),'violating_classes':sum(len(c)>k for c in groups.values())}

def main():
    results=[]
    for family in [*RESET,'word']:
        for period in [None,*range(1,65)]:
            first=[state(ANCHORS[0]+j,family,period) for j in range(6)]
            second=[state(ANCHORS[1]+j,family,period) for j in range(6)]
            row={'family':family,'period':period,'anchor_states':[first,second],'all_six_states_match':first==second}
            if first==second:
                for name,items,k,split in [('AI_all',SINGLE,2,False),('AIO_all',SINGLE,3,False),('AI_remove_red',[w for w in SINGLE if (w['page'],w['page_rune_offset']) not in [(6,75),(54,0)]],2,False),('AI_share_only_anchor_sections',SINGLE,2,True),('AI_anchor_sections_only',[w for w in SINGLE if w['section'] in (6,8)],2,False),('AIO_anchor_sections_only',[w for w in SINGLE if w['section'] in (6,8)],3,False)]:
                    row[name]=capacity(items,family,period,k,split)
            results.append(row)
    matching=[r for r in results if r['all_six_states_match']]
    # Capacity theorem control: arbitrary per-state permutations of A/I preserve
    # at most two ciphertext types; injecting a third requires an exception.
    toy=[0,0,1,1,1]
    assert sum(Counter(toy).values())-sum(n for _,n in Counter(toy).most_common(2))==0
    toy.append(2)
    assert sum(Counter(toy).values())-sum(n for _,n in Counter(toy).most_common(2))==1
    rng=np.random.default_rng(52001)
    permutations=[rng.permutation(29) for _ in range(42)]
    planted=[];recovered=[];plain=[]
    for w in SINGLE:
        phase=state(w['rune_offset'],'page',42);p=int(rng.choice([24,10]));c=int(permutations[phase][p])
        planted.append({**w,'values':[c]});plain.append(p)
        recovered.append(int(np.flatnonzero(permutations[phase]==c)[0]))
    assert plain==recovered
    assert capacity(planted,'page',42,2)['minimum_exception_tokens']==0
    out={'source_sha256':hashlib.sha256((ROOT/'liber-primus__transcription--master.txt').read_bytes()).hexdigest(),'constructed_control':{'singletons':len(planted),'arbitrary_per_phase_permutations':42,'inverse_recovery_exact':plain==recovered,'minimum_exception_tokens':capacity(planted,'page',42,2)['minimum_exception_tokens']},'singleton_count':len(SINGLE),'red_excluded_count':len([w for w in SINGLE if (w['page'],w['page_rune_offset']) not in [(6,75),(54,0)]]),'anchor_global_offsets':ANCHORS,'raw_anchor_counters':[[ROWS[a+j] for j in range(6)] for a in ANCHORS],'settings':len(results),'matching_settings':len(matching),'matching_and_singleton_compatible':{name:sum(r[name]['minimum_exception_tokens']==0 for r in matching) for name in ['AI_all','AIO_all','AI_remove_red','AI_share_only_anchor_sections','AI_anchor_sections_only','AIO_anchor_sections_only']},'results':results}
    pair_items=[w for w in SINGLE if w['section'] in (6,8)]
    by_phase=defaultdict(list)
    for w in pair_items:by_phase[state(w['rune_offset'],'page',42)].append(w)
    witnesses=[]
    for phase,items in sorted(by_phase.items()):
        shifts={}
        for allowed_name,allowed in [('AI',[24,10]),('AIO',[24,10,3])]:
            for sign in [1,-1]:
                possible=set(range(29))
                for w in items:possible &= {(sign*w['values'][0]-p)%29 for p in allowed}
                shifts[f'{allowed_name}_sign_{sign}']=sorted(possible)
        witnesses.append({'phase':phase,'occurrences':[{'page':w['page'],'page_rune_offset':w['page_rune_offset'],'cipher_value':w['values'][0]} for w in items],'possible_additive_shifts':shifts})
    out['anchor_only_page42']={'singletons':len(pair_items),'occupied_phase_classes':len(by_phase),'repeated_phase_classes':sum(len(x)>1 for x in by_phase.values()),'witnesses':witnesses,'failed_AI_phase_classes':sum(not w['possible_additive_shifts']['AI_sign_1'] for w in witnesses)}
    (ROOT/'reset_state_results.json').write_text(json.dumps(out,indent=2)+'\n')
    print({k:v for k,v in out.items() if k not in ['results','raw_anchor_counters','anchor_only_page42']})

if __name__=='__main__':main()
