#!/usr/bin/env python3
"""
Take the original Junction Box STLs and scale them to target dimensions.
Original: 60x60x17 base + 60x60x10 lid
Target:   80x70x32 base + 80x70x18 lid
"""
import struct, numpy as np, os

ORIG = "/app/projects/esp32-box/original"
OUT = "/app/projects/esp32-box/exports"
os.makedirs(OUT, exist_ok=True)

# Scale factors
SX = 80.0/60.0  # 1.333
SY = 70.0/60.0  # 1.167
SZ_BASE = 32.0/17.0  # 1.882
SZ_LID = 18.0/10.0   # 1.8

def load_stl(path):
    with open(path, "rb") as f:
        f.read(80)
        n = struct.unpack("<I", f.read(4))[0]
        tris = []
        for _ in range(n):
            norm = struct.unpack("<fff", f.read(12))
            verts = []
            for _ in range(3):
                verts.append(struct.unpack("<fff", f.read(12)))
            f.read(2)
            tris.append((norm, verts))
    return tris

def write_stl(tris, path):
    with open(path, "wb") as f:
        f.write(b"\x00" * 80)
        f.write(struct.pack("<I", len(tris)))
        for norm, verts in tris:
            f.write(struct.pack("<fff", *norm))
            for v in verts:
                f.write(struct.pack("<fff", *v))
            f.write(b"\x00\x00")

def scale_stl(tris, sx, sy, sz):
    """Scale STL triangles. Recomputes normals."""
    new_tris = []
    for norm, verts in tris:
        new_verts = []
        for vx, vy, vz in verts:
            new_verts.append((vx * sx, vy * sy, vz * sz))
        # Recompute face normal
        v0, v1, v2 = new_verts
        ex = (v1[0]-v0[0], v1[1]-v0[1], v1[2]-v0[2])
        ey = (v2[0]-v0[0], v2[1]-v0[1], v2[2]-v0[2])
        nx = ex[1]*ey[2] - ex[2]*ey[1]
        ny = ex[2]*ey[0] - ex[0]*ey[2]
        nz = ex[0]*ey[1] - ex[1]*ey[0]
        L = (nx*nx + ny*ny + nz*nz) ** 0.5
        if L > 0:
            nx, ny, nz = nx/L, ny/L, nz/L
        new_tris.append(((nx, ny, nz), new_verts))
    return new_tris

# Scale Box -> Base
print("Scaling base (Box.stl -> base.stl)...")
box = load_stl(os.path.join(ORIG, "Box.stl"))
base = scale_stl(box, SX, SY, SZ_BASE)
write_stl(base, os.path.join(OUT, "base.stl"))
print("  base.stl: 80x70x32mm")

# Scale Lid
print("Scaling lid (Lid.stl -> lid.stl)...")
lid = load_stl(os.path.join(ORIG, "Lid.stl"))
l_scaled = scale_stl(lid, SX, SY, SZ_LID)
write_stl(l_scaled, os.path.join(OUT, "lid.stl"))
print("  lid.stl: 80x70x18mm")

# Scale Windows (all types)
win_sources = {
    "window_smooth": "Window 20x60mm - liso.stl",
    "window_keystone_dc": "Window 20x60mm.stl",
    "window_usb": "Window - Flex Gland.stl",
}

for name, src in win_sources.items():
    src_path = os.path.join(ORIG, src)
    if os.path.exists(src_path):
        win = load_stl(src_path)
        # Windows scale with X,Y proportions (not Z since they slide vertically)
        win_scaled = scale_stl(win, SX, SY, 1.0)
        write_stl(win_scaled, os.path.join(OUT, "%s.stl" % name))
        print("  %s.stl" % name)
    else:
        print("  SKIP %s: %s not found" % (name, src))

# Scale Screw
print("Scaling screw...")
screw = load_stl(os.path.join(ORIG, "Screw.stl"))
screw_scaled = scale_stl(screw, SX, SY, min(SZ_BASE, SZ_LID))
write_stl(screw_scaled, os.path.join(OUT, "screw.stl"))
print("  screw.stl")

print("\nDONE - All original parts scaled to 80x70mm footprint")
