RZ_API char *rz_analysis_fcn_format_sig(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisFunction *fcn, RZ_NULLABLE char *fcn_name,
	RZ_NULLABLE RzAnalysisFcnVarsCache *reuse_cache, RZ_NULLABLE const char *fcn_name_pre, RZ_NULLABLE const char *fcn_name_post) {
	RzAnalysisFcnVarsCache *cache = NULL;

	if (!fcn_name) {
		fcn_name = fcn->name;
		if (!fcn_name) {
			return NULL;
		}
	}

	RzStrBuf *buf = rz_strbuf_new(NULL);
	if (!buf) {
		return NULL;
	}

	char *type_fcn_name = rz_analysis_function_name_guess(analysis->typedb, fcn_name);
	if (type_fcn_name && rz_type_func_exist(analysis->typedb, type_fcn_name)) {
		RzType *fcn_type = rz_type_func_ret(analysis->typedb, type_fcn_name);
		if (fcn_type) {
			char *fcn_type_str = rz_type_as_string(analysis->typedb, fcn_type);
			if (fcn_type_str) {
				const char *sp = fcn_type->kind == RZ_TYPE_KIND_POINTER ? "" : " ";
				rz_strbuf_appendf(buf, "%s%s", fcn_type_str, sp);
				free(fcn_type_str);
			}
		}
	}

	if (fcn_name_pre) {
		rz_strbuf_append(buf, fcn_name_pre);
	}
	rz_strbuf_append(buf, fcn_name);
	if (fcn_name_post) {
		rz_strbuf_append(buf, fcn_name_post);
	}
	rz_strbuf_append(buf, " (");

	if (type_fcn_name && rz_type_func_exist(analysis->typedb, type_fcn_name)) {
		int i, argc = rz_type_func_args_count(analysis->typedb, type_fcn_name);
		bool comma = true;
		// This avoids false positives present in argument recovery
		// and straight away print arguments fetched from types db
		for (i = 0; i < argc; i++) {
			RzType *type = rz_type_func_args_type(analysis->typedb, type_fcn_name, i);
			const char *name = rz_type_func_args_name(analysis->typedb, type_fcn_name, i);
			if (!type || !name) {
				eprintf("Missing type for %s\n", type_fcn_name);
				goto beach;
			}
			char *type_str = rz_type_as_string(analysis->typedb, type);
			if (i == argc - 1) {
				comma = false;
			}
			const char *sp = type->kind == RZ_TYPE_KIND_POINTER ? "" : " ";
			rz_strbuf_appendf(buf, "%s%s%s%s", type_str, sp, name, comma ? ", " : "");
			free(type_str);
		}
		goto beach;
	}
	RZ_FREE(type_fcn_name);

	cache = reuse_cache;
	if (!cache) {
		cache = RZ_NEW0(RzAnalysisFcnVarsCache);
		if (!cache) {
			type_fcn_name = NULL;
			goto beach;
		}
		rz_analysis_fcn_vars_cache_init(analysis, cache, fcn);
	}

	bool comma = true;
	bool arg_bp = false;
	size_t tmp_len;
	RzAnalysisVar *var;
	RzListIter *iter;

	rz_list_foreach (cache->rvars, iter, var) {
		// assume self, error are always the last
		if (!strcmp(var->name, "self") || !strcmp(var->name, "error")) {
			rz_strbuf_slice(buf, 0, rz_strbuf_length(buf) - 2);
			break;
		}
		char *vartype = rz_type_as_string(analysis->typedb, var->type);
		tmp_len = strlen(vartype);
		rz_strbuf_appendf(buf, "%s%s%s%s", vartype,
			tmp_len && vartype[tmp_len - 1] == '*' ? "" : " ",
			var->name, iter->n ? ", " : "");
		free(vartype);
	}

	rz_list_foreach (cache->bvars, iter, var) {
		if (var->isarg) {
			if (!rz_list_empty(cache->rvars) && comma) {
				rz_strbuf_append(buf, ", ");
				comma = false;
			}
			arg_bp = true;
			char *vartype = rz_type_as_string(analysis->typedb, var->type);
			tmp_len = strlen(vartype);
			rz_strbuf_appendf(buf, "%s%s%s%s", vartype,
				tmp_len && vartype[tmp_len - 1] == '*' ? "" : " ",
				var->name, iter->n ? ", " : "");
			free(vartype);
		}
	}

	comma = true;
	const char *maybe_comma = ", ";
	rz_list_foreach (cache->svars, iter, var) {
		if (var->isarg) {
			if (!*maybe_comma || ((arg_bp || !rz_list_empty(cache->rvars)) && comma)) {
				comma = false;
				rz_strbuf_append(buf, ", ");
			}
			char *vartype = rz_type_as_string(analysis->typedb, var->type);
			tmp_len = strlen(vartype);
			if (iter->n && ((RzAnalysisVar *)iter->n->data)->isarg) {
				maybe_comma = ", ";
			} else {
				maybe_comma = "";
			}
			rz_strbuf_appendf(buf, "%s%s%s%s", vartype,
				tmp_len && vartype[tmp_len - 1] == '*' ? "" : " ",
				var->name, maybe_comma);
			free(vartype);
		}
	}

beach:
	rz_strbuf_append(buf, ");");
	RZ_FREE(type_fcn_name);
	if (!reuse_cache) {
		// !reuse_cache => we created our own cache
		rz_analysis_fcn_vars_cache_fini(cache);
		free(cache);
	}
	return rz_strbuf_drain(buf);
}