///|
fn Evaluator::eval_list(
  self : Evaluator,
  items : Array[Expr],
) -> Value raise EvalError {
  Value::List(items.map(item => self.eval_expr(item)))
}

///|
fn Evaluator::eval_map(
  self : Evaluator,
  entries : Array[MapExprEntry],
) -> Value raise EvalError {
  let values : Array[ValueEntry] = []
  for entry in entries {
    let key = self.eval_expr(entry.key)
    let value = self.eval_expr(entry.value)
    if values.any(existing => existing.key == key) {
      raise InvalidArgument(
        message="duplicate map key \{key.display()}",
        span=entry.span,
      )
    }
    values.push({ key, value })
  }
  Value::Map(values)
}

///|
fn Evaluator::eval_select(
  self : Evaluator,
  target : Expr,
  field : String,
  span : SourceSpan,
) -> Value raise EvalError {
  match self.eval_expr(target) {
    Value::Map(entries) => {
      for entry in entries {
        if entry.key == Value::String(field) {
          return entry.value
        }
      }
      raise NoSuchField(field~, span~)
    }
    other => type_mismatch("map", other, span)
  }
}

///|
fn Evaluator::eval_index(
  self : Evaluator,
  target : Expr,
  index : Expr,
  span : SourceSpan,
) -> Value raise EvalError {
  let target_value = self.eval_expr(target)
  let index_value = self.eval_expr(index)
  match (target_value, index_value) {
    (Value::List(values), Value::Int(position)) => {
      let position = position.to_int()
      if position < 0 || position >= values.length() {
        raise IndexOutOfBounds(index=position, length=values.length(), span~)
      }
      values[position]
    }
    (Value::Map(entries), key) => {
      for entry in entries {
        if entry.key == key {
          return entry.value
        }
      }
      raise NoSuchKey(key=key.display(), span~)
    }
    (Value::List(_), other) => type_mismatch("int index", other, span)
    (other, _) => type_mismatch("list or map", other, span)
  }
}

///|
fn concat_bytes(left : Bytes, right : Bytes) -> Bytes {
  let combined = left.to_array()
  combined.append(right.to_array())
  Bytes::from_array(combined)
}

///|
fn concat_lists(left : Array[Value], right : Array[Value]) -> Array[Value] {
  let combined = left.copy()
  combined.append(right)
  combined
}

///|
fn conversion_error(name : String, span : SourceSpan) -> Value raise EvalError {
  raise InvalidArgument(message="invalid argument to \{name}", span~)
}

///|
fn eval_int_conversion(
  value : Value,
  span : SourceSpan,
) -> Value raise EvalError {
  match value {
    Value::UInt(value) => {
      let result = @string.parse_int64(value.to_string()) catch {
        _ => return conversion_error("int", span)
      }
      Value::Int(result)
    }
    Value::Double(value) => Value::Int(value.to_int64())
    Value::String(value) => {
      let result = @string.parse_int64(value) catch {
        _ => return conversion_error("int", span)
      }
      Value::Int(result)
    }
    other => type_mismatch("uint, double, or string", other, span)
  }
}

///|
fn eval_uint_conversion(
  value : Value,
  span : SourceSpan,
) -> Value raise EvalError {
  match value {
    Value::Int(value) => {
      if value < 0L {
        return conversion_error("uint", span)
      }
      let result = @string.parse_uint64(value.to_string()) catch {
        _ => return conversion_error("uint", span)
      }
      Value::UInt(result)
    }
    Value::Double(value) => {
      if value < 0.0 {
        return conversion_error("uint", span)
      }
      Value::UInt(value.to_uint64())
    }
    Value::String(value) => {
      let result = @string.parse_uint64(value) catch {
        _ => return conversion_error("uint", span)
      }
      Value::UInt(result)
    }
    other => type_mismatch("int, double, or string", other, span)
  }
}

///|
fn eval_double_conversion(
  value : Value,
  span : SourceSpan,
) -> Value raise EvalError {
  match value {
    Value::Int(value) => Value::Double(value.to_double())
    Value::UInt(value) => Value::Double(value.to_double())
    Value::String(value) => {
      let result = @string.parse_double(value) catch {
        _ => return conversion_error("double", span)
      }
      Value::Double(result)
    }
    other => type_mismatch("int, uint, or string", other, span)
  }
}

///|
fn eval_string_conversion(
  value : Value,
  span : SourceSpan,
) -> Value raise EvalError {
  match value {
    Value::Bool(value) => Value::String(if value { "true" } else { "false" })
    Value::Int(value) => Value::String(value.to_string())
    Value::UInt(value) => Value::String(value.to_string())
    Value::Double(value) => Value::String(value.to_string())
    Value::Bytes(value) =>
      Value::String(
        @utf8.decode(value) catch {
          _ => return conversion_error("string", span)
        },
      )
    other => type_mismatch("bool, number, or bytes", other, span)
  }
}

///|
fn eval_global_call(
  name : String,
  arguments : Array[Value],
  span : SourceSpan,
) -> Value raise EvalError {
  match (name, arguments) {
    ("size", [value]) =>
      match value.size() {
        Some(size) => Value::Int(Int64::from_int(size))
        None => type_mismatch("string, bytes, list, or map", value, span)
      }
    ("type", [value]) => Value::String(value.kind_name())
    ("int", [value]) => eval_int_conversion(value, span)
    ("uint", [value]) => eval_uint_conversion(value, span)
    ("double", [value]) => eval_double_conversion(value, span)
    ("string", [value]) => eval_string_conversion(value, span)
    ("bytes", [Value::String(value)]) => Value::Bytes(@utf8.encode(value))
    _ => raise UnknownFunction(name~, span~)
  }
}

///|
fn eval_receiver_call(
  receiver : Value,
  name : String,
  arguments : Array[Value],
  span : SourceSpan,
) -> Value raise EvalError {
  match (receiver, name, arguments) {
    (value, "size", []) =>
      match value.size() {
        Some(size) => Value::Int(Int64::from_int(size))
        None => type_mismatch("string, bytes, list, or map", value, span)
      }
    (Value::String(value), "contains", [Value::String(part)]) =>
      Value::Bool(value.contains(part))
    (Value::String(value), "startsWith", [Value::String(prefix)]) =>
      Value::Bool(value.has_prefix(prefix))
    (Value::String(value), "endsWith", [Value::String(suffix)]) =>
      Value::Bool(value.has_suffix(suffix))
    (Value::String(value), "matches", [Value::String(pattern)]) =>
      Value::Bool(bounded_regex_matches(value, pattern, span))
    (_, function_name, _) => raise UnknownFunction(name=function_name, span~)
  }
}

///|
fn Evaluator::eval_call(
  self : Evaluator,
  name : String,
  arguments : Array[Expr],
  span : SourceSpan,
) -> Value raise EvalError {
  eval_global_call(name, arguments.map(arg => self.eval_expr(arg)), span)
}

///|
fn Evaluator::eval_receiver(
  self : Evaluator,
  target : Expr,
  name : String,
  arguments : Array[Expr],
  span : SourceSpan,
) -> Value raise EvalError {
  let receiver = self.eval_expr(target)
  let values = arguments.map(arg => self.eval_expr(arg))
  eval_receiver_call(receiver, name, values, span)
}