"""Folded lower-pickup architecture, not fitted chassis or repair geometry."""
from pathlib import Path
import copy
import hashlib
import json
import cadquery as cq

ROOT = Path(__file__).resolve().parent
S = 2032 / 2.4
SOURCE = 'front-structure-reference'
OUTPUT = 'front-pickup-channel-reference'
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}
changes = {}
records = {}
T = .003 * S
HALF_SPAN = .03 * S
BASE_Y = .25 * S
BORE_Y = .27 * S

def bracket(center_x, center_z, side):
    # The uncalibrated old ear axis/plane stays. New ramp and connecting
    # sheet are a replacement-photo comparison, not a traced pressing.
    x = abs(center_x)
    parts = []
    for direction, radius in [(-1, 13.0), (1, 6.25)]:
        z = center_z + direction * HALF_SPAN - T / 2
        sheet = (cq.Workplane('XY', origin=(0, 0, z))
                 .moveTo(x - 64, BASE_Y).lineTo(x + 22, BASE_Y)
                 .lineTo(x + 22, BORE_Y)
                 .threePointArc((x + 15, BORE_Y + 18), (x, BORE_Y + 22))
                 .lineTo(x - 16, BORE_Y + 19)
                 .lineTo(x - 64, BASE_Y + T).close().extrude(T).val())
        hole = cq.Solid.makeCylinder(radius, T + 4, (x, BORE_Y, z - 2), (0, 0, 1))
        parts.append(sheet.cut(hole))
    web = cq.Solid.makeBox(86, T, 2 * HALF_SPAN + T,
                           (x - 64, BASE_Y, center_z - HALF_SPAN - T / 2))
    result = web.fuse(*parts).clean()
    if side < 0:
        result = result.mirror('YZ')
    assert result.isValid() and len(result.Solids()) == 1
    return result

for label, side in [('left', 1), ('right', -1)]:
    built = []
    entries = []
    for position, z_units in [('rear', .98), ('front', 1.42)]:
        x = side * .36 * S
        z = z_units * S
        built.append(bracket(x, z, side))
        # BMH application map only. These generic handed/mirrored sections
        # do not recover the exact CZJ542 versus CZJ543 factory pressing.
        part = 'CZJ542' if (label, position) in [('right', 'front'), ('left', 'rear')] else 'CZJ543'
        entries.append(dict(position=position, catalog_role=part,
                            pivot_axis_center_mm=[x, BORE_Y, z],
                            retained_plate_center_z_mm=[z-HALF_SPAN, z+HALF_SPAN],
                            estimated_bore_radii_mm=[13, 6.25],
                            bore_orientation='Large access opening at negative Z follows the existing illustrative head direction; actual handed clocking remains unresolved.',
                            selected_gauge_mm=T, selected_web_y_mm=BASE_Y,
                            selected_inboard_extension_mm=64, selected_outer_end_offset_mm=22))
    changes['front_structure_' + label + '_lower_pickup_ear_comparison'] = cq.Compound.makeCompound(built)
    records[label] = entries

limits = ('Manufacturer CZJ542/CZJ543 applications and supplier product photographs establish folded connecting sheet, ramped sides and unequal opposed openings. '
          'All numerical sections, 13mm access radius, 2.54mm gauge, ramp contour, web station and extension are construction estimates. '
          'The 6.25mm receiver radius and all four pivot axes retain the old drawing references. '
          'Catalog handed positions are recorded separately; exact handed pressing and installed access-face clocking are not recovered. '
          'Photographed base holes and square apertures are not copied as an unsupported drilling or weld map. '
          'The rail/pocket, wishbone, bush/pin/head/washer/nut, spring and upper platform sources stay unchanged. '
          'Source contacts and gaps do not establish actual weld ownership, bolt engagement, loaded rubber, strength, alignment, tool access, safe support or a verified replacement path.')
revision = dict(evidence='front-pickup-channel-evidence.json', owner_dimensions_applied=False,
                brackets=records, limits=limits,
                source_sha256={ext:hashlib.sha256((ROOT/(SOURCE+ext)).read_bytes()).hexdigest() for ext in ['.json','.step']})
result = copy.deepcopy(data)
assembly = cq.Assembly(name='folded_lower_pickup_comparisons')
for part in result['parts']:
    name = part['name']
    shape = changes.get(name, native[name])
    assembly.add(shape, name=name, color=cq.Color(*part['color']))
    if name in changes:
        vertices, triangles = shape.tessellate(.4, .12)
        part['vertices'] = [round(v,4) for p in vertices for v in p.toTuple()]
        part['triangles'] = [v for t in triangles for v in t]
result['lower_pickup_channel_revision'] = revision
result['limits'] += ' ' + limits
assembly.export(str(ROOT/(OUTPUT+'.step')))
p = ROOT/(OUTPUT+'.step')
p.write_text('\n'.join(line.rstrip() for line in p.read_text().splitlines())+'\n')
# Check the actual exchange result before publishing the mesh companion.
restored = cq.Assembly.importStep(str(p))
for name in changes:
    shape = restored.objects[name].obj
    assert shape.isValid() and len(shape.Solids()) == 2
(ROOT/(OUTPUT+'.json')).write_text(json.dumps(result,separators=(',',':'))+'\n')
print(json.dumps(dict(groups=len(result['parts']), changed=list(changes), brackets=records)))
