// Shared-edge components, local consistency, and signed shell orientation.

///|
pub(all) enum WindingOrientation {
  PositiveVolume
  NegativeVolume
  Indeterminate
} derive(Eq)

///|
pub extend WindingOrientation with Eq::{not_equal, equal}

///|
pub fn WindingOrientation::label(self : WindingOrientation) -> String {
  match self {
    PositiveVolume => "正体积方向"
    NegativeVolume => "负体积方向"
    Indeterminate => "朝向未确定"
  }
}

///|
pub(all) struct WindingComponent {
  index : Int
  /// Original face indices, for locating the component in the input mesh.
  faces : Array[Int]
  flipped_faces : Int
  closed : Bool
  consistent : Bool
  signed_volume : Double
  orientation : WindingOrientation
  bounds : Aabb
  /// A conservative AABB overlap test, not a shell nesting classification.
  potentially_nested : Bool
}

///|
pub(all) struct WindingReport {
  flipped_faces : Int
  consistent : Bool
  /// Aggregate diagnostic only: signs of different components may cancel.
  signed_volume : Double
  components : Array[WindingComponent]
}

///|
pub fn WindingReport::negative_components(self : WindingReport) -> Int {
  self.components.filter(c => c.orientation == NegativeVolume).length()
}

///|
pub fn WindingReport::orientation_reliable(self : WindingReport) -> Bool {
  !self.components.is_empty() &&
  !self.components.any(c => {
    c.orientation != PositiveVolume || c.potentially_nested
  })
}

///|
priv struct WindingPartition {
  flags : Array[Bool]
  faces : Array[Array[Int]]
  closed : Array[Bool]
  orientable : Array[Bool]
}

///|
fn winding_edge_key(a : Int, b : Int) -> String {
  if a < b {
    "\{a},\{b}"
  } else {
    "\{b},\{a}"
  }
}

///|
fn winding_partition(mesh : Mesh, tolerance : Double) -> WindingPartition {
  let n = mesh.triangle_count()
  let flags = Array::make(n, false)
  let labels = Array::make(n, -1)
  let weld = weld_vertices(mesh, tolerance)
  let ids = Array::makei(n, i => {
    (weld.ids[i * 3], weld.ids[i * 3 + 1], weld.ids[i * 3 + 2])
  })
  let edges : Map[String, Array[(Int, Int)]] = Map([])
  for i, (a, b, c) in ids {
    for (u, v) in [(a, b), (b, c), (c, a)] {
      let key = winding_edge_key(u, v)
      let list = match edges.get(key) {
        Some(list) => list
        None => {
          let list = []
          edges.set(key, list)
          list
        }
      }
      list.push((i, if u < v { 1 } else { -1 }))
    }
  }
  let faces = []
  let closed = []
  let orientable = []
  for seed = 0; seed < n; seed = seed + 1 {
    if labels[seed] >= 0 {
      continue
    }
    let component = faces.length()
    let group = []
    let stack = [seed]
    let mut is_closed = true
    let mut is_orientable = true
    labels[seed] = component
    while !stack.is_empty() {
      let cur = stack.pop().unwrap()
      group.push(cur)
      let (a, b, c) = ids[cur]
      for (u, v) in [(a, b), (b, c), (c, a)] {
        let list = edges.get(winding_edge_key(u, v)).unwrap()
        if list.length() != 2 || u == v {
          is_closed = false
        }
        let dir = if u < v { 1 } else { -1 }
        for (j, other_dir) in list {
          if j == cur {
            continue
          }
          let expected = if dir == other_dir { !flags[cur] } else { flags[cur] }
          if labels[j] < 0 {
            labels[j] = component
            flags[j] = expected
            stack.push(j)
          } else if flags[j] != expected {
            is_orientable = false
          }
        }
      }
    }
    faces.push(group)
    closed.push(is_closed)
    orientable.push(is_orientable)
  }
  { flags, faces, closed, orientable, }
}

///|
fn winding_bounds_overlap(a : Aabb, b : Aabb) -> Bool {
  a.min.x <= b.max.x &&
  b.min.x <= a.max.x &&
  a.min.y <= b.max.y &&
  b.min.y <= a.max.y &&
  a.min.z <= b.max.z &&
  b.min.z <= a.max.z
}

///|
pub fn check_winding(mesh : Mesh, tolerance : Double) -> WindingReport {
  let part = winding_partition(mesh, tolerance)
  let components = []
  let mut total_flips = 0
  let mut total_signed = 0.0
  let mut all_consistent = true
  for index, group in part.faces {
    let origin = mesh.triangles[group[0]].a
    let mut lo = origin
    let mut hi = origin
    let mut signed = 0.0
    let mut compensation = 0.0
    let mut flips = 0
    for face in group {
      if part.flags[face] {
        flips = flips + 1
      }
      let t = mesh.triangles[face]
      // Local origin and compensated summation reduce cancellation under translation.
      let contribution = t.a
        .sub(origin)
        .dot(t.b.sub(origin).cross(t.c.sub(origin))) /
        6.0
      let corrected = contribution - compensation
      let sum = signed + corrected
      compensation = sum - signed - corrected
      signed = sum
      for v in [t.a, t.b, t.c] {
        lo = Vec3::new(lo.x.min(v.x), lo.y.min(v.y), lo.z.min(v.z))
        hi = Vec3::new(hi.x.max(v.x), hi.y.max(v.y), hi.z.max(v.z))
      }
    }
    let consistent = flips == 0 && part.orientable[index]
    let orientation = if !part.closed[index] ||
      !consistent ||
      signed.is_nan() ||
      signed.is_inf() ||
      signed == 0.0 {
      Indeterminate
    } else if signed < 0.0 {
      NegativeVolume
    } else {
      PositiveVolume
    }
    components.push({
      index,
      faces: group,
      flipped_faces: flips,
      closed: part.closed[index],
      consistent,
      signed_volume: signed,
      orientation,
      bounds: { min: lo, max: hi, },
      potentially_nested: false,
    })
    total_flips = total_flips + flips
    total_signed = total_signed + signed
    if !consistent {
      all_consistent = false
    }
  }
  let order = Array::makei(components.length(), i => i)
  order.sort_by((i, j) => {
    components[i].bounds.min.x.compare(components[j].bounds.min.x)
  })
  for a = 0; a < order.length(); a = a + 1 {
    let i = order[a]
    for b = a + 1; b < order.length(); b = b + 1 {
      let j = order[b]
      if components[j].bounds.min.x > components[i].bounds.max.x {
        break
      }
      if winding_bounds_overlap(components[i].bounds, components[j].bounds) {
        components[i] = { ..components[i], potentially_nested: true, }
        components[j] = { ..components[j], potentially_nested: true, }
      }
    }
  }
  {
    flipped_faces: total_flips,
    consistent: all_consistent,
    signed_volume: total_signed,
    components,
  }
}

///|
fn flip_winding(t : Triangle) -> Triangle {
  { ..t, b: t.c, c: t.b, }
}

///|
/// Make adjacent faces consistent, then orient isolated closed components positive.
/// Potentially nested/overlapping shells retain their sign for manual review.
fn unify_winding_details(mesh : Mesh, tolerance : Double) -> (Mesh, Int) {
  let part = winding_partition(mesh, tolerance)
  let triangles = mesh.triangles.copy()
  for i, group in part.faces {
    if !part.orientable[i] {
      continue
    }
    for face in group {
      if part.flags[face] {
        triangles[face] = flip_winding(triangles[face])
      }
    }
  }
  let report = check_winding(Mesh::new(triangles), tolerance)
  let mut reoriented = 0
  for component in report.components {
    if component.orientation == NegativeVolume && !component.potentially_nested {
      for face in component.faces {
        triangles[face] = flip_winding(triangles[face])
      }
      reoriented = reoriented + 1
    }
  }
  (Mesh::new(triangles), reoriented)
}

///|
pub fn unify_winding(mesh : Mesh, tolerance : Double) -> Mesh {
  let (result, _) = unify_winding_details(mesh, tolerance)
  result
}

///|
pub fn check_winding_default(mesh : Mesh) -> WindingReport {
  check_winding(mesh, default_weld_tolerance())
}