#!/usr/bin/env python3
from __future__ import annotations
import json, math, random
from common import load_manifest, write_receipt

def simulate(threshold,tau_leak,tau_recovery,seed=0,duration=60.0,dt=.001,gain=50.0):
    rng=random.Random(seed); q=0.0; r=1.0; noise=0.0; events=[]
    tau_noise=.05; sigma=.18; steps=int(duration/dt)
    for k in range(steps):
        t=k*dt
        noise += dt*(-noise/tau_noise) + sigma*math.sqrt(2*dt/tau_noise)*rng.gauss(0,1)
        intensity=max(.03,1.0+.25*math.sin(2*math.pi*.7*t)+noise)
        if tau_recovery>0: r=min(1.0,r+dt*(1-r)/tau_recovery)
        else: r=1.0
        q += dt*(gain*intensity*r-q/tau_leak)
        if q>=threshold:
            events.append(t); q=0.0
            if tau_recovery>0: r=0.0
    return events

def close_pair_ratio(events,duration,window=.015):
    n=len(events); rate=n/duration
    if n<2 or rate<=0: return float('inf'),0,0.0
    count=0; j=1
    for i,t in enumerate(events):
        if j<i+1: j=i+1
        while j<n and events[j]-t<=window: j+=1
        count += max(0,j-i-1)
    expected=rate*rate*max(0.0,duration*window-.5*window*window)
    return count/expected if expected>0 else float('inf'),count,expected

def main():
    m=load_manifest('detector-sandbox-v1.json'); duration=60.0; candidates=[]
    for tau_r in m['candidate_grid']['tau_recovery']:
      for th in m['candidate_grid']['threshold']:
       for tau_l in m['candidate_grid']['tau_leak']:
        events=simulate(th,tau_l,tau_r,seed=20260919,duration=duration)
        ratio,observed,expected=close_pair_ratio(events,duration)
        adequate=len(events)>=m['tolerances']['minimum_events']; suppressed=ratio<=m['tolerances']['suppressed_close_pair_ratio']
        waits=[b-a for a,b in zip(events,events[1:])]
        candidates.append({'params':{'threshold':th,'tau_leak':tau_l,'tau_recovery':tau_r},'event_count':len(events),'event_rate':len(events)/duration,
          'near_zero_pair_ratio':ratio,'close_pairs_observed':observed,'close_pairs_poisson_expected':expected,
          'minimum_wait':min(waits) if waits else None,'adequate_events':adequate,'passes_suppression_target':adequate and suppressed})
    adequate=[c for c in candidates if c['adequate_events']]; best=min(adequate,key=lambda c:c['near_zero_pair_ratio'])
    failed=[c for c in candidates if not c['passes_suppression_target']]
    receipt={'schema':'classical-reconstruction-receipt/v1','benchmark_id':m['benchmark_id'],'ok':len(adequate)>0,
      'ontology_contract_sha256':m['ontology_contract_sha256'],'simulation':{'duration':duration,'dt':.001,'input':'positive sinusoid + OU-like stochastic modulation','event_generation':'threshold crossings of explicit q,r detector state'},
      'candidate_count':len(candidates),'candidates':candidates,'best_candidate':best,'failed_candidates':failed,
      'worst_residual':{'criterion':'distance above suppression target','candidate':max(candidates,key=lambda c:c['near_zero_pair_ratio'])},
      'claim':'Detector threshold/reset/recovery dynamics can themselves reshape a point process; this sandbox does not reconstruct the source physics.',
      'q1_q5':m['q1_q5'],'scope_exclusion':m['scope_exclusion']}
    write_receipt('detector-sandbox-v1.json',receipt); return receipt
if __name__=='__main__': print(json.dumps(main(),indent=2))
