///|
/// 12-bifilar-electromagnet
///
/// Usage (stdout):
///   moon run --target native examples/12-bifilar-electromagnet -- > bifilar-electromagnet.step
///
/// Usage (write file):
///   moon run --target native examples/12-bifilar-electromagnet -- -o bifilar-electromagnet.step
async fn main {
  guard parse_params() is Some(params) else { return }

  //
  if params.report {
    report_electrical_geometry(params)
  }

  //
  match params.export_centerlines_path {
    None => ()
    Some(path) => export_helix_centerlines_json(params, path)
  }

  //
  match params.export_conductor_network_path {
    None => ()
    Some(path) => export_conductor_network_json(params, path)
  }

  //
  let mut design = @cad.Design::new(
    name="bifilar-electromagnet",
    snapping=params.snapping,
    documentation=params.documentation,
  )
  let wire_nodes = []
  let support_nodes = []
  // Add coil pairs if requested
  if !params.nocoil {
    for pair_num in 1..<=params.num_pairs {
      if params.debug {
        @cli.eprintln("Generating pair #\{pair_num} of \{params.num_pairs}")
      }
      let coil_pair = make_coil_pair(pair_num~, params~)
      wire_nodes..push(coil_pair.0).push(coil_pair.1)
    }
  }
  // Add cage if requested
  if !params.nocage {
    let (radius, height) = coil_turn_dimensions(params.num_pairs, params)
    let (wires, support) = make_bfem_cage(
      size=(radius, radius, height),
      params~,
    )
    wire_nodes.append(wires)
    support_nodes.append(support)
  }
  if params.union {
    design = design.add(@cad.union_all(wire_nodes, name="wires"))
    design = design.add(@cad.union_all(support_nodes, name="support"))
  } else {
    for node in wire_nodes {
      design = design.add(node)
    }
    for node in support_nodes {
      design = design.add(node)
    }
  }
  @common.write_output(design, params.output)
}

///|
fn make_coil_pair(
  pair_num~ : Int32,
  params~ : Params,
) -> (@cad.SceneNode, @cad.SceneNode) {
  let delta_z = (params.wire_gap + params.wire_width) /
    (params.num_pairs.to_double() - 1)
  let new_pos = (
    params.pos.0,
    params.pos.1,
    params.pos.2 + (pair_num - 1).to_double() * delta_z,
  )
  let params = { ..params, pos: new_pos, }

  //
  let start_angle = 180.0 *
    (pair_num.to_double() - 1) /
    params.num_pairs.to_double()
  // Create the two helices for the bifilar coil pair
  let color = @cad.Rgb::red()
  let coil1 = make_coil(start_angle, pair_num~, params~, color~).with_name(
    "bfem:coil:red:pair-\{pair_num}",
  )
  let color = @cad.Rgb::green()
  let coil2 = make_coil(start_angle + 180, pair_num~, params~, color~).with_name(
    "bfem:coil:green:pair-\{pair_num}",
  )
  return (coil1, coil2)
}

///|
/// coil_turn_dimensions returns the helix path X/Y radius and Z rise per turn
/// for the given coil pair index (1-based).
fn coil_turn_dimensions(pair_num : Int32, params : Params) -> (Double, Double) {
  let hw = params.wire_width / 2.0
  let radius = params.inner_diam / 2.0 +
    hw +
    (pair_num - 1).to_double() * (params.wire_gap + params.wire_width)
  let height = 2.0 * (params.wire_width + params.wire_gap)
  return (radius, height)
}

///|
fn square_wire_profile(hw : Double) -> @cad.SweepProfile {
  let wire_pts = [
    (-hw, 0.0, -hw),
    (hw, 0.0, -hw),
    (hw, 0.0, hw),
    (-hw, 0.0, hw),
  ]
  @cad.SweepProfile::new(wire_pts)
}

///|
fn make_coil(
  start_angle : Double,
  pair_num~ : Int32,
  params~ : Params,
  color~ : @cad.Rgb = @cad.Rgb::red(),
) -> @cad.SceneNode {
  let hw = params.wire_width / 2.0
  let (radius, height) = coil_turn_dimensions(pair_num, params)
  let helix_path = @cad.helix_path(
    pos=params.pos,
    size_per_turn=(radius, radius, height),
    num_turns=params.vert_turns,
    num_segs_per_turn=params.num_segs,
    start_angle~,
  )
  let wire_profile = square_wire_profile(hw)
  // The wire cross-section must follow the helical path.
  let coil_shape = @cad.SweptProfile::new(
    path=helix_path,
    profile=wire_profile,
    straight_outline=false,
  ).with_color(color)
  coil_shape
}