"""Exploratory secondary summaries and accounting of every scheduled episode."""
import argparse
from collections import Counter
import json
from pathlib import Path

from analysis import collect,interval
from experiment import FAMILIES
from provider import dump


def tool_census(run):
 """Interpret literal public event types; preserve the frozen adapter's broader flag."""
 flagged=0;tools=0;errors=0;attempts=set()
 for path in Path(run).glob('episodes/*/provider/*/metadata.json'):
  metadata=json.loads(path.read_text(encoding='utf-8'));flagged+=metadata.get('tool_event_count',0)
  if not metadata.get('tool_event_count'):continue
  audit=path.parent/'provider-event-audit.json';raw=path.parent/'stdout.txt'
  if audit.exists():rows=json.loads(audit.read_text(encoding='utf-8'))['derived_event_census']
  elif raw.exists():
   rows=[]
   for line in raw.read_text(encoding='utf-8').splitlines():
    try:event=json.loads(line)
    except ValueError:continue
    if event.get('event')=='step_update':rows.append(event.get('step_update',{}))
  else:raise ValueError('Flagged invocation lacks event evidence: '+metadata['id'])
  for row in rows:
   if row.get('step_type')=='tool':
    tools+=1;attempts.add((metadata['id'],row.get('step_index')))
   if row.get('step_type')=='error_message':errors+=1
 return {'adapter_flagged_event_records':flagged,'verified_native_tool_lifecycle_records':tools,'verified_native_tool_attempts':len(attempts),'flagged_non_tool_error_records':errors,'interpretation':'The frozen adapter uses a conservative unknown-step allowlist. Its tool_event_count is a flag count, not a literal tool-action count. One Flash error_message was flagged in replication; this did not change the already-failed provider outcome. Two Pro send_message attempts each generated ACTIVE and ERROR records.'}


def details(run,out):
 run=Path(run);results,calls=collect(run);families={};issues=[]
 for r in results:
  metrics=r.get('metrics',{})
  if r['status']!='completed' or metrics.get('valid') is False:
   issues.append({'episode':r['job']['id'],'study':r['job']['study'],'family':r['job'].get('family'),'status':r['status'],'error':r.get('error'),'invalid_action':metrics.get('invalid_action'),'invalid_reason':metrics.get('invalid_reason')})
 for family,(h,l) in FAMILIES.items():
  subset=[r for r in results if r['job'].get('family')==family and r['job']['study']=='negotiation'];finished=[r for r in subset if r['status']=='completed']
  index={(r['job']['rep'],r['job']['condition']):r['metrics'] for r in finished};blocks=[]
  for rep in sorted({r['job']['rep'] for r in subset}):
   hl=index.get((rep,'hl'));lh=index.get((rep,'lh'))
   if hl is not None and lh is not None:
    advantage=((hl['seller_surplus_fraction']-hl['buyer_surplus_fraction'])+(lh['buyer_surplus_fraction']-lh['seller_surplus_fraction']))/2
    blocks.append({'rep':rep,'high_minus_low_role_balanced_surplus_fraction':advantage})
  efficiency_blocks=[];false_claims=0
  for rep in sorted({r['job']['rep'] for r in subset}):
   if all((rep,c) in index for c in ['hh','hl','lh','ll']):efficiency_blocks.append(sum(index[(rep,c)]['efficiency'] for c in ['hh','hl','lh','ll'])/4)
  for r in subset:
   transcript=run/'episodes'/r['job']['id']/'transcript.json'
   if transcript.exists():
    for action in json.loads(transcript.read_text(encoding='utf-8')):
     limit=r['job']['cost'] if action['role']=='seller_0' else r['job']['value']
     false_claims+=action['claimed_limit'] is not None and action['claimed_limit']!=limit
  families[family]={'planned_conversations':len(subset),'finished':len(finished),'valid':sum(r['metrics']['valid'] for r in finished),'invalid':sum(not r['metrics']['valid'] for r in finished),'agreements':sum(r['metrics']['deal'] for r in finished),'negative_utility_contracts':sum(r['metrics']['negative_utility'] for r in finished),'false_numeric_claims':false_claims,'efficiency_complete_value_blocks':interval(efficiency_blocks),'role_balanced_high_minus_low':{**interval([b['high_minus_low_role_balanced_surplus_fraction'] for b in blocks]),'blocks':blocks},'interpretation':'Exploratory paired-value-block comparison; H/L are profile labels. Business-invalid/no-deal outcomes have zero utility. Infrastructure-incomplete conversations are not imputed. False claims include all parsed transcript prefixes; efficiency intervals resample complete four-pairing value blocks.'}
 started=list((run/'episodes').glob('*/provider/*/started.json'))
 metadata_ids={c['id'] for c in calls};unknown=[str(p.relative_to(run)) for p in started if json.loads(p.read_text(encoding='utf-8'))['id'] not in metadata_ids]
 full_markets=[r for r in results if r['job']['study']=='market' and r['status']=='completed' and r['metrics'].get('valid') and r['metrics'].get('rounds_completed')==r['job']['rounds']]
 executed=sum(r['job']['study'] in ['calibration','negotiation'] and any(c['episode']==r['job']['id'] for c in calls) or r['job']['study']=='market' and any(c['episode']==r['job']['id'] for c in calls) for r in results)
 data={'scheduled_episodes':len(results),'episodes_with_model_invocation_records':executed,'status_counts':dict(Counter(r['status'] for r in results)),'study_counts':dict(Counter(r['job']['study'] for r in results)),'business_invalid_episodes':sum(r['status']=='completed' and r.get('metrics',{}).get('valid') is False for r in results),'full_valid_markets':len(full_markets),'model_invocation_records':len(calls),'unique_model_invocation_ids':len(metadata_ids),'adapter_status_counts':dict(Counter(c['status'] for c in calls)),'adapter_failure_reasons':dict(Counter(c.get('failure') for c in calls if c['status']!='succeeded')),'reported_tool_lifecycle_events':sum(c.get('tool_event_count',0) for c in calls),'started_without_metadata':unknown,'families_bargaining':families,'episode_issues':issues}
 finalization=run/'administrative-finalization.json'
 data.pop('reported_tool_lifecycle_events')
 data['tool_event_census']=tool_census(run)
 if finalization.exists():data['administrative_finalization']=json.loads(finalization.read_text(encoding='utf-8'))
 dump(out,data);print(json.dumps({k:v for k,v in data.items() if k not in ['families_bargaining','episode_issues']},ensure_ascii=False,indent=2));return data


if __name__=='__main__':
 ap=argparse.ArgumentParser();ap.add_argument('run');ap.add_argument('--out',required=True);args=ap.parse_args();details(args.run,args.out)
