"""Bounded marker/reset search; rank on prefix, report unselected suffix."""
import json
from lp_core import *

reset_modes={'continuous':'','line':'/','page':'%','paragraph':'&$',
             'clause':'.;&$','word':'-.,;&$'}
offs=np.arange(65)
def sweep(text,label):
    records=[];count=0
    for scope,source in [('all_text',text),('first_clause_per_paragraph_removed',without_first_clauses(text))]:
        c=vals(source)
        if len(c)<60:continue
        cut=int(len(c)*0.6)
        for resetname,reset in reset_modes.items():
            for advance in ('','-.,;'):
                for skipf in (False,True):
                    ix,_=positions(source,reset,advance,skipf)
                    for seqname,seq in SEQS.items():
                        keys=seq[offs[:,None]+ix[None,:]]
                        for a in (1,-1):
                            for sign in (-1,1):
                                d=(a*c[None,:]+sign*keys)%29
                                if skipf:d[:,c==0]=0
                                prefix=scores(d[:,:cut]);j=int(np.argmax(prefix))
                                records.append({'target':label,'scope':scope,'reset':resetname,'delimiter_advance':bool(advance),'skip_all_cipher_F':skipf,'sequence':seqname,'a':a,'sign':sign,'offset':int(offs[j]),'prefix_score':float(prefix[j]),'suffix_score':float(scores(d[j:j+1,cut:])[0]),'rune_count':len(c),'split_after_runes':cut,'text':render(source,d[j])})
                                count+=len(offs)
    records.sort(key=lambda x:x['prefix_score'],reverse=True)
    return records[:10],count

def controls():
    # Independent round trip, with plaintext held out from the scoring model.
    source=lp[57];plain=vals(source)
    _,flags=positions(source,reset='.')
    ix,_=positions(source,reset='.')
    cipher=(plain+SEQS['prime_minus_one'][ix+7])%29
    it=iter(cipher);s=''.join(R[int(next(it))] if c in R else c for c in source)
    decoded=(vals(s)-SEQS['prime_minus_one'][positions(s,reset='.')[0]+7])%29
    assert np.array_equal(decoded,plain)
    top,n=sweep(s,'synthetic_clause_reset_control')
    assert any(x['text']==render(source,plain) for x in top),top[0]
    return {'synthetic_test':'LP2 page 57 plaintext, encrypted with prime-minus-one offset 7 and clause resets','top':top[:2],'candidate_count':n,'exact_recovery_in_top10':True}

if __name__=='__main__':
    control=controls();print('Boundary control recovered',flush=True)
    sections=[s for s in '%'.join(lp[:56]).split('$') if len(vals(s))]
    out={'controls':control,'sections':[],'candidate_count':0,'limits':['Markers are community transcription conventions; only selected images were visually checked.','Nine-rune section is excluded from language ranking.','Offsets 0..64; specified resets and counters only.','Within each target, choices are ranked on first 60 percent of runes; suffix scores are reported without selecting on them.','Small LP1 model; negative findings are not exhaustive.']}
    for i,s in enumerate(sections):
        if len(vals(s))<60:
            out['sections'].append({'section_index':i,'runes':len(vals(s)),'status':'too_short_for_ranking'});continue
        top,n=sweep(s,f'marked_section_{i}')
        out['candidate_count']+=n
        out['sections'].append({'section_index':i,'runes':len(vals(s)),'top_prefix_candidates':top})
        print('Section',i,'runes',len(vals(s)),'prefix',top[0]['prefix_score'],'suffix',top[0]['suffix_score'],flush=True)
    (ROOT/'boundary_results.json').write_text(json.dumps(out,ensure_ascii=False,indent=2))
    print('Boundary search settings',out['candidate_count'],flush=True)
