///|
fn validate_bridge_config(
  nodes : Array[LocatedNode],
  header : String,
  config : GenerateConfig,
) -> Unit raise {
  let main_functions : Map[String, CFunction] = Map([])
  let all_functions : Map[String, CFunction] = Map([])
  for entry in nodes {
    if text_field(entry.node, "kind") == Some("FunctionDecl") {
      match c_function(entry.node, entry.source) {
        Some(function) => {
          all_functions[function.name] = function
          if same_header(entry.source.file, header) {
            main_functions[function.name] = function
          }
        }
        None => ()
      }
    }
  }
  for key, _ in config.output_params {
    validate_parameter_key(key, main_functions)
    let parts = key.split(".").to_array()
    if !config.allows(parts[0].to_owned()) {
      fail("configured bridge function is filtered: " + key)
    }
  }
  for key, _ in config.null_inputs {
    validate_parameter_key(key, main_functions)
    let parts = key.split(".").to_array()
    if !config.has_bridge(parts[0].to_owned()) ||
      !config.allows(parts[0].to_owned()) {
      fail("null_inputs requires a bridged function: " + key)
    }
  }
  for name, _ in config.utf8_borrowed_returns {
    if !main_functions.contains(name) || !config.allows(name) {
      fail("unknown UTF-8 return function: " + name)
    }
  }
  for name, free_name in config.utf8_owned_returns {
    if !main_functions.contains(name) || !config.allows(name) {
      fail("unknown UTF-8 return function: " + name)
    }
    match all_functions.get(free_name) {
      Some(free_function) => {
        if normalized_type(free_function.canonical_return_type) != "void" ||
          free_function.parameters.length() != 1 ||
          free_function.c_signature.contains("...") {
          fail("invalid string release function: " + free_name)
        }
        let argument = normalized_type(
          free_function.parameters[0].canonical_type,
        )
        if argument != "void*" && argument != "char*" {
          fail("invalid string release function: " + free_name)
        }
      }
      None => fail("unknown string release function: " + free_name)
    }
  }
}

///|
fn validate_parameter_key(
  key : String,
  functions : Map[String, CFunction],
) -> Unit raise {
  let parts = key.split(".").to_array()
  if parts.length() != 2 {
    fail("expected function.parameter: " + key)
  }
  match functions.get(parts[0].to_owned()) {
    Some(function) => {
      for parameter in function.parameters {
        if parameter.name == parts[1].to_owned() {
          return
        }
      }
      fail("unknown parameter: " + key)
    }
    None => fail("unknown function: " + key)
  }
}

///|
fn render_bridge(
  function : CFunction,
  aliases : Map[String, String],
  opaque_types : Map[String, Bool],
  enums : Map[String, Bool],
  config : GenerateConfig,
) -> (String?, String?, String) {
  if function.c_signature.contains("...") {
    return (None, None, "variadic function")
  }
  let string_return = config.utf8_borrowed_returns.contains(function.name) ||
    config.utf8_owned_returns.contains(function.name)
  let mut output_count = 0
  let mut output_moon_type = ""
  let mut output_c_type = ""
  let moon_params : Array[String] = []
  let moon_args : Array[String] = []
  let c_params : Array[String] = []
  let c_args : Array[String] = []
  let borrowed : Array[String] = []
  let used_names : Map[String, Int] = Map([])
  for index, parameter in function.parameters {
    let ctype = normalized_type(parameter.canonical_type)
    if config.is_output(function.name, parameter.name) {
      output_count += 1
      if !ctype.has_suffix("*") ||
        parameter.canonical_type.trim().has_prefix("const ") {
        return (
          None,
          None,
          "unsupported output parameter type: " + parameter.c_type,
        )
      }
      let pointee = ctype[:ctype.length() - 1].trim().to_owned()
      let resolved = resolve_type(
        pointee,
        aliases,
        opaque_types,
        enums,
        result=false,
      )
      match resolved {
        Some(value) => {
          output_moon_type = value
          output_c_type = pointee
          c_args.push("&mbg_value")
        }
        None =>
          return (
            None,
            None,
            "unsupported output parameter type: " + parameter.c_type,
          )
      }
    } else if config.is_null_input(function.name, parameter.name) {
      if !ctype.has_suffix("*") || ctype.contains("(") {
        return (None, None, "null input is not a pointer: " + parameter.name)
      }
      c_args.push("NULL")
    } else {
      let resolved = match
        resolve_configured_type(
          parameter.c_type,
          parameter.canonical_type,
          aliases,
          opaque_types,
          enums,
          config,
          utf8_input=config.is_utf8_input(function.name, parameter.name),
          result=false,
        ) {
        Some(value) => value
        None =>
          return (None, None, "unsupported parameter type: " + parameter.c_type)
      }
      let base_name = moonbit_value_name(parameter.name)
      let count = used_names.get(base_name).unwrap_or(0) + 1
      used_names[base_name] = count
      let name = if count == 1 {
        base_name
      } else {
        base_name + "_" + count.to_string()
      }
      moon_params.push(name + " : " + resolved.moon_type)
      moon_args.push(name)
      if resolved.borrowed {
        borrowed.push(name)
        c_params.push("moonbit_bytes_t p" + index.to_string())
        c_args.push("(" + parameter.c_type + ")p" + index.to_string())
      } else {
        c_params.push(parameter.c_type + " p" + index.to_string())
        c_args.push("p" + index.to_string())
      }
    }
  }
  if (string_return && output_count != 0) ||
    (!string_return && output_count != 1) {
    return (None, None, "bridge needs exactly one output or string return")
  }
  let bridge_name = "mbg_bridge_" + function.moon_name
  let ffi_params = moon_params.copy()
  let ffi_args = moon_args.copy()
  let c_signature = c_params.copy()
  let extra_name = if string_return { "mbg_absent" } else { "mbg_status" }
  ffi_params.push(extra_name + " : FixedArray[Int]")
  ffi_args.push(extra_name)
  c_signature.push("int32_t *" + extra_name)
  borrowed.push(extra_name)
  let ffi_borrow = "#borrow(" + borrowed.join(", ") + ")\n"
  let public_parameters = moon_params.join(", ")
  let call = function.name + "(" + c_args.join(", ") + ")"
  let ffi_return = if string_return { "Bytes" } else { output_moon_type }
  let ffi_line = "extern \"C\" fn " +
    bridge_name +
    "(" +
    ffi_params.join(", ") +
    ") -> " +
    ffi_return +
    " = \"" +
    bridge_name +
    "\"\n"
  let ffi_declaration = if ffi_params.length() > 2 || ffi_line.length() > 110 {
    "extern \"C\" fn " +
    bridge_name +
    "(\n" +
    ffi_params.map(param => "  " + param + ",").join("\n") +
    "\n) -> " +
    ffi_return +
    " = \"" +
    bridge_name +
    "\"\n"
  } else {
    ffi_line
  }
  let public_head = if moon_params.length() > 2 {
    "pub fn " +
    function.moon_name +
    "(\n" +
    moon_params.map(param => "  " + param + ",").join("\n") +
    "\n)"
  } else {
    "pub fn " + function.moon_name + "(" + public_parameters + ")"
  }
  if string_return {
    if normalized_type(function.canonical_return_type) != "char*" {
      return (None, None, "unsupported return type: " + function.return_type)
    }
    let owned_free = config.utf8_owned_returns.get(function.name)
    if owned_free is Some(_) &&
      !function.canonical_return_type.trim().has_prefix("char *") &&
      !function.canonical_return_type.trim().has_prefix("char*") {
      return (None, None, "owned string return must be char *")
    }
    let moon = "///|\n" +
      ffi_borrow +
      ffi_declaration +
      "\n///|\n" +
      public_head +
      " -> String? raise {\n" +
      "  let " +
      extra_name +
      " = FixedArray::make(1, 0)\n" +
      "  let bytes = " +
      bridge_name +
      "(" +
      ffi_args.join(", ") +
      ")\n" +
      "  if " +
      extra_name +
      "[0] == 1 {\n    None\n" +
      "  } else if " +
      extra_name +
      "[0] == 2 {\n" +
      "    fail(\"returned C string is too large\")\n" +
      "  } else {\n    Some(@utf8.decode(bytes))\n  }\n}\n"
    let c_type = if owned_free is Some(_) { "char *" } else { "const char *" }
    let free_line = match owned_free {
      Some(name) => "    " + name + "(mbg_text);\n"
      None => ""
    }
    let free_after_copy = match owned_free {
      Some(name) => "  " + name + "(mbg_text);\n"
      None => ""
    }
    let c = "MOONBIT_FFI_EXPORT moonbit_bytes_t " +
      bridge_name +
      "(" +
      c_signature.join(", ") +
      ") {\n  " +
      c_type +
      "mbg_text = " +
      call +
      ";\n  if (mbg_text == NULL) {\n    mbg_absent[0] = 1;\n" +
      "    return moonbit_make_bytes(0, 0);\n  }\n" +
      "  size_t mbg_size = strlen(mbg_text);\n" +
      "  if (mbg_size > INT32_MAX) {\n" +
      free_line +
      "    mbg_absent[0] = 2;\n    return moonbit_make_bytes(0, 0);\n  }\n" +
      "  mbg_absent[0] = 0;\n" +
      "  moonbit_bytes_t mbg_bytes = moonbit_make_bytes((int32_t)mbg_size, 0);\n" +
      "  memcpy(mbg_bytes, mbg_text, mbg_size);\n" +
      free_after_copy +
      "  return mbg_bytes;\n}\n"
    (Some(moon), Some(c), "")
  } else {
    if normalized_type(function.canonical_return_type) != "int" &&
      normalized_type(function.canonical_return_type) != "signed int" {
      return (None, None, "output function must return int status")
    }
    let moon = "///|\n" +
      ffi_borrow +
      ffi_declaration +
      "\n///|\n" +
      public_head +
      " -> (Int, " +
      output_moon_type +
      ") {\n" +
      "  let " +
      extra_name +
      " = FixedArray::make(1, 0)\n" +
      "  let value = " +
      bridge_name +
      "(" +
      ffi_args.join(", ") +
      ")\n" +
      "  (" +
      extra_name +
      "[0], value)\n}\n"
    let c = "MOONBIT_FFI_EXPORT " +
      output_c_type +
      " " +
      bridge_name +
      "(" +
      c_signature.join(", ") +
      ") {\n  " +
      output_c_type +
      " mbg_value = 0;\n  mbg_status[0] = " +
      call +
      ";\n  return mbg_value;\n}\n"
    (Some(moon), Some(c), "")
  }
}