#!/usr/bin/env python3
"""A84 static design screen; NOT a circuit simulator or manufacturing release."""
import argparse, hashlib, itertools, json, pathlib, xml.etree.ElementTree as ET

P = pathlib.Path(__file__).resolve().parent

def corners(resistors, tol=.01, extra_error_v=.010):
    # Ladder: AVDD--top--HI--middle--LO--bottom--GND.
    # Worst-case static inputs only. Dynamic CSA cross-offset, blanking,
    # rail tracking, switching transients and physical delay are excluded.
    cases=[]
    for factors in itertools.product((1-tol,1+tol),repeat=3):
        t,m,b=[r*f for r,f in zip(resistors,factors)]
        for vref, ge, offset_a, other_v, side in itertools.product(
                (3.135,3.465),(-.105,.105),(-.066,.066),
                (-extra_error_v,extra_error_v),('low','high')):
            gain=.3*(1+ge)
            center=vref/2
            threshold=vref*(b if side=='low' else b+m)/(t+m+b)
            distance=abs(threshold-center)
            current=(distance+other_v)/gain+offset_a
            cases.append(dict(current_a=current,vref=vref,gain=gain,
                              offset_a=offset_a,other_error_v=other_v,
                              side=side,resistors=[t,m,b]))
    n=sum(resistors)
    nominal=3.3*resistors[1]/n/2/.3
    return dict(cases=len(cases),nominal_a=nominal,
                min_case=min(cases,key=lambda c:c['current_a']),
                max_case=max(cases,key=lambda c:c['current_a']))

def q_next(q,clear_n,arm_edge,run=0):
    # Functional latch model, preset held inactive; no analog timing claim.
    return 0 if not clear_n else ((1-run) if arm_edge else q)

def check():
    a=corners((10500,12000,10500))
    b=corners((14000,10700,14000))
    assert abs(a['nominal_a']-2)<1e-12
    assert a['max_case']['current_a']>2.4
    assert b['max_case']['current_a']<2
    assert b['min_case']['current_a']>1.1
    checks=4
    for q,clear_n,arm,run in itertools.product((0,1),repeat=4):
        nq=q_next(q,clear_n,arm,run)
        drive=nq & run & clear_n
        if not clear_n: assert nq==0 and drive==0
        if not run: assert drive==0
        if run and arm: assert nq==0 and drive==0
        checks+=1
    # Each of the six comparisons independently overrides RUN and ARM.
    for phase,side in itertools.product('ABC',('low','high')):
        q=1
        q=q_next(q,0,1)
        assert q==0
        for run in (0,1):
            for _ in range(100):
                q=q_next(q,1,0)
                assert (q & run)==0
                # This is one retained-off trace, not 100 independent cases.
            checks+=1
        q=q_next(q,1,1)
        assert q==1
        checks+=2
    # Counterexamples: valid centered SOx while low side is not sensing;
    # gain configuration changed to 0.15V/A; open/stuck midpoint sensor.
    nominal=b['nominal_a']
    actual=3.0
    source_voltage=1.65  # no observability; not a model of a real motor waveform
    lo=3.3*14000/38700
    hi=3.3*(14000+10700)/38700
    assert lo < source_voltage < hi and actual>2
    assert nominal*.3/.15>3
    checks+=2
    return dict(checks=checks,nominal_2a_ladder=a,
                conservative_candidate_ladder=b,
                sensor_fault_counterexample='3A asserted but midpoint voltage remains in-window',
                wrong_gain_counterexample_nominal_trip_a=nominal*.3/.15,
                physical_verified=False,hardware_integrated=False,
                motor_run_permission=False,manufacturing_release=False)

def baseline(netlist):
    data=netlist.read_bytes()
    root=ET.fromstring(data)
    nets={n.attrib['name']:{(p.attrib['ref'],p.attrib['pin']) for p in n.findall('node')}
          for n in root.findall('nets/net')}
    expected={'/DRV_RUN_REQ':{('J1','10'),('J4','1'),('R5','1')},
              '/Q1_GATE':{('R5','2'),('R6','1'),('Q1','1')},
              '/DRV_OFF':{('Q1','3'),('R15','2'),('U1','21')},
              '/SO_A':{('U1','40'),('R10','1')},
              '/SO_B':{('U1','39'),('R11','1')},
              '/SO_C':{('U1','38'),('R12','1')}}
    for name,endpoints in expected.items():
        assert nets[name]==endpoints,(name,nets.get(name))
    # The unmodified R5 path bypasses the proposed latch. Baseline MUST fail
    # cutoff-readiness, even though source nets match expected input topology.
    return dict(sha256=hashlib.sha256(data).hexdigest(),checked_nets=6,
                baseline_run_path='J1.10 / J4.1 -> R5 -> Q1.G -> DRVOFF pull-down',
                cutoff_integrated=False,
                release_status='HOLD_NOT_INTEGRATED',
                required_change='Disconnect R5.1 from raw RUN/J4; reconnect to hardware-gated RUN only')

if __name__=='__main__':
    p=argparse.ArgumentParser(); p.add_argument('--baseline-netlist',type=pathlib.Path,required=True)
    p.add_argument('--output',type=pathlib.Path,required=True); args=p.parse_args()
    result=check(); result['baseline']=baseline(args.baseline_netlist)
    args.output.write_text(json.dumps(result,indent=2)+'\n')
    print(json.dumps({'checks':result['checks'],'baseline':result['baseline']['release_status'],
       'candidate_static_trip_a':[result['conservative_candidate_ladder'][x]['current_a'] for x in ('min_case','max_case')],
       'old_static_max_a':result['nominal_2a_ladder']['max_case']['current_a'],
       'motor_run_permission':False}))
