///|
pub fn wrapper_source(config : Config) -> String {
  let mut text = "///|\nfn main { () }\n\n"
  for fn_ in config.functions {
    // The typed function binding rejects raises, generics and incompatible ABI.
    let params = fn_.params
      .map(p => p.name + " : " + p.kind.moon_name())
      .join(", ")
    let args = fn_.params.map(p => p.name).join(", ")
    text += "///|\npub fn moonohos_raw_" +
      fn_.name +
      "(" +
      params +
      ") -> " +
      fn_.result.moon_name() +
      " {\n  @core." +
      fn_.name +
      "(" +
      args +
      ")\n}\n\n"
  }
  text
}

///|
pub fn wrapper_package(config : Config) -> String {
  let exports = config.functions
    .map(f => "\"moonohos_raw_" + f.name + "\"")
    .join(", ")
  "import { \"" +
  config.package_name +
  "\" @core, }\npkgtype(kind: \"executable\")\noptions(link: { \"native\": { \"exports\": [" +
  exports +
  "] } })\n"
}

///|
pub fn c_header(config : Config) -> String {
  let mut text = "#pragma once\n#include \n#include \n// Reference arguments are borrowed; reference results carry one owned reference.\n// Internal ABI for the pinned MoonBit compiler only.\n#ifdef __cplusplus\nextern \"C\" {\n#endif\nvoid moonohos_initialize(void);\n"
  for fn_ in config.functions {
    let args = if fn_.params.is_empty() {
      "void"
    } else {
      fn_.params.map(p => p.kind.c_name()).join(", ")
    }
    text += fn_.result.c_name() + " moonohos_" + fn_.name + "(" + args + ");\n"
  }
  text + "#ifdef __cplusplus\n}\n#endif\n"
}

///|
pub fn declarations(config : Config) -> String {
  let mut text = "// Generated by MoonOHOS. Synchronous calls may throw TypeError/RangeError.\n"
  for fn_ in config.functions {
    let params = fn_.params
      .map(p => p.name + ": " + p.kind.ark_name())
      .join(", ")
    text += "export const " +
      fn_.name +
      ": (" +
      params +
      ") => " +
      fn_.result.ark_name() +
      ";\n"
  }
  text
}

///|
pub fn embed_source(config : Config) -> String {
  let mut text = (
      #|#include "moonohos.h"
      #|#define main moonohos_unused_main
      #|#include "core.c"
      #|#undef main
      #|void moonohos_initialize(void) {
      #|  static char name[] = "moonohos";
      #|  static char *args[] = {name, 0};
      #|  moonbit_runtime_init(1, args);
      #|  moonbit_init();
      #|}
      #|
    ) +
    "\n"
  // This compiler lowers Unit exports to int32_t; normalize them to C void.
  for fn_ in config.functions {
    let params = if fn_.params.is_empty() {
      "void"
    } else {
      fn_.params
      .mapi((i, p) => p.kind.c_name() + " arg" + i.to_string())
      .join(", ")
    }
    let args = fn_.params.mapi((i, _) => "arg" + i.to_string()).join(", ")
    text += fn_.result.c_name() +
      " moonohos_" +
      fn_.name +
      "(" +
      params +
      ") { " +
      (if fn_.result == Void { "(void)" } else { "return " }) +
      "moonohos_raw_" +
      fn_.name +
      "(" +
      args +
      "); }\n"
  }
  text
}

///|
pub fn native_source(config : Config) -> String {
  let mut text = (
      #|#include 
      #|#include 
      #|#include 
      #|#include 
      #|#include 
      #|#include 
      #|#include 
      #|#include 
      #|#include "moonohos.h"
      #|static std::mutex gate;
      #|static std::once_flag initialized;
      #|static bool Check(napi_env env, napi_status status) {
      #|  if (status == napi_ok) return true;
      #|  if (status != napi_pending_exception) {
      #|    const napi_status thrown = napi_throw_error(env, nullptr, "Node-API operation failed");
      #|    if (thrown != napi_ok && thrown != napi_pending_exception) return false;
      #|  }
      #|  return false;
      #|}
      #|static bool ReadNumber(napi_env env, napi_value value, double &out) {
      #|  napi_valuetype type;
      #|  if (!Check(env, napi_typeof(env, value, &type))) return false;
      #|  if (type != napi_number) { Check(env, napi_throw_type_error(env, nullptr, "number required")); return false; }
      #|  if (!Check(env, napi_get_value_double(env, value, &out))) return false;
      #|  if (!std::isfinite(out)) { Check(env, napi_throw_range_error(env, nullptr, "finite number required")); return false; }
      #|  return true;
      #|}
      #|static bool ReadInt(napi_env env, napi_value value, int32_t &out) {
      #|  double number;
      #|  if (!ReadNumber(env, value, number)) return false;
      #|  if (std::trunc(number) != number || number < INT32_MIN || number > INT32_MAX) {
      #|    Check(env, napi_throw_range_error(env, nullptr, "signed 32-bit integer required")); return false;
      #|  }
      #|  out = static_cast(number); return true;
      #|}
      #|static bool ReadBool(napi_env env, napi_value value, int32_t &out) {
      #|  napi_valuetype type;
      #|  if (!Check(env, napi_typeof(env, value, &type))) return false;
      #|  if (type != napi_boolean) { Check(env, napi_throw_type_error(env, nullptr, "boolean required")); return false; }
      #|  bool boolean;
      #|  if (!Check(env, napi_get_value_bool(env, value, &boolean))) return false;
      #|  out = boolean ? 1 : 0; return true;
      #|}
      #|
    ) +
    "\n" +
    reference_helpers()
  for fn_ in config.functions {
    text += "static napi_value Call_" +
      fn_.name +
      "(napi_env env, napi_callback_info info) {\n try {\n"
    if !fn_.params.is_empty() {
      let count = fn_.params.length().to_string()
      text += "  size_t argc = " +
        count +
        ";\n  napi_value argv[" +
        count +
        "] = {};\n" +
        "  if (!Check(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr))) return nullptr;\n" +
        "  if (argc < " +
        count +
        ") { Check(env, napi_throw_type_error(env, nullptr, \"missing arguments\")); return nullptr; }\n"
    }
    for index, p in fn_.params {
      let argument = "arg" + index.to_string()
      let reader = match p.kind {
        Int32 => "ReadInt"
        Float64 => "ReadNumber"
        Boolean => "ReadBool"
        Text => "ReadText"
        Binary => "ReadBytes"
        Void => ""
      }
      text += "  " +
        (match p.kind {
          Text => "std::u16string"
          Binary => "std::vector"
          _ => p.kind.c_name()
        }) +
        " " +
        argument +
        ";\n  if (!" +
        reader +
        "(env, argv[" +
        index.to_string() +
        "], " +
        argument +
        ")) return nullptr;\n"
    }
    let args = fn_.params
      .mapi((i, p) => {
        (if p.kind == Text || p.kind == Binary { "owned" } else { "arg" }) +
        i.to_string() +
        (if p.kind == Text || p.kind == Binary { ".ptr" } else { "" })
      })
      .join(", ")
    let call = "moonohos_" +
      fn_.name +
      "(" +
      args +
      (if fn_.result == Text || fn_.result == Binary {
        ")};\n"
      } else {
        ");\n"
      })
    if fn_.result != Void {
      text += "  " +
        (match fn_.result {
          Text => "std::u16string"
          Binary => "std::vector"
          _ => fn_.result.c_name()
        }) +
        " value;\n"
    }
    text += "  {\n    std::lock_guard lock(gate);\n    std::call_once(initialized, moonohos_initialize);\n"
    for index, p in fn_.params {
      if p.kind == Text || p.kind == Binary {
        text += "    MoonOwner<" +
          p.kind.c_name() +
          "> owned" +
          index.to_string() +
          "{" +
          (if p.kind == Text { "MakeText" } else { "MakeBytes" }) +
          "(arg" +
          index.to_string() +
          ")};\n"
      }
    }
    text += "    " +
      (if fn_.result == Void {
        ""
      } else if fn_.result == Text || fn_.result == Binary {
        "MoonOwner<" + fn_.result.c_name() + "> returned{"
      } else {
        "value = "
      }) +
      call +
      (if fn_.result == Text || fn_.result == Binary {
        "    value = " +
        (if fn_.result == Text { "CopyText" } else { "CopyBytes" }) +
        "(returned.ptr);\n"
      } else {
        ""
      }) +
      "  }\n  napi_value result;\n"
    let creation = match fn_.result {
      Int32 => "napi_create_int32(env, value, &result)"
      Float64 => "napi_create_double(env, value, &result)"
      Boolean => "napi_get_boolean(env, value != 0, &result)"
      Void => "napi_get_undefined(env, &result)"
      Text =>
        "napi_create_string_utf16(env, value.data(), value.size(), &result)"
      Binary => "CreateBytes(env, value, &result)"
    }
    if fn_.result == Float64 {
      text += "  if (!std::isfinite(value)) { Check(env, napi_throw_range_error(env, nullptr, \"non-finite result\")); return nullptr; }\n"
    }
    text += "  if (!Check(env, " +
      creation +
      ")) return nullptr;\n  return result;\n } catch (const std::length_error &) { Check(env, napi_throw_range_error(env, nullptr, \"buffer capacity exceeded\")); return nullptr; }\n catch (const std::exception &) { Check(env, napi_throw_error(env, nullptr, \"native allocation or runtime operation failed\")); return nullptr; }\n}\n\n"
  }
  text += "static napi_value Init(napi_env env, napi_value exports) {\n  napi_property_descriptor properties[] = {\n"
  for fn_ in config.functions {
    text += "    {\"" +
      fn_.name +
      "\", nullptr, Call_" +
      fn_.name +
      ", nullptr, nullptr, nullptr, napi_default, nullptr},\n"
  }
  text += "  };\n  if (!Check(env, napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties))) return nullptr;\n  return exports;\n}\nNAPI_MODULE(" +
    config.native_module +
    ", Init)\n"
  text
}

///|
pub fn cmake_source(config : Config) -> String {
  let target = config.native_module
  (
    #|cmake_minimum_required(VERSION 3.16)
    #|project(MoonOHOS LANGUAGES C CXX)
    #|add_library(__TARGET__ SHARED embed.c napi_init.cpp
    #|  "${MOON_HOME}/lib/runtime/runtime.c" "${MOON_HOME}/lib/runtime/env.c"
    #|  "${MOON_HOME}/lib/runtime/backtrace.c" "${MOON_HOME}/lib/runtime/sync_io.c"
    #|  "${MOON_HOME}/lib/runtime/utf.c")
    #|target_include_directories(__TARGET__ PRIVATE "${MOON_HOME}/include")
    #|target_compile_features(__TARGET__ PRIVATE c_std_11 cxx_std_17)
    #|target_compile_definitions(__TARGET__ PRIVATE _GNU_SOURCE MOONBIT_ALLOCATOR=MOONBIT_ALLOCATOR_SYSTEM MOONBIT_NATIVE_EXIT_ON_PANIC)
    #|target_compile_options(__TARGET__ PRIVATE -fwrapv -fno-strict-aliasing -fvisibility=hidden -Werror=implicit-function-declaration)
    #|target_link_options(__TARGET__ PRIVATE -Wl,--no-undefined)
    #|target_link_libraries(__TARGET__ PRIVATE libace_napi.z.so m)
    #|
  ).replace_all(old="__TARGET__", new=target)
}