// vim:fileencoding=utf-8:foldmethod=marker

#include "datatypes.h"
#include "dbl_list.h"
#include "type_enums.h"
#include "wapp_core.h"

#define ERR_MSG "Not enough capacity in dst buffer"
#define CFILE_DISCLAIMER "/**\n" \
  " * THIS FILE IS AUTOMATICALLY GENERATED. ANY MODIFICATIONS TO IT WILL BE OVERWRITTEN.\n" \
  " */\n\n"

void cobject_to_string(Str8 *dst, const CObject *object) {
  wapp_debug_assert(dst != NULL && object != NULL, "`allocator`, `dst` and `object` should not be NULL");

  switch (object->kind) {
    case COBJECT_CTYPE:
      ctype_to_string(dst, object->object.c_type);
      break;
    case COBJECT_CQUALIFIER:
      cqualifier_to_string(dst, object->object.c_qualifier);
      break;
    case COBJECT_CPOINTERTYPE:
      cpointertype_to_string(dst, object->object.c_pointertype);
      break;
    case COBJECT_CPOINTER:
      cpointer_to_string(dst, &(object->object.c_pointer));
      break;
    case COBJECT_CENUMVAL:
      cenumval_to_string(dst, &(object->object.c_enumval));
      break;
    case COBJECT_CENUM:
      cenum_to_string(dst, &(object->object.c_enum));
      break;
    case COBJECT_CMACRO:
      cmacro_to_string(dst, &(object->object.c_macro));
      break;
    case COBJECT_CSTRUCT:
      cstruct_to_string(dst, &(object->object.c_struct));
      break;
    case COBJECT_CUSERTYPE:
      cusertype_to_string(dst, &(object->object.c_usertype));
      break;
    case COBJECT_CDATATYPE:
      cdatatype_to_string(dst, &(object->object.c_datatype));
      break;
    case COBJECT_CARG:
      carg_to_string(dst, &(object->object.c_arg));
      break;
    case COBJECT_CFUNC:
      cfunc_to_string(dst, &(object->object.c_func));
      break;
    case COBJECT_CINCULDE:
      cinclude_to_string(dst, &(object->object.c_include));
      break;
    case COBJECT_CHEADER:
      cheader_to_string(dst, &(object->object.c_header));
      break;
    case COBJECT_CSOURCE:
      csource_to_string(dst, &(object->object.c_source));
      break;
    default:
      break;
  }
}

void ctype_to_string(Str8 *dst, CType ctype) {
  wapp_runtime_assert(ctypes[ctype].size <= dst->capacity, ERR_MSG);
  wapp_str8_copy_str8_capped(dst, &ctypes[ctype]);
}

void cqualifier_to_string(Str8 *dst, CQualifier cqualifier) {
  wapp_runtime_assert(cqualifiers[cqualifier].size <= dst->capacity, ERR_MSG);
  wapp_str8_copy_str8_capped(dst, &cqualifiers[cqualifier]);
}

void cpointertype_to_string(Str8 *dst, CPointerType cpointertype) {
  wapp_runtime_assert(cpointertypes[cpointertype].size <= dst->capacity, ERR_MSG);
  wapp_str8_copy_str8_capped(dst, &cpointertypes[cpointertype]);
}

void cpointer_to_string(Str8 *dst, const CPointer *cpointer) {
  wapp_debug_assert(dst != NULL && cpointer != NULL, "`dst` and `cpointer` should not be NULL");

  u64 total_size = cpointertypes[cpointer->type].size + cqualifiers[cpointer->qualifier].size;
  wapp_runtime_assert(total_size <= dst->capacity, ERR_MSG);
  wapp_str8_format(dst,
                   WAPP_STR8_SPEC WAPP_STR8_SPEC,
                   wapp_str8_varg(cpointertypes[cpointer->type]),
                   wapp_str8_varg(cqualifiers[cpointer->qualifier]));
}

void cenumval_to_string(Str8 *dst, const CEnumVal *cenumval) {
  wapp_debug_assert(dst != NULL && cenumval != NULL, "`dst` and `cenumval` should not be NULL");

  Str8 tmp     = wapp_str8_buf(CCGEN_BUF_TINY);
  u64 tmp_size = 0;
  if (cenumval->value != NULL) {
    wapp_str8_format(&tmp, " = %d", *(cenumval->value));
    tmp_size = tmp.size;
  }

  u64 total_size = cenumval->name.size + tmp_size;
  wapp_runtime_assert(total_size <= dst->capacity, ERR_MSG);
  wapp_str8_format(dst,
                   WAPP_STR8_SPEC WAPP_STR8_SPEC,
                   wapp_str8_varg(cenumval->name),
                   wapp_str8_varg(tmp));
}

void cenum_to_string(Str8 *dst, const CEnum *cenum) {
  wapp_debug_assert(dst != NULL && cenum != NULL, "`dst` and `cenum` should not be NULL");

  Str8 header = wapp_str8_buf(CCGEN_BUF_LARGE);
  Str8 values = wapp_str8_buf(CCGEN_BUF_MAX);
  Str8 footer = wapp_str8_buf(CCGEN_BUF_LARGE);
  Str8 tmp    = wapp_str8_buf(CCGEN_BUF_LARGE);

  if (cenum->add_typedef) {
    wapp_str8_copy_cstr_capped(&header, "typedef enum {\n");
    wapp_str8_format(&footer, "} " WAPP_STR8_SPEC ";\n", wapp_str8_varg(cenum->name));
  } else {
    wapp_str8_format(&header, "enum " WAPP_STR8_SPEC " {\n", wapp_str8_varg(cenum->name));
    wapp_str8_copy_cstr_capped(&footer, "};\n");
  }

  for (u64 i = 0; i < cenum->values.node_count; ++i) {
    cenumval_to_string(&tmp, wapp_cenumval_list_get(&(cenum->values), i)->item);
    // Add 4 for extra characters
    wapp_runtime_assert(tmp.size + 4 <= values.capacity - values.size, ERR_MSG);
    wapp_str8_concat_capped(&values, &wapp_str8_lit_ro("  "));
    wapp_str8_concat_capped(&values, &tmp);
    wapp_str8_concat_capped(&values, &wapp_str8_lit_ro(",\n"));
  }

  u64 total_size = header.size + values.size + footer.size;
  wapp_runtime_assert(total_size <= dst->capacity, ERR_MSG);
  wapp_str8_format(dst,
                   WAPP_STR8_SPEC WAPP_STR8_SPEC WAPP_STR8_SPEC,
                   wapp_str8_varg(header),
                   wapp_str8_varg(values),
                   wapp_str8_varg(footer));
}

void cmacro_to_string(Str8 *dst, const CMacro *cmacro) {
  wapp_debug_assert(dst != NULL && cmacro != NULL, "`dst` and `cmacro` should not be NULL");

  Str8 def       = wapp_str8_lit("#define ");
  u64 total_size = def.size + cmacro->name.size + cmacro->value.size + 1; // Add 1 for newline
  wapp_runtime_assert(total_size <= dst->capacity, ERR_MSG);
  wapp_str8_format(dst,
                   WAPP_STR8_SPEC WAPP_STR8_SPEC " " WAPP_STR8_SPEC "\n",
                   wapp_str8_varg(def),
                   wapp_str8_varg(cmacro->name),
                   wapp_str8_varg(cmacro->value));
}

void cstruct_to_string(Str8 *dst, const CStruct *cstruct) {
  wapp_debug_assert(dst != NULL && cstruct != NULL, "`dst` and `cstruct` should not be NULL");

  Str8 declaration = wapp_str8_buf(CCGEN_BUF_LARGE);
  Str8 definition  = wapp_str8_buf(CCGEN_BUF_MAX);

  declare_cstruct(&declaration, cstruct);
  define_cstruct(&definition, cstruct);

  u64 total_size = declaration.size + definition.size;
  wapp_runtime_assert(total_size <= dst->capacity, ERR_MSG);
  wapp_str8_format(dst, WAPP_STR8_SPEC WAPP_STR8_SPEC,
                   wapp_str8_varg(declaration), wapp_str8_varg(definition));
}

void declare_cstruct(Str8 *dst, const CStruct *cstruct) {
  wapp_debug_assert(dst != NULL && cstruct != NULL, "`dst` and `cstruct` should not be NULL");

  Str8 tmp = wapp_str8_buf(CCGEN_BUF_MAX);
  wapp_str8_format(&tmp,
                   "typedef struct " WAPP_STR8_SPEC " " WAPP_STR8_SPEC ";\n",
                   wapp_str8_varg(cstruct->name),
                   wapp_str8_varg(cstruct->typedef_name.size > 0 ? cstruct->typedef_name : cstruct->name));

  wapp_runtime_assert(tmp.size <= dst->capacity, ERR_MSG);
  wapp_str8_copy_str8_capped(dst, &tmp);
}

void define_cstruct(Str8 *dst, const CStruct *cstruct) {
  wapp_debug_assert(dst != NULL && cstruct != NULL, "`dst` and `cstruct` should not be NULL");

  Str8 definition = wapp_str8_buf(CCGEN_BUF_LARGE);
  Str8 args       = wapp_str8_buf(CCGEN_BUF_MAX);
  Str8 footer     = wapp_str8_lit_ro("};\n");
  Str8 tmp        = wapp_str8_buf(CCGEN_BUF_MEDIUM);

  wapp_str8_format(&definition, "struct " WAPP_STR8_SPEC " {\n", wapp_str8_varg(cstruct->name));

  for (u64 i = 0; i < cstruct->args.node_count; ++i) {
    carg_to_string(&tmp, wapp_carg_list_get(&(cstruct->args), i)->item);
    // Add 4 for extra characters
    wapp_runtime_assert(tmp.size + 4 <= args.capacity - args.size, ERR_MSG);
    wapp_str8_concat_capped(&args, &wapp_str8_lit_ro("  "));
    wapp_str8_concat_capped(&args, &tmp);
    wapp_str8_concat_capped(&args, &wapp_str8_lit_ro(";\n"));
  }

  u64 total_size = definition.size + args.size + footer.size;
  wapp_runtime_assert(total_size <= dst->capacity, ERR_MSG);
  wapp_str8_format(dst,
                   WAPP_STR8_SPEC WAPP_STR8_SPEC WAPP_STR8_SPEC,
                   wapp_str8_varg(definition),
                   wapp_str8_varg(args),
                   wapp_str8_varg(footer));
}

void cusertype_to_string(Str8 *dst, const CUserType *cusertype) {
  wapp_debug_assert(dst != NULL && cusertype != NULL, "`dst` and `cusertype` should not be NULL");

  switch (cusertype->kind) {
    case CUSERTYPE_CENUM:
      cenum_to_string(dst, &(cusertype->type.c_enum));
      break;
    case CUSERTYPE_CSTRUCT:
      cstruct_to_string(dst, &(cusertype->type.c_struct));
      break;
    default:
      break;
  }
}

void cdatatype_to_string(Str8 *dst, const CDataType *cdatatype) {
  wapp_debug_assert(dst != NULL && cdatatype != NULL, "`dst` and `cdatatype` should not be NULL");

  switch (cdatatype->kind) {
    case CDATATYPE_CTYPE:
      ctype_to_string(dst, cdatatype->type.c_type);
      break;
    case CDATATYPE_CUSERTYPE:
      cusertype_to_string(dst, &(cdatatype->type.c_usertype));
      break;
    case CDATATYPE_STR:
      wapp_runtime_assert(cdatatype->type.str.size <= dst->capacity, ERR_MSG);
      wapp_str8_copy_str8_capped(dst, &(cdatatype->type.str));
      break;
    default:
      break;
  }
}

void carg_to_string(Str8 *dst, const CArg *carg) {
  wapp_debug_assert(dst != NULL && carg != NULL, "`dst` and `carg` should not be NULL");

  Str8 qualifier = wapp_str8_buf(CCGEN_BUF_MEDIUM);
  Str8 type      = wapp_str8_buf(CCGEN_BUF_LARGE);
  Str8 pointer   = wapp_str8_buf(CCGEN_BUF_MEDIUM);
  Str8 array     = wapp_str8_buf(CCGEN_BUF_MEDIUM);

  cqualifier_to_string(&qualifier, carg->qualifier);
  cdatatype_to_string(&type, &(carg->type));
  wapp_str8_concat_capped(&type, &wapp_str8_lit_ro(" "));
  cpointer_to_string(&pointer, &(carg->pointer));
  if (carg->is_array) { wapp_str8_copy_cstr_capped(&array, "[]"); }

  u64 total_size = qualifier.size + type.size + pointer.size + array.size + carg->name.size;
  wapp_runtime_assert(total_size <= dst->capacity, ERR_MSG);
  wapp_str8_format(dst, WAPP_STR8_SPEC WAPP_STR8_SPEC WAPP_STR8_SPEC WAPP_STR8_SPEC WAPP_STR8_SPEC,
                   wapp_str8_varg(qualifier), wapp_str8_varg(type), wapp_str8_varg(pointer),
                   wapp_str8_varg(carg->name), wapp_str8_varg(array));
}

void cfunc_to_string(Str8 *dst, const CFunc *cfunc) {
  wapp_debug_assert(dst != NULL && cfunc != NULL, "`dst` and `cfunc` should not be NULL");

  Str8 qualifiers = wapp_str8_buf(CCGEN_BUF_LARGE);
  Str8 args       = wapp_str8_buf(CCGEN_BUF_LARGE);
  Str8 ret_type   = wapp_str8_buf(CCGEN_BUF_SMALL);
  Str8 pointer    = wapp_str8_buf(CCGEN_BUF_SMALL);
  Str8 tmp        = wapp_str8_buf(CCGEN_BUF_MEDIUM);
  CQualifier *qf  = NULL;
  CArg *arg       = NULL;

  for (u64 i = 0; i < cfunc->qualifiers.node_count; ++i) {
    qf = wapp_cqualifier_list_get(&(cfunc->qualifiers), i)->item;
    if (*qf == CQUALIFIER_NONE) { continue; }

    cqualifier_to_string(&tmp, *qf);

    wapp_runtime_assert(tmp.size + 1 <= qualifiers.capacity - qualifiers.size, ERR_MSG);
    if (qualifiers.size > 0) { wapp_str8_concat_capped(&qualifiers, &wapp_str8_lit_ro(" ")); }

    wapp_str8_concat_capped(&qualifiers, &tmp);
  }

  for (u64 i = 0; i < cfunc->args.node_count; ++i) {
    arg = wapp_carg_list_get(&(cfunc->args), i)->item;
    carg_to_string(&tmp, arg);

    wapp_runtime_assert(tmp.size + 2 <= args.capacity - args.size, ERR_MSG);
    wapp_str8_concat_capped(&args, &tmp);
    if (i + 1 < cfunc->args.node_count) { wapp_str8_concat_capped(&args, &wapp_str8_lit_ro(", ")); }
  }

  cdatatype_to_string(&ret_type, &(cfunc->ret_type));
  cpointer_to_string(&pointer, &(cfunc->pointer));

  u64 total_size = cfunc->name.size + qualifiers.size + args.size + ret_type.size + pointer.size + 4;
  wapp_runtime_assert(total_size <= dst->capacity, ERR_MSG);
  wapp_str8_format(dst,
                   WAPP_STR8_SPEC WAPP_STR8_SPEC " " WAPP_STR8_SPEC WAPP_STR8_SPEC "(" WAPP_STR8_SPEC ")",
                   wapp_str8_varg(qualifiers),
                   wapp_str8_varg(ret_type),
                   wapp_str8_varg(pointer),
                   wapp_str8_varg(cfunc->name),
                   wapp_str8_varg(args));
}

void declare_cfunc(Str8 *dst, const CFunc *cfunc) {
  wapp_debug_assert(dst != NULL && cfunc != NULL, "`dst` and `cfunc` should not be NULL");

  Str8 tmp = wapp_str8_buf(CCGEN_BUF_MAX);
  cfunc_to_string(&tmp, cfunc);

  wapp_runtime_assert(tmp.size + 2 <= dst->capacity, ERR_MSG);
  wapp_str8_format(dst, WAPP_STR8_SPEC ";\n", wapp_str8_varg(tmp));
}

void define_cfunc(Str8 *dst, const CFunc *cfunc) {
  wapp_debug_assert(dst != NULL && cfunc != NULL, "`dst` and `cfunc` should not be NULL");

  Str8 tmp = wapp_str8_buf(CCGEN_BUF_MAX);
  cfunc_to_string(&tmp, cfunc);

  wapp_runtime_assert(tmp.size + cfunc->body.size + 8 <= dst->capacity, ERR_MSG);
  wapp_str8_format(dst, WAPP_STR8_SPEC " {\n" WAPP_STR8_SPEC "\n}\n\n",
                   wapp_str8_varg(tmp), wapp_str8_varg(cfunc->body));
}

void cinclude_to_string(Str8 *dst, const CInclude *cinclude) {
  wapp_debug_assert(dst != NULL && cinclude != NULL, "`dst` and `cinclude` should not be NULL");

  Str8 header_str = wapp_str8_buf(CCGEN_BUF_LARGE);
  if (!cheaderinclude_to_string(&header_str, &(cinclude->header))) { return; }

  Str8 inc = wapp_str8_lit("#include ");
  Str8 open_symbol = wapp_str8_buf(CCGEN_BUF_MIN);
  Str8 close_symbol = wapp_str8_buf(CCGEN_BUF_MIN);

  if (cinclude->is_local) {
    wapp_str8_concat_capped(&open_symbol, &wapp_str8_lit_ro("\""));
    wapp_str8_concat_capped(&close_symbol, &wapp_str8_lit_ro("\""));
    if (cinclude->same_dir) { wapp_str8_concat_capped(&open_symbol, &wapp_str8_lit_ro("./")); }
  } else {
    wapp_str8_concat_capped(&open_symbol, &wapp_str8_lit_ro("<"));
    wapp_str8_concat_capped(&close_symbol, &wapp_str8_lit_ro(">"));
  }

  u64 total_size = header_str.size + inc.size + open_symbol.size + close_symbol.size;
  wapp_runtime_assert(total_size <= dst->capacity, ERR_MSG);
  wapp_str8_format(dst,
                   WAPP_STR8_SPEC WAPP_STR8_SPEC WAPP_STR8_SPEC WAPP_STR8_SPEC "\n",
                   wapp_str8_varg(inc),
                   wapp_str8_varg(open_symbol),
                   wapp_str8_varg(header_str),
                   wapp_str8_varg(close_symbol));
}

void cheader_to_string(Str8 *dst, const CHeader *cheader) {
  wapp_debug_assert(dst != NULL && cheader != NULL, "`dst` and `cheader` should not be NULL");

  Allocator arena = wapp_mem_arena_allocator_init(MB(64));
  Str8 *output = wapp_str8_alloc_buf(&arena, KB(32));
  wapp_runtime_assert(output != NULL, "Failed to allocate buffer");

  Str8 header_guard_name = wapp_str8_buf(CCGEN_BUF_SMALL);
  Str8 tmp = wapp_str8_buf(CCGEN_BUF_MAX);

  // ADD HEADER GUARD AND START C LINKAGE
  wapp_str8_to_upper(&header_guard_name, &(cheader->name));
  wapp_str8_concat_capped(&header_guard_name, &wapp_str8_lit_ro("_H"));
  wapp_str8_format(&tmp, "#ifndef " WAPP_STR8_SPEC "\n#define " WAPP_STR8_SPEC "\n\n"
                   "#ifdef WAPP_PLATFORM_CPP\nBEGIN_C_LINKAGE\n#endif // !WAPP_PLATFORM_CPP\n\n",
                   wapp_str8_varg(header_guard_name), wapp_str8_varg(header_guard_name));
  wapp_str8_alloc_concat(&arena, output, &tmp);

  for (u64 i = 0; i < cheader->includes.node_count; ++i) {
    cinclude_to_string(&tmp, wapp_cinclude_list_get(&(cheader->includes), i)->item);
    wapp_str8_alloc_concat(&arena, output, &tmp);
  }
  if (cheader->includes.node_count > 0) { wapp_str8_alloc_concat(&arena, output, &wapp_str8_lit_ro("\n")); }

  for (u64 i = 0; i < cheader->macros.node_count; ++i) {
    cmacro_to_string(&tmp, wapp_cmacro_list_get(&(cheader->macros), i)->item);
    wapp_str8_alloc_concat(&arena, output, &tmp);
  }
  if (cheader->macros.node_count > 0) { wapp_str8_alloc_concat(&arena, output, &wapp_str8_lit_ro("\n")); }

  if (cheader->cpp_macros.node_count > 0) {
    wapp_str8_alloc_concat(&arena, output, &wapp_str8_lit_ro("#ifdef WAPP_PLATFORM_CPP\n"));

    for (u64 i = 0; i < cheader->cpp_macros.node_count; ++i) {
      cmacro_to_string(&tmp, wapp_cmacro_list_get(&(cheader->cpp_macros), i)->item);
      wapp_str8_alloc_concat(&arena, output, &tmp);
    }

    wapp_str8_alloc_concat(&arena, output, &wapp_str8_lit_ro("#else\n"));

    for (u64 i = 0; i < cheader->c_macros.node_count; ++i) {
      cmacro_to_string(&tmp, wapp_cmacro_list_get(&(cheader->c_macros), i)->item);
      wapp_str8_alloc_concat(&arena, output, &tmp);
    }

    wapp_str8_alloc_concat(&arena, output, &wapp_str8_lit_ro("#endif // !WAPP_PLATFORM_CPP\n\n"));
  }

  for (u64 i = 0; i < cheader->decl_types.node_count; ++i) {
    declare_cstruct(&tmp, wapp_cstruct_list_get(&(cheader->decl_types), i)->item);
    wapp_str8_alloc_concat(&arena, output, &tmp);
  }
  if (cheader->decl_types.node_count > 0) { wapp_str8_alloc_concat(&arena, output, &wapp_str8_lit_ro("\n")); }

  for (u64 i = 0; i < cheader->types.node_count; ++i) {
    cusertype_to_string(&tmp, wapp_cusertype_list_get(&(cheader->types), i)->item);
    wapp_str8_concat_capped(&tmp, &wapp_str8_lit_ro("\n"));
    wapp_str8_alloc_concat(&arena, output, &tmp);
  }

  for (u64 i = 0; i < cheader->funcs.node_count; ++i) {
    declare_cfunc(&tmp, wapp_cfunc_list_get(&(cheader->funcs), i)->item);
    wapp_str8_alloc_concat(&arena, output, &tmp);
  }

  // END C LINKAGE AND CLOSE HEADER GUARD
  wapp_str8_format(&tmp, "\n#ifdef WAPP_PLATFORM_CPP\nEND_C_LINKAGE\n#endif // !WAPP_PLATFORM_CPP\n\n"
                   "#endif // !" WAPP_STR8_SPEC "\n", wapp_str8_varg(header_guard_name));
  wapp_str8_alloc_concat(&arena, output, &tmp);

  wapp_runtime_assert(output->size <= dst->capacity, ERR_MSG);
  wapp_str8_copy_str8_capped(dst, output);

  wapp_mem_arena_allocator_destroy(&arena);
}

void csource_to_string(Str8 *dst, const CSource *csource) {
  wapp_debug_assert(dst != NULL && csource != NULL, "`dst` and `csource` should not be NULL");

  Allocator arena = wapp_mem_arena_allocator_init(MB(64));
  Str8 *output = wapp_str8_alloc_buf(&arena, KB(32));
  Str8 *internal_funcs_def = wapp_str8_alloc_buf(&arena, KB(16));
  wapp_runtime_assert(output != NULL && internal_funcs_def != NULL, "Failed to allocate buffer");

  Str8 tmp = wapp_str8_buf(CCGEN_BUF_MAX);

  for (u64 i = 0; i < csource->includes.node_count; ++i) {
    cinclude_to_string(&tmp, wapp_cinclude_list_get(&(csource->includes), i)->item);
    wapp_str8_alloc_concat(&arena, output, &tmp);
  }
  if (csource->includes.node_count > 0) { wapp_str8_alloc_concat(&arena, output, &wapp_str8_lit_ro("\n")); }

  for (u64 i = 0; i < csource->macros.node_count; ++i) {
    cmacro_to_string(&tmp, wapp_cmacro_list_get(&(csource->macros), i)->item);
    wapp_str8_alloc_concat(&arena, output, &tmp);
  }
  if (csource->macros.node_count > 0) { wapp_str8_alloc_concat(&arena, output, &wapp_str8_lit_ro("\n")); }

  for (u64 i = 0; i < csource->decl_types.node_count; ++i) {
    declare_cstruct(&tmp, wapp_cstruct_list_get(&(csource->decl_types), i)->item);
    wapp_str8_alloc_concat(&arena, output, &tmp);
  }
  if (csource->decl_types.node_count > 0) { wapp_str8_alloc_concat(&arena, output, &wapp_str8_lit_ro("\n")); }

  for (u64 i = 0; i < csource->types.node_count; ++i) {
    cusertype_to_string(&tmp, wapp_cusertype_list_get(&(csource->types), i)->item);
    wapp_str8_concat_capped(&tmp, &wapp_str8_lit_ro("\n"));
    wapp_str8_alloc_concat(&arena, output, &tmp);
  }

  Str8RO _wapp_intern = wapp_str8_lit_ro("wapp_intern ");
  for (u64 i = 0; i < csource->internal_funcs.node_count; ++i) {
    declare_cfunc(&tmp, wapp_cfunc_list_get(&(csource->internal_funcs), i)->item);
    wapp_str8_alloc_concat(&arena, output, &_internal);
    wapp_str8_alloc_concat(&arena, output, &tmp);

    define_cfunc(&tmp, wapp_cfunc_list_get(&(csource->internal_funcs), i)->item);
    wapp_str8_alloc_concat(&arena, internal_funcs_def, &_internal);
    wapp_str8_alloc_concat(&arena, internal_funcs_def, &tmp);
  }
  if (csource->internal_funcs.node_count > 0) { wapp_str8_alloc_concat(&arena, output, &wapp_str8_lit_ro("\n")); }

  for (u64 i = 0; i < csource->funcs.node_count; ++i) {
    define_cfunc(&tmp, wapp_cfunc_list_get(&(csource->funcs), i)->item);
    wapp_str8_alloc_concat(&arena, output, &tmp);
  }

  wapp_runtime_assert(output->size + internal_funcs_def->size <= dst->capacity, ERR_MSG);
  wapp_str8_copy_str8_capped(dst, output);
  wapp_str8_concat_capped(dst, internal_funcs_def);

  wapp_mem_arena_allocator_destroy(&arena);
}

b8 cheaderinclude_to_string(Str8 *dst, const CHeaderInclude *cheaderinclude) {
  wapp_debug_assert(dst != NULL && cheaderinclude != NULL, "`dst` and `cheaderinclude` should not be NULL");

  switch (cheaderinclude->kind) {
    case C_HEADER_INCLUDE_STR:
      wapp_runtime_assert(cheaderinclude->header.name.size <= dst->capacity, ERR_MSG);
      wapp_str8_format(dst, WAPP_STR8_SPEC, wapp_str8_varg(cheaderinclude->header.name));
      return true;
    case C_HEADER_INCLUDE_HEADER:
      // Take extension into account
      wapp_runtime_assert(cheaderinclude->header.header.name.size + 2 <= dst->capacity, ERR_MSG);
      wapp_str8_format(
        dst,
        WAPP_STR8_SPEC ".%s",
        wapp_str8_varg(cheaderinclude->header.header.name),
        cheaderinclude->header.header.extension == CFILE_EXT_H ? "h" : "c"
      );
      return true;
    default:
      return false;
  }

  return false;
}
