// SPDX-License-Identifier: Apache-2.0

///|
/// Work budgets for automatic repair. They do not guarantee process memory or
/// running time. Hole rings additionally need one incoming and outgoing edge,
/// a planar simple polygon and nondegenerate patch triangles.
pub(all) struct RepairLimits {
  max_faces : Int32
  max_boundary_edges : Int32
  max_hole_vertices : Int32
  max_holes : Int32
}

///|
pub fn RepairLimits::default() -> RepairLimits {
  {
    max_faces: 2000000,
    max_boundary_edges: 10000,
    max_hole_vertices: 512,
    max_holes: 128,
  }
}

///|
/// Repair finite input with checked options and bounded, simple planar holes.
/// Refused repairs return an error without modifying the input mesh. This is
/// not a general self-intersection, boolean or manufacturing repair operation.
pub fn repair_checked(
  mesh : Mesh,
  options : RepairOptions,
  limits~ : RepairLimits = RepairLimits::default(),
) -> Result[RepairResult, String] {
  if mesh_option_error(mesh, options.tolerance) is Some(message) {
    return Err(message)
  }
  if limits.max_faces < 1 ||
    limits.max_boundary_edges < 1 ||
    limits.max_hole_vertices < 3 ||
    limits.max_hole_vertices > 4096 ||
    limits.max_holes < 1 {
    return Err("invalid repair limits")
  }
  if mesh.triangle_count() > limits.max_faces {
    return Err("resource_limit: repair max_faces")
  }
  repair_inner(mesh, options, limits)
}

///|
/// Compatibility repair API. Refused/invalid input yields an unchanged
/// snapshot with zero change counts; use repair_checked() for an explanation.
pub fn repair(mesh : Mesh, options : RepairOptions) -> RepairResult {
  match repair_checked(mesh, options) {
    Ok(result) => result
    Err(_) =>
      {
        mesh: Mesh::new(mesh.triangles.copy()),
        removed_degenerate: 0,
        removed_duplicates: 0,
        filled_holes: 0,
        added_triangles: 0,
        recomputed_normals: 0,
        flipped_faces: 0,
        reoriented_components: 0,
      }
  }
}

///|
fn fill_holes_safe(
  triangles : Array[Triangle],
  ids : Array[(Int32, Int32, Int32)],
  limits : RepairLimits,
) -> Result[(Array[Triangle], Int32), String] {
  let counts : Map[(Int32, Int32), Int32] = Map([])
  let directions : Map[(Int32, Int32), (Int32, Int32)] = Map([])
  let positions : Map[Int32, Vec3] = Map([])
  for i, (a, b, c) in ids {
    let triangle = triangles[i]
    positions[a] = triangle.a
    positions[b] = triangle.b
    positions[c] = triangle.c
    for (start, finish) in [(a, b), (b, c), (c, a)] {
      let key = edge_key(start, finish)
      counts[key] = counts.get(key).unwrap_or(0) + 1
      directions[key] = (start, finish)
    }
  }
  let outgoing : Map[Int32, Int32] = Map([])
  let incoming : Map[Int32, Int32] = Map([])
  for key, count in counts {
    if count > 2 {
      return Err("unsafe hole repair: non-manifold edge")
    }
    if count != 1 {
      continue
    }
    let (a, b) = directions[key]
    if a == b || outgoing.contains(a) || incoming.contains(b) {
      return Err("unsafe hole repair: branching boundary")
    }
    outgoing[a] = b
    incoming[b] = a
    if outgoing.length() > limits.max_boundary_edges {
      return Err("resource_limit: repair max_boundary_edges")
    }
  }
  for vertex, _ in outgoing {
    if !incoming.contains(vertex) {
      return Err("unsafe hole repair: open boundary chain")
    }
  }
  let used : Map[Int32, Unit] = Map([])
  let patch : Array[Triangle] = []
  let mut holes = 0
  for start, _ in outgoing {
    if used.contains(start) {
      continue
    }
    let ring : Array[Vec3] = []
    let mut current = start
    while !used.contains(current) {
      used[current] = ()
      ring.push(positions[current])
      if ring.length() > limits.max_hole_vertices {
        return Err("resource_limit: repair max_hole_vertices")
      }
      current = match outgoing.get(current) {
        Some(next) => next
        None => return Err("unsafe hole repair: open boundary chain")
      }
    }
    if current != start || ring.length() < 3 {
      return Err("unsafe hole repair: non-simple ring")
    }
    holes += 1
    if holes > limits.max_holes {
      return Err("resource_limit: repair max_holes")
    }
    let faces = match triangulate_polygon(ring) {
      Ok(faces) => faces
      Err(message) => return Err("unsafe hole repair: " + message)
    }
    for (a, b, c) in faces {
      let triangle = Triangle::from_vertices(ring[a], ring[c], ring[b])
      if triangle_is_degenerate(triangle) {
        return Err("unsafe hole repair: degenerate patch")
      }
      if triangles.length().to_int64() + patch.length().to_int64() + 1L >
        limits.max_faces.to_int64() {
        return Err("resource_limit: repair max_faces after hole filling")
      }
      patch.push(triangle)
    }
  }
  let result = triangles.copy()
  for triangle in patch {
    result.push(triangle)
  }
  Ok((result, holes))
}