"""Crossflow tube/fin recognition geometry within the retained opaque-core bounds."""
from pathlib import Path
import copy,hashlib,json
import cadquery as cq
from front_wing_geometry import absolute_tessellation

ROOT=Path(__file__).resolve().parent
SOURCE='lower-hose-connected-reference'
OUTPUT='radiator-core-reference'
OLD='radiator_retained_opaque_core_envelope_comparison'
BANK='radiator_flat_tube_bank_and_open_headers_comparison'
FINS='radiator_separate_sheet_fin_pack_comparison'
data=json.loads((ROOT/(SOURCE+'.json')).read_text())
a=cq.Assembly.importStep(str(ROOT/(SOURCE+'.step')))
native={n:o.obj for n,o in a.objects.items()if o.obj is not None}
bb=native[OLD].BoundingBox()
X=cq.Vector(1,0,0)
TUBE_HEIGHT,TUBE_DEPTH,WALL=3.,14.,.35
HEADER=1.2
ROWS=24
FIN_COUNT,FIN_GAUGE=96,.15
ys=[bb.ymin+5+(bb.ylen-10)*i/(ROWS-1)for i in range(ROWS)]
zs=[bb.zmin+10,bb.zmax-10]

def wire(x,y,z,inside=False):
    offset=WALL if inside else 0
    w=cq.Workplane(cq.Plane(origin=(x,y,z),xDir=(0,1,0),normal=X)).rect(TUBE_HEIGHT-2*offset,TUBE_DEPTH-2*offset).val()
    return w.fillet2D(1.-offset,w.Vertices())

inners=[];tubes=[];outertools=[]
for y in ys:
    for z in zs:
        outer=wire(bb.xmin,y,z);inner=wire(bb.xmin,y,z,True)
        tubes.append(cq.Solid.extrudeLinear(outer,[inner],X*bb.xlen))
        inners.append(cq.Solid.extrudeLinear(inner,[],X*(bb.xlen+2)).translate((-1,0,0)))
        outertools.append(cq.Solid.extrudeLinear(outer,[],X*(bb.xlen+2)).translate((-1,0,0)))
headers=[]
for x in [bb.xmin,bb.xmax-HEADER]:
    plate=cq.Solid.makeBox(HEADER,bb.ylen,bb.zlen,(x,bb.ymin,bb.zmin))
    headers.append(plate.cut(cq.Compound.makeCompound(inners)))
bank=headers[0].fuse(headers[1],*tubes).clean()
assert bank.isValid()and len(bank.Solids())==1
# One slotted sheet is reused across the core. This is a deliberately sparse
# flat-sheet comparison, not a traced corrugation/louvre or a fitted fin count.
prototype=cq.Solid.makeBox(FIN_GAUGE,bb.ylen,bb.zlen,(bb.xmin,bb.ymin,bb.zmin))
prototype=prototype.cut(cq.Compound.makeCompound(outertools)).clean()
assert prototype.isValid()and len(prototype.Solids())==1
fin_x=[bb.xmin+HEADER+2+(bb.xlen-2*HEADER-4-FIN_GAUGE)*i/(FIN_COUNT-1)for i in range(FIN_COUNT)]
fins=cq.Compound.makeCompound([prototype.moved(cq.Location(cq.Vector(x-bb.xmin,0,0)))for x in fin_x])

limits=('Primary NRP1028 replacement photograph supports side tanks,crossflow tube rows and a fin field,not fitted identity. '
        'Existing owner6854/6896views mainly show cowl,fan and hoses;they do not calibrate hidden tube/fin construction. '
        'Retained422x258x40mm core bounds/station are earlier reference dimensions,not owner measurements. '
        'Selected24tube levels,two depth rows,3x14mm flattened sections,.35mm walls,1.2mm headers,96flat fins and.15mm fin gauge '
        'are construction estimates. Fin pitch is deliberately sparse recognition geometry,not recovered corrugation,louvres, '
        'tube pass count,baffles,material or actual production. '
        'Open tubes/header passages and finite sheet sections do not prove a sealed tank/core joint,pressure integrity, '
        'flow distribution,heat rejection,air resistance,fan performance or serviceability. '
        'The opaque core is withdrawn only from this whole-car source;old engine/reference exports remain historical. '
        'Eight other mesh records/native volumes,tanks,ports,hoses,clamps,cowl,guard,mounts,fan and engine/body frames remain. '
        'No additional receiver,seal,braze joint or mounting hole is fabricated. '
        'Source contacts are drawing relationships,not fitted clearance,loaded support or a verified replacement/removal path.')
revision=dict(evidence='radiator-core-evidence.json',owner_dimensions_applied=False,
              retained_bounds_mm={k:[getattr(bb,k+'min'),getattr(bb,k+'max')]for k in ['x','y','z']},
              estimated_tube_levels_y_mm=ys,estimated_depth_rows_z_mm=zs,
              estimated_tube_outer_height_depth_mm=[TUBE_HEIGHT,TUBE_DEPTH],estimated_tube_wall_mm=WALL,
              estimated_tube_corner_radius_mm=1.,estimated_header_gauge_mm=HEADER,
              selected_fin_count=FIN_COUNT,estimated_fin_x_mm=fin_x,estimated_fin_gauge_mm=FIN_GAUGE,
              repeated_fin_step_instances=True,
              mesh_absolute_tolerances_mm=dict(bank=.05,fins=.12),
              mesh_angular_deflections_radians=dict(bank=.3,fins=.8),
              superseded_opaque_name=OLD,limits=limits,
              source_sha256={e:hashlib.sha256((ROOT/(SOURCE+e)).read_bytes()).hexdigest()for e in ['.json','.step']})
result=copy.deepcopy(data);out=cq.Assembly(name='radiator_with_crossflow_tubes_and_sheet_fin_comparisons');parts=[]
for p in result['parts']:
    if p['name']==OLD:
        out.add(bank,name=BANK,color=cq.Color(.25,.33,.35))
        pack=cq.Assembly(name=FINS)
        for i,x in enumerate(fin_x):
            pack.add(prototype,name='fin_'+str(i).zfill(3),loc=cq.Location(cq.Vector(x-bb.xmin,0,0)),color=cq.Color(.25,.30,.29))
        out.add(pack)
        for name,shape,color in [(BANK,bank,[.25,.33,.35]),(FINS,prototype,[.25,.30,.29])]:
            vs,ts=absolute_tessellation(shape,.05 if name==BANK else .12,.3 if name==BANK else .8)
            if name==FINS:
                local_vs,local_ts=vs,ts
                vs=[v+cq.Vector(x-bb.xmin,0,0)for x in fin_x for v in local_vs]
                ts=[tuple(v+i*len(local_vs)for v in tri)for i in range(FIN_COUNT)for tri in local_ts]
            parts.append(dict(name=name,color=color,vertices=[round(v,4)for p in vs for v in p.toTuple()],triangles=[v for t in ts for v in t]))
    else:
        out.add(native[p['name']],name=p['name'],color=cq.Color(*p['color']));parts.append(p)
result['parts']=parts;result['radiator_core_revision']=revision
out.export(str(ROOT/(OUTPUT+'.step')))
p=ROOT/(OUTPUT+'.step');p.write_text('\n'.join(l.rstrip()for l in p.read_text().splitlines())+'\n')
(ROOT/(OUTPUT+'.json')).write_text(json.dumps(result,separators=(',',':'))+'\n')
restored=cq.importers.importStep(str(p)).val()
assert restored.isValid()and all(s.Volume()>0 for s in restored.Solids())
print(json.dumps(dict(groups=len(parts),valid_positive_solids=len(restored.Solids()),connected_bank=True,separate_fins=FIN_COUNT,held_other_meshes=8)))
