///|
/// A G-single witness has one item anti-dependency and otherwise only WW/WR.
/// This entry point accepts only point-register/append-list item graphs.
/// Seed each RW independently, then find a WW/WR path back to its source.
/// The path search cannot traverse a second RW or a real-time edge.
fn g_single_cycle_v4(
  node_count : Int32,
  edges : Array[Edge],
  graph : GraphIndex,
  budget : ClassificationV3Budget,
) -> Array[Edge] {
  let seen = FixedArray::make(node_count, 0)
  let parent = FixedArray::make(node_count, -1)
  let queue = FixedArray::make(node_count, 0)
  let mut epoch = 0
  for seed_index, seed in edges {
    if !classification_v3_charge_edge(budget) {
      return []
    }
    if seed.kind != RW {
      continue
    }
    let start = graph.targets[seed_index]
    let target = graph.sources[seed_index]
    // Own-transaction effects are handled by internal-consistency checks.
    if start == target {
      continue
    }
    epoch += 1
    queue[0] = start
    seen[start] = epoch
    let mut head = 0
    let mut tail = 1
    while head < tail {
      if !classification_v3_charge_state(budget) {
        return []
      }
      let vertex = queue[head]
      head += 1
      for edge_index in graph.adj[vertex] {
        if !classification_v3_charge_edge(budget) {
          return []
        }
        let edge = edges[edge_index]
        if edge.kind != WW && edge.kind != WR {
          continue
        }
        let next = graph.targets[edge_index]
        if next == target {
          let backward = [edge]
          let mut current = vertex
          while current != start {
            let previous = parent[current]
            backward.push(edges[previous])
            current = graph.sources[previous]
          }
          backward.rev_in_place()
          let cycle = [seed]
          for edge in backward {
            cycle.push(edge)
          }
          return cycle
        }
        if seen[next] != epoch {
          seen[next] = epoch
          parent[next] = edge_index
          queue[tail] = next
          tail += 1
        }
      }
    }
  }
  []
}

///|
fn g_single_category_witness_v4(witness : Json, cycle : Array[Edge]) -> Json {
  let fields = object_fields_unchecked_v3(witness)
  fields["specificCategory"] = Json("G-single-item")
  fields["requestedCategory"] = Json("G-single")
  fields["rwScope"] = Json("item")
  fields["rwEdgeCount"] = Json(cycle.filter(edge => edge.kind == RW).length())
  fields["cycleRule"] = Json("exactly-one-item-rw-and-only-ww-wr-remainder/v1")
  witness
}