///|
/// 23-cable-grommet-ring
///
/// A parametric grommet for cable pass-throughs with lip and retention features.
///
/// Usage (stdout):
///   moon run --target native examples/23-cable-grommet-ring -- --innerDiameter 10 --panelThickness 3 > grommet.step
async fn main {
  let base_args_spec = {
    "innerDiameter": @cli.opt_double(
      10.0,
      help="Inner hole diameter for the cable (mm)",
      min=1.0,
    ),
    "panelDiameter": @cli.opt_double(
      16.0,
      help="Diameter of the hole in the panel (mm)",
      min=2.0,
    ),
    "lipDiameter": @cli.opt_double(
      22.0,
      help="Outer diameter of the grommet lips (mm)",
      min=3.0,
    ),
    "panelThickness": @cli.opt_double(
      3.0,
      help="Thickness of the panel it fits into (mm)",
      min=0.5,
    ),
    "lipThickness": @cli.opt_double(
      2.0,
      help="Thickness of the retention lips (mm)",
      min=0.5,
    ),
    "split": @cli.flag(help="Split the grommet into two halves"),
  }
  let args_spec = @cli.with_translate(@common.with_output_args(base_args_spec))
  guard @cli.parse(
      args_spec,
      prog="23-cable-grommet-ring",
      description="Generate a parametric cable grommet.",
    )
    is Some(args) else {
    return
  }
  let id = args.double("innerDiameter")
  let pd = args.double("panelDiameter")
  let ld = args.double("lipDiameter")
  let pt = args.double("panelThickness")
  let lt = args.double("lipThickness")
  let split = args.flag("split")
  let output = @common.output_paths(args)
  let (tx, ty, tz) = match args.translate_xyz() {
    Some(v) => v
    None => {
      @cli.eprintln("error: invalid translation")
      return
    }
  }
  if id >= pd {
    @cli.eprintln("error: innerDiameter must be less than panelDiameter")
    return
  }
  if pd >= ld {
    @cli.eprintln("error: panelDiameter must be less than lipDiameter")
    return
  }
  let nodes = cable_grommet(id, pd, ld, pt, lt, split~)
  let mut design = @cad.Design::new(
    name="cable-grommet",
    documentation=args.documentation(),
  )
  for node in nodes {
    design = design.add(node.translate(x=tx, y=ty, z=tz))
  }
  @common.write_output(design, output)
}

///|
fn cable_grommet(
  id : Double,
  pd : Double,
  ld : Double,
  pt : Double,
  lt : Double,
  split~ : Bool = false,
) -> Array[@cad.SceneNode] {
  if !split {
    // Single part
    let bottom_lip = @cad.tube(ld, id, lt)
      .with_name("bottom-lip")
      .with_color(@cad.Rgb::blue())
    let groove = @cad.tube(pd, id, pt)
      .with_name("groove")
      .with_color(@cad.Rgb::cyan())
      .translate(z=lt)
    let top_lip = @cad.tube(ld, id, lt)
      .with_name("top-lip")
      .with_color(@cad.Rgb::blue())
      .translate(z=lt + pt)
    [bottom_lip, groove, top_lip]
  } else {
    // Two halves
    let nodes = []
    let half_spacing = 5.0
    for i in 0..<2 {
      let ty = if i == 0 { half_spacing } else { -half_spacing }
      let name_suffix = if i == 0 { "a" } else { "b" }
      let rotation = if i == 0 { 0.0 } else { 180.0 }

      // We can't easily "cut" a tube in half with current primitives
      // unless we define a semi-circle profile.
      let start_angle = 0.0
      let end_angle = @math.PI
      let outer_arc = @cad.Profile2D::arc(ld, start_angle, end_angle)
      let inner_arc = @cad.Profile2D::arc(id, start_angle, end_angle).rev()
      let half_lip_profile = [..outer_arc, ..inner_arc]
      let outer_groove_arc = @cad.Profile2D::arc(pd, start_angle, end_angle)
      let half_groove_profile = [..outer_groove_arc, ..inner_arc]
      let b_lip = @cad.ExtrudedProfile::new(half_lip_profile, lt)
        .with_name("bottom-lip-\{name_suffix}")
        .with_color(@cad.Rgb::blue())
        .rotate_z(rotation)
        .translate(y=ty)
      let grv = @cad.ExtrudedProfile::new(half_groove_profile, pt)
        .with_name("groove-\{name_suffix}")
        .with_color(@cad.Rgb::cyan())
        .rotate_z(rotation)
        .translate(y=ty, z=lt)
      let t_lip = @cad.ExtrudedProfile::new(half_lip_profile, lt)
        .with_name("top-lip-\{name_suffix}")
        .with_color(@cad.Rgb::blue())
        .rotate_z(rotation)
        .translate(y=ty, z=lt + pt)
      nodes.push(b_lip)
      nodes.push(grv)
      nodes.push(t_lip)
    }
    nodes
  }
}