///|
/// Aggregate requirements for one variable across all uses in a template.
/// RFC 6570 variables remain optional unless the caller uses `expand_required`.
pub(all) struct VariableProfile {
  name : String
  occurrences : Int32
  first_start : Int32
  last_end : Int32
  scalar_only : Bool
  uses_explode : Bool
  uses_reserved : Bool
  uses_query : Bool
  maximum_prefix : Int32?
}

///|
/// Derive an SDK-oriented variable schema from a compiled template. Profiles
/// retain first-appearance order. A prefix modifier on any occurrence forces
/// a scalar binding for that variable under this implementation.
pub fn Template::variable_profiles(self : Template) -> Array[VariableProfile] {
  let profiles : Array[VariableProfile] = []
  let indexes : Map[String, Int32] = Map([])
  for segment in self.segments {
    match segment {
      Literal(_) => ()
      Expression(op, specs, _, _) =>
        for spec in specs {
          let query = op is Query || op is QueryContinuation
          let reserved = operator_allows_reserved(op)
          match indexes.get(spec.name) {
            None => {
              indexes[spec.name] = profiles.length()
              profiles.push({
                name: spec.name,
                occurrences: 1,
                first_start: spec.start,
                last_end: spec.end,
                scalar_only: spec.prefix is Some(_),
                uses_explode: spec.explode,
                uses_reserved: reserved,
                uses_query: query,
                maximum_prefix: spec.prefix,
              })
            }
            Some(index) => {
              let previous = profiles[index]
              let max_prefix = match (previous.maximum_prefix, spec.prefix) {
                (None, None) => None
                (Some(value), None) | (None, Some(value)) => Some(value)
                (Some(left), Some(right)) =>
                  Some(if left > right { left } else { right })
              }
              profiles[index] = {
                name: previous.name,
                occurrences: previous.occurrences + 1,
                first_start: previous.first_start,
                last_end: spec.end,
                scalar_only: previous.scalar_only || spec.prefix is Some(_),
                uses_explode: previous.uses_explode || spec.explode,
                uses_reserved: previous.uses_reserved || reserved,
                uses_query: previous.uses_query || query,
                maximum_prefix: max_prefix,
              }
            }
          }
        }
    }
  }
  profiles
}

///|
/// Names that can accept a list or association value in every occurrence.
/// This does not imply a composite is required; scalar values also work.
pub fn Template::composite_capable_variables(self : Template) -> Array[String] {
  let names : Array[String] = []
  for profile in self.variable_profiles() {
    if !profile.scalar_only {
      names.push(profile.name)
    }
  }
  names
}