#ifndef PROTOTYPE1_SRC_L1_CODEGEN_UTIL_TEMPLATES_H
#define PROTOTYPE1_SRC_L1_CODEGEN_UTIL_TEMPLATES_H

#include "codegen.h"

// todo: add macro that iterates over vector

/* if !primitive, requires methods T_drop, and, if also clonable, requires method T_clone */
NODISCARD VecU8 generate_VecT_struct_and_base_methods(SpanU8 T, bool primitive, bool clonable) {
    VecU8 g_VecT = VecU8_fmt("Vec%s", T);
    SpanU8 VecT = VecU8_to_span(&g_VecT);
    VecU8 res = VecU8_fmt(
    "typedef struct {\n"
    SPACE "%s* buf;\n"
    SPACE "size_t len;\n"
    SPACE "size_t capacity;\n"
    "} %s;\n\n", T, VecT);

    VecU8_append_vec(&res, VecU8_fmt("#define %s_new() ((%s){ 0 })\n\n", VecT, VecT));

    VecU8_append_vec(&res, VecU8_fmt("void %s_drop(%s self) {\n", VecT, VecT));
    if (!primitive) {
        VecU8_append_vec(&res, VecU8_fmt(
            SPACE "for (size_t i = 0; i < self.len; i++) \n"
            SPACE SPACE "%s_drop(self.buf[i]);\n", T));
    }
    VecU8_append_vec(&res, VecU8_fmt(
    SPACE "free(self.buf);\n"
    "}\n\n"));

    VecU8_append_vec(&res, VecU8_fmt(
    "NODISCARD %s %s_new_reserved(size_t n) {\n"
    SPACE "return (%s){ .buf = (%s*)safe_calloc(n, sizeof(%s)), .len = 0, .capacity = n };\n"
    "}\n\n", VecT, VecT, VecT, T, T));

    VecU8_append_vec(&res, VecU8_fmt(
    "void %s_append(%s* self, %s el) {\n"  /* VecT, VecT, T */
    SPACE "size_t new_length = self->len + 1;\n"
    SPACE "if (new_length > self->capacity) {\n"
    SPACE SPACE "size_t new_capacity = Vec_get_new_capacity(self->capacity, new_length);\n"
    SPACE SPACE "self->buf = (%s*)safe_realloc(self->buf, new_capacity * sizeof(%s));\n"  /* T, T */
    SPACE SPACE "self->capacity = new_capacity;\n"
    SPACE "}\n"
    SPACE "self->buf[self->len] = el;\n"
    SPACE "self->len = new_length;\n"
    "}\n\n", VecT, VecT, T, T, T));

    VecU8_append_vec(&res, VecU8_fmt(
    "%s* %s_mat(%s* self, size_t i) {\n"
    SPACE "assert(i < self->len);\n"
    SPACE "return &self->buf[i];\n"
    "}\n\n", T, VecT, VecT));

    VecU8_append_vec(&res, VecU8_fmt(
    "const %s* %s_at(const %s* self, size_t i) {\n"
    SPACE "assert(i < self->len);\n"
    SPACE "return &self->buf[i];\n"
    "}\n\n", T, VecT, VecT));

    VecU8_append_vec(&res, VecU8_fmt(
    "void %s_sink(%s* self, size_t new_len) {\n"           /* VecT, VecT */
    SPACE "assert(new_len <= self->len);\n"
    "%v"                                                   /* dropping */
    SPACE "self->len = new_len;\n"
    "}\n\n", VecT, VecT,
    primitive ? vcstr("") : VecU8_fmt(
        SPACE "for (size_t i = new_len; i < self->len; i++)\n"
        SPACE SPACE "%s_drop(self->buf[i]);\n",            /* T */
        T)));

    if (clonable) {
        VecU8_append_vec(&res, VecU8_fmt(
        "NODISCARD %s %s_clone(const %s* self) {\n" /* VecT, VecT, VecT */
        SPACE "%s res = (%s){.buf = (%s*)safe_calloc(self->len, sizeof(%s)), .len = self->len, .capacity = self->len};\n",
           VecT, VecT, VecT, VecT, VecT, T, T));
        if (primitive) {
            VecU8_append_vec(&res, VecU8_fmt(SPACE "memcpy(res.buf, self->buf, self->len * sizeof(%s));\n", T));
        } else {
            VecU8_append_vec(&res, VecU8_fmt(
                SPACE "for (size_t i = 0; i < self->len; i++)\n"
                SPACE SPACE "res.buf[i] = %s_clone(&self->buf[i]);\n", T));
        }
        VecU8_append_span(&res, cstr(SPACE "return res;\n}\n\n"));
    }

    VecU8_append_vec(&res, VecU8_fmt(
    "void %s_append_vec(%s* self, %s b) {\n"  /* VecT, VecT, VecT */
    SPACE "size_t new_length = self->len + b.len;\n"
    SPACE "if (new_length > self->capacity) {\n"
    SPACE SPACE "size_t new_capacity = Vec_get_new_capacity(self->capacity, new_length);\n"
    SPACE SPACE "self->buf = (%s*)safe_realloc(self->buf, new_capacity * sizeof(%s));\n"  /* T, T */
    SPACE SPACE "self->capacity = new_capacity;\n"
    SPACE "}\n"
    SPACE "for (size_t i = 0; i < b.len; i++){\n"
    SPACE SPACE "self->buf[self->len + i] = b.buf[i];\n"
    SPACE "}\n"
    SPACE "self->len = new_length;\n"
    SPACE "free(b.buf);\n"
    "}\n\n", VecT, VecT, VecT, T, T));

    if (primitive) {
        VecU8_append_vec(&res, VecU8_fmt(
        "NODISCARD %s %s_new_zeroinit(size_t len) {\n"  /* VecT, VecT*/
        SPACE "return (%s){.buf = (%s*)safe_calloc(len, sizeof(%s)), .len = len, .capacity = len};\n"  /* VecT, T, T */
        "}\n\n",
        VecT, VecT, VecT, T, T));
    }

    VecU8_drop(g_VecT);  // VecT invalidated too
    return res;
}

/* if !primitive, requires methods T_clone, T_drop */
NODISCARD VecU8 generate_VecT_trivmove_extended_methods(SpanU8 T, bool primitive, bool clonable) {
    VecU8 g_VecT = VecU8_fmt("Vec%s", T);
    SpanU8 VecT = VecU8_to_span(&g_VecT);
    VecU8 res = VecU8_new();

    VecU8_append_vec(&res, VecU8_fmt(
    "%s%s %s_pop(%s* self) {\n"
    SPACE "assert(self->len > 0);\n"
    SPACE "self->len--;\n"
    SPACE "return self->buf[self->len];\n"
    "}\n\n", primitive ? cstr("") : cstr("NODISCARD "), T, VecT, VecT));

    if (!primitive) {
        VecU8_append_vec(&res, VecU8_fmt(
        "void %s_pop_and_drop(%s* self) {\n"
        SPACE "assert(self->len > 0);\n"
        SPACE "%s_drop(self->buf[self->len - 1]);\n"
        SPACE "self->len--;\n"
        "}\n\n", VecT, VecT, T));
    }

    VecU8_append_vec(&res, VecU8_fmt(
        "%s%s %s_unordered_pop(%s* self, size_t ind) {\n"
        SPACE "assert(ind < self->len);\n"
        SPACE "%s res = self->buf[ind];\n"
        SPACE "self->buf[ind] = self->buf[self->len - 1];\n"
        SPACE "self->len--;\n"
        SPACE "return res;\n"
        "}\n\n", primitive ? cstr("") : cstr("NODISCARD "), T, VecT, VecT, T));

    if (!primitive) {
        VecU8_append_vec(&res, VecU8_fmt(
            "void %s_unordered_pop_and_drop(%s* self, size_t ind) {\n"
            SPACE "assert(ind < self->len);\n"
            SPACE "%s_drop(self->buf[ind]);\n"
            SPACE "self->buf[ind] = self->buf[self->len - 1];\n"
            SPACE "self->len--;\n"
            "}\n\n", VecT, VecT, T));
    }

    VecU8_append_vec(&res, VecU8_fmt(
    "NODISCARD %s %s_swap_with_empty(%s* cell) {\n"
    SPACE "%s val = *cell;\n"
    SPACE "*cell = (%s){NULL, 0, 0};\n"
    SPACE "return val;\n"
    "}\n\n", VecT, VecT, VecT, VecT, VecT));

    if (primitive) {
        VecU8_append_vec(&res, VecU8_fmt(
        "NODISCARD %s %s_new_filled(size_t len, %s el) {\n"   /* VecT, VecT, T */
        SPACE "%s res = (%s){.buf = (%s*)safe_calloc(len, sizeof(%s)), .len = len, .capacity = len};\n"  /* VecT, VecT, T, T */
        SPACE "for (size_t i = 0; i < len; i++)\n"
        SPACE SPACE "res.buf[i] = el;\n"
        SPACE "return res;\n"
        "}\n\n", VecT, VecT, T, VecT, VecT, T, T));
    } else if (clonable) {
        VecU8_append_vec(&res, VecU8_fmt(
        "NODISCARD %s %s_new_filled(size_t len, const %s* el) {\n"       /* VecT, VecT, T */
        SPACE "%s res = (%s){.buf = (%s*)safe_calloc(len, sizeof(%s)), .len = len, .capacity = len};\n"   /* VecT, VecT, T, T */
        SPACE "for (size_t i = 0; i < len; i++)\n"
        SPACE SPACE "res.buf[i] = %s_clone(el);\n"  /* T */
        SPACE "return res;\n"
        "}\n\n", VecT, VecT, T, VecT, VecT, T, T, T));
    }

    VecU8_drop(g_VecT);  // VecT invalidated
    return res;
}

/* if !integer requires method T_equal_T */
NODISCARD VecU8 generate_VecT_equal_method(SpanU8 T, bool integer) {
    VecU8 g_VecT = VecU8_fmt("Vec%s", T);
    SpanU8 VecT = VecU8_to_span(&g_VecT);
    VecU8 res = VecU8_fmt(
    "bool %s_equal_%s(const %s* A, const %s* B) {\n"
    SPACE "if (A->len != B->len)\n"
    SPACE SPACE "return false;\n"
    SPACE "for (size_t i = 0; i < A->len; i++) {\n", VecT, VecT, VecT, VecT);
    if (integer) {
        VecU8_append_span(&res, cstr(SPACE8 "if (A->buf[i] != B->buf[i])\n"));
    } else {
        VecU8_append_vec(&res, VecU8_fmt(SPACE8 "if (!%s_equal_%s(A->buf + i, B->buf + i))\n", T, T));
    }
    VecU8_append_span(&res, cstr(
        SPACE SPACE SPACE "return false;\n"
        SPACE "}\n"
        SPACE "return true;\n"
        "}\n\n"
    ));

    VecU8_drop(g_VecT);
    return res;
}

/* requires method T_new */
NODISCARD VecU8 generate_VecT_new_of_size_method(SpanU8 T) {
    VecU8 g_VecT = VecU8_fmt("Vec%s", T);
    SpanU8 VecT = VecU8_to_span(&g_VecT);
    VecU8 res = VecU8_fmt(
    "NODISCARD %s %s_new_of_size(size_t len) {\n"   /* VecT, VecT */
    SPACE "%s res = (%s){.buf = (%s*)safe_calloc(len, sizeof(%s)), .len = len, .capacity = len};\n" /* VecT, VecT, T, T */
    SPACE "for (size_t i = 0; i < len; i++)\n"
    SPACE SPACE "res.buf[i] = %s_new();\n"  /* T */
    SPACE "return res;\n"
    "}\n", VecT, VecT, VecT, VecT, T, T, T);

    VecU8_drop(g_VecT);
    return res;
}

/* helper function. SpanT is either SpanT or MutSpanT */
void codegen_append_some_span_equal_method(VecU8* res, SpanU8 SpanT) {
    VecU8_append_vec(res, VecU8_fmt(
    "bool %s_equal_%s(%s A, %s B) {\n"
    SPACE "return A->data == B->data && A->len == B->len;\n"
    "}\n\n", SpanT, SpanT, SpanT, SpanT));
}

/* helper function. (SpanT, mod) is either (SpanT "const ") or (MutSpanT, "") */
void codegen_append_some_span_struct(VecU8* res, SpanU8 T, SpanU8 SpanT, SpanU8 mod) {
    VecU8_append_vec(res, VecU8_fmt(
        "typedef struct {\n"
        SPACE "%s%s* data;\n"
        SPACE "size_t len;\n"
        "} %s;\n\n", mod, T, SpanT));
}

/* helper function. (SpanT, mod) is either (SpanT "const ") or (MutSpanT, "") */
void codegen_append_some_span_at_method(VecU8* res, SpanU8 T, SpanU8 SpanT, SpanU8 mod) {
    VecU8_append_vec(res, VecU8_fmt(
        "%s%s* %s_at(%s self, size_t i) {\n"
        SPACE "assert(i < self.len);\n"
        SPACE "return self.data + i;\n"
        "}\n\n", mod, T, SpanT, SpanT));
}

/* helper function. SpanT is either SpanT or MutSpanT
 * span method retrieves subspan  */
void codegen_append_some_span_span_method(VecU8* res, SpanU8 SpanT) {
    VecU8_append_vec(res, VecU8_fmt(
    "%s %s_span(%s self, size_t start, size_t len){\n"
    SPACE "assert(start < SIZE_MAX - len && start + len <= self.len);\n"
    SPACE "return (%s){.data = self.data + start, .len = len};\n"
    "}\n\n", SpanT, SpanT, SpanT, SpanT));
}

/* T must be sized. Option `add_sort` requires option `add_mutable` and method T_less
 * add_mutable option generates MutSpanT.
 * add_equal option generates equal method.  add_extended option generated extended methods
 */
NODISCARD VecU8 generate_SpanT_struct_and_methods(
    SpanU8 T, bool integer, bool add_mutable, bool add_equal, bool add_extended
    ) {
    VecU8 g_SpanT = VecU8_fmt("Span%s", T);
    VecU8 g_MutSpanT = VecU8_fmt("MutSpan%s", T);
    SpanU8 SpanT = VecU8_to_span(&g_SpanT);
    SpanU8 MutSpanT = VecU8_to_span(&g_MutSpanT);
    VecU8 res = VecU8_new();

    codegen_append_some_span_struct(&res, T, SpanT, cstr("const "));
    if (add_mutable)
        codegen_append_some_span_struct(&res, T, MutSpanT, cstr(""));
    if (add_equal) {
        codegen_append_some_span_equal_method(&res, SpanT);
        if (add_mutable)
            codegen_append_some_span_equal_method(&res, MutSpanT);
    }
    if (add_mutable) {
        VecU8_append_vec(&res, VecU8_fmt(
            "%s %s_to_%s(%s self) {\n"
            SPACE "return (%s){.data = self.data, .len = self.len};\n"
            "}\n\n", SpanT, MutSpanT, SpanT, MutSpanT, SpanT));
    }
    codegen_append_some_span_at_method(&res, T, SpanT, cstr("const "));
    if (add_mutable)
        codegen_append_some_span_at_method(&res, T, MutSpanT, cstr(""));
    if (add_extended) {
        codegen_append_some_span_span_method(&res, SpanT);
        if (add_mutable)
            codegen_append_some_span_span_method(&res, MutSpanT);
    }

    VecU8_drop(g_MutSpanT);
    VecU8_drop(g_SpanT);
    return res;
}

NODISCARD VecU8 generate_span_company_sort_methods(SpanU8 T, bool t_integer, bool mut_span, bool vec){
    VecU8 res = VecU8_new();

    VecU8_append_vec(&res, VecU8_fmt(
        "int %s_qcompare(const void* a, const void* b) {\n"  /* T */
        SPACE "const %s* A = a;\n"           /* T */
        SPACE "const %s* B = b;\n"           /* T */
        SPACE "return %v;\n"                  /* we return stuff */
        "}\n\n",
        T, T, T, t_integer ? vcstr("(int)(*B < *A) - (int)(*A < *B)") :
            VecU8_fmt("(int)%s_less_%s(B, A) - (int)%s_less_%s(A, B)", T, T, T, T)));

    if (mut_span) {
        VecU8_append_vec(&res, VecU8_fmt(
            "void MutSpan%s_sort(MutSpan%s self) {\n"
            SPACE "qsort(self.data, self.len, sizeof(%s), %s_qcompare);\n"
            "}\n\n", T, T, T, T));
    }
    if (vec) {
        VecU8_append_vec(&res, VecU8_fmt(
            "void Vec%s_sort(Vec%s* self) {\n"
            SPACE "qsort(self->buf, self->len, sizeof(%s), %s_qcompare);\n"
            "}\n\n", T, T, T, T));
    }

    return res;
}

/* T must be trivially movable. If !primitive, requires methods T_drop (implicitly) and, if clonable, requires T_clone */
NODISCARD VecU8 generate_SpanT_VecT_trivmove_collab(SpanU8 T, bool primitive, bool clonable, bool add_mutable, bool add_extended) {
    VecU8 g_SpanT = VecU8_fmt("Span%s", T);
    VecU8 g_MutSpanT = VecU8_fmt("MutSpan%s", T);
    VecU8 g_VecT = VecU8_fmt("Vec%s", T);
    SpanU8 SpanT = VecU8_to_span(&g_SpanT);
    SpanU8 MutSpanT = VecU8_to_span(&g_MutSpanT);
    SpanU8 VecT = VecU8_to_span(&g_VecT);
    VecU8 res = VecU8_new();

    if (clonable) {
        VecU8_append_vec(&res, VecU8_fmt(
        "NODISCARD %s %s_from_span(%s src){\n"     /* VecT, VecT, SpanT */
        SPACE "%s res = (%s){ .buf = (%s*)safe_calloc(src.len, sizeof(%s)), .len = src.len, .capacity = src.len };\n", /* VecT, VecT, T, T */
        VecT, VecT, SpanT, VecT, VecT, T, T));
        if (primitive) {
            VecU8_append_vec(&res, VecU8_fmt(
                SPACE "memcpy(res.buf, src.data, src.len * sizeof(%s));\n", T));
        } else {
            VecU8_append_vec(&res, VecU8_fmt(
            SPACE "for (size_t i = 0; i < src.len; i++)\n"
            SPACE8 "res.buf[i] = %s_clone(&src.data[i]);\n", T));
        }
        VecU8_append_span(&res, cstr(SPACE "return res;\n}\n\n"));

    }
    VecU8_append_vec(&res, VecU8_fmt(
        "%s %s_to_span(const %s* vec){\n"
        SPACE "return (%s){vec->buf, vec->len};\n"
        "}\n\n", SpanT, VecT, VecT, SpanT));
    if (add_mutable) {
        VecU8_append_vec(&res, VecU8_fmt(
            "%s %s_to_mspan(%s* vec){\n"
            SPACE "return (%s){vec->buf, vec->len};\n"
            "}\n\n", MutSpanT, VecT, VecT, MutSpanT));
    }

    if (clonable) {
        VecU8_append_vec(&res, VecU8_fmt(
            "void %s_append_span(%s* self, %s b) {\n"   /* VecT, VecT, SpanT */
            SPACE "size_t new_length = self->len + b.len;\n"
            SPACE "if (new_length > self->capacity) {\n"
            SPACE SPACE "size_t new_capacity = Vec_get_new_capacity(self->capacity, new_length);\n"
            SPACE SPACE "self->buf = (%s*)safe_realloc(self->buf, new_capacity * sizeof(%s));\n"    /* T, T */
            SPACE SPACE "self->capacity = new_capacity;\n"
            SPACE "}\n", VecT, VecT, SpanT, T, T));
        if (primitive) {
            VecU8_append_vec(&res, VecU8_fmt(
            SPACE "memcpy(self->buf + self->len, b.data, b.len * sizeof(%s));\n", T));
        } else {
            VecU8_append_vec(&res, VecU8_fmt(
            SPACE "for (size_t i = 0; i < b.len; i++)\n"
            SPACE SPACE "self->buf[self->len + i] = %s_clone(&b.data[i]);\n", T));
        }
        VecU8_append_span(&res, cstr(
            SPACE "self->len = new_length;\n"
            "}\n\n"));
    }

    if (add_extended) {
        VecU8_append_vec(&res, VecU8_fmt(
            "%s %s_span(const %s* vec, size_t start, size_t len) {\n"
            SPACE "assert(start < SIZE_MAX - len && start + len <= vec->len);\n"
            SPACE "return (%s){.data = vec->buf + start, .len = len};\n"
            "}\n\n", SpanT, VecT, VecT, SpanT));
        if (add_mutable) {
            VecU8_append_vec(&res, VecU8_fmt(
                "%s %s_mspan(%s* vec, size_t start, size_t len) {\n"
                SPACE "assert(start < SIZE_MAX - len && start + len <= vec->len);\n"
                SPACE "return (%s){.data = vec->buf + start, .len = len};\n"
                "}\n\n", MutSpanT, VecT, VecT, MutSpanT));
        }
    }

    VecU8_drop(g_VecT);
    VecU8_drop(g_MutSpanT);
    VecU8_drop(g_SpanT);
    return res;
}

/* The only reason this function exists is because in C it is easier to supply a lot of brace list arguments,
 * than function arguments. This struct is an argument for generate_util_templates_instantiation
 * It is assumed that all necessary properties of T have been met, including cases where
 * T needs to be sized (everywhere) and trivially movable (for VecT)
 */
typedef struct {
    SpanU8 T;
    bool t_integer;
    bool t_primitive;
    bool t_clonable;
    bool vec;
    bool vec_extended;
    bool vec_equal;
    bool vec_new_of_size;
    bool span;
    bool mut_span;
    bool span_extended;
    bool sort;
    bool collab_vec_span;
    bool collab_vec_span_extended;
} util_templates_instantiation_options;

void util_templates_instantiation_options_fix(util_templates_instantiation_options* op) {
    assert(op->T.len > 0);
    if (op->t_integer)
        op->t_primitive = true;
    if (op->t_primitive)
        op->t_clonable = true;
    if (op->vec_extended)
        op->vec = true;
    if (op->vec_equal)
        op->vec = true;
    if (op->span_extended)
        op->span = true;
    if (op->mut_span)
        op->span = true;
    if (op->collab_vec_span_extended)
        op->collab_vec_span = true;
    if (op->collab_vec_span) {
        op->span = true;
        op->vec = true;
    }
    assert(op->vec || op->span);
    assert(op->t_primitive || !op->t_integer);
    assert(!op->t_primitive || op->t_clonable);
}

NODISCARD VecU8 generate_util_templates_instantiation(util_templates_instantiation_options op) {
    VecU8 res = VecU8_new();
    util_templates_instantiation_options_fix(&op);
    if (op.vec) {
        VecU8_append_vec(&res, generate_VecT_struct_and_base_methods(op.T, op.t_primitive, op.t_clonable));
    }
    if (op.vec_extended) {
        assert(op.vec);
        VecU8_append_vec(&res, generate_VecT_trivmove_extended_methods(op.T, op.t_primitive, op.t_clonable));
    }
    if (op.vec_equal) {
        assert(op.vec);
        VecU8_append_vec(&res, generate_VecT_equal_method(op.T, op.t_integer));
    }
    if (op.vec_new_of_size) {
        assert(op.vec);
        VecU8_append_vec(&res, generate_VecT_new_of_size_method(op.T));
    }
    if (op.span) {
        VecU8_append_vec(&res, generate_SpanT_struct_and_methods(op.T, op.t_integer, op.mut_span, false, op.span_extended));
    }
    if (op.sort) {
        VecU8_append_vec(&res, generate_span_company_sort_methods(op.T, op.t_integer, op.mut_span, op.vec));
    }
    if (op.collab_vec_span) {
        assert(op.vec && op.span);
        VecU8_append_vec(&res, generate_SpanT_VecT_trivmove_collab(op.T, op.t_primitive, op.t_clonable, op.mut_span, op.collab_vec_span_extended));
    }
    return res;
}

NODISCARD VecU8 util_templates_instantiation_get_appropriate_filename(util_templates_instantiation_options op) {
    util_templates_instantiation_options_fix(&op);
    return VecU8_fmt("%s%s%s""%s%s",
        op.vec ? cstr("Vec") : cstr(""), op.vec && op.span ? cstr("And") : cstr(""), op.span ? cstr("Span") : cstr(""),
        (int)op.vec + (int)op.span > 1 ? cstr("_") : cstr(""), op.T);
}

void generate_util_templ_inst_eve_header(SpanU8 layer, SpanU8 bonus_ns, util_templates_instantiation_options op) {
    generate_SOME_templ_inst_eve_header(layer, bonus_ns, generate_util_templates_instantiation(op),
        util_templates_instantiation_get_appropriate_filename(op));
}

void generate_eve_span_company_for_primitive(SpanU8 layer, SpanU8 ns, SpanU8 T, bool with_vector, bool with_span) {
    generate_util_templ_inst_eve_header(layer, ns, (util_templates_instantiation_options){
        .T = T,
        .t_primitive = true, .vec = with_vector, .span = with_span, .collab_vec_span = with_vector && with_span
    });
}

void generate_eve_span_company_for_non_primitive_clonable(SpanU8 layer, SpanU8 ns, SpanU8 T, bool with_vector, bool with_span) {
    generate_util_templ_inst_eve_header(layer, ns, (util_templates_instantiation_options){
        .T = T,
        .t_clonable = true, .vec = with_vector, .span = with_span, .collab_vec_span = with_vector && with_span
    });
}

void generate_eve_span_company_for_non_primitive_non_clonable(SpanU8 layer, SpanU8 ns, SpanU8 T, bool with_vector, bool with_span) {
    generate_util_templ_inst_eve_header(layer, ns, (util_templates_instantiation_options){
        .T = T,
        .vec = with_vector, .span = with_span, .collab_vec_span = with_vector && with_span
    });
}

void generate_util_templ_inst_guarded_header(
    SpanU8 layer, SpanU8 bonus_ns, SpanU8 dependencies, util_templates_instantiation_options op
    ) {
    generate_SOME_templ_inst_guarded_header(layer, bonus_ns, VecU8_fmt("%v%s\n",
        codegen_include_relative_to_root(bonus_ns, cstr("src/l1/core/util.h")), dependencies),
        generate_util_templates_instantiation(op), util_templates_instantiation_get_appropriate_filename(op));
}

void generate_guarded_span_company_for_primitive(
    SpanU8 layer, SpanU8 ns, SpanU8 T, SpanU8 dependencies, bool with_vector, bool with_span
    ) {
    generate_util_templ_inst_guarded_header(layer, ns, dependencies, (util_templates_instantiation_options){
        .T = T,
        .t_primitive = true, .vec = with_vector, .span = with_span, .collab_vec_span = with_vector && with_span
    });
}

void generate_guarded_span_company_for_non_primitive_clonable(
    SpanU8 layer, SpanU8 ns, SpanU8 T, SpanU8 dependencies, bool with_vector, bool with_span
    ) {
    generate_util_templ_inst_guarded_header(layer, ns, dependencies, (util_templates_instantiation_options){
        .T = T,
        .t_clonable = true, .vec = with_vector, .span = with_span, .collab_vec_span = with_vector && with_span
    });
}

void generate_guarded_span_company_for_non_primitive_non_clonable(
    SpanU8 layer, SpanU8 ns, SpanU8 T, SpanU8 dependencies, bool with_vector, bool with_span
    ) {
    generate_util_templ_inst_guarded_header(layer, ns, dependencies, (util_templates_instantiation_options){
        .T = T,
        .vec = with_vector, .span = with_span, .collab_vec_span = with_vector && with_span
    });
}

NODISCARD VecU8 get_ResultType_inst_name(SpanU8 OkT, SpanU8 ErrT){
    bool ok_t_void = OkT.len == 0;
    bool err_t_void = ErrT.len == 0;
    assert(!ok_t_void || !err_t_void);
    return VecU8_fmt("Result%s%s%s", !ok_t_void ? OkT : cstr("Void"),
        !err_t_void ? cstr("Or") : cstr(""), !err_t_void ? ErrT : cstr(""));
}

/* We are talking about Result<OkT, ErrT> template instantiation */
NODISCARD VecU8 generate_result_template_inst(SpanU8 OkT, SpanU8 ErrT, bool ok_t_primitive, bool err_t_primitive) {
    bool ok_t_void = OkT.len == 0;
    bool err_t_void = ErrT.len == 0;
    assert(!ok_t_void || !err_t_void);
    assert(!ok_t_void || ok_t_primitive);
    assert(!err_t_void || err_t_primitive);

    VecU8 g_ResultT = get_ResultType_inst_name(OkT, ErrT);
    SpanU8 ResultT = VecU8_to_span(&g_ResultT);

    VecU8 res = VecU8_new();

    VecU8_append_span(&res, cstr(
        "typedef struct {\n"
        SPACE "Result_variant variant;\n"));
    if (ok_t_void && !err_t_void) {
        VecU8_append_vec(&res, VecU8_fmt(SPACE "%s err;\n", ErrT));
    } else if (!ok_t_void && err_t_void) {
        VecU8_append_vec(&res, VecU8_fmt(SPACE "%s ok;\n", OkT));
    } else {
        VecU8_append_vec(&res, VecU8_fmt(
            SPACE "union {\n"
            SPACE SPACE "%s ok;\n"
            SPACE SPACE "%s err;\n"
            SPACE "};\n", OkT, ErrT));
    }
    VecU8_append_vec(&res, VecU8_fmt("} %s;\n\n", ResultT));

    if (!ok_t_primitive || !err_t_primitive) {
        VecU8_append_vec(&res, VecU8_fmt(
            "void %s_drop(%s self) {\n", ResultT, ResultT));
        if (ok_t_primitive && !err_t_primitive) {
            VecU8_append_vec(&res, VecU8_fmt(
                SPACE "if (self.variant == Result_Err)\n"
                SPACE SPACE "%s_drop(self.err);\n", ErrT));
        } else if (!ok_t_primitive && err_t_primitive) {
            VecU8_append_vec(&res, VecU8_fmt(
                SPACE "if (self.variant == Result_Ok)\n"
                SPACE SPACE "%s_drop(self.ok);\n", OkT));
        } else {
            VecU8_append_vec(&res, VecU8_fmt(
                SPACE "if (self.variant == Result_Ok)\n"
                SPACE SPACE "%s_drop(self.ok);\n"
                SPACE "else\n"
                SPACE SPACE "%s_drop(self.err);\n", OkT, ErrT));
        }
        VecU8_append_span(&res, cstr("}\n\n"));
    }
    VecU8_drop(g_ResultT); /* ResultT variable invalidated */
    return res;
}

void generate_ResultType_templ_inst_eve_header(
    SpanU8 layer, SpanU8 bonus_ns, SpanU8 OkT, SpanU8 ErrT, bool ok_t_primitive, bool err_t_primitive
    ) {
    generate_SOME_templ_inst_eve_header(layer, bonus_ns,
        generate_result_template_inst(OkT, ErrT, ok_t_primitive, err_t_primitive), get_ResultType_inst_name(OkT, ErrT));
}

void generate_ResultType_templ_inst_guarded_header(
    SpanU8 layer, SpanU8 bonus_ns, SpanU8 OkT, SpanU8 ErrT, SpanU8 dependencies, bool ok_t_primitive, bool err_t_primitive
    ){
    generate_SOME_templ_inst_guarded_header(layer, bonus_ns, VecU8_fmt("%v%s\n",
        codegen_include_relative_to_root(bonus_ns, cstr("src/l1/core/util.h")), dependencies),
        generate_result_template_inst(OkT, ErrT, ok_t_primitive, err_t_primitive), get_ResultType_inst_name(OkT, ErrT));
}

typedef struct{
    SpanU8 T;
    bool t_ptr;
    bool t_integer;
    bool t_primitive;
    bool t_clonable;
} option_template_instantiation_op;

void option_template_instantiation_op_fix(option_template_instantiation_op* self){
    assert(self->T.len > 0);
    if (self->t_ptr)
        self->t_integer = true;
    if (self->t_integer)
        self->t_primitive = true;
    if (self->t_primitive)
        self->t_clonable = true;
}

NODISCARD VecU8 generate_OptionT_struct_and_methods(option_template_instantiation_op op) {
    option_template_instantiation_op_fix(&op);
    VecU8 g_OptionT = VecU8_fmt("Option%s", op.T);
    SpanU8 OptionT = VecU8_to_span(&g_OptionT);

    VecU8 res = VecU8_new();
    if (op.t_ptr) {
        VecU8_append_vec(&res, VecU8_fmt("typedef %s %s;\n", op.T, OptionT));
        VecU8_append_vec(&res, VecU8_fmt("#define None_%s() NULL\n", op.T));
        VecU8_append_vec(&res, VecU8_fmt("%s Some_%s(%s ref) {\n" SPACE "return ref;\n}\n\n", OptionT, op.T, op.T));
        VecU8_append_vec(&res, VecU8_fmt(
            "%s %s_expect(%s self) {\n"
            SPACE "return self;\n"
            "}\n\n", op.T, OptionT, OptionT));
    } else {
        VecU8_append_vec(&res, VecU8_fmt(
            "typedef struct {\n"
            SPACE "Option_variant variant;\n"
            SPACE "%s some;\n"
            "} %s;\n\n", op.T, OptionT));
        VecU8_append_vec(&res, VecU8_fmt("#define None_%s() (%s){ .variant = Option_None }\n\n", op.T, OptionT));
        VecU8_append_vec(&res, VecU8_fmt(
            "NODISCARD %s Some_%s(%s obj) {\n"
            SPACE "return (%s){ .variant = Option_Some, .some = obj };\n"
            "}\n\n", OptionT, op.T, op.T, OptionT));
        VecU8_append_vec(&res, VecU8_fmt(
            "NODISCARD %s %s_expect(%s self){\n"
            SPACE "if (self.variant == Option_None)\n"
            SPACE SPACE "abortf(\"Expected something in %s got None\\n\");\n"
            SPACE "return self.some;\n"
            "}\n\n", op.T, OptionT, OptionT, OptionT));
        if (!op.t_primitive) {
            VecU8_append_vec(&res, VecU8_fmt(
                "void %s_drop(%s self) {\n"
                SPACE "if (self.variant == Option_None)\n"
                SPACE SPACE "%s_drop(self.some);\n"
                "}\n\n", OptionT, OptionT, op.T));
            if (op.t_clonable) {
                VecU8_append_vec(&res, VecU8_fmt(
                   "NODISCARD %s %s_clone(const %s* self) {\n"
                   SPACE "if (self->variant == Option_None)\n"
                   SPACE SPACE "return (%s) { .variant = Option_None };\n"
                   SPACE "return (%s){ .variant = Option_Some, .some = %s_clone(&self->some) };\n"
                   "}\n\n", OptionT, OptionT, OptionT, OptionT, OptionT, op.T));
            }
        }
    }
    VecU8_drop(g_OptionT);
    return res;
}

void generate_Option_templ_inst_eve_header(SpanU8 layer, SpanU8 bonus_ns, option_template_instantiation_op op) {
    generate_SOME_templ_inst_eve_header(layer, bonus_ns, generate_OptionT_struct_and_methods(op),
        VecU8_fmt("Option%s", op.T));
}

void generate_Option_templ_inst_guarded_header(
    SpanU8 layer, SpanU8 bonus_ns, SpanU8 dependencies, option_template_instantiation_op op
    ) {
    generate_SOME_templ_inst_guarded_header(layer, bonus_ns, VecU8_fmt("%v%s\n",
        codegen_include_relative_to_root(bonus_ns, cstr("src/l1/core/util.h")), dependencies),
        generate_OptionT_struct_and_methods(op), VecU8_fmt("Option%s", op.T));
}

#endif
