"""
Assembly check - places every part in the letterbox frame and verifies:

  1. nothing intersects anything else
  2. nothing escapes the letterbox interior
  3. the IR beam from each lid LED actually reaches its floor sensor
  4. everything fits the Centauri Carbon build volume when printed

This is the check that matters. Each part passing on its own proves nothing
about whether they work together.

Run:  python assembly.py
"""

from pathlib import Path
import numpy as np
import trimesh
import params as P

OUT = Path(__file__).resolve().parent.parent / "out"


def load(name):
    return trimesh.load(str(OUT / name))


def main():
    insert = load("insert.stl")
    carrier = load("carrier.stl")
    cover = load("bay_cover.stl")

    # ---- place the carrier on the lid underside -------------------------
    # carrier is built with Z=0 at its glue face; installed, that face is at
    # the lid (Z = BOX_H) and the part hangs downward.
    car = carrier.copy()
    car.apply_transform(trimesh.transformations.rotation_matrix(np.pi, [1, 0, 0]))
    car.apply_translation([0, 0, P.BOX_H])
    # carrier is built with local y = -box Y, so the rotation above already
    # puts it in box coordinates; nothing more to do

    print("=" * 66)
    print("PLACED EXTENTS  (letterbox interior 153 wide x 255 deep x 85)")
    print("=" * 66)
    interior_fail = False
    for nm, mesh in (("insert", insert), ("carrier", car), ("cover", cover)):
        b = mesh.bounds
        print(f"{nm:8s} X {b[0][0]:7.1f}..{b[1][0]:7.1f}   "
              f"Y {b[0][1]:6.1f}..{b[1][1]:6.1f}   "
              f"Z {b[0][2]:6.1f}..{b[1][2]:6.1f}")
        if b[0][0] < -P.BOX_W / 2 - 0.1 or b[1][0] > P.BOX_W / 2 + 0.1:
            print(f"         !! {nm} exceeds box length"); interior_fail = True
        if b[0][1] < -0.1 or b[1][1] > P.BOX_D + 0.1:
            print(f"         !! {nm} exceeds box depth"); interior_fail = True
        if b[0][2] < -0.1 or b[1][2] > P.BOX_H + 0.1:
            print(f"         !! {nm} exceeds box height"); interior_fail = True

    # ---- pairwise collision ---------------------------------------------
    print()
    print("=" * 66)
    print("COLLISION CHECK")
    print("=" * 66)
    # Boolean intersection - no FCL dependency, and a more direct test.
    # A non-empty intersection volume means real interference.
    pairs = (("insert", insert, "carrier", car),
             ("insert", insert, "cover", cover),
             ("cover", cover, "carrier", car))
    hit = False
    for na, ma, nb, mb in pairs:
        # cheap bbox reject first
        a0, a1 = ma.bounds
        b0, b1 = mb.bounds
        if (a1 < b0).any() or (b1 < a0).any():
            print(f"  {na:8s} <-> {nb:8s}  clear (no bbox overlap)")
            continue
        try:
            inter = ma.intersection(mb)
            vol = float(inter.volume) if inter is not None else 0.0
        except Exception as e:
            print(f"  {na} <-> {nb}: boolean failed ({e})")
            continue
        if vol > 1.0:      # mm^3, ignore numerical slivers
            print(f"  {na:8s} <-> {nb:8s}  COLLISION  {vol/1000:.2f} cm3")
            hit = True
        else:
            print(f"  {na:8s} <-> {nb:8s}  clear (overlap {vol:.2f} mm3)")

    # ---- beam path ------------------------------------------------------
    print()
    print("=" * 66)
    print("OPTICAL PATH  (LED in lid -> sensor in floor)")
    print("=" * 66)
    beam_ok = True
    combined = trimesh.util.concatenate([insert, car, cover])
    for nm, y in (("beam 1 (front)", P.SENSOR_Y[0]), ("beam 2 (rear)", P.SENSOR_Y[1])):
        origin = np.array([[P.BEAM_X, y, P.BOX_H - 0.5]])
        direction = np.array([[0, 0, -1.0]])
        locs, _, _ = combined.ray.intersects_location(origin, direction)
        zs = sorted((float(l[2]) for l in locs), reverse=True)
        if zs:
            print(f"  {nm}: BLOCKED at Z = {[round(z,2) for z in zs]}")
            beam_ok = False
        else:
            print(f"  {nm}: clear from lid to sensor")

    # ---- print volume ---------------------------------------------------
    print()
    print("=" * 66)
    print("PRINTABILITY  (Centauri Carbon 256^3)")
    print("=" * 66)
    lim = P.BUILD_VOL - P.PRINT_MARGIN
    print_fail = False
    for nm, f in (("insert", "insert.stl"), ("carrier", "carrier.stl"),
                  ("cover", "bay_cover.stl")):
        m = load(f)
        s = m.extents
        fits = all(d <= lim for d in sorted(s))
        flag = "ok" if fits else "TOO BIG"
        if not fits:
            print_fail = True
        print(f"  {nm:8s} {s[0]:6.1f} x {s[1]:6.1f} x {s[2]:6.1f}   {flag}")

    # ---- mesh sanity -----------------------------------------------------
    print()
    print("=" * 66)
    print("MESH SANITY")
    print("=" * 66)
    mesh_fail = False
    for nm, f in (("insert", "insert.stl"), ("carrier", "carrier.stl"),
                  ("cover", "bay_cover.stl")):
        m = load(f)
        bodies = len(m.split(only_watertight=False))
        wt = m.is_watertight
        ok = wt and bodies == 1
        if not ok:
            mesh_fail = True
        print(f"  {nm:8s} watertight={wt!s:5s} bodies={bodies}  "
              f"vol={m.volume/1000:6.1f} cm3  {'ok' if ok else 'CHECK'}")

    print()
    print("=" * 66)
    allok = not (interior_fail or hit or not beam_ok or print_fail or mesh_fail)
    print("ASSEMBLY: PASS" if allok else "ASSEMBLY: PROBLEMS ABOVE")
    print("=" * 66)
    return allok


if __name__ == "__main__":
    main()
