///|
/// Cage/exits conductor metrics (non-geometry utilities).
///
/// This file intentionally keeps analysis/estimation code separate from the
/// cage geometry construction in bfem-cage.mbt.

///|
/// ConductorTotals accumulates simple DC-ish metrics for prismatic conductors.
///
/// Units:
/// - length_mm: sum of conductor lengths in mm (for extrusions, the extrude height)
/// - volume_mm3: sum of volumes in mm^3
/// - mm_over_mm2: sum of (length_mm / area_mm2), used for Rdc estimates
struct ConductorTotals {
  length_mm : Double
  volume_mm3 : Double
  mm_over_mm2 : Double
  segments : Int32
}

///|
struct CageConductorMetrics {
  cage : ConductorTotals
  exit_wires : ConductorTotals
}

///|
fn totals_zero() -> ConductorTotals {
  { length_mm: 0.0, volume_mm3: 0.0, mm_over_mm2: 0.0, segments: 0, }
}

///|
fn totals_add_prism(
  t : ConductorTotals,
  area_mm2 : Double,
  height_mm : Double,
) -> ConductorTotals {
  if area_mm2 <= 0.0 || height_mm <= 0.0 {
    return t
  }
  {
    length_mm: t.length_mm + height_mm,
    volume_mm3: t.volume_mm3 + area_mm2 * height_mm,
    mm_over_mm2: t.mm_over_mm2 + height_mm / area_mm2,
    segments: t.segments + 1,
  }
}

///|
fn polygon_area_mm2(points : Array[Vec2]) -> Double {
  if points.length() < 3 {
    return 0.0
  }
  let mut sum = 0.0
  let n = points.length()
  for i in 0.. Double {
  if radius <= 0.0 || segments < 3 {
    return 0.0
  }
  let pts = []
  for i in 1..<=segments {
    let angle = -2.0 * @math.PI * i.to_double() / segments.to_double()
    pts.push((radius * @math.cos(angle), radius * @math.sin(angle)))
  }
  polygon_area_mm2(pts)
}

///|
pub fn estimate_bfem_cage_conductors(
  size~ : Vec3,
  params~ : Params,
) -> CageConductorMetrics {
  let ctx = make_cage_context(size, params)
  let mut cage = totals_zero()
  let mut exit_wires = totals_zero()

  // axial connectors
  for i in 0..<(2 * ctx.npairs) {
    let i_d = i.to_double()
    let inner_start_angle = i_d * ctx.rail_angle_delta
    let inner_end_angle = (i_d + 1) * ctx.rail_angle_delta -
      2 * ctx.inner_dtheta
    let inner_angle_delta = (inner_end_angle - inner_start_angle) /
      ctx.segments_per_rail.to_double()
    let outer_start_angle = i_d * ctx.rail_angle_delta
    let outer_end_angle = (i_d + 1) * ctx.rail_angle_delta -
      2 * ctx.outer_dtheta
    let outer_angle_delta = (outer_end_angle - outer_start_angle) /
      ctx.segments_per_rail.to_double()
    if inner_angle_delta <= 0 || outer_angle_delta <= 0 {
      abort("wire_gap too large with given num_pairs and size")
    }
    let polygon = Array(capacity=ctx.segments_per_rail * 2 + 2)
    for j in 0..<=ctx.segments_per_rail {
      push_arc_point(
        ctx.center,
        inner_start_angle,
        inner_angle_delta,
        ctx.inner_radius,
        j,
        polygon,
      )
    }
    for j in 0..<=ctx.segments_per_rail {
      push_arc_point(
        ctx.center,
        outer_start_angle,
        outer_angle_delta,
        ctx.outer_radius,
        ctx.segments_per_rail - j,
        polygon,
      )
    }
    let area = polygon_area_mm2(polygon)
    let height = if i == 0 {
      ctx.extrude_height +
      1.5 * params.wire_width +
      params.front_thickness +
      params.back_thickness
    } else {
      ctx.max_axial_connector_top_zs -
      ctx.lower_z +
      params.front_thickness +
      params.back_thickness
    }
    cage = totals_add_prism(cage, area, height)
  }

  // connector pre-calcs (same as make_bfem_cage)
  let rotations = Array(capacity=ctx.npairs)
  let line_lengths = Array(capacity=ctx.npairs)
  let zs = Array(capacity=ctx.npairs)
  for i in 0.. 0 {
      let top_hww = 0.5 * @math.asin(params.wire_width / (2 * ctx.outer_radius))
      top_angle = rotation - ctx.rail_angle_delta / 2 + top_hww
    }
    let top_helix_z = z + ctx.coil_height - 2 * params.wire_width
    let over_height = ctx.max_axial_connector_top_zs -
      top_helix_z +
      params.back_thickness
    cage = totals_add_prism(
      cage,
      params.wire_width * params.wire_width,
      over_height,
    )

    // back/top red over (arc polygon + 2 points)
    let over_start_angle = -(i_d + 1) * ctx.rail_angle_delta
    let polygon = gen_inner_arc_points(ctx, over_start_angle, inner_angle_delta)
    let sx4 = params.pos.0 + connector_radius * @math.cos(top_angle)
    let sy4 = params.pos.1 + connector_radius * @math.sin(top_angle)
    let sx2 = params.pos.0 +
      (connector_radius + params.wire_width) * @math.cos(top_angle)
    let sy2 = params.pos.1 +
      (connector_radius + params.wire_width) * @math.sin(top_angle)
    let sx3 = sx2 + params.wire_width * @math.cos(top_angle - @math.PI / 2)
    let sy3 = sy2 + params.wire_width * @math.sin(top_angle - @math.PI / 2)
    polygon.push((sx2, sy2))
    polygon.push((sx3, sy3))
    let area = polygon_area_mm2(polygon)
    let top_thickness = if i_d == ctx.num_pairs {
      over_height
    } else {
      over_height - params.wire_width - params.wire_gap
    }
    cage = totals_add_prism(cage, area, top_thickness - params.wire_gap)

    // red helix link (only for i > 0)
    if i > 0 {
      let sx5 = params.pos.0 + connector_radius * @math.cos(rotation)
      let sy5 = params.pos.1 + connector_radius * @math.sin(rotation)
      let sx6 = params.pos.0 +
        (connector_radius + params.wire_width) * @math.cos(rotation)
      let sy6 = params.pos.1 +
        (connector_radius + params.wire_width) * @math.sin(rotation)
      let polygon = [(sx5, sy5), (sx6, sy6), (sx2, sy2), (sx4, sy4)]
      let area = polygon_area_mm2(polygon)
      cage = totals_add_prism(cage, area, params.wire_width)
    }

    // front/bottom green connector (opposite)
    let polygon = gen_inner_arc_points(
      ctx,
      inner_start_angle + @math.PI,
      inner_angle_delta,
    )
    let sx = params.pos.0 + connector_radius * @math.cos(rotation + @math.PI)
    let sy = params.pos.1 + connector_radius * @math.sin(rotation + @math.PI)
    let tx = params.pos.0 + connector_radius * @math.cos(t_angle + @math.PI)
    let ty = params.pos.1 + connector_radius * @math.sin(t_angle + @math.PI)
    polygon.push((tx, ty))
    polygon.push((sx, sy))
    let area = polygon_area_mm2(polygon)
    cage = totals_add_prism(cage, area, bottom_thickness)

    // even / green back-top segments
    let connector_dtheta = @math.asin(params.wire_gap / (2 * connector_radius))
    let mut top_angle2 = top_angle
    if ni >= ctx.npairs {
      top_angle2 = rotation - connector_dtheta
    }
    // outer final connector rectangle (only on last pair)
    if ni >= ctx.npairs {
      cage = totals_add_prism(
        cage,
        params.wire_width * params.wire_width,
        params.wire_width,
      )
      // inner flat-top output connector
      let flat_top_height = 2.5 * params.wire_width + params.back_thickness
      // polygon area is not constant; approximate with its actual quad area
      let angle_diff = ctx.rail_angle_delta - 2 * ctx.outer_dtheta
      let sx4 = params.pos.0 +
        connector_radius * @math.cos(top_angle2 + @math.PI)
      let sy4 = params.pos.1 +
        connector_radius * @math.sin(top_angle2 + @math.PI)
      let sx1 = params.pos.0 +
        connector_radius * @math.cos(top_angle2 + @math.PI - angle_diff)
      let sy1 = params.pos.1 +
        connector_radius * @math.sin(top_angle2 + @math.PI - angle_diff)
      let ew_inner_radius = connector_radius -
        0.6 * params.exit_wire_separation -
        params.exit_wire_diameter
      let s2 = vertex_at(
        center=(params.pos.0, params.pos.1),
        radius=ew_inner_radius,
        angle=top_angle2 + @math.PI,
      )
      let s3 = vertex_at(
        center=(params.pos.0, params.pos.1),
        radius=ew_inner_radius,
        angle=top_angle2 + @math.PI - angle_diff,
      )
      let polygon = [(sx4, sy4), (sx1, sy1), s3, s2]
      let area = polygon_area_mm2(polygon)
      cage = totals_add_prism(cage, area, flat_top_height)

      // exit wires: two vertical cylinders
      if !params.nowires {
        let r = params.exit_wire_diameter / 2
        let area = circle_polygon_area_mm2(r, 12)
        exit_wires = totals_add_prism(exit_wires, area, params.connector_length)
        exit_wires = totals_add_prism(exit_wires, area, params.connector_length)
      }
    }
    if ni < ctx.npairs {
      // green vertical post: square cross-section
      cage = totals_add_prism(
        cage,
        params.wire_width * params.wire_width,
        over_height,
      )

      // green over part: arc polygon + 2 points
      let over_start_angle = -(i_d + 1) * ctx.rail_angle_delta + @math.PI
      let polygon = gen_inner_arc_points(
        ctx, over_start_angle, inner_angle_delta,
      )
      // post.top_p0 and post.top_p1 correspond to (sx2,sy2) and (sx3,sy3)
      // for the even coil post at angle top_angle2+PI.
      let sx2 = params.pos.0 +
        (connector_radius + params.wire_width) *
        @math.cos(top_angle2 + @math.PI)
      let sy2 = params.pos.1 +
        (connector_radius + params.wire_width) *
        @math.sin(top_angle2 + @math.PI)
      let sx3 = sx2 + params.wire_width * @math.cos(top_angle2 + @math.PI / 2)
      let sy3 = sy2 + params.wire_width * @math.sin(top_angle2 + @math.PI / 2)
      polygon.push((sx2, sy2))
      polygon.push((sx3, sy3))
      let area = polygon_area_mm2(polygon)
      cage = totals_add_prism(cage, area, top_thickness - params.wire_gap)
    }
    if ni > 1 {
      // green helix link wedge (quadrilateral swept in Z)
      let start_angle = top_angle2 + @math.PI
      let end_angle = rotation + @math.PI
      let sx5 = params.pos.0 + connector_radius * @math.cos(end_angle)
      let sy5 = params.pos.1 + connector_radius * @math.sin(end_angle)
      let sx6 = params.pos.0 +
        (connector_radius + params.wire_width) * @math.cos(end_angle)
      let sy6 = params.pos.1 +
        (connector_radius + params.wire_width) * @math.sin(end_angle)
      let sx2 = params.pos.0 +
        (connector_radius + params.wire_width) * @math.cos(start_angle)
      let sy2 = params.pos.1 +
        (connector_radius + params.wire_width) * @math.sin(start_angle)
      let sx4 = params.pos.0 + connector_radius * @math.cos(start_angle)
      let sy4 = params.pos.1 + connector_radius * @math.sin(start_angle)
      let polygon = [(sx5, sy5), (sx6, sy6), (sx2, sy2), (sx4, sy4)]
      let area = polygon_area_mm2(polygon)
      cage = totals_add_prism(cage, area, params.wire_width)
    }
  }
  { cage, exit_wires, }
}