# Copyright (c) 2026 K2Aley. GAR-BOT. All rights reserved.
#
# Regenerates 3D printable STL files.
# Preference order:
#  1) OpenSCAD -> export exact PART_TO_PRINT geometry from GAR_BOT_WATER_RESISTANT_PARTS.scad
#  2) fallback primitive meshes for environments without OpenSCAD.

$ErrorActionPreference = "Stop"

$root = Split-Path -Parent $MyInvocation.MyCommand.Path
$outDir = Join-Path $root "stl_exports"
New-Item -ItemType Directory -Force -Path $outDir | Out-Null

function Get-OpenScadPath {
  $cmd = Get-Command openscad.exe -ErrorAction SilentlyContinue
  if ($cmd) {
    return $cmd.Source
  }
  $candidateRoots = @(
    $env:ProgramFiles,
    ${env:ProgramFiles(x86)}
  )

  foreach ($rootPath in $candidateRoots) {
    if (-not $rootPath) { continue }
    $candidate = Join-Path $rootPath 'OpenSCAD\openscad.exe'
    if (Test-Path $candidate) {
      return (Resolve-Path $candidate).Path
    }
  }

  return $null
}

$OpenScadPath = Get-OpenScadPath
$HasOpenScad = [bool]$OpenScadPath

function Add-Tri {
  param(
    [System.Collections.Generic.List[string]] $Lines,
    [double[]] $A,
    [double[]] $B,
    [double[]] $C
  )
  $ux = $B[0] - $A[0]; $uy = $B[1] - $A[1]; $uz = $B[2] - $A[2]
  $vx = $C[0] - $A[0]; $vy = $C[1] - $A[1]; $vz = $C[2] - $A[2]
  $nx = ($uy * $vz) - ($uz * $vy)
  $ny = ($uz * $vx) - ($ux * $vz)
  $nz = ($ux * $vy) - ($uy * $vx)
  $len = [Math]::Sqrt(($nx * $nx) + ($ny * $ny) + ($nz * $nz))
  if ($len -gt 0) {
    $nx /= $len; $ny /= $len; $nz /= $len
  }
  $Lines.Add(("  facet normal {0:G6} {1:G6} {2:G6}" -f $nx,$ny,$nz))
  $Lines.Add("    outer loop")
  $Lines.Add(("      vertex {0:G6} {1:G6} {2:G6}" -f $A[0],$A[1],$A[2]))
  $Lines.Add(("      vertex {0:G6} {1:G6} {2:G6}" -f $B[0],$B[1],$B[2]))
  $Lines.Add(("      vertex {0:G6} {1:G6} {2:G6}" -f $C[0],$C[1],$C[2]))
  $Lines.Add("    endloop")
  $Lines.Add("  endfacet")
}

function Write-BoxStl {
  param([string] $Name, [double] $L, [double] $W, [double] $H)
  $p = @(
    @(0, 0, 0), @($L, 0, 0), @($L, $W, 0), @(0, $W, 0),
    @(0, 0, $H), @($L, 0, $H), @($L, $W, $H), @(0, $W, $H)
  )
  $lines = [System.Collections.Generic.List[string]]::new()
  $lines.Add("solid $Name")
  Add-Tri $lines $p[0] $p[2] $p[1]; Add-Tri $lines $p[0] $p[3] $p[2]
  Add-Tri $lines $p[4] $p[5] $p[6]; Add-Tri $lines $p[4] $p[6] $p[7]
  Add-Tri $lines $p[0] $p[1] $p[5]; Add-Tri $lines $p[0] $p[5] $p[4]
  Add-Tri $lines $p[1] $p[2] $p[6]; Add-Tri $lines $p[1] $p[6] $p[5]
  Add-Tri $lines $p[2] $p[3] $p[7]; Add-Tri $lines $p[2] $p[7] $p[6]
  Add-Tri $lines $p[3] $p[0] $p[4]; Add-Tri $lines $p[3] $p[4] $p[7]
  $lines.Add("endsolid $Name")
  Set-Content -LiteralPath (Join-Path $outDir "$Name.stl") -Value $lines -Encoding ASCII
}

function Write-CylinderStl {
  param([string] $Name, [double] $Diameter, [double] $Height, [int] $Segments = 64)
  $r = [double]$Diameter / 2.0
  $lines = [System.Collections.Generic.List[string]]::new()
  $lines.Add("solid $Name")
  $bottomCenter = @(0, 0, 0)
  $topCenter = @(0, 0, $Height)
  for ($i = 0; $i -lt $Segments; $i++) {
    $a1 = 2.0 * [Math]::PI * $i / [double]$Segments
    $a2 = 2.0 * [Math]::PI * (($i + 1) % $Segments) / [double]$Segments
    $b1x = [double]$r * [Math]::Cos($a1)
    $b1y = [double]$r * [Math]::Sin($a1)
    $b2x = [double]$r * [Math]::Cos($a2)
    $b2y = [double]$r * [Math]::Sin($a2)
    $b1 = @($b1x, $b1y, 0.0)
    $b2 = @($b2x, $b2y, 0.0)
    $t1 = @($b1x, $b1y, [double]$Height)
    $t2 = @($b2x, $b2y, [double]$Height)
    Add-Tri $lines $bottomCenter $b2 $b1
    Add-Tri $lines $topCenter $t1 $t2
    Add-Tri $lines $b1 $b2 $t2
    Add-Tri $lines $b1 $t2 $t1
  }
  $lines.Add("endsolid $Name")
  Set-Content -LiteralPath (Join-Path $outDir "$Name.stl") -Value $lines -Encoding ASCII
}

function Export-Part {
  param([string] $Name, [double] $L = 0, [double] $W = 0, [double] $H = 0, [double] $D = 0)

  $outPath = Join-Path $outDir "$Name.stl"
  $partScadPath = Join-Path $root "tmp_part_$Name.scad"
  $moduleCall = "$Name();"
  $partScad = @"
EXPORT_TARGET = true;
include <GAR_BOT_WATER_RESISTANT_PARTS.scad>;
$moduleCall
"@

  if ($script:HasOpenScad) {
    Set-Content -LiteralPath $partScadPath -Value $partScad -Encoding ASCII

    $stdoutLog = Join-Path $env:TEMP "garbot_part_${Name}_stdout.log"
    $stderrLog = Join-Path $env:TEMP "garbot_part_${Name}_stderr.log"

    try {
      if (Test-Path $outPath) {
        Remove-Item -LiteralPath $outPath -Force
      }
      $process = Start-Process -FilePath $script:OpenScadPath -ArgumentList @("-o", "`"$outPath`"", $partScadPath) `
        -WorkingDirectory $root -NoNewWindow -Wait -PassThru `
        -RedirectStandardOutput $stdoutLog -RedirectStandardError $stderrLog

      if ($process.ExitCode -ne 0) {
        if (Test-Path $stdoutLog) { $stdout = Get-Content $stdoutLog -Raw }
        if (Test-Path $stderrLog) { $stderr = Get-Content $stderrLog -Raw }
        throw "OpenSCAD export failed for $Name (exit $($process.ExitCode))."
      }

      if (-not (Test-Path $outPath)) {
        throw "OpenSCAD export failed for $Name"
      }
      if ((Get-Item $outPath).Length -lt 200) {
        throw "OpenSCAD export produced empty mesh for $Name"
      }
      if (Test-Path $stdoutLog) {
        $ocLog = Get-Content $stdoutLog -Raw
        if ($ocLog -match "Current top level object is empty") {
          throw "OpenSCAD script exported no geometry for $Name"
        }
      }
      if (Test-Path $stderrLog) {
        $errLog = Get-Content $stderrLog -Raw
        if ($errLog -match "Parser error") {
          throw "OpenSCAD parser error for $Name"
        }
      }
      return
    } finally {
      if (Test-Path $stdoutLog) { Remove-Item -LiteralPath $stdoutLog -Force }
      if (Test-Path $stderrLog) { Remove-Item -LiteralPath $stderrLog -Force }
      if (Test-Path $partScadPath) { Remove-Item -LiteralPath $partScadPath -Force }
    }
  } 

  if ($L -gt 0 -and $W -gt 0 -and $H -gt 0) {
    Write-BoxStl -Name $Name -L $L -W $W -H $H
    return
  }
  if ($D -gt 0 -and $H -gt 0) {
    Write-CylinderStl -Name $Name -Diameter $D -Height $H
    return
  }

  throw "No fallback geometry available for $Name"
}

$parts = @(
  @{N="electronics_bay";L=230;W=160;H=58},
  @{N="gasketed_lid";L=230;W=160;H=4},
  @{N="cable_gland_panel";L=130;W=52;H=5},
  @{N="ibt2_driver_cover";L=62;W=55;H=24},
  @{N="buck_converter_cover";L=78;W=50;H=28},
  @{N="motor_splash_shield";L=120;W=78;H=28},
  @{N="touchscreen_bezel";L=205;W=135;H=5},
  @{N="touchscreen_rain_hood";L=223;W=55;H=153},
  @{N="left_claw_base_mount";L=86;W=62;H=10},
  @{N="right_claw_base_mount";L=86;W=62;H=10},
  @{N="round_shoulder_mount";L=76;W=76;H=10;},
  @{N="sealed_servo_pocket";L=48;W=42;H=30},
  @{N="wrist_servo_cradle";L=64;W=38;H=24},
  @{N="claw_palm";L=62;W=34;H=12},
  @{N="claw_gripper_finger";L=92;W=16;H=10},
  @{N="claw_link_bar";L=74;W=9;H=5},
  @{N="claw_gear_guard";L=44;W=44;H=7},
  @{N="forearm_wire_cover";L=130;W=24;H=16},
  @{N="claw_controller_cover";L=88;W=55;H=20},
  @{N="drip_loop_cable_clip";L=28;W=12;H=8},
  @{N="remote_zero2w_pico_voice_case_front";L=96;W=62;H=22},
  @{N="remote_zero2w_pico_voice_case_back";L=96;W=62;H=20},
  @{N="remote_lora_antenna_bulkhead";D=38;H=14},
  @{N="lora_antenna_bulkhead_guard";D=38;H=14},
  @{N="k2aley_lid_badge";L=150;W=48;H=4},
  @{N="k2aley_side_badge";L=185;W=40;H=4},
  @{N="water_warning_badge";L=180;W=62;H=4},
  @{N="frame_left_side_rail";L=320;W=32;H=24},
  @{N="frame_right_side_rail";L=320;W=32;H=24},
  @{N="frame_front_crossmember";L=260;W=38;H=34},
  @{N="frame_rear_crossmember";L=260;W=38;H=36},
  @{N="frame_center_battery_tray";L=230;W=128;H=20},
  @{N="frame_electronics_deck_plate";L=236;W=166;H=8},
  @{N="frame_bottom_skid_plate";L=280;W=170;H=16},
  @{N="frame_touchscreen_upright_left";L=58;W=24;H=145},
  @{N="frame_touchscreen_upright_right";L=58;W=24;H=145},
  @{N="frame_touchscreen_top_bar";L=225;W=28;H=16},
  @{N="frame_claw_bridge";L=245;W=104;H=24}
)

foreach ($part in $parts) {
  Export-Part -Name $part.N -L $part.L -W $part.W -H $part.H -D $part.D
}

$manifest = [System.Collections.Generic.List[psobject]]::new()
foreach ($part in $parts) {
  $partFile = Join-Path $outDir "$($part.N).stl"
  if (-not (Test-Path $partFile)) {
    throw "Missing expected STL for $($part.N)"
  }
  $stl = Get-Item $partFile
  $manifest += [pscustomobject]@{
    File = $stl.Name
    SizeBytes = $stl.Length
    Note = if ($HasOpenScad) { "Generated from GAR_BOT_WATER_RESISTANT_PARTS.scad via OpenSCAD" } else { "Starter STL geometry fallback" }
  }
}

$manifest | Export-Csv -LiteralPath (Join-Path $outDir "STL_MANIFEST.csv") -NoTypeInformation -Encoding ASCII

$mode = if ($HasOpenScad) { "OpenSCAD" } else { "Fallback" }
Write-Host "Generated $($manifest.Count) STL files in $outDir using $mode mode"
