"""Session 30. Fixed phase-enrichment scan of repeat-event positions.
Periods 3..32, section/page counters, every phase: 1,050 comparisons.
Conditional event-label randomization, preserving eight section repeat counts.
Python + NumPy; no network, decryption or parameter tuning.
"""
import hashlib,json
from lp_core import ROOT,R,LET,lp,vals,np

SEED=3301030
B=9999
PERIODS=range(3,33)
POLICIES=['section','page']

def sample_positions(rng,n,k,rows):
    a=rng.integers(n,size=(rows,k))
    if k>1:
        duplicate=np.any(np.diff(np.sort(a,axis=1),axis=1)==0,axis=1)
        while duplicate.any():
            a[duplicate]=rng.integers(n,size=(int(duplicate.sum()),k))
            duplicate=np.any(np.diff(np.sort(a,axis=1),axis=1)==0,axis=1)
    return a

def histogram(a,p):
    if a.ndim==1:return np.bincount(a%p,minlength=p)
    row=np.arange(a.shape[0])[:,None]*p
    return np.bincount((a%p+row).ravel(),minlength=a.shape[0]*p).reshape(a.shape[0],p)

def main():
    loc=np.array([(page,offset) for page,s in enumerate(lp[:56]) for offset in range(len(vals(s)))])
    data=[];cursor=0;events=[]
    for section,s in enumerate('%'.join(lp[:56]).split('$')):
        c=vals(s);n=len(c)
        if n>100:
            found=np.flatnonzero(c[:-1]==c[1:]);k=len(found)
            counters={'section':np.arange(n-1),'page':loc[cursor:cursor+n-1,1]}
            data.append(dict(section=section,n=n-1,k=k,found=found,counters=counters))
            for i in found:
                events.append(dict(section=section,left_section_offset=int(i),
                    left_lp2_page=int(loc[cursor+i,0]),left_page_offset=int(loc[cursor+i,1]),
                    label=LET[c[i]]))
        cursor+=n
    assert [d['section'] for d in data]==[0,1,2,4,5,6,7,8]
    assert sum(d['n'] for d in data)==12939 and sum(d['k'] for d in data)==86
    old=json.loads((ROOT/'double_context_results.json').read_text())['event_inventory']
    assert [{k:e[k] for k in events[0]} for e in old]==events
    rng=np.random.default_rng(SEED)
    picks=[sample_positions(rng,d['n'],d['k'],B) for d in data]
    # All comparisons reuse the same random event configurations.
    specifications=[];means=[];variances=[];observed=[];random_counts=[];opportunities=[]
    for policy in POLICIES:
        for p in PERIODS:
            mu=np.zeros(p);var=np.zeros(p);obs=np.zeros(p,dtype=int)
            available=np.zeros(p,dtype=int);sim=np.zeros((B,p),dtype=np.int16)
            for d,ix in zip(data,picks):
                counter=d['counters'][policy];a=histogram(counter,p);prob=a/d['n']
                available+=a;mu+=d['k']*prob
                var+=d['k']*prob*(1-prob)*(d['n']-d['k'])/(d['n']-1)
                obs+=histogram(counter[d['found']],p)
                sim+=histogram(counter[ix],p).astype(np.int16)
            specifications.extend((policy,p,phase) for phase in range(p))
            means.extend(mu);variances.extend(var);observed.extend(obs);opportunities.extend(available)
            random_counts.append(sim)
    mu=np.array(means);sd=np.sqrt(variances);obs=np.array(observed)
    sim=np.concatenate(random_counts,axis=1);z=(obs-mu)/sd
    assert len(specifications)==1050 and np.all(sd>0)
    maxnull=np.max((sim-mu)/sd,axis=1);maxobs=float(z.max())
    rows=[]
    for j,(policy,p,phase) in enumerate(specifications):
        rows.append(dict(counter=policy,period=p,phase=phase,events=int(obs[j]),
            eligible_edges=int(opportunities[j]),conditional_expected=float(mu[j]),
            conditional_sd=float(sd[j]),standardized_excess=float(z[j]),
            raw_upper_tail=(1+int(np.count_nonzero(sim[:,j]>=obs[j])))/(B+1),
            family_upper_tail=(1+int(np.count_nonzero(maxnull>=z[j]-1e-12)))/(B+1)))
    ranked=sorted(rows,key=lambda x:x['standardized_excess'],reverse=True)
    controls=[]
    for ci,(chosen_policy,chosen_p,chosen_phase) in enumerate([('section',7,2),('page',11,3)]):
        control_rng=np.random.default_rng(SEED+10+ci)
        chosen=[]
        for d in data:
            eligible=np.flatnonzero(d['counters'][chosen_policy]%chosen_p==chosen_phase)
            assert len(eligible)>=d['k']
            chosen.append(control_rng.choice(eligible,size=d['k'],replace=False))
        counts=[]
        for policy in POLICIES:
            for p in PERIODS:
                counts.extend(sum(histogram(d['counters'][policy][ix],p) for d,ix in zip(data,chosen)))
        cz=(np.array(counts)-mu)/sd;j=specifications.index((chosen_policy,chosen_p,chosen_phase))
        adjusted=(1+int(np.count_nonzero(maxnull>=cz[j]-1e-12)))/(B+1)
        assert counts[j]==86 and adjusted<=.001
        controls.append(dict(injected_counter=chosen_policy,injected_period=chosen_p,injected_phase=chosen_phase,
            events_at_injected_phase=int(counts[j]),standardized_excess=float(cz[j]),family_upper_tail=adjusted,
            caveat='All 86 artificial events placed in one phase, preserving section counts; a deliberately strong label-level control, not encrypted text or a measured detection threshold.'))
    out=dict(seed=SEED,simulations=B,periods=list(PERIODS),counters=POLICIES,
        comparisons=len(rows),events=86,eligible_edges=12939,
        counter_definition='Zero-based index of the LEFT rune of an adjacent pair. Section counter resets at marked section; page counter uses original page offset even when section starts mid-page. Non-runes do not advance counters. Cross-section edges excluded; within-section cross-page edges included.',
        null='Conditional on each section repeat count, choose that many distinct eligible edges uniformly without replacement, independently between sections. Reuse each simulated configuration for all 1,050 phase tests. These are event labels, not shuffled runes.',
        statistic='One-sided positive phase enrichment standardized by exact sum-of-hypergeometric means and variances. Global maximum and max-statistic family-adjusted tail comparisons.',
        global_max_standardized=maxobs,
        null_max_95_percent=float(np.quantile(maxnull,.95)),
        global_at_or_above=int(np.count_nonzero(maxnull>=maxobs-1e-12)),
        global_family_upper_tail=(1+int(np.count_nonzero(maxnull>=maxobs-1e-12)))/(B+1),
        top_ten=ranked[:10],measurements=rows,
        sections=[{k:int(d[k]) for k in ['section','n','k']} for d in data],
        event_inventory=events,constructed_controls=controls,
        event_inventory_crosscheck=True,
        transcription_sha256=hashlib.sha256((ROOT/'liber-primus__transcription--master.txt').read_bytes()).hexdigest(),
        limits=['Retrospective: same corpus and event inventory as earlier sessions; no untouched validation data.',
            'Null assumes event positions exchangeable within each section, not rune-specific counts, local dependencies or uniform cipher generation.',
            'This family correction covers only the declared 1,050 comparisons, not every historical hypothesis considered.',
            'Tests a shared phase across sections or pages under the specified counter; not independently chosen phase offsets for every section.',
            'No periods outside 3..32, non-rune advances, selective skips, word/line counters, phase combinations or standalone section searches.',
            'No inference of plaintext, keys, intent or an actual cipher period. Strong artificial controls do not establish power for weak effects.'])
    (ROOT/'repeat_period_results.json').write_text(json.dumps(out,indent=2)+'\n')
    print(json.dumps({k:out[k] for k in ['global_max_standardized','null_max_95_percent','global_family_upper_tail','global_at_or_above','top_ten','constructed_controls']},indent=2))
if __name__=='__main__':main()
