"""
Case parametrico per LilyGO T-SIM7080G-S3 (candidato Visla Tag).

Genera: bottom shell + lid, con pocket per il PCB, post di fissaggio M2,
cutout USB-C e apertura per cavo antenna.

Esegui:
    .venv/bin/python visla-tag-case/case.py

Output in ./output/: case.step, case.stl, lid.step, lid.stl, assembly.step
"""

from pathlib import Path
import cadquery as cq

# ─── Parametri PCB (misurare il PCB reale prima di stampare) ──────────
PCB_W = 25.4          # larghezza PCB (X)
PCB_L = 65.0          # lunghezza PCB (Y)
PCB_T = 1.6           # spessore PCB
PCB_HOLE_D = 2.2      # diametro foro di fissaggio sul PCB (M2 + clearance)
PCB_HOLE_MARGIN = 2.5 # distanza foro dal bordo PCB (X e Y)

# ─── Parametri case ───────────────────────────────────────────────────
WALL = 1.6            # spessore pareti
FLOOR = 1.6           # spessore fondo
LID_T = 1.6           # spessore coperchio
CLEARANCE_XY = 0.4    # gioco PCB↔pareti
CLEARANCE_BELOW = 4.0 # spazio sotto il PCB (componenti SMD + post)
CLEARANCE_ABOVE = 8.0 # spazio sopra il PCB (header, GPS antenna SMA, ecc.)
FILLET_OUT = 2.0      # raccordo esterno
LIP_H = 2.0           # altezza linguetta di accoppiamento lid↔base
LIP_CLEARANCE = 0.2   # gioco fra lid e base (per FDM)

# ─── Post di fissaggio (M2 self-tapping in plastica) ──────────────────
POST_OD = 4.0         # diametro esterno post
POST_ID = 1.6         # diametro foro per vite M2 self-tap
POST_H = CLEARANCE_BELOW  # altezza post = quanto stacca il PCB dal fondo

# ─── Cutout USB-C (lato corto, asse +Y, lato a Y_max) ─────────────────
USB_W = 9.5           # larghezza apertura USB-C (con clearance)
USB_H = 4.0           # altezza apertura USB-C
USB_Z_FROM_PCB = 1.0  # offset Z dal top del PCB al centro del connettore

# ─── Cutout antenna (lato opposto, Y_min) ─────────────────────────────
ANT_HOLE_D = 6.5      # diametro foro per pigtail SMA (femmina pannello)


# ─── Derived ──────────────────────────────────────────────────────────
INNER_W = PCB_W + 2 * CLEARANCE_XY
INNER_L = PCB_L + 2 * CLEARANCE_XY
INNER_H = CLEARANCE_BELOW + PCB_T + CLEARANCE_ABOVE

OUTER_W = INNER_W + 2 * WALL
OUTER_L = INNER_L + 2 * WALL
OUTER_H = INNER_H + FLOOR  # senza lid; il lid si appoggia sopra


def make_base() -> cq.Workplane:
    """Bottom shell con pocket, post, e cutout."""
    # Guscio esterno
    base = (
        cq.Workplane("XY")
        .box(OUTER_W, OUTER_L, OUTER_H, centered=(True, True, False))
        .edges("|Z")
        .fillet(FILLET_OUT)
    )

    # Scava il pocket interno (lascia FLOOR sotto)
    base = (
        base.faces(">Z")
        .workplane()
        .rect(INNER_W, INNER_L)
        .cutBlind(-(OUTER_H - FLOOR))
    )

    # Post di fissaggio nei 4 angoli (centrati sui fori del PCB)
    post_x = PCB_W / 2 - PCB_HOLE_MARGIN
    post_y = PCB_L / 2 - PCB_HOLE_MARGIN
    posts = (
        cq.Workplane("XY")
        .pushPoints([(post_x, post_y), (-post_x, post_y),
                     (post_x, -post_y), (-post_x, -post_y)])
        .circle(POST_OD / 2)
        .extrude(FLOOR + POST_H)
    )
    # Foro pilota M2 self-tap nei post
    posts = (
        posts.faces(">Z")
        .workplane()
        .pushPoints([(post_x, post_y), (-post_x, post_y),
                     (post_x, -post_y), (-post_x, -post_y)])
        .circle(POST_ID / 2)
        .cutBlind(-(POST_H - 0.5))  # non passante, lascia 0.5mm di "fondo"
    )

    base = base.union(posts)

    # Cutout USB-C su Y_max
    usb_z_center = FLOOR + POST_H + PCB_T + USB_Z_FROM_PCB
    base = (
        base.faces(">Y")
        .workplane(centerOption="CenterOfBoundBox")
        .center(0, usb_z_center - OUTER_H / 2)
        .rect(USB_W, USB_H)
        .cutThruAll()
    )

    # Foro antenna SMA su Y_min
    ant_z_center = FLOOR + POST_H + PCB_T + CLEARANCE_ABOVE / 2
    base = (
        base.faces("<Y")
        .workplane(centerOption="CenterOfBoundBox")
        .center(0, ant_z_center - OUTER_H / 2)
        .circle(ANT_HOLE_D / 2)
        .cutThruAll()
    )

    # Lip interna per accoppiamento lid (gradino in cima al pocket)
    lip_thick = WALL / 2
    base = (
        base.faces(">Z")
        .workplane()
        .rect(OUTER_W, OUTER_L)
        .rect(INNER_W + 2 * lip_thick + 2 * LIP_CLEARANCE,
              INNER_L + 2 * lip_thick + 2 * LIP_CLEARANCE)
        .cutBlind(-LIP_H)
    )

    return base


def make_lid() -> cq.Workplane:
    """Coperchio piatto con linguetta che si inserisce nella lip della base."""
    lip_thick = WALL / 2

    # Piastra esterna (stessa pianta del case)
    lid = (
        cq.Workplane("XY")
        .box(OUTER_W, OUTER_L, LID_T, centered=(True, True, False))
        .edges("|Z")
        .fillet(FILLET_OUT)
    )

    # Linguetta che scende nel pocket
    lip = (
        cq.Workplane("XY")
        .workplane(offset=-LIP_H)
        .rect(OUTER_W - 2 * WALL,
              OUTER_L - 2 * WALL)
        .rect(INNER_W + 2 * lip_thick,
              INNER_L + 2 * lip_thick)
        .extrude(LIP_H)
    )

    return lid.union(lip)


def main() -> None:
    out_dir = Path(__file__).parent / "output"
    out_dir.mkdir(exist_ok=True)

    base = make_base()
    lid = make_lid()

    # Export singoli (STEP per CAD, STL per slicer)
    cq.exporters.export(base, str(out_dir / "case.step"))
    cq.exporters.export(base, str(out_dir / "case.stl"))
    cq.exporters.export(lid, str(out_dir / "lid.step"))
    cq.exporters.export(lid, str(out_dir / "lid.stl"))

    # Assembly (lid posizionato sopra la base) per anteprima accoppiamento
    assy = cq.Assembly()
    assy.add(base, name="base", color=cq.Color("gray"))
    assy.add(
        lid,
        name="lid",
        loc=cq.Location(cq.Vector(0, 0, OUTER_H)),
        color=cq.Color("lightblue"),
    )
    assy.save(str(out_dir / "assembly.step"))

    # GLB export per <model-viewer> nel sito (AR Android/Quick Look fallback)
    # Assembly esporta .glb se l'estensione è .glb
    try:
        assy.save(str(out_dir / "assembly.glb"))
    except Exception as e:
        print(f"  ⚠ GLB export fallito: {e}")

    print("Generated:")
    for f in sorted(out_dir.iterdir()):
        size_kb = f.stat().st_size / 1024
        print(f"  {f.name:20s}  {size_kb:8.1f} KB")

    print("\nDimensioni esterne case:")
    print(f"  {OUTER_W:.1f} × {OUTER_L:.1f} × {OUTER_H + LID_T:.1f} mm (con lid)")


if __name__ == "__main__":
    main()
