"""Audit retained starter/body sources,without manufacturing fitted registration."""
from pathlib import Path
import hashlib,json,math,subprocess
import cadquery as cq

ROOT=Path(__file__).resolve().parent
BASE='d0744dba'
body=json.loads((ROOT.parent/'body-data.json').read_text())
manifest=json.loads((ROOT/'starter-installed-context.json').read_text())
S=body['published_reference_dimensions_mm']['wheelbase']/2.4
roles={k:[]for k in manifest['roles']};sources=[];held={}
for spec in manifest['sources']:
    placement=body[spec['key']];filename=placement['source_url'].split('/')[-1].split('?')[0]
    data=json.loads((ROOT/filename).read_text());stem=filename.removesuffix('.json')
    assembly=cq.Assembly.importStep(str(ROOT/(stem+'.step')))
    shapes={n:o.obj for n,o in assembly.objects.items()if o.obj is not None}
    delta=tuple(v*S-a for a,v in zip(placement['native_anchor_mm'],placement['body_anchor_units']))
    selected=[]
    for selector in spec['selectors']:
        wanted=set(selector.get('names',[]))
        for group in selector.get('group_ids',[]):wanted.update(placement['source_groups'][group]['names'])
        chosen=[p for p in data['parts']if selector.get('all')or p['name']in wanted or any(p['name'].startswith(prefix)for prefix in selector.get('prefixes',[]))]
        assert chosen,(spec['key'],selector)
        assert wanted.issubset({p['name']for p in chosen})
        for part in chosen:
            n=part['name'];shape=shapes[n].translate(delta)
            assert shape.isValid()and shape.Volume()>0,n
            roles[selector['role']].append(dict(name=n,shape=shape,source=spec['key']))
            selected.append(dict(name=n,role=selector['role']))
    for ext in ['.json','.step']:
        name=stem+ext;content=(ROOT/name).read_bytes()
        assert content==subprocess.check_output(['git','show',BASE+':cars/1975-mg-midget/cad/'+name])
        held[name]=hashlib.sha256(content).hexdigest()
    sources.append(dict(key=spec['key'],url=placement['source_url'],translation_body_mm=list(delta),parts=selected))
assert (ROOT.parent/'body-data.json').read_bytes()==subprocess.check_output(['git','show',BASE+':cars/1975-mg-midget/body-data.json'])
def bounds(s):
    b=s.BoundingBox();return {k:[getattr(b,k+'min'),getattr(b,k+'max')]for k in ['x','y','z']}
contacts=[];checks=0
neighbors=[p for role in ['engine','peripherals','plate','housing','gearbox','body','steering','exhaust']for p in roles[role]]
for motor in roles['starter']:
    a=motor['shape'].BoundingBox()
    for neighbor in neighbors:
        b=neighbor['shape'].BoundingBox()
        if not all(getattr(a,k+'min')<getattr(b,k+'max')and getattr(b,k+'min')<getattr(a,k+'max')for k in ['x','y','z']):continue
        volume=motor['shape'].intersect(neighbor['shape']).Volume(1e-7);checks+=1
        if volume>1e-3:contacts.append(dict(starter=motor['name'],neighbor=neighbor['name'],overlap_mm3=round(volume,6)))
print('Completed bounded starter/neighbor intersections',checks,flush=True)
outer=[p for p in roles['starter']if any(token in p['name']for token in ['_yoke_','_drive_end_','_commutator_end_'])]
footwell=[p for p in roles['body']if p['source']=='native_front_footwell_placement']
def lower_bound(a,b):
    a,b=a.BoundingBox(),b.BoundingBox()
    return math.sqrt(sum(max(getattr(a,k+'min')-getattr(b,k+'max'),getattr(b,k+'min')-getattr(a,k+'max'),0)**2 for k in ['x','y','z']))
best=math.inf;pair=None;distance_checks=0
candidates=sorted([(lower_bound(m['shape'],n['shape']),m,n)for m in outer for n in footwell],key=lambda p:p[0])
for lower,m,n in candidates:
    if lower>best:continue
    distance=m['shape'].distance(n['shape']);distance_checks+=1
    if distance<best:best=distance;pair=[m['name'],n['name']]
report=dict(schema='starter-installed-context-audit-v1',baseline_commit=BASE,geometry_changed=False,
            owner_dimensions_applied=False,source_count=len(sources),role_counts={k:len(v)for k,v in roles.items()},
            sources=sources,starter_part_bounds_body_mm={p['name']:bounds(p['shape'])for p in roles['starter']},
            positive_scoped_source_contacts_mm3=contacts,bounded_intersection_checks=checks,
            outer_casing_to_selected_footwells=dict(minimum_static_source_distance_mm=best,parts=pair,
                                                  exact_distance_checks=distance_checks,candidate_pairs=len(candidates)),
            retained_source_sha256=held,body_register_sha256=hashlib.sha256((ROOT.parent/'body-data.json').read_bytes()).hexdigest(),
            evidence=dict(owner_photos=['IMG_6966','IMG_6969','IMG_6970'],scope='Existing owner views support starter/distributor/filter context;drive-end mounting,marking,full cable route and hidden body relationships remain unmeasured.',
                          sources=['https://www.spridgets.de/downloads/Parts-1977.pdf#page=185','https://www.motorarkiv.dk/books/dvk007558.pdf#page=87'],factory_scope='Existing documented M35J catalog/bench roles,not a fitted vehicle release sequence or dimensional body datum.'),
            limits=manifest['limits']+' Static source distances and contacts do not establish actual-car clearance or fit. The casing distance covers only yoke,drive end,rear cap against selected footwell/bulkhead solids;it is not minimum clearance to the whole car or a swept removal path.')
(ROOT/'starter-installed-context-audit.json').write_text(json.dumps(report,indent=2)+'\n')
print(json.dumps({k:v for k,v in report.items()if k not in ['sources','retained_source_sha256','starter_part_bounds_body_mm']},indent=2))
