///|
priv struct CallbackShape {
  return_type : String
  parameters : Array[String]
}

///|
fn callback_shape(raw : String) -> CallbackShape? {
  let parts = raw.split("(*").to_array()
  if parts.length() != 2 {
    return None
  }
  let return_type = parts[0].trim().to_owned()
  let suffix = parts[1].trim().to_owned()
  if !suffix.has_prefix(")(") || !suffix.has_suffix(")") {
    return None
  }
  let args = suffix[2:suffix.length() - 1].trim().to_owned()
  let parameters : Array[String] = []
  if args != "" && args != "void" {
    for part in args.split(",") {
      parameters.push(part.trim().to_owned())
    }
  }
  Some({ return_type, parameters, })
}

///|
fn callback_direct_type(raw : String, result~ : Bool) -> String? {
  match normalized_type(raw) {
    "void" => if result { Some("Unit") } else { None }
    "int" | "signed int" => Some("Int")
    "unsigned int" => Some("UInt")
    "float" => Some("Float")
    "double" => Some("Double")
    _ => None
  }
}

///|
fn call_callback_type(shape : CallbackShape) -> String? {
  let return_type = match callback_direct_type(shape.return_type, result=true) {
    Some(value) => value
    None => return None
  }
  let parameters : Array[String] = []
  for parameter in shape.parameters {
    match callback_direct_type(parameter, result=false) {
      Some(value) => parameters.push(value)
      None => return None
    }
  }
  Some("FuncRef[(" + parameters.join(", ") + ") -> " + return_type + "]")
}

///|
fn validate_v2_callbacks(
  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.callbacks {
    let function = match main.get(policy.function_name) {
      Some(value) if config.allows(policy.function_name) => value
      _ =>
        fail("unknown or filtered callback function: " + policy.function_name)
    }
    if policy.callback >= function.parameters.length() ||
      function.c_signature.contains("...") {
      fail(
        "callback position or variadic signature is unsupported: " +
        function.name,
      )
    }
    if config.buffer_for(function.name) is Some(_) ||
      config.return_buffer_for(function.name) is Some(_) ||
      config.multi_output_for(function.name) is Some(_) ||
      config.resource_create_for(function.name) is Some(_) ||
      config.resource_view_for(function.name) is Some(_) {
      fail("callback conflicts with another bridge policy: " + function.name)
    }
    let shape = match
      callback_shape(function.parameters[policy.callback].canonical_type) {
      Some(value) => value
      None =>
        fail("callback parameter is not a function pointer: " + function.name)
    }
    if policy.lifetime == "call" {
      if call_callback_type(shape) is None {
        fail(
          "call-scoped callback has unsupported scalar ABI: " + function.name,
        )
      }
    } else {
      if function.parameters.length() != 2 ||
        policy.user_data >= function.parameters.length() ||
        normalized_type(function.canonical_return_type) != "void" ||
        normalized_type(function.parameters[policy.user_data].canonical_type) !=
        "void*" ||
        normalized_type(shape.return_type) != "void" ||
        shape.parameters.length() != 2 ||
        normalized_type(shape.parameters[0]) != "void*" ||
        normalized_type(shape.parameters[1]) != "int" {
        fail(
          "persistent callback requires void (void*, int) and void* user data: " +
          function.name,
        )
      }
      let unregister = match all.get(policy.unregister) {
        Some(value) => value
        None =>
          fail("unknown callback unregister function: " + policy.unregister)
      }
      if unregister.parameters.length() != 1 ||
        normalized_type(unregister.parameters[0].canonical_type) != "void*" ||
        normalized_type(unregister.canonical_return_type) != "void" ||
        unregister.c_signature.contains("...") {
        fail("callback unregister must be void (void*): " + policy.unregister)
      }
    }
  }
}

///|
fn render_v2_call_callback(
  function : CFunction,
  aliases : Map[String, String],
  opaque_types : Map[String, Bool],
  enums : Map[String, Bool],
  config : GenerateConfig,
) -> (String?, String?, String) {
  guard config.callback_for(function.name) is Some(policy) else {
    return (None, None, "missing callback policy")
  }
  if policy.lifetime == "persistent" {
    return render_v2_persistent_callback(function, policy)
  }
  let shape = match
    callback_shape(function.parameters[policy.callback].canonical_type) {
    Some(value) => value
    None => return (None, None, "callback parameter is not a function pointer")
  }
  let callback_type = match call_callback_type(shape) {
    Some(value) => value
    None => return (None, None, "unsupported callback ABI")
  }
  let moon_return = match
    callback_direct_type(function.canonical_return_type, result=true) {
    Some(value) => value
    None => return (None, None, "unsupported function return ABI")
  }
  let params : Array[String] = []
  for index, parameter in function.parameters {
    let moon_type = if index == policy.callback {
      callback_type
    } else {
      if v2_abi_kind(parameter.c_type, parameter.canonical_type) != "" {
        return (None, None, "target-dependent callback call parameter")
      }
      match
        resolve_configured_type(
          parameter.c_type,
          parameter.canonical_type,
          aliases,
          opaque_types,
          enums,
          config,
          utf8_input=false,
          result=false,
        ) {
        Some(value) if value.moon_type == "Int" ||
          value.moon_type == "UInt" ||
          value.moon_type == "Float" ||
          value.moon_type == "Double" => value.moon_type
        _ => return (None, None, "unsupported callback call parameter")
      }
    }
    params.push("arg" + index.to_string() + " : " + moon_type)
  }
  let name = function.moon_name
  let line = v2_extern(name, params, moon_return)
    .replace_all(old="extern \"C\"", new="pub extern \"C\"")
    .replace_all(old="\"" + name + "\"", new="\"" + function.symbol + "\"")
  (Some("///|\n" + line), None, "")
}

///|
fn render_v2_persistent_callback(
  function : CFunction,
  policy : CallbackPolicy,
) -> (String?, String?, String) {
  let name = function.moon_name
  let state_type = "MbgRegistration_" + name
  let data_type = "MbgCallbackData_" + name
  let c_state = "mbg_registration_" + name
  let bridge = "mbg_callback_" + name
  let raw_parameters = if policy.callback == 0 {
    "callback : FuncRef[(" +
    data_type +
    ", Int) -> Unit], user_data : " +
    data_type
  } else {
    "user_data : " +
    data_type +
    ", callback : FuncRef[(" +
    data_type +
    ", Int) -> Unit]"
  }
  let moon = "///|\n#external\npub type " +
    data_type +
    "\n\n" +
    "///|\npub extern \"C\" fn " +
    name +
    "_raw(" +
    raw_parameters +
    ") -> Unit = \"" +
    function.symbol +
    "\"\n\n" +
    "///|\npub extern \"C\" fn " +
    name +
    "_unregister_raw(user_data : " +
    data_type +
    ") -> Unit = \"" +
    policy.unregister +
    "\"\n\n" +
    "///|\npub type " +
    state_type +
    "\n\n" +
    "///|\n#borrow(callback)\n" +
    v2_extern(
      bridge,
      [
        "invoke : FuncRef[((Int) -> Unit, Int) -> Unit]", "callback : (Int) -> Unit",
      ],
      state_type,
    ) +
    "\n" +
    "///|\n#borrow(state)\n" +
    v2_extern(bridge + "_close", ["state : " + state_type], "Bool") +
    "\n" +
    "///|\n#borrow(state)\n" +
    v2_extern(bridge + "_active", ["state : " + state_type], "Bool") +
    "\n" +
    "///|\npub fn " +
    name +
    "(callback : (Int) -> Unit) -> " +
    state_type +
    " {\n" +
    "  " +
    bridge +
    "(fn(f, value) { f(value) }, callback)\n}\n\n" +
    "///|\npub fn " +
    state_type +
    "::close(self : " +
    state_type +
    ") -> Bool {\n" +
    "  " +
    bridge +
    "_close(self)\n}\n\n" +
    "///|\npub fn " +
    state_type +
    "::is_closed(self : " +
    state_type +
    ") -> Bool {\n" +
    "  !" +
    bridge +
    "_active(self)\n}\n"
  let register_args = if policy.callback == 0 {
    bridge + "_dispatch, state"
  } else {
    "state, " + bridge + "_dispatch"
  }
  let c = "typedef struct {\n  void (*invoke)(void*, int32_t);\n  void *closure;\n  int32_t active;\n} " +
    c_state +
    ";\n" +
    "static void " +
    bridge +
    "_close_state(" +
    c_state +
    " *state) {\n" +
    "  if (!state->active) return;\n  state->active = 0;\n" +
    "  " +
    policy.unregister +
    "(state);\n" +
    "  void *closure = state->closure;\n  state->closure = NULL;\n" +
    "  moonbit_decref(closure);\n}\n" +
    "static void " +
    bridge +
    "_finalize(void *raw) {\n" +
    "  " +
    bridge +
    "_close_state((" +
    c_state +
    " *)raw);\n}\n" +
    "static void " +
    bridge +
    "_dispatch(void *data, int value) {\n" +
    "  " +
    c_state +
    " *state = (" +
    c_state +
    " *)data;\n" +
    "  if (!state->active) return;\n" +
    "  void *closure = state->closure;\n  moonbit_incref(state);\n" +
    "  moonbit_incref(closure);\n  state->invoke(closure, (int32_t)value);\n" +
    "  moonbit_decref(closure);\n  moonbit_decref(state);\n}\n" +
    "MOONBIT_FFI_EXPORT " +
    c_state +
    " *" +
    bridge +
    "(void (*invoke)(void*, int32_t), void *closure) {\n" +
    "  typedef char mbg_check_int[(sizeof(int) == sizeof(int32_t)) ? 1 : -1];\n" +
    "  " +
    c_state +
    " *state = (" +
    c_state +
    " *)moonbit_make_external_object(" +
    bridge +
    "_finalize, sizeof(" +
    c_state +
    "));\n" +
    "  state->invoke = invoke;\n  state->closure = closure;\n  state->active = 1;\n" +
    "  moonbit_incref(closure);\n  " +
    function.name +
    "(" +
    register_args +
    ");\n" +
    "  return state;\n}\n" +
    "MOONBIT_FFI_EXPORT int32_t " +
    bridge +
    "_close(" +
    c_state +
    " *state) {\n" +
    "  if (!state->active) return 0;\n  " +
    bridge +
    "_close_state(state);\n  return 1;\n}\n" +
    "MOONBIT_FFI_EXPORT int32_t " +
    bridge +
    "_active(" +
    c_state +
    " *state) { return state->active; }\n"
  (Some(moon), Some(c), "")
}