///|
pub(all) enum SignaturePolicy {
  Fill
  Balanced
}

///|
/// Balanced distributes whole sheets across the fewest possible gatherings.
/// Sizes differ by at most one sheet; padding is still confined to the end.
pub fn plan_gatherings(
  pages : Int32,
  maximum : Int32,
  policy : SignaturePolicy,
) -> Result[Array[Signature], WorkflowError] {
  match validate_config(pages, maximum) {
    Err(_) =>
      return Err(
        workflow_error("gatherings", "invalid page count or maximum capacity"),
      )
    Ok(_) => ()
  }
  match policy {
    Fill =>
      match signatures(pages, maximum) {
        Ok(groups) => Ok(groups)
        Err(_) =>
          Err(
            workflow_error(
              "gatherings", "invalid page count or maximum capacity",
            ),
          )
      }
    Balanced => {
      let sheets = (pages + 3) / 4
      let count = (sheets + maximum / 4 - 1) / (maximum / 4)
      let base = sheets / count
      let extra = sheets % count
      let sizes = Array::makei(count, fn(index) {
        (base + (if index < extra { 1 } else { 0 })) * 4
      })
      explicit_gatherings(pages, sizes)
    }
  }
}

///|
/// Use a binder-specified sequence of gathering capacities, with no empty group.
pub fn explicit_gatherings(
  pages : Int32,
  sizes : Array[Int32],
) -> Result[Array[Signature], WorkflowError] {
  if pages < 1 || pages > 100000 || sizes.is_empty() || sizes.length() > 25000 {
    return Err(
      workflow_error("gatherings", "invalid document length or gathering count"),
    )
  }
  let groups : Array[Signature] = []
  let mut start = 1
  for index, capacity in sizes {
    if capacity < 4 || capacity > 128 || capacity % 4 != 0 {
      return Err(
        workflow_error(
          "gatherings[\{index}]",
          "capacity must be a multiple of four in 4..128",
        ),
      )
    }
    let remaining = pages - start + 1
    if remaining <= 0 {
      return Err(
        workflow_error("gatherings[\{index}]", "empty trailing gathering"),
      )
    }
    let real_pages = if remaining < capacity { remaining } else { capacity }
    if real_pages < capacity - 3 {
      return Err(
        workflow_error(
          "gatherings[\{index}]",
          "excess padding; shrink the final capacity",
        ),
      )
    }
    groups.push({ index, start, pages: capacity, real_pages, })
    start += real_pages
  }
  if start != pages + 1 {
    return Err(
      workflow_error("gatherings", "capacities do not cover the document"),
    )
  }
  Ok(groups)
}

///|
pub fn validate_gatherings(
  pages : Int32,
  groups : Array[Signature],
) -> Result[Unit, WorkflowError] {
  if groups.is_empty() || groups.length() > 25000 {
    return Err(workflow_error("gatherings", "expected 1..25000 gatherings"))
  }
  let expected = match
    explicit_gatherings(pages, groups.map(fn(g) { g.pages })) {
    Err(e) => return Err(e)
    Ok(groups) => groups
  }
  for index in 0..