///|
/// Structural metrics captured from a transducer snapshot. Epsilon counts are
/// independent: an epsilon/epsilon arc contributes to both counters.
pub(all) struct TransducerStats {
  state_count : Int32
  arc_count : Int32
  epsilon_input_arc_count : Int32
  epsilon_output_arc_count : Int32
  final_state_count : Int32
  max_out_degree : Int32
  reachable_state_count : Int32
  coaccessible_state_count : Int32
  has_start : Bool
} derive(Eq)

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

///|
/// Return a stable copy of all arcs entering `destination`, ordered first by
/// source-state id and then by each source's insertion order. Parallel arcs are
/// retained as separate entries. The returned array is independent of the graph.
pub fn[W] transducer_incoming_arcs(
  transducer : Transducer[W],
  destination : Int32,
) -> Result[Array[(Int32, Arc[W])], WfstError] {
  let count = transducer.outgoing.length()
  if destination < 0 || destination >= count {
    return Err(WfstError::InvalidStateId(destination))
  }
  let incoming : Array[(Int32, Arc[W])] = []
  for source in 0.. TransducerStats {
  let mut arc_count = 0
  let mut epsilon_input_arc_count = 0
  let mut epsilon_output_arc_count = 0
  let mut final_state_count = 0
  let mut max_out_degree = 0
  for state in 0.. max_out_degree {
      max_out_degree = degree
    }
    if transducer.final_weights[state] is Some(_) {
      final_state_count = final_state_count + 1
    }
    for arc in transducer.outgoing[state] {
      if arc.input is Label::Epsilon {
        epsilon_input_arc_count = epsilon_input_arc_count + 1
      }
      if arc.output is Label::Epsilon {
        epsilon_output_arc_count = epsilon_output_arc_count + 1
      }
    }
  }
  let reachable = reachable_states(transducer)
  let coaccessible = coaccessible_states(transducer)
  let mut reachable_state_count = 0
  let mut coaccessible_state_count = 0
  for state in 0..