"""Derive review cases, field-level missingness and traceable temporal warnings."""
from analyze import *

def main():
    es=lines(ROOT/'analysis/events.jsonl');ocs=read(ROOT/'analysis/entry-occurrences.json');cp=read(ROOT/'analysis/temporal-checkpoints.json')
    runs=defaultdict(dict)
    for e in es:runs[e['app']+'-'+e['rep']][e['transition_id']]=e
    cases=[]
    def add(cid,run,fn,focus,title,reason,kind):
        assert focus in runs[run]
        e=runs[run][focus]
        eligible=[o for o in ocs if o['run']==run and o['function']==fn and focus in o['segments']['B']['ids']]
        fixed=[o for o in ocs if o['run']==run and o['function']==fn and focus in o['segments']['A20']['ids']]
        oo=max(eligible or fixed,key=lambda o:o['order']) if (eligible or fixed) else None
        cases.append(dict(id=cid,run=run,function=fn,focus_event=focus,entry=oo['entry'] if oo else None,
            title=title,reason=reason,kind=kind,inventory_level=e['inventory_level'],
            linkage='entry-associated observed B segment' if eligible else 'fixed-window association; inspect boundary contamination' if fixed else 'page-context supplemental observation; not a certified entry occurrence',
            human_confirmed=False,reviewer='agent offline evidence review; not human gold'))
    add('odk-r1-late-checkbox','odkcollect-Rep1','odkcollect.download','g16e256:1979',
        'ODK Rep1：State 稳定后，Checkbox 仍首次执行并产生新转换',
        'Download g16e255:1978 后一步；80%时已有未访问ModelAction提示。同Activity控件做过不等于此State上下文结果已覆盖。下载页完整GUI树缺失，剩余Concrete为NA。','B_model_warning')
    # Stable late entry, not a fabricated complete inventory.
    for run,fn,cid,title in [
        ('odkcollect-Rep2','odkcollect.download','odk-r2-repeat','ODK Rep2：晚期重复路径，库存仍缺失'),
        ('scarletnotes-Rep2','scarletnotes.create-note-empty-state','scarlet-r2-repeat','Scarlet Rep2：空态新建高重复，不能证明机会耗尽')]:
        oo=[o for o in ocs if o['run']==run and o['function']==fn]
        o=oo[math.floor(.85*len(oo))]
        add(cid,run,fn,o['entry'],title,'查看后续真实轨迹和80%留出；零新增是观察事实，可执行未尝试动作仍为NA。','A_like_remaining_UNKNOWN')
    for run,fn,focus,cid,title in [
        ('activitydiary-Rep1','activitydiary.edit-existing-activity','g28e190:232','ad-r1-edit-save','ActivityDiary Rep1：编辑既有对象后的 Save'),
        ('activitydiary-Rep1','activitydiary.add-activity','g28e234:276','ad-r1-add-save','ActivityDiary Rep1：同State同Save，创建结果不同'),
        ('activitydiary-Rep2','activitydiary.add-activity','g27e277:2208','ad-r2-save-stays','ActivityDiary Rep2：Save 自环，不自动等于退出'),
        ('wikipedia-Rep2','wikipedia.global-search','g90e1616:1973','wiki-r2-query','Wikipedia Rep2：查询字段依赖输入参数，晚期仍有新State')]:
        add(cid,run,fn,focus,title,'操作身份、输入值、当前模式与结果必须分别保存；相同控件不代表所有结果已探索。','C_new_result_or_mode')
    # Select actual later discoveries and a same-GUI retained-tree view for each App/Rep.
    for run,fn in [('omninotes-Rep1','omninotes.settings'),('omninotes-Rep2','omninotes.search-notes'),
                   ('scarletnotes-Rep1','scarletnotes.create-note-empty-state'),('wikipedia-Rep1','wikipedia.global-search'),
                   ('activitydiary-Rep2','activitydiary.edit-existing-activity')]:
        c=next(x for x in cp if x['run']==run and x['function']==fn and x['fraction']==.8)
        tids=c['future_validation']['discovery_events']
        options=[runs[run][t] for t in tids]
        if not options:
            options=[runs[run][t] for o in ocs if o['run']==run and o['function']==fn for t in o['segments']['B']['ids']]
        e=max(options,key=lambda e:(bool(e['retained_source_gui_paths']),e['new_state'],e['first_ID_only_model_transition']))
        add(run+'-late',run,fn,e['transition_id'],run+'：低State增量与后续操作/结果的对照',
            '从80%以后真实发现中选取；MainActivity复用的范围仅是候选发生关联，不能把全部后续变化归因于入口功能。','C_or_boundary_uncertain')
    for app,fn in [('omninotes','omninotes.set-reminder'),('omninotes','omninotes.attachment'),('wikipedia','wikipedia.article-gallery')]:
        for rep in ['Rep1','Rep2']:
            run=app+'-'+rep;oo=[o for o in ocs if o['run']==run and o['function']==fn]
            ev=[runs[run][t] for o in oo for t in o['segments']['B']['ids']]
            e=max(ev,key=lambda e:(bool(e['retained_source_gui_paths']),e['new_state'],e['order']))
            add(run+'-'+fn.split('.')[-1],run,fn,e['transition_id'],run+'：'+fn.split('.')[-1]+' 的动作证据缺口',
                '同GUI树若存在可复核节点属性，但未记录完整Naming→ModelAction→逐成员有效性→策略资格；没有树时只展示选中动作和代表截图。','inventory_partial')
    for rep in ['Rep1','Rep2']:
        run='scarletnotes-'+rep
        ee=[e for e in runs[run].values() if short(e['source_activity'])=='CreateListNoteActivity' and e['retained_source_gui_paths']]
        e=max(ee,key=lambda e:(e['action_type']=='MODEL_CLICK' and e['class'].endswith('EditText'),e['order']))
        add(run+'-list-context',run,'scarletnotes.create-list-note',e['transition_id'],run+'：CreateList 局部编辑，入口分组仍待确认',
            '这是已确认执行的列表编辑页面上下文，不补造Ⅰ类入口。动态块、字段和输入值不能按同一ModelAction抹平。','pending_entry_context')
    # Partial retained-tree inventory case: real properties are visible; membership still UNKNOWN.
    for rep in ['Rep1','Rep2']:
        run='odkcollect-'+rep
        ee=[e for e in runs[run].values() if e['retained_source_gui_paths']]
        e=max(ee,key=lambda e:(e['class'].endswith('CheckBox'),e['order']))
        add(run+'-settings-context',run,'odkcollect.settings',e['transition_id'],run+'：设置行不能按 Checkbox/ModelAction 合并',
            '同类型、同资源或同ModelAction不能证明控制的是同一个配置项；保留祖先路径、行上下文、checked与真实结果。','configuration_counterexample')
    for tid,cid,row in [('g16e203:355','odk-r2-checkbox-high-res','High res'),('g16e203:364','odk-r2-checkbox-delete','Delete a')]:
        add(cid,'odkcollect-Rep2','odkcollect.settings',tid,'ODK Rep2：同ModelAction的不同配置行 '+row,
            'g16s67/g16a349/android:id/checkbox相同，祖先行index分别10与7，记录的兄弟title不同。两次均有同GUI树；同ModelAction不能替代逐Concrete操作覆盖。','configuration_counterexample')
    write('analysis/cases.json',cases)
    warnings=[]
    for c in cp:
        for h in c['future_validation']['later_executed_historical_ModelAction_opportunities']:
            e=runs[c['run']][h['first_future_execution']]
            warnings.append(dict(checkpoint=c['id'],run=c['run'],function=c['function'],fraction=c['fraction'],cutoff_ns=c['cutoff_ns'],
                **h,source_state=e['state'],input_version=e['input_version'],action_type=e['action_type'],
                enabled_valid_at_old_snapshot_for_this_ID='UNKNOWN',past_Concrete_member_identity='UNKNOWN',
                validation='later execution proves later opportunity, not continuous prior availability'))
    safe_table('historical-opportunity-warnings',warnings)
    # Whole-run state evidence and function ledger are deliberately separate denominators.
    layers=[]
    for q in read(ROOT/'analysis/archive-inventory-coverage.json'):
        for stage,unit,exact,partial,missing in [
          ('recorded_ModelAction_counts','decision',q['decision_points'],0,0),
          ('all_ModelAction_IDs_union_of_unvisited_and_selected','decision',q['complete_ModelAction_ID_inventory_EXACT'],0,q['decision_points']-q['complete_ModelAction_ID_inventory_EXACT']),
          ('all_resolved_members_with_ModelAction_mapping','decision',0,0,q['decision_points']),
          ('all_members_Native_validity','decision',0,0,q['decision_points']),
          ('all_strategy_eligible_Concrete','decision',0,0,q['decision_points']),
          ('selected_control_confirmed_input','confirmed_source',q['targeted_confirmed_inputs'],0,q['confirmed_source_visits']-q['targeted_confirmed_inputs']),
          ('retained_same_GUI_node_tree','confirmed_source',q['same_GUI_source_visits'],0,q['confirmed_source_visits']-q['same_GUI_source_visits'])]:
            layers.append(dict(run=q['run'],field=stage,denominator_unit=unit,denominator=q['decision_points'] if unit=='decision' else q['confirmed_source_visits'],
                EXACT=exact,PARTIAL=partial,MISSING=missing,scope='field only; does not upgrade overall executable inventory'))
    safe_table('inventory-field-coverage',layers)
    signal=[]
    for run in runs:
        xx=[c for c in cp if c['run']==run and c['recent_completed_count']==5]
        for sig in ['zero_recent_State','zero_recent_State_Transition_Concrete']:
            yy=[c for c in xx if c['recent_metrics']['native_new_state']==0 and (sig=='zero_recent_State' or not(c['recent_metrics']['first_ID_only_model_transition'] or c['recent_metrics']['first_local_concrete']))]
            signal.append(dict(run=run,signal=sig,checkpoints=len(yy),future_State=sum(c['future_validation']['native_new_state']>0 for c in yy),
                future_Transition=sum(c['future_validation']['new_ID_only_model_transition_key']>0 for c in yy),
                future_Concrete=sum(c['future_validation']['new_concrete_key']>0 for c in yy),
                note='overlapping exploratory checkpoints, retrospective cohort; no accuracy or causal claims'))
    safe_table('temporal-signal-diagnostics',signal)
    # Source excerpts are immutable observations, not modified source code.
    source_ranges={
     'MonkeySourceApe.java':[(338,375),(425,475),(480,573)],
     'MonkeySourceApeInput.java':[(26,57),(80,130)],
     'ape/tree/GUITreeBuilder.java':[(129,145),(199,240),(263,285),(309,351),(480,516),(662,695)],
     'ape/tree/GUITree.java':[(180,216)],'ape/tree/GUITreeNode.java':[(251,281),(499,507)],
     'ape/naming/Naming.java':[(212,242)],'ape/naming/ActionPatchNamer.java':[(50,78)],
     'ape/model/State.java':[(50,90),(210,218),(303,360)],
     'ape/model/ModelAction.java':[(35,45),(90,98),(186,244)],
     'ape/model/Graph.java':[(145,194),(508,542)],'ape/model/GraphElement.java':[(25,88)],
     'ape/model/StateActionDiffer.java':[(43,110)],'ape/model/Action.java':[(59,86)],
     'ape/ActionFilter.java':[(20,130)],'ape/agent/AgentActionScorer.java':[(48,94)],
     'ape/agent/ApeAgent.java':[(175,185),(278,292)],
     'ape/agent/StatefulAgent.java':[(537,574),(811,861)],
     'ape/agent/SataAgent.java':[(304,319),(387,505),(599,621),(982,1005)],
     'ape/jev/JevRuntime.java':[(764,793),(848,874),(1022,1067)],
     'ape/jev/JevEvidenceAudit.java':[(90,125),(203,243)],
     'ape/jev/JevRegistry.java':[(295,389),(802,842)],
     'ape/jev/JevIdentity.java':[(50,76)],'ape/jev/ConcreteActionIdentity.java':[(20,100)]}
    parts=[];index=[]
    for rel,rs in source_ranges.items():
        p=REPO/'native/src/com/android/commands/monkey'/rel;b=p.read_bytes();h=hashlib.sha256(b).hexdigest();ll=b.decode().splitlines()
        index.append(dict(path=str(p.relative_to(REPO)),sha256=h,ranges=rs))
        for a,z in rs:parts.append('\n'+str(p.relative_to(REPO))+f':{a}-{z} SHA256 {h}\n'+'\n'.join(f'{n}: {ll[n-1]}' for n in range(a,min(z,len(ll))+1)))
    (ROOT/'evidence/source-excerpts.txt').write_text('\n'.join(parts))
    write('evidence/source-index.json',index)
    write('evidence/enrichment-read-source-sha256.json',READ_FILES)
    print('cases',len(cases),'opportunity warnings',len(warnings),'field audit',len(layers))

if __name__=='__main__':main()
