///|
/// 09-dovetail-slider-jig
///
/// A parametric dovetail slider jig, demonstrating how to create
/// matching parts with clearances using extruded profiles.
///
/// Usage (stdout):
///   moon run --target native examples/09-dovetail-slider-jig -- > jig.step
///
/// Usage (write file):
///   moon run --target native examples/09-dovetail-slider-jig -- -o jig.step
async fn main {
  let base_args_spec = {
    "baseWidth": @cli.opt_double(
      50.0,
      help="Base block width (mm)",
      min=0.0,
      min_exclusive=true,
    ),
    "baseHeight": @cli.opt_double(
      15.0,
      help="Base block height (mm)",
      min=0.0,
      min_exclusive=true,
    ),
    "baseLength": @cli.opt_double(
      100.0,
      help="Base block length (mm)",
      min=0.0,
      min_exclusive=true,
    ),
    "dovetailWidth": @cli.opt_double(
      20.0,
      help="Dovetail neck width (mm)",
      min=0.0,
      min_exclusive=true,
    ),
    "dovetailHeight": @cli.opt_double(
      8.0,
      help="Dovetail groove depth (mm)",
      min=0.0,
      min_exclusive=true,
    ),
    "dovetailAngle": @cli.opt_double(
      14.0,
      help="Dovetail angle (degrees from vertical)",
      min=0.0,
      max=45.0,
    ),
    "sliderLength": @cli.opt_double(
      40.0,
      help="Slider length (mm)",
      min=0.0,
      min_exclusive=true,
    ),
    "sliderPos": @cli.opt_double(
      30.0,
      help="Slider position along the base (mm)",
    ),
    "clearance": @cli.opt_double(
      0.1,
      help="Clearance between slider and base (mm)",
      min=0.0,
    ),
  }
  let args_spec = @cli.with_translate(@common.with_output_args(base_args_spec))
  guard @cli.parse(
      args_spec,
      prog="09-dovetail-slider-jig",
      description="Generate a parametric dovetail slider jig.",
    )
    is Some(args) else {
    return
  }
  let base_width = args.double("baseWidth")
  let base_height = args.double("baseHeight")
  let base_length = args.double("baseLength")
  let dovetail_width = args.double("dovetailWidth")
  let dovetail_height = args.double("dovetailHeight")
  let dovetail_angle = args.double("dovetailAngle")
  let slider_length = args.double("sliderLength")
  let slider_pos = args.double("sliderPos")
  let clearance = args.double("clearance")
  let output = @common.output_paths(args)
  let (tx, ty, tz) = match args.translate_xyz() {
    Some(v) => v
    None => {
      @cli.eprintln("error: invalid translation")
      abort("")
    }
  }

  // Validate dovetail fits in base
  if dovetail_width >= base_width {
    @cli.eprintln("error: dovetailWidth must be less than baseWidth")
    abort("")
  }
  if dovetail_height >= base_height {
    @cli.eprintln("error: dovetailHeight must be less than baseHeight")
    abort("")
  }
  let base = dovetail_base(
      base_width, base_height, base_length, dovetail_width, dovetail_height, dovetail_angle,
    )
    .with_name("dovetail-base")
    .with_color(@cad.Rgb::purple())
    .translate(x=tx, y=ty, z=tz)

  // Slider position:
  // - Centered in X (same as base)
  // - Neck of slider is at Y = base_height / 2 (top of base)
  // - Slider points downwards into the groove.
  let slider = dovetail_slider(
      dovetail_width,
      dovetail_height,
      slider_length,
      dovetail_angle,
      clearance~,
    )
    .with_name("dovetail-slider")
    .with_color(@cad.Rgb::orange())
    .translate(x=tx, y=ty + base_height / 2.0, z=tz + slider_pos)
  let design = @cad.Design::new(
      name="dovetail-slider-jig",
      documentation=args.documentation(),
    )
    .add(base)
    .add(slider)
  @common.write_output(design, output)
}

///|
fn dovetail_base(
  width : Double,
  height : Double,
  length : Double,
  dovetail_width : Double,
  dovetail_height : Double,
  dovetail_angle : Double,
) -> @cad.Shape {
  let hw = width / 2.0
  let hh = height / 2.0
  let dhw = dovetail_width / 2.0
  let angle_rad = dovetail_angle * @math.PI / 180.0
  let dx = dovetail_height * @math.tan(angle_rad)

  // Construct a single CCW loop including the dovetail cutout at the top.
  let outer = [
    (-hw, hh),
    (-hw, -hh),
    (hw, -hh),
    (hw, hh),
    (dhw, hh),
    (dhw + dx, hh - dovetail_height),
    (-dhw - dx, hh - dovetail_height),
    (-dhw, hh),
  ]
  @cad.ExtrudedProfile::new(outer, length)
}

///|
fn dovetail_slider(
  width : Double,
  height : Double,
  length : Double,
  angle : Double,
  clearance~ : Double = 0.0,
) -> @cad.Shape {
  // Apply clearance: narrow the width and shorten the height.
  let c = clearance
  let w = width - 2.0 * c
  let h = height - c
  let angle_rad = angle * @math.PI / 180.0
  let dx = h * @math.tan(angle_rad)
  let half_width = w / 2.0
  let dt = [
    (-half_width, 0.0),
    (half_width, 0.0),
    (half_width + dx, h),
    (-half_width - dx, h),
  ]
  // Flip Y to match the groove orientation (neck at the top, pointing down).
  // We also reverse the order to maintain CCW orientation.
  let n = dt.length()
  let flipped = []
  for i in 0..