///|
// Analytic square-hole washer: one cylindrical outer face, two planar end
// faces with a square hole, and four planar square-hole walls. This is the
// topology of the DIN 440 V large washers (7 faces, 15 edges, 10 vertices).

///|
fn shw_line_edge(
  ctx : StepContext,
  a_cp : @step.Ref,
  a_vp : @step.Ref,
  _b_cp : @step.Ref,
  b_vp : @step.Ref,
  a_pos : Vec3,
  b_pos : Vec3,
) -> @step.Ref {
  let direction = v_norm(v_sub(b_pos, a_pos))
  let dir_ref = ctx.direction(direction.0, direction.1, direction.2)
  let vector_ref = ctx.vector(dir_ref, 1.0)
  let line_ref = ctx.line(a_cp, vector_ref)
  ctx.edge_curve(a_vp, b_vp, line_ref, true)
}

///|
fn shw_plane(
  ctx : StepContext,
  origin : Vec3,
  axis : Vec3,
  reference : Vec3,
) -> @step.Ref {
  let origin_cp = ctx.cartesian_point(origin.0, origin.1, origin.2)
  let axis_ref = ctx.direction(axis.0, axis.1, axis.2)
  let ref_ref = ctx.direction(reference.0, reference.1, reference.2)
  let placement = ctx.axis2_placement_3d(origin_cp, axis_ref, ref_ref)
  ctx.add(@repository.any_entity_plane(@geometry.Plane::new("", placement)))
}

///|
/// compile_square_hole_washer emits one closed solid for a centered square-hole
/// washer.
pub fn compile_square_hole_washer(
  ctx : StepContext,
  node : SceneNode,
  w : SquareHoleWasher,
  reverse~ : Bool = false,
  parent~ : Transform? = None,
) -> Array[@step.Ref] {
  let r = w.outer_diameter_mm / 2.0
  let a = w.square_side_mm
  let h = w.thickness_mm
  if r <= 0.0 || a <= 0.0 || h <= 0.0 || a * 0.5 * 1.4142135623730951 >= r {
    return []
  }
  let xform = node_transform(node, parent)
  let up = xform.apply_dir((0.0, 0.0, 1.0))
  let down = xform.apply_dir((0.0, 0.0, -1.0))
  let right = xform.apply_dir((1.0, 0.0, 0.0))
  let dir_up = ctx.direction(up.0, up.1, up.2)
  let dir_right = ctx.direction(right.0, right.1, right.2)

  // Outer cylinder and its seam.
  let center_b = xform.apply((0.0, 0.0, 0.0))
  let center_t = xform.apply((0.0, 0.0, h))
  let cp_b = ctx.cartesian_point(center_b.0, center_b.1, center_b.2)
  let cp_t = ctx.cartesian_point(center_t.0, center_t.1, center_t.2)
  let a2p_b = ctx.axis2_placement_3d(cp_b, dir_up, dir_right)
  let a2p_t = ctx.axis2_placement_3d(cp_t, dir_up, dir_right)
  let radius_text = @step.fmt_num(r)
  let cylinder = ctx.add(
    @repository.any_entity_cylindrical_surface(
      @geometry.CylindricalSurface::new("", a2p_b, radius_text),
    ),
  )
  let circle_b = ctx.add(
    @repository.any_entity_circle(@geometry.Circle::new("", a2p_b, radius_text)),
  )
  let circle_t = ctx.add(
    @repository.any_entity_circle(@geometry.Circle::new("", a2p_t, radius_text)),
  )
  let seam_b = xform.apply((r, 0.0, 0.0))
  let seam_t = xform.apply((r, 0.0, h))
  let cp_sb = ctx.cartesian_point(seam_b.0, seam_b.1, seam_b.2)
  let cp_st = ctx.cartesian_point(seam_t.0, seam_t.1, seam_t.2)
  let vp_sb = ctx.vertex_point(cp_sb)
  let vp_st = ctx.vertex_point(cp_st)
  let seam = ctx.edge_curve(
    vp_sb,
    vp_st,
    ctx.line(cp_sb, ctx.vector(dir_up, 1.0)),
    true,
  )
  let edge_cb = ctx.edge_curve(vp_sb, vp_sb, circle_b, true)
  let edge_ct = ctx.edge_curve(vp_st, vp_st, circle_t, true)
  let outer_loop = ctx.edge_loop([
    ctx.oriented_edge(seam, true),
    ctx.oriented_edge(edge_ct, false),
    ctx.oriented_edge(seam, false),
    ctx.oriented_edge(edge_cb, true),
  ])
  let faces : Array[@step.Ref] = []
  faces.push(
    ctx.advanced_face(
      [ctx.face_outer_bound(outer_loop, true)],
      cylinder,
      !reverse,
    ),
  )

  // Square hole vertices: bottom 0..3, top 4..7.
  let half = a / 2.0
  let corners : Array[Vec2] = [
    (half, half),
    (-half, half),
    (-half, -half),
    (half, -half),
  ]
  let pts : Array[Vec3] = []
  for z in [0.0, h] {
    for p in corners {
      pts.push(xform.apply((p.0, p.1, z)))
    }
  }
  let cp_refs = pts.map(fn(p) { ctx.cartesian_point(p.0, p.1, p.2) })
  let vp_refs = cp_refs.map(fn(cp) { ctx.vertex_point(cp) })
  let bottom_edges : Array[@step.Ref] = []
  let top_edges : Array[@step.Ref] = []
  let vertical_edges : Array[@step.Ref] = []
  for i in 0..<4 {
    let j = (i + 1) % 4
    bottom_edges.push(
      shw_line_edge(
        ctx,
        cp_refs[i],
        vp_refs[i],
        cp_refs[j],
        vp_refs[j],
        pts[i],
        pts[j],
      ),
    )
    top_edges.push(
      shw_line_edge(
        ctx,
        cp_refs[4 + i],
        vp_refs[4 + i],
        cp_refs[4 + j],
        vp_refs[4 + j],
        pts[4 + i],
        pts[4 + j],
      ),
    )
    vertical_edges.push(
      shw_line_edge(
        ctx,
        cp_refs[i],
        vp_refs[i],
        cp_refs[4 + i],
        vp_refs[4 + i],
        pts[i],
        pts[4 + i],
      ),
    )
  }

  // Square-hole walls, outward normals pointing into the hole.
  for i in 0..<4 {
    let j = (i + 1) % 4
    let p0 = pts[i]
    let p1 = pts[j]
    let p2 = pts[4 + i]
    let edge_dir = v_sub(p1, p0)
    let expected = v_norm(v_cross(edge_dir, v_sub(p2, p0)))
    let wall_plane = shw_plane(
      ctx,
      p0,
      v_scale(expected, -1.0),
      v_norm(v_sub(p2, p0)),
    )
    let wall_loop = ctx.edge_loop([
      ctx.oriented_edge(vertical_edges[i], true),
      ctx.oriented_edge(top_edges[i], true),
      ctx.oriented_edge(vertical_edges[j], false),
      ctx.oriented_edge(bottom_edges[i], false),
    ])
    faces.push(
      ctx.advanced_face(
        [ctx.face_outer_bound(wall_loop, true)],
        wall_plane,
        !reverse,
      ),
    )
  }

  // End caps: outer circle plus the square hole loop.
  let bottom_hole = ctx.edge_loop([
    ctx.oriented_edge(bottom_edges[0], true),
    ctx.oriented_edge(bottom_edges[1], true),
    ctx.oriented_edge(bottom_edges[2], true),
    ctx.oriented_edge(bottom_edges[3], true),
  ])
  let top_hole = ctx.edge_loop([
    ctx.oriented_edge(top_edges[3], false),
    ctx.oriented_edge(top_edges[2], false),
    ctx.oriented_edge(top_edges[1], false),
    ctx.oriented_edge(top_edges[0], false),
  ])
  let bottom_outer = ctx.edge_loop([ctx.oriented_edge(edge_cb, false)])
  let top_outer = ctx.edge_loop([ctx.oriented_edge(edge_ct, true)])
  faces.push(
    add_plane_face_from_loops(
      ctx,
      cp_b,
      down,
      right,
      bottom_outer,
      [bottom_hole],
      reverse~,
    ),
  )
  faces.push(
    add_plane_face_from_loops(
      ctx,
      cp_t,
      up,
      right,
      top_outer,
      [top_hole],
      reverse~,
    ),
  )
  faces
}

///|
fn shw_area(w : SquareHoleWasher) -> Double {
  let r = w.outer_diameter_mm / 2.0
  @math.PI * r * r - w.square_side_mm * w.square_side_mm
}

///|
fn an_square_hole_inventory(w : SquareHoleWasher) -> SurfaceInventory {
  if shw_area(w) <= CAD_DEGENERATE_EPS || w.thickness_mm <= 0.0 {
    return an_inventory(0, 0, 0, 0, 0, 0, 0, 0)
  }
  an_inventory(6, 1, 0, 0, 0, 0, 0, 0)
}

///|
fn an_square_hole_section(
  w : SquareHoleWasher,
  z : Double,
) -> Result[Section, AnalysisError] {
  if z < -CAD_LENGTH_EPS || z > w.thickness_mm + CAD_LENGTH_EPS {
    return Err(EmptySection)
  }
  let r = w.outer_diameter_mm / 2.0
  let a = w.square_side_mm
  let area = shw_area(w)
  if area <= CAD_DEGENERATE_EPS {
    return Err(EmptySection)
  }
  let inner = a / 2.0
  Ok({
    z_mm: z,
    area_mm2: area,
    perimeter_mm: 2.0 * @math.PI * r + 4.0 * a,
    outer_radius_mm: r,
    inner_radius_mm: inner,
    rings: [{ inner_radius_mm: inner, outer_radius_mm: r, }],
  })
}

///|
fn square_hole_mesh(
  w : SquareHoleWasher,
  segments~ : Int32 = 96,
) -> (Array[Vec3], Array[Array[Int32]]) {
  let n = if segments < 16 { 16 } else { segments }
  let r = w.outer_diameter_mm / 2.0
  let half = w.square_side_mm / 2.0
  let h = w.thickness_mm
  let outer_b : Array[Vec3] = []
  let outer_t : Array[Vec3] = []
  let inner_b : Array[Vec3] = []
  let inner_t : Array[Vec3] = []
  for i in 0.. sy.abs() { cx.abs() } else { sy.abs() }
    let scale = half / max_abs
    outer_b.push((r * cx, r * sy, 0.0))
    outer_t.push((r * cx, r * sy, h))
    inner_b.push((scale * cx, scale * sy, 0.0))
    inner_t.push((scale * cx, scale * sy, h))
  }
  let verts : Array[Vec3] = []
  for p in outer_b {
    verts.push(p)
  }
  for p in inner_b {
    verts.push(p)
  }
  for p in outer_t {
    verts.push(p)
  }
  for p in inner_t {
    verts.push(p)
  }
  let base_ob = 0
  let base_ib = n
  let base_ot = 2 * n
  let base_it = 3 * n
  let faces : Array[Array[Int32]] = []
  for i in 0..