///|
fn defined_value(value : UriValue) -> Bool {
  match value {
    Scalar(_) => true
    List(values) => !values.is_empty()
    Assoc(values) => !values.is_empty()
  }
}

///|
fn named_value(operator : Operator, name : String, encoded : String) -> String {
  if !operator.named() {
    encoded
  } else if encoded.is_empty() {
    name + operator.empty_suffix()
  } else {
    name + "=" + encoded
  }
}

///|
fn expand_scalar(
  operator : Operator,
  variable : VariableSpec,
  value : String,
) -> Array[String] {
  let value = match variable.modifier {
    Prefix(length) => @internal.prefix_value(value, length)
    NoModifier | Explode => value
  }
  let encoded = @internal.encode_value(value, operator.allow_reserved())
  [named_value(operator, variable.name, encoded)]
}

///|
fn expand_list(
  operator : Operator,
  variable : VariableSpec,
  values : Array[String],
) -> Array[String] raise UriTemplateError {
  match variable.modifier {
    Prefix(_) =>
      raise InvalidValue(
        variable=variable.name,
        message="prefix modifier cannot be applied to a list",
      )
    NoModifier => {
      let encoded = values
        .map(fn(value) {
          @internal.encode_value(value, operator.allow_reserved())
        })
        .join(",")
      [named_value(operator, variable.name, encoded)]
    }
    Explode => {
      let result : Array[String] = []
      for value in values {
        let encoded = @internal.encode_value(value, operator.allow_reserved())
        if operator.named() {
          result.push(named_value(operator, variable.name, encoded))
        } else {
          result.push(encoded)
        }
      }
      result
    }
  }
}

///|
fn expand_assoc(
  operator : Operator,
  variable : VariableSpec,
  values : Array[(String, String)],
) -> Array[String] raise UriTemplateError {
  match variable.modifier {
    Prefix(_) =>
      raise InvalidValue(
        variable=variable.name,
        message="prefix modifier cannot be applied to an associative array",
      )
    NoModifier => {
      let flattened : Array[String] = []
      for pair in values {
        let (key, value) = pair
        flattened.push(@internal.encode_value(key, operator.allow_reserved()))
        flattened.push(@internal.encode_value(value, operator.allow_reserved()))
      }
      [named_value(operator, variable.name, flattened.join(","))]
    }
    Explode => {
      // Per RFC 6570 ยง3.2.5, exploded associative arrays always use key=value
      // form regardless of operator, so operator.named() is not checked here.
      let result : Array[String] = []
      for pair in values {
        let (key, value) = pair
        let key = @internal.encode_value(key, operator.allow_reserved())
        let value = @internal.encode_value(value, operator.allow_reserved())
        if value.is_empty() {
          result.push(key + operator.empty_suffix())
        } else {
          result.push(key + "=" + value)
        }
      }
      result
    }
  }
}

///|
fn expand_expression(
  operator : Operator,
  variable_specs : Array[VariableSpec],
  variables : Map[String, UriValue],
) -> String raise UriTemplateError {
  let result : Array[String] = []
  for variable in variable_specs {
    match variables.get(variable.name) {
      None => ()
      Some(value) =>
        if defined_value(value) {
          let items = match value {
            Scalar(value) => expand_scalar(operator, variable, value)
            List(values) => expand_list(operator, variable, values)
            Assoc(values) => expand_assoc(operator, variable, values)
          }
          for item in items {
            result.push(item)
          }
        }
    }
  }
  if result.is_empty() {
    ""
  } else {
    operator.first() + result.join(operator.separator())
  }
}

///|
/// Default maximum expanded URI length, measured in ASCII output characters.
pub const DEFAULT_MAX_OUTPUT_LENGTH : Int = 1048576

///|
/// Expand this template while enforcing a maximum output length.
pub fn UriTemplate::expand_with_limit(
  self : UriTemplate,
  variables : Map[String, UriValue],
  max_output_length~ : Int,
) -> String raise UriTemplateError {
  let builder = StringBuilder(size_hint=self.source.length())
  let mut produced = 0
  for part in self.parts {
    let expanded = match part {
      Literal(value) => @internal.encode_value(value, true)
      Expression(operator, variable_specs) =>
        expand_expression(operator, variable_specs, variables)
    }
    produced += expanded.length()
    if produced > max_output_length {
      raise OutputLimitExceeded(limit=max_output_length)
    }
    builder.write_string(expanded)
  }
  builder.to_string()
}

///|
/// Expand this parsed template with an immutable snapshot of variable values.
pub fn UriTemplate::expand(
  self : UriTemplate,
  variables : Map[String, UriValue],
) -> String raise UriTemplateError {
  self.expand_with_limit(variables, max_output_length=DEFAULT_MAX_OUTPUT_LENGTH)
}