///|
/// The number of top-level capture groups of all regexps, added together.
fn count_capture_groups(
  name : String,
  regexps : Array[RegexPattern],
) -> Int raise ParameterTypeError {
  let mut total = 0
  for regexp in regexps {
    guard create_group_builder(regexp.to_string()) is Some(builder) else {
      raise InvalidRegexp(name~, regexp=regexp.to_string())
    }
    total = total + builder.children.length()
  }
  total
}

///|
/// Register a typed parameter type that decodes its capture groups with
/// `captures`, and return its handle.
///
/// `captures` must decode as many values as the regexps have top-level
/// capture groups, added over all regexps. A decoder of one value also
/// accepts regexps with no groups; it then gets the whole match. Otherwise
/// `ArityMismatch` is raised. The checks of `register` also apply, before
/// the arity check.
///
/// Groups are counted over all regexps. So with the regexps `(a)b` and `c`,
/// a decoder of one value is accepted, but a match of `c` raises
/// `GroupDidNotMatch`, because the group of the first regexp did not match.
/// Wrap the part in `Captures::optional`, or give each regexp the same
/// groups.
pub fn[T] ParamTypeRegistry::define_with(
  self : ParamTypeRegistry,
  name : String,
  regexps : Array[RegexPattern],
  captures : Captures1[T],
  use_for_snippets? : Bool = true,
  prefer_for_regexp_match? : Bool = false,
) -> ParameterType[T] raise ParameterTypeError {
  self.check_name_and_regexps(name, regexps)
  let groups = count_capture_groups(name, regexps)
  let expected = captures.count
  let fits = if expected == 1 { groups <= 1 } else { groups == expected }
  if !fits {
    raise ArityMismatch(name~, expected~, groups~)
  }
  let handle : ParameterType[T] = ParameterType::new(name)
  self.register(
    name,
    Custom(name),
    regexps,
    transformer=Transformer::with_missing(values => {
      handle.wrap(captures.run(name, values))
    }),
    use_for_snippets~,
    prefer_for_regexp_match~,
  )
  handle
}