///|
priv struct StandardOverload {
  name : String
  receiver : CelType?
  parameters : Array[CelType]
  result : CelType
}

///|
fn global_overload(
  name : String,
  parameters : Array[CelType],
  result : CelType,
) -> StandardOverload {
  { name, receiver: None, parameters, result }
}

///|
fn receiver_overload(
  name : String,
  receiver : CelType,
  parameters : Array[CelType],
  result : CelType,
) -> StandardOverload {
  { name, receiver: Some(receiver), parameters, result }
}

///|
fn standard_overloads() -> Array[StandardOverload] {
  [
    global_overload("size", [CelType::String], CelType::Int),
    global_overload("size", [CelType::Bytes], CelType::Int),
    global_overload("size", [CelType::List(CelType::Dyn)], CelType::Int),
    global_overload(
      "size",
      [CelType::Map(CelType::Dyn, CelType::Dyn)],
      CelType::Int,
    ),
    global_overload("type", [CelType::Dyn], CelType::String),
    global_overload("int", [CelType::UInt], CelType::Int),
    global_overload("int", [CelType::Double], CelType::Int),
    global_overload("int", [CelType::String], CelType::Int),
    global_overload("uint", [CelType::Int], CelType::UInt),
    global_overload("uint", [CelType::Double], CelType::UInt),
    global_overload("uint", [CelType::String], CelType::UInt),
    global_overload("double", [CelType::Int], CelType::Double),
    global_overload("double", [CelType::UInt], CelType::Double),
    global_overload("double", [CelType::String], CelType::Double),
    global_overload("string", [CelType::Bool], CelType::String),
    global_overload("string", [CelType::Int], CelType::String),
    global_overload("string", [CelType::UInt], CelType::String),
    global_overload("string", [CelType::Double], CelType::String),
    global_overload("string", [CelType::Bytes], CelType::String),
    global_overload("bytes", [CelType::String], CelType::Bytes),
    receiver_overload("size", CelType::String, [], CelType::Int),
    receiver_overload("size", CelType::Bytes, [], CelType::Int),
    receiver_overload("size", CelType::List(CelType::Dyn), [], CelType::Int),
    receiver_overload(
      "size",
      CelType::Map(CelType::Dyn, CelType::Dyn),
      [],
      CelType::Int,
    ),
    receiver_overload(
      "contains",
      CelType::String,
      [CelType::String],
      CelType::Bool,
    ),
    receiver_overload(
      "startsWith",
      CelType::String,
      [CelType::String],
      CelType::Bool,
    ),
    receiver_overload(
      "endsWith",
      CelType::String,
      [CelType::String],
      CelType::Bool,
    ),
    receiver_overload(
      "matches",
      CelType::String,
      [CelType::String],
      CelType::Bool,
    ),
  ]
}

///|
fn StandardOverload::matches(
  self : StandardOverload,
  receiver : CelType?,
  arguments : Array[CelType],
) -> Bool {
  let receiver_matches = match (self.receiver, receiver) {
    (None, None) => true
    (Some(expected), Some(actual)) => expected.accepts(actual)
    _ => false
  }
  if !receiver_matches || self.parameters.length() != arguments.length() {
    return false
  }
  for index = 0; index < arguments.length(); index = index + 1 {
    if !self.parameters[index].accepts(arguments[index]) {
      return false
    }
  }
  true
}

///|
fn StandardOverload::signature(self : StandardOverload) -> String {
  let arguments = self.parameters.map(item => item.display()).join(", ")
  match self.receiver {
    Some(receiver) =>
      "\{receiver.display()}.\{self.name}(\{arguments}) -> \{self.result.display()}"
    None => "\{self.name}(\{arguments}) -> \{self.result.display()}"
  }
}

///|
fn Checker::resolve_standard_call(
  self : Checker,
  name : String,
  receiver : CelType?,
  arguments : Array[CelType],
  span : SourceSpan,
) -> CelType {
  let named = standard_overloads().filter(overload => overload.name == name)
  if named.is_empty() {
    self.error("CEL-CHECK-FUNCTION", "unknown function '\{name}'", span)
    return CelType::Dyn
  }
  match named.search_by(overload => overload.matches(receiver, arguments)) {
    Some(index) => named[index].result
    None => {
      let actual_arguments = arguments.map(item => item.display()).join(", ")
      let actual = match receiver {
        Some(target) => "\{target.display()}.\{name}(\{actual_arguments})"
        None => "\{name}(\{actual_arguments})"
      }
      let expected = named.map(overload => overload.signature()).join("; ")
      self.error(
        "CEL-CHECK-OVERLOAD",
        "no matching overload for \{actual}; expected \{expected}",
        span,
      )
      CelType::Dyn
    }
  }
}

///|
/// Names provided by the v0.1 standard environment.
pub fn standard_function_names() -> Array[String] {
  [
    "bytes", "double", "int", "size", "string", "type", "uint", "contains", "endsWith",
    "matches", "startsWith",
  ]
}