///|
fn validate_v2_resources(
  nodes : Array[LocatedNode],
  header : String,
  config : GenerateConfig,
) -> Unit raise {
  if config.schema_version != 2 {
    return
  }
  let main : Map[String, CFunction] = Map([])
  let all : 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[function.name] = function
          if same_header(entry.source.file, header) {
            main[function.name] = function
          }
        }
        None => ()
      }
    }
  }
  for policy in config.resources {
    let create = match main.get(policy.create) {
      Some(value) if config.allows(policy.create) => value
      _ =>
        fail("unknown or filtered resource create function: " + policy.create)
    }
    if config.buffer_for(policy.create) is Some(_) ||
      config.return_buffer_for(policy.create) is Some(_) ||
      config.multi_output_for(policy.create) is Some(_) ||
      config.utf8_borrowed_returns.contains(policy.create) ||
      config.utf8_owned_returns.contains(policy.create) {
      fail("conflicting resource creation policy: " + policy.create)
    }
    let release = match all.get(policy.release) {
      Some(value) => value
      None => fail("unknown resource release function: " + policy.release)
    }
    let handle_type = normalized_type(create.canonical_return_type)
    if create.c_signature.contains("...") || !handle_type.has_suffix("*") {
      fail("resource create must return a pointer: " + policy.create)
    }
    if release.parameters.length() != 1 ||
      normalized_type(release.parameters[0].canonical_type) != handle_type ||
      release.c_signature.contains("...") {
      fail("resource release must accept the created handle: " + policy.release)
    }
    if policy.retain != "" {
      let retain = match all.get(policy.retain) {
        Some(value) => value
        None => fail("unknown resource retain function: " + policy.retain)
      }
      if normalized_type(retain.canonical_return_type) != "void" ||
        retain.parameters.length() != 1 ||
        normalized_type(retain.parameters[0].canonical_type) != handle_type ||
        retain.c_signature.contains("...") {
        fail(
          "resource retain must be void and accept the created handle: " +
          policy.retain,
        )
      }
    }
    for view_name in policy.views {
      if config.buffer_for(view_name) is Some(_) ||
        config.return_buffer_for(view_name) is Some(_) ||
        config.multi_output_for(view_name) is Some(_) {
        fail("conflicting resource view policy: " + view_name)
      }
      let view = match main.get(view_name) {
        Some(value) if config.allows(view_name) => value
        _ => fail("unknown or filtered resource view function: " + view_name)
      }
      if view.c_signature.contains("...") ||
        view.parameters.length() != 1 ||
        normalized_type(view.parameters[0].canonical_type) != handle_type ||
        normalized_type(view.canonical_return_type) != handle_type {
        fail("resource view must take and return the same handle: " + view_name)
      }
    }
  }
}

///|
fn v2_resource_needs_manual_release(
  nodes : Array[LocatedNode],
  config : GenerateConfig,
  name : String,
) -> Bool {
  guard config.resource_create_for(name) is Some(policy) else { return false }
  for entry in nodes {
    if text_field(entry.node, "kind") == Some("FunctionDecl") &&
      text_field(entry.node, "name") == Some(policy.release) {
      match c_function(entry.node, entry.source) {
        Some(function) =>
          return normalized_type(function.canonical_return_type) != "void"
        None => return true
      }
    }
  }
  true
}

///|
fn render_v2_resource_create(
  function : CFunction,
  aliases : Map[String, String],
  opaque_types : Map[String, Bool],
  enums : Map[String, Bool],
  config : GenerateConfig,
) -> (String?, String?, String) {
  guard config.resource_create_for(function.name) is Some(policy) else {
    return (None, None, "missing resource policy")
  }
  let handle_type = match
    resolve_type(
      function.canonical_return_type,
      aliases,
      opaque_types,
      enums,
      result=true,
    ) {
    Some(value) if value.has_prefix("C_") => value
    _ => return (None, None, "resource create must return an opaque handle")
  }
  if handle_type != "C_" + policy.name {
    return (None, None, "resource name must match the opaque C type")
  }
  let resource_type = "MbgResource_" + policy.name
  let state_type = "mbg_state_" + policy.name
  let bridge_name = "mbg_resource_" + function.moon_name
  let moon_params : Array[String] = []
  let ffi_params : Array[String] = []
  let ffi_args : Array[String] = []
  let c_params : Array[String] = []
  let c_args : Array[String] = []
  let borrowed : Array[String] = []
  let guards : Array[String] = []
  for index, parameter in function.parameters {
    let kind = v2_abi_kind(parameter.c_type, parameter.canonical_type)
    let moon_type = if kind != "" {
      v2_abi_moon_type(kind)
    } else {
      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.moon_type
        None =>
          return (None, None, "unsupported parameter type: " + parameter.c_type)
      }
    }
    let name = "arg" + index.to_string()
    moon_params.push(name + " : " + moon_type)
    ffi_params.push(name + " : " + moon_type)
    ffi_args.push(name)
    let c_name = "p" + index.to_string()
    if kind != "" {
      c_params.push(v2_abi_c_type(kind) + " " + c_name)
      c_args.push("(" + parameter.c_type + ")" + c_name)
      let check = v2_abi_range_check(kind, c_name)
      if check != "" {
        guards.push(check)
      }
    } else if moon_type == "Bytes" {
      borrowed.push(name)
      c_params.push("moonbit_bytes_t " + c_name)
      c_args.push("(" + parameter.c_type + ")" + c_name)
    } else {
      c_params.push(parameter.c_type + " " + c_name)
      c_args.push(c_name)
    }
  }
  let raw_name = function.moon_name + "_raw"
  let raw_line = v2_extern(raw_name, moon_params, handle_type)
    .replace_all(old="extern \"C\"", new="pub extern \"C\"")
    .replace_all(old="\"" + raw_name + "\"", new="\"" + function.symbol + "\"")
  let moon = "///|\n" +
    "pub type " +
    resource_type +
    "\n\n" +
    "///|\n" +
    (if borrowed.length() == 0 {
      ""
    } else {
      "#borrow(" + borrowed.join(", ") + ")\n"
    }) +
    raw_line +
    "\n" +
    "///|\n" +
    (if borrowed.length() == 0 {
      ""
    } else {
      "#borrow(" + borrowed.join(", ") + ")\n"
    }) +
    v2_extern(bridge_name, ffi_params, resource_type) +
    "\n" +
    "///|\n#borrow(state)\n" +
    v2_extern(bridge_name + "_error", ["state : " + resource_type], "Int") +
    "\n" +
    "///|\n#borrow(state)\n" +
    v2_extern(bridge_name + "_valid", ["state : " + resource_type], "Bool") +
    "\n" +
    "///|\n#borrow(state)\n" +
    v2_extern(bridge_name + "_close", ["state : " + resource_type], "Bool") +
    "\n" +
    "///|\n#borrow(state)\n" +
    v2_extern(bridge_name + "_raw", ["state : " + resource_type], handle_type) +
    "\n" +
    (if policy.retain == "" {
      ""
    } else {
      "///|\n#borrow(state)\n" +
      v2_extern(
        bridge_name + "_retain",
        ["state : " + resource_type],
        resource_type,
      ) +
      "\n"
    }) +
    "///|\n" +
    v2_public_head(function.moon_name, moon_params) +
    " -> " +
    resource_type +
    "? raise {\n  let state = " +
    bridge_name +
    "(" +
    ffi_args.join(", ") +
    ")\n  if " +
    bridge_name +
    "_error(state) != 0 {\n    fail(\"C scalar argument is out of range\")\n  }\n" +
    "  if " +
    bridge_name +
    "_valid(state) { Some(state) } else { None }\n}\n\n" +
    "///|\npub fn " +
    resource_type +
    "::close(self : " +
    resource_type +
    ") -> Bool {\n  " +
    bridge_name +
    "_close(self)\n}\n\n" +
    "///|\npub fn " +
    resource_type +
    "::is_closed(self : " +
    resource_type +
    ") -> Bool {\n  !" +
    bridge_name +
    "_valid(self)\n}\n\n" +
    "///|\npub fn " +
    resource_type +
    "::unsafe_raw(self : " +
    resource_type +
    ") -> " +
    handle_type +
    " raise {\n  if self.is_closed() {\n" +
    "    fail(\"resource is closed\")\n  }\n  " +
    bridge_name +
    "_raw(self)\n}\n\n" +
    "///|\npub fn[T] " +
    resource_type +
    "::with_raw(self : " +
    resource_type +
    ", action : (" +
    handle_type +
    ") -> T) -> T raise {\n" +
    "  if self.is_closed() {\n    fail(\"resource is closed\")\n  }\n" +
    "  let result = action(" +
    bridge_name +
    "_raw(self))\n  ignore(self.is_closed())\n  result\n}\n" +
    (if policy.retain == "" {
      ""
    } else {
      "\n///|\npub fn " +
      resource_type +
      "::retain(self : " +
      resource_type +
      ") -> " +
      resource_type +
      " raise {\n  if self.is_closed() {\n" +
      "    fail(\"resource is closed\")\n  }\n  " +
      bridge_name +
      "_retain(self)\n}\n"
    })
  let c = "typedef struct {\n  " +
    function.return_type +
    " handle;\n" +
    "  int32_t error;\n} " +
    state_type +
    ";\n" +
    "static void " +
    bridge_name +
    "_finalize(void *raw) {\n" +
    "  " +
    state_type +
    " *state = (" +
    state_type +
    " *)raw;\n" +
    "  if (state->handle != NULL) {\n    " +
    function.return_type +
    " handle = state->handle;\n    state->handle = NULL;\n    " +
    policy.release +
    "(handle);\n  }\n}\n" +
    "MOONBIT_FFI_EXPORT " +
    state_type +
    " *" +
    bridge_name +
    "(" +
    c_params.join(", ") +
    ") {\n  " +
    state_type +
    " *state = (" +
    state_type +
    " *)moonbit_make_external_object(" +
    bridge_name +
    "_finalize, sizeof(" +
    state_type +
    "));\n" +
    "  state->handle = NULL;\n  state->error = 0;\n" +
    (if guards.length() == 0 {
      ""
    } else {
      "  if (" + guards.join(" || ") + ") { state->error = 1; return state; }\n"
    }) +
    "  state->handle = " +
    function.name +
    "(" +
    c_args.join(", ") +
    ");\n  return state;\n}\n" +
    "MOONBIT_FFI_EXPORT int32_t " +
    bridge_name +
    "_error(" +
    state_type +
    " *state) { return state->error; }\n" +
    "MOONBIT_FFI_EXPORT int32_t " +
    bridge_name +
    "_valid(" +
    state_type +
    " *state) { return state->handle != NULL; }\n" +
    "MOONBIT_FFI_EXPORT int32_t " +
    bridge_name +
    "_close(" +
    state_type +
    " *state) {\n  if (state->handle == NULL) return 0;\n  " +
    function.return_type +
    " handle = state->handle;\n" +
    "  state->handle = NULL;\n  " +
    policy.release +
    "(handle);\n  return 1;\n}\n" +
    "MOONBIT_FFI_EXPORT " +
    function.return_type +
    " " +
    bridge_name +
    "_raw(void *state_raw) { " +
    state_type +
    " *state = (" +
    state_type +
    " *)state_raw; return state->handle; }\n" +
    (if policy.retain == "" {
      ""
    } else {
      "MOONBIT_FFI_EXPORT " +
      state_type +
      " *" +
      bridge_name +
      "_retain(" +
      state_type +
      " *state) {\n" +
      "  " +
      state_type +
      " *copy = (" +
      state_type +
      " *)moonbit_make_external_object(" +
      bridge_name +
      "_finalize, sizeof(" +
      state_type +
      "));\n" +
      "  copy->handle = NULL;\n  copy->error = 0;\n" +
      "  if (state->handle != NULL) {\n    " +
      policy.retain +
      "(state->handle);\n    copy->handle = state->handle;\n" +
      "  }\n  return copy;\n}\n"
    })
  (Some(moon), Some(c), "")
}

///|
fn render_v2_resource_view(
  function : CFunction,
  config : GenerateConfig,
) -> (String?, String?, String) {
  guard config.resource_view_for(function.name) is Some(policy) else {
    return (None, None, "missing resource view policy")
  }
  let create_name = moonbit_value_name(config.renamed(policy.create))
  let resource_type = "MbgResource_" + policy.name
  let view_type = "MbgView_" + function.moon_name
  let handle_type = "C_" + policy.name
  let bridge_name = "mbg_view_" + function.moon_name
  let resource_getter = "mbg_resource_" + create_name + "_raw"
  let raw_name = function.moon_name + "_raw"
  let raw_line = v2_extern(raw_name, ["raw : " + handle_type], handle_type)
    .replace_all(old="extern \"C\"", new="pub extern \"C\"")
    .replace_all(old="\"" + raw_name + "\"", new="\"" + function.symbol + "\"")
  let moon = "///|\npub struct " +
    view_type +
    " {\n" +
    "  owner : " +
    resource_type +
    "\n  raw : " +
    handle_type +
    "\n}\n\n" +
    "///|\n" +
    raw_line +
    "\n" +
    "///|\n#borrow(owner, absent)\n" +
    v2_extern(
      bridge_name,
      ["owner : " + resource_type, "absent : FixedArray[Int]"],
      handle_type,
    ) +
    "\n" +
    "///|\npub fn " +
    function.moon_name +
    "(owner : " +
    resource_type +
    ") -> " +
    view_type +
    "? raise {\n" +
    "  let absent = FixedArray::make(1, 0)\n" +
    "  let raw = " +
    bridge_name +
    "(owner, absent)\n" +
    "  if absent[0] == 2 {\n    fail(\"resource is closed\")\n" +
    "  } else if absent[0] == 1 {\n    None\n" +
    "  } else {\n    Some({ owner, raw, })\n  }\n}\n\n" +
    "///|\npub fn " +
    view_type +
    "::unsafe_raw(self : " +
    view_type +
    ") -> " +
    handle_type +
    " raise {\n" +
    "  if self.owner.is_closed() {\n    fail(\"resource is closed\")\n" +
    "  }\n  self.raw\n}\n\n" +
    "///|\npub fn[T] " +
    view_type +
    "::with_raw(self : " +
    view_type +
    ", action : (" +
    handle_type +
    ") -> T) -> T raise {\n" +
    "  if self.owner.is_closed() {\n    fail(\"resource is closed\")\n  }\n" +
    "  let result = action(self.raw)\n" +
    "  ignore(self.owner.is_closed())\n  result\n}\n"
  let c = "extern " +
    function.return_type +
    " " +
    resource_getter +
    "(void *state_raw);\n" +
    "MOONBIT_FFI_EXPORT " +
    function.return_type +
    " " +
    bridge_name +
    "(void *owner, int32_t *absent) {\n" +
    "  " +
    function.return_type +
    " handle = " +
    resource_getter +
    "(owner);\n" +
    "  if (handle == NULL) { absent[0] = 2; return NULL; }\n" +
    "  " +
    function.return_type +
    " view = " +
    function.name +
    "(handle);\n" +
    "  absent[0] = view == NULL ? 1 : 0;\n  return view;\n}\n"
  (Some(moon), Some(c), "")
}