///|
/// A mathematical cleavage subset, not a kinetic/probabilistic prediction.
pub(all) struct PartialScenario {
  mask : Int
  active_top_cuts : Array[Int]
  fragments : Array[Fragment]
} derive(Eq, Debug, ToJson)

///|
/// Prefix enumeration always advertises whether it is exhaustive.
pub(all) struct PartialReport {
  candidate_cuts : Int
  total_subsets : Int
  truncated : Bool
  scenarios : Array[PartialScenario]
} derive(Eq, Debug, ToJson)

///|
/// Enumerate subsets in ascending bitmask order, including uncut input first.
pub fn partial_digests(
  dna : Dna,
  enzymes : Array[Enzyme],
  max_scenarios? : Int = 32,
) -> PartialReport raise RestrictError {
  if max_scenarios < 1 || max_scenarios > 64 {
    raise InvalidInput("PARTIAL_MAX_SCENARIOS")
  }
  let complete = digest_many(dna, enzymes)
  if complete.cuts.length() > 12 {
    raise LimitExceeded("PARTIAL_CUT_MAX_12")
  }
  let total = 1 << complete.cuts.length()
  let count = total.min(max_scenarios)
  if count * dna.length() > 2000000 {
    raise LimitExceeded("PARTIAL_OUTPUT_BASES")
  }
  let scenarios : Array[PartialScenario] = []
  for mask in 0.. s.top),
      fragments: result.fragments,
    })
  }
  {
    candidate_cuts: complete.cuts.length(),
    total_subsets: total,
    truncated: count < total,
    scenarios,
  }
}