///|
/// 24-angled-drill-guide-block
///
/// A drill guide with configurable angles and bushing seat geometry.
///
/// Usage (stdout):
///   moon run --target native examples/24-angled-drill-guide-block -- --angle 15 --holeDiameter 9.5 > guide.step
async fn main {
  let base_args_spec = {
    "angle": @cli.opt_double(
      0.0,
      help="Angle from vertical (degrees)",
      min=-45.0,
      max=45.0,
    ),
    "holeDiameter": @cli.opt_double(
      9.5,
      help="Inner hole diameter for the drill bit (mm). 9.5mm is standard for pocket holes.",
      min=1.0,
    ),
    "outerDiameter": @cli.opt_double(
      20.0,
      help="Outer diameter of the guide body (mm)",
      min=5.0,
    ),
    "height": @cli.opt_double(
      40.0,
      help="Total height of the guide tube (mm)",
      min=10.0,
    ),
    "seatDiameter": @cli.opt_double(
      12.0,
      help="Diameter of the bushing seat (mm)",
      min=0.0,
    ),
    "seatDepth": @cli.opt_double(
      5.0,
      help="Depth of the bushing seat (mm)",
      min=0.0,
    ),
    "baseWidth": @cli.opt_double(
      60.0,
      help="Width of the mounting base (mm)",
      min=20.0,
    ),
    "baseLength": @cli.opt_double(
      40.0,
      help="Length of the mounting base (mm)",
      min=20.0,
    ),
    "baseThickness": @cli.opt_double(
      5.0,
      help="Thickness of the mounting base (mm)",
      min=2.0,
    ),
  }
  let args_spec = @cli.with_translate(@common.with_output_args(base_args_spec))
  guard @cli.parse(
      args_spec,
      prog="24-angled-drill-guide-block",
      description="Generate a parametric angled drill guide block.",
    )
    is Some(args) else {
    return
  }
  let angle = args.double("angle")
  let hole_dia = args.double("holeDiameter")
  let outer_dia = args.double("outerDiameter")
  let height = args.double("height")
  let seat_dia = args.double("seatDiameter")
  let seat_depth = args.double("seatDepth")
  let base_w = args.double("baseWidth")
  let base_l = args.double("baseLength")
  let base_t = args.double("baseThickness")
  let output = @common.output_paths(args)
  let (tx, ty, tz) = match args.translate_xyz() {
    Some(v) => v
    None => {
      @cli.eprintln("error: invalid translation")
      return
    }
  }
  let nodes = drill_guide(
    angle, hole_dia, outer_dia, height, seat_dia, seat_depth, base_w, base_l, base_t,
  )
  let mut design = @cad.Design::new(
    name="angled-drill-guide",
    documentation=args.documentation(),
  )
  for node in nodes {
    design = design.add(node.translate(x=tx, y=ty, z=tz))
  }
  @common.write_output(design, output)
}

///|
fn drill_guide(
  angle : Double,
  id : Double,
  od : Double,
  h : Double,
  sd : Double,
  s_depth : Double,
  bw : Double,
  bl : Double,
  bt : Double,
) -> Array[@cad.SceneNode] {
  // 1. The Mounting Base
  // Create a rounded base with four mounting holes and one central angled hole.
  let base_outer = @cad.Profile2D::rounded_rect(bw, bl, 5.0)
  let mounting_hole_dia = 5.0
  let mounting_hole_profile = @cad.Profile2D::circle(mounting_hole_dia)
  let mounting_hole_locs = [
    (10.0, 10.0),
    (bw - 10.0, 10.0),
    (bw - 10.0, bl - 10.0),
    (10.0, bl - 10.0),
  ]

  // Angle hole in base (projection of the guide hole)
  // Width remains 'id', length becomes 'id / cos(angle)'
  let angle_rad = angle * @math.PI / 180.0
  let hole_l = id / @math.cos(angle_rad)
  let guide_hole = @cad.Profile2D::ellipse(id, hole_l)
    .map(fn(p) { (p.0 + bw / 2.0, p.1 + bl / 2.0) })
    .rev()
  let base_profile = @cad.Profile2D::new(base_outer)
    .add_holes_at(mounting_hole_locs, mounting_hole_profile)
    .add_hole(guide_hole)
  let base = @cad.ExtrudedProfile::new(
      base_profile.outer,
      bt,
      holes=base_profile.holes,
    )
    .with_name("base")
    .with_color(@cad.Rgb::blue())
    .translate(x=-bw / 2.0, y=-bl / 2.0)

  // 2. The Guide Tube
  // Extend the tube slightly so it penetrates the base correctly.
  let extra = od / 2.0 * @math.sin(angle_rad.abs())
  let main_guide_h = h - s_depth + extra
  let guide_body = @cad.tube(od, id, main_guide_h)
    .with_name("guide-body")
    .with_color(@cad.Rgb::cyan())
    .translate(z=-extra) // Move start point down
  let seat_body = if s_depth > 0.0 && sd > id {
    @cad.tube(od, sd, s_depth)
    .with_name("bushing-seat")
    .with_color(@cad.Rgb::cyan())
    .translate(z=h - s_depth)
  } else {
    @cad.tube(od, id, s_depth)
    .with_name("guide-top")
    .with_color(@cad.Rgb::cyan())
    .translate(z=h - s_depth)
  }

  // Group guide parts and rotate
  let guide_parts = [guide_body, seat_body]
  let rotated_guide = guide_parts.map(fn(node) {
    let (tx, ty, tz) = @cad.rotate_point(
      (node.tx, node.ty, node.tz),
      angle,
      0.0,
      0.0,
    )
    let new_node = node
      .translate(x=tx - node.tx, y=ty - node.ty, z=tz - node.tz)
      .rotate_x(angle)
    // Place so the rotation point (0,0,0 local) is at top of base
    new_node.translate(z=bt)
  })
  let nodes = [base]
  nodes.push_iter(rotated_guide.iter())
  nodes
}