"""Read-only AST + registry audit. Does not import or execute the upstream scaffold."""
import ast,json,math
from collections import Counter
from pathlib import Path
from plot_utils import svg,text,line,rect,circle
BASE=Path(__file__).resolve().parent

def analyze():
    reg=json.loads((BASE/'registry.json').read_text());tree=json.loads((BASE/'source_tree.json').read_text());paths={x['path'] for x in tree['tree'] if x['type']=='blob'}
    source=ast.parse((BASE/'scaffold_source.py').read_text());constants={}
    for node in source.body:
        if isinstance(node,ast.Assign):
            for t in node.targets:
                if isinstance(t,ast.Name) and t.id in ['MOCK_MODEL_P_CORRECT','MOCK_MONITOR_SCOPE_QUALITY']:constants[t.id]=ast.literal_eval(node.value)
    assert len(constants)==2
    classes={n.name for n in source.body if isinstance(n,ast.ClassDef)};assert {'MockModel','MockGraders'}<=classes
    def resolve(ref):
        n=reg
        for part in ref.split('.'):n=n[part]
        assert isinstance(n,dict);return n
    rows=[];checked_refs=[];prompt_refs=[]
    for path,cfg in reg['datasets'].items():
        for key in ['y_rule','z_rule']:
            ref=cfg[key]
            if isinstance(ref,str) and ref.startswith('shared_rules.'):
                resolve(ref);checked_refs.append(ref)
        for key in ['model_prompts','monitor_prompts','answer_evaluation_prompts']:
            for ref in cfg.get(key,[]):
                if ref.startswith(('model_prompts/','monitor_prompts/')):
                    assert ref in paths,ref;prompt_refs.append(ref)
        if not path.endswith('.jsonl'):status='description only'
        elif cfg.get('scaffold_supported',True) is False:status='explicitly skipped'
        else:status='metadata-eligible for mock scaffold'
        rows.append({'dataset':path,'archetype':cfg['archetype'],'status':status,'deprecated':path=='intervention/memory_advice.jsonl','skip_reason':cfg.get('scaffold_skip_reason'),'y_rule':cfg['y_rule'],'z_rule':cfg['z_rule']})
    counts=Counter(r['status'] for r in rows);assert len(rows)==14 and counts=={'metadata-eligible for mock scaffold':8,'explicitly skipped':4,'description only':2}
    assert Counter(r['archetype'] for r in rows)=={'intervention':9,'process':2,'outcome':3}
    # Literal configuration consequences, not a benchmark estimate.
    scope=[{'scope':s,'mock_tpr':v['tpr'],'mock_tnr':v['tnr'],'configured_product':v['tpr']*v['tnr'],'configured_balanced_accuracy':(v['tpr']+v['tnr'])/2} for s,v in constants['MOCK_MONITOR_SCOPE_QUALITY'].items()]
    assert all(0<=v['configured_product']<=1 for v in scope)
    result={'entries':rows,'entry_count':len(rows),'status_counts':dict(counts),'archetype_counts':dict(Counter(r['archetype'] for r in rows)),'resolved_rule_reference_occurrences':len(checked_refs),'unique_resolved_rule_references':len(set(checked_refs)),'verified_prompt_reference_occurrences':len(prompt_refs),'unique_verified_prompt_paths':len(set(prompt_refs)),'mock_constants':constants,'scope_configuration_arithmetic':scope,'dataset_archive_inspected':False,'pair_integrity_verified':False,'model_performance_measured':False}
    (BASE/'results.json').write_text(json.dumps(result,indent=2)+'\n');return result

def draw(r):
    body='';colors={'metadata-eligible for mock scaffold':'#3d5744','explicitly skipped':'#b68c65','description only':'#c7c7bb'}
    for i,archetype in enumerate(['intervention','process','outcome']):
        rows=[x for x in r['entries'] if x['archetype']==archetype];y=140+i*100;body+=text(36,y+24,archetype.capitalize());x=220
        for status in colors:
            n=sum(v['status']==status for v in rows)
            if n:body+=rect(x,y,n*56,38,colors[status])+text(x+n*28,y+25,n,anchor='middle',fill='#fff' if status!='description only' else '#292d29');x+=n*56
    for i,(status,color) in enumerate(colors.items()):body+=rect(36,440+i*26,12,12,color)+text(58,451+i*26,status.capitalize())
    (BASE/'release-matrix.svg').write_text(svg('Fourteen registry entries do not mean fourteen runnable evaluations','Registry gates only: archive rows, tool adapters and actual models are separate requirements.',body,555))
    body='';labels={'cot_only':'CoT only','answer_only':'Answer only','all_messages':'All messages'}
    for i,row in enumerate(r['scope_configuration_arithmetic']):
        y=135+i*95;body+=text(36,y+18,labels[row['scope']])
        for off,key,color in [(0,'mock_tpr','#3d5744'),(29,'mock_tnr','#b68c65')]:
            body+=rect(230,y+off,540*row[key],21,color)+text(240+540*row[key],y+off+16,f'{row[key]:.2f}')
    body+=rect(230,455,12,12,'#3d5744')+text(252,467,'Configured TPR')+rect(500,455,12,12,'#b68c65')+text(522,467,'Configured TNR')
    (BASE/'mock-configuration.svg').write_text(svg('The visibility ordering is written into the mock','Literal source-code probabilities. These are not measured monitor capabilities.',body,515))
if __name__=='__main__':
    r=analyze();draw(r);print(json.dumps({k:v for k,v in r.items() if k not in ['entries','mock_constants']},indent=2));print('Assertions passed. Wrote results.json and two SVG figures.')
