"""Interference, clearance and printability verification of the full assembly."""
import math, itertools, sys
from build123d import *
from OCP.BRepExtrema import BRepExtrema_DistShapeShape
import parts as P

A = P.assembly()
NAMES = [n for n,_,_ in A]
SOL  = {n:s for n,s,_ in A}
PRT  = {n for n,_,p in A if p}

def bb(s):
    b = s.bounding_box(); return (b.min.X,b.min.Y,b.min.Z,b.max.X,b.max.Y,b.max.Z)
def bb_overlap(a,b,pad=0.0):
    return not (a[3]+pad<b[0] or b[3]+pad<a[0] or a[4]+pad<b[1] or
                b[4]+pad<a[1] or a[5]+pad<b[2] or b[5]+pad<a[2])
def min_dist(a,b):
    d = BRepExtrema_DistShapeShape(a.wrapped, b.wrapped)
    return d.Value() if d.IsDone() else None

BBS = {n: bb(SOL[n]) for n in NAMES}

print("="*78); print("  INTERFERENCE MATRIX — every part pair, exact boolean"); print("="*78)
# Joints: interpenetration BY DESIGN (threaded / bonded / press fit).
# Real interference checks whitelist these; they are reported with engagement.
JOINTS = {
 frozenset(("secondary_holder","secondary_stalk")): "M10 stud into rear boss",
 frozenset(("spider_hub","secondary_stalk")):       "stalk threaded into hub",
}
INTERFERE, TOUCH, JOINTED, checked = [], [], [], 0
VOL_TOL = 1.0        # mm^3, below this is numerical noise
for a,b in itertools.combinations(NAMES,2):
    if not bb_overlap(BBS[a],BBS[b]): continue
    checked += 1
    try:
        v = (SOL[a] & SOL[b]).volume
    except Exception:
        v = 0.0
    if v > VOL_TOL:
        key = frozenset((a,b))
        if key in JOINTS: JOINTED.append((a,b,v,JOINTS[key]))
        else:             INTERFERE.append((a,b,v))
    else:
        d = min_dist(SOL[a],SOL[b])
        if d is not None and d < 0.001: TOUCH.append((a,b))
print(f"  pairs total {len(NAMES)*(len(NAMES)-1)//2}   bbox-overlapping {checked}   "
      f"exact-checked {checked}")
if INTERFERE:
    print(f"\n  *** {len(INTERFERE)} INTERFERENCE(S) — MUST BE RESOLVED ***")
    for a,b,v in sorted(INTERFERE, key=lambda x:-x[2]):
        print(f"    {a:<22} x {b:<22} {v:10.1f} mm3")
else:
    print("\n  ZERO INTERFERENCES across the assembly.")
if JOINTED:
    print(f"\n  {len(JOINTED)} DESIGNED JOINT(S) — interpenetration is intentional:")
    for a,b,v,why in JOINTED:
        print(f"    {a:<22} + {b:<20} {v:8.1f} mm3   {why}")
if TOUCH:
    print(f"\n  {len(TOUCH)} face-contact pair(s) (0 clearance, no overlap):")
    for a,b in TOUCH: print(f"    {a:<22} - {b}")

print("\n"+"="*78); print("  FIT SCHEDULE — designed dimensions, arithmetic on the driving parameters"); print("="*78)
CELL_OD = P.TUBE_ID - 2*0.55
FITS = [
 ("Mirror OD to edge-roller face (radial)", (100.75 + 10.6) - 10.6 - P.MIR_DIA/2, P.FIT["mirror_radial"]),
 ("Cell OD to tube ID (per side)",         (P.TUBE_ID - CELL_OD)/2,        P.FIT["cell_to_tube"]),
 ("Vane thickness to hub slot (per side)", (0.62-0.50)/2,                  P.FIT["vane_slot"]),
 ("Secondary to holder seat (per side)",   0.40,                           P.FIT["sec_seat"]),
 ("Ring bore to tube OD (per side)",       0.25,                           P.FIT["ring_to_tube"]),
 ("Drawtube to board bore (per side)",     30.75-30.00,                    P.FIT["focuser_bore"]),
 ("Clip face above glass front",           0.40,                           P.FIT["clip_standoff"]),
 ("Printed mating clearance",              0.25,                           P.FIT["print_clearance"]),
]
print(f"  {'Fit':<44}{'designed':>10}{'spec':>16}  verdict")
fails = 0
for label,(val),(lo,hi) in FITS:
    spec = f"{lo:.2f}-{hi:.2f}" if hi is not None else f">= {lo:.2f}"
    ok = val >= lo-1e-9 and (hi is None or val <= hi+1e-9)
    if not ok: fails += 1
    print(f"  {label:<44}{val:>10.3f} {spec:>15}  {'PASS' if ok else 'FAIL'}")

print("\n" + "  CONTACT REGISTER — pairs that MUST touch (0 gap, 0 overlap):")
CONTACT = [("primary","mirror_cell"),("focuser_board","tube"),("focuser_body","focuser_board"),
           ("fan_shroud","mirror_cell"),("dovetail","tube_ring_1")]
for a,b in CONTACT:
    if a not in SOL or b not in SOL: continue
    d = min_dist(SOL[a],SOL[b]); v = 0.0
    try: v = (SOL[a] & SOL[b]).volume
    except Exception: pass
    ok = d is not None and d < 0.02 and v <= VOL_TOL
    print(f"    {a:<20} - {b:<20} gap {d:6.3f}  overlap {v:7.1f} mm3  "
          f"{'CONTACT OK' if ok else ('NOT TOUCHING' if d and d>=0.02 else 'OVERLAP')}")

print("\n"+"="*78); print("  OPTICAL SWEPT-ENVELOPE CHECK"); print("="*78)
light_r = P.D/2
tube_ir = P.TUBE_ID/2
print(f"  Light cone radius at primary      {light_r:.1f} mm")
print(f"  Tube inner radius                 {tube_ir:.1f} mm")
print(f"  Radial clearance, beam to tube    {tube_ir-light_r:.1f} mm  "
      f"{'PASS' if tube_ir-light_r > 15 else 'TIGHT'}")
obstruct = []
for n in NAMES:
    if n in ("tube","primary","secondary","secondary_holder","spider_hub",
             "dew_shield_half_1","dew_shield_half_2") or n.startswith("spider_vane"): continue
    x0,y0,z0,x1,y1,z1 = BBS[n]
    if z1 <= P.Z_GLASS_FRONT or z0 > P.Z_SPIDER: continue
    try:
        rmin = min(math.hypot(v.X, v.Y) for v in SOL[n].vertices())
    except Exception:
        rmin = 1e9
    if rmin < light_r:
        obstruct.append((n, round(rmin,1)))
print(f"  Parts intruding into the light path between primary and secondary: "
      f"{obstruct if obstruct else 'NONE'}")
print(f"  (radial distance of the nearest vertex to the optical axis; beam radius {light_r:.0f} mm)")

print("\n"+"="*78); print(f"  PRINTABILITY — Bambu H2C envelope {P.H2C[0]:.0f} x {P.H2C[1]:.0f} x {P.H2C[2]:.0f}, "
      f"{P.H2C_MARGIN:.0f} mm margin"); print("="*78)
usable = tuple(v-2*P.H2C_MARGIN for v in P.H2C)
seen = set()
print(f"  {'Part':<22}{'X':>8}{'Y':>8}{'Z':>8}{'best fit':>10}{'overhang':>10}  verdict")
pf = 0
for n in NAMES:
    base = n.rsplit("_",1)[0] if n.rsplit("_",1)[-1].isdigit() else n
    if base not in PRT and n not in PRT: continue
    if base in seen: continue
    seen.add(base)
    x0,y0,z0,x1,y1,z1 = BBS[n]
    dims = sorted([x1-x0, y1-y0, z1-z0])
    fit_ok = dims[0] <= usable[2] and dims[1] <= max(usable[0],usable[1]) and dims[2] <= max(usable[0],usable[1])
    # overhang: planar faces whose normal points down steeper than 45 deg
    oh_area = 0.0; tot_area = 0.0
    try:
        for f in SOL[n].faces():
            ar = f.area; tot_area += ar
            try: nz = f.normal_at().Z
            except Exception: continue
            if nz < -math.cos(math.radians(45)): oh_area += ar
    except Exception: pass
    ohp = 100*oh_area/tot_area if tot_area else 0
    if not fit_ok: pf += 1
    print(f"  {base:<22}{dims[2]:>8.1f}{dims[1]:>8.1f}{dims[0]:>8.1f}"
          f"{'OK' if fit_ok else 'TOO BIG':>10}{ohp:>9.1f}%  "
          f"{'PASS' if fit_ok else 'FAIL - SPLIT REQUIRED'}")
print(f"\n  Usable build box after margin: {usable[0]:.0f} x {usable[1]:.0f} x {usable[2]:.0f} mm")
print(f"\n  SUMMARY: interferences {len(INTERFERE)} | clearance fails {fails} | print fails {pf}")
sys.exit(1 if (INTERFERE or fails or pf) else 0)
