///|
fn safe_label(value : String) -> Bool {
  value.length() > 0 &&
  value.length() <= 128 &&
  !value.iter().any(c => c.to_int() < 32 || c.to_int() == 127)
}

///|
pub fn problem(
  demands : Array[Demand],
  stocks : Array[Stock],
  kerf~ : Int32 = 0,
  min_remnant~ : Int32 = 1,
) -> Result[Problem, String] {
  if demands.length() > 12 || stocks.length() > 16 {
    return Err("at most 12 demand types and 16 stock types")
  }
  if kerf < 0 || kerf > 1000000 || min_remnant < 1 || min_remnant > 1000000 {
    return Err("kerf must be 0..1000000; min_remnant 1..1000000")
  }
  let mut pieces = 0
  for i, d in demands {
    if !safe_label(d.id) || !safe_label(d.material) {
      return Err(
        "demand id and material must have 1..128 code units without ASCII controls",
      )
    }
    if d.length < 1 || d.length > 1000000 || d.quantity < 0 || d.quantity > 128 {
      return Err("demand length 1..1000000 and quantity 0..128 required")
    }
    pieces += d.quantity
    for j in 0.. 128 {
    return Err("at most 128 requested pieces")
  }
  for i, s in stocks {
    if !safe_label(s.id) || !safe_label(s.material) {
      return Err(
        "stock id and material must have 1..128 code units without ASCII controls",
      )
    }
    if s.length < 1 ||
      s.length > 1000000 ||
      s.cost < 0 ||
      s.cost > 1000000 ||
      s.quantity < -1 ||
      s.quantity > 128 {
      return Err("stock length/cost/inventory outside bounded domain")
    }
    if s.trim_start < 0 ||
      s.trim_end < 0 ||
      s.trim_start > s.length ||
      s.trim_end > s.length - s.trim_start {
      return Err("stock trims exceed length")
    }
    for j in 0.. Result[Unit, String] {
  if limits.patterns < 1 ||
    limits.patterns > 1000000 ||
    limits.nodes < 1 ||
    limits.nodes > 1000000 {
    Err("pattern and search budgets must be 1..1000000")
  } else {
    Ok(())
  }
}

///|
pub fn Problem::demands(self : Problem) -> Array[Demand] {
  self.demands.copy()
}

///|
pub fn Problem::stocks(self : Problem) -> Array[Stock] {
  self.stocks.copy()
}

///|
pub fn Problem::kerf(self : Problem) -> Int32 {
  self.kerf
}

///|
pub fn Problem::min_remnant(self : Problem) -> Int32 {
  self.min_remnant
}