const std = @import("std");
const Dir = std.fs.Dir;
const FnMeta = std.builtin.TypeInfo.Fn;
const FnDecl = std.builtin.TypeInfo.Declaration.Data.FnDecl;
const StructMeta = std.builtin.TypeInfo.Struct;
const EnumMeta = std.builtin.TypeInfo.Enum;
const UnionMeta = std.builtin.TypeInfo.Union;
const warn = std.debug.warn;
const StaticExport = @import("./static_export.zig");
const typeBaseName = @import("../../meta.zig").typeBaseName;

const TypeNameMap = std.StringHashMap([]const u8);

fn isCppObject(comptime Type: type) bool {
    return switch (@typeInfo(Type)) {
        .Struct, .Union, .Opaque => true,
        .Enum => @hasDecl(Type, "Type"),
        else => false,
    };
}

const ENABLE_REWRITE_RETURN = false;

pub fn cTypeLabel(comptime Type: type) ?[]const u8 {
    return switch (comptime Type) {
        *StaticExport.c_char => "char*",
        [*c]u8, *const StaticExport.c_char => "const char*",
        StaticExport.c_char => "char",
        *void => "void",
        bool => "bool",
        usize => "size_t",
        isize => "int",
        u8 => "unsigned char",
        u16 => "uint16_t",
        u32 => "uint32_t",
        u64 => "uint64_t",
        i8 => "int8_t",
        i16 => "int16_t",
        i24 => "int24_t",
        i32 => "int32_t",
        i64 => "int64_t",
        f64 => "double",
        f32 => "float",
        *anyopaque => "void*",
        *const anyopaque => "const void*",
        [*]bool => "bool*",
        [*]usize => "size_t*",
        [*]isize => "int*",
        [*]u8 => "unsigned char*",
        [*]u16 => "uint16_t*",
        [*]u32 => "uint32_t*",
        [*]u64 => "uint64_t*",
        [*]i8 => "int8_t*",
        [*]i16 => "int16_t*",
        [*]i32 => "int32_t*",
        [*]i64 => "int64_t*",
        [*]const bool => "const bool*",
        [*]const usize => "const size_t*",
        [*]const isize => "const int*",
        [*c]const u8, [*]const u8 => "const unsigned char*",
        [*]const u16 => "const uint16_t*",
        [*]const u32 => "const uint32_t*",
        [*]const u64 => "const uint64_t*",
        [*]const i8 => "const int8_t*",
        [*]const i16 => "const int16_t*",
        [*]const i32 => "const int32_t*",
        [*]const i64 => "const int64_t*",
        else => null,
    };
}

var buffer = std.ArrayList(u8).init(std.heap.c_allocator);
var writer = buffer.writer();
var impl_buffer = std.ArrayList(u8).init(std.heap.c_allocator);
var impl_writer = impl_buffer.writer();
var bufset = std.BufSet.init(std.heap.c_allocator);
var type_names = TypeNameMap.init(std.heap.c_allocator);
var opaque_types = std.BufSet.init(std.heap.c_allocator);
var size_map = std.StringHashMap(u32).init(std.heap.c_allocator);
var align_map = std.StringHashMap(u29).init(std.heap.c_allocator);

pub const C_Generator = struct {
    filebase: []const u8,

    direction: Direction = .export_cpp,
    const Self = @This();

    pub const Direction = enum {
        export_cpp,
        export_zig,
    };

    pub fn init(comptime src_file: []const u8, comptime Writer: type, _: Writer) Self {
        var res = Self{ .filebase = src_file };

        return res;
    }

    pub fn deinit(_: *const Self) void {
        // self.file.writeAll("\n/**** </") catch unreachable;
        // self.file.writeAll(self.filebase) catch unreachable;
        // self.file.writeAll("> ****/\n\n") catch unreachable;
    }

    pub fn gen_func(
        self: *Self,
        comptime name: []const u8,
        comptime func: anytype,
        comptime meta: FnMeta,
        comptime _: []const []const u8,
        comptime rewrite_return: bool,
    ) void {
        switch (comptime meta.calling_convention) {
            .Naked => self.write("__attribute__((naked)) "),
            .Stdcall => self.write("__attribute__((stdcall)) "),
            .Fastcall => self.write("__attribute__((fastcall)) "),
            .Thiscall => self.write("__attribute__((thiscall)) "),
            else => {},
        }

        switch (self.direction) {
            .export_cpp => self.write("CPP_DECL "),
            .export_zig => self.write("ZIG_DECL "),
        }

        const return_type: type = comptime if (@TypeOf(func.return_type) == ?type)
            (func.return_type orelse void)
        else
            func.return_type;

        if (comptime rewrite_return) {
            self.writeType(void);
        } else {
            self.writeType(comptime return_type);
        }

        self.write(" " ++ name ++ "(");

        if (comptime rewrite_return) {
            self.writeType(comptime return_type);
            self.write("_buf ret_value");

            if (comptime meta.args.len > 0) {
                self.write(", ");
            }
        }

        inline for (meta.args) |arg, i| {
            const ArgType = comptime arg.arg_type.?;

            switch (@typeInfo(ArgType)) {
                .Fn => {
                    self.gen_closure(comptime arg.arg_type.?, comptime std.fmt.comptimePrint(" ArgFn{d}", .{i}));
                },
                else => {
                    self.writeType(comptime arg.arg_type.?);

                    switch (@typeInfo(ArgType)) {
                        .Enum => {
                            self.write(comptime std.fmt.comptimePrint(" {s}{d}", .{ typeBaseName(@typeName(ArgType)), i }));
                        },

                        else => {
                            self.write(comptime std.fmt.comptimePrint(" arg{d}", .{i}));
                        },
                    }
                },
            }

            // if (comptime func.arg_names.len > 0 and func.arg_names.len > i) {
            //     self.write(comptime arg_names[i]);
            // } else {

            //TODO: Figure out how to get arg names; for now just do arg0..argN
            if (i != meta.args.len - 1)
                self.write(", ");
        }

        self.write(")");
        defer self.write(";\n");
        // const ReturnTypeInfo: std.builtin.TypeInfo = comptime @typeInfo(func.return_type);
        // switch (comptime ReturnTypeInfo) {
        //     .Pointer => |Pointer| {
        //         self.write(" __attribute__((returns_nonnull))");
        //     },
        //     .Optional => |Optional| {},
        //     else => {},
        // }
    }

    pub fn gen_closure(
        self: *Self,
        comptime Function: type,
        comptime name: []const u8,
    ) void {
        const func: std.builtin.TypeInfo.Fn = @typeInfo(Function).Fn;
        self.writeType(func.return_type orelse void);
        self.write(" (*" ++ name ++ ")(");
        inline for (func.args) |arg, i| {
            self.writeType(arg.arg_type.?);
            // if (comptime func.arg_names.len > 0 and func.arg_names.len > i) {
            //     self.write(comptime arg_names[i]);
            // } else {
            const ArgType = arg.arg_type.?;
            if (@typeInfo(ArgType) == .Enum) {
                self.write(comptime std.fmt.comptimePrint(" {s}{d}", .{ typeBaseName(@typeName(ArgType)), i }));
            } else {
                self.write(comptime std.fmt.comptimePrint(" arg{d}", .{i}));
            }
            // }

            //TODO: Figure out how to get arg names; for now just do arg0..argN
            if (i != func.args.len - 1)
                self.write(", ");
        }

        self.write(")");
        // const ReturnTypeInfo: std.builtin.TypeInfo = comptime @typeInfo(func.return_type);
        // switch (comptime ReturnTypeInfo) {
        //     .Pointer => |Pointer| {
        //         self.write(" __attribute__((returns_nonnull))");
        //     },
        //     .Optional => |Optional| {},
        //     else => {},
        // }
    }

    pub fn gen_struct(
        self: *Self,
        comptime name: []const u8,
        comptime meta: StructMeta,
        comptime static_types: anytype,
    ) void {
        self.write("typedef struct ");

        if (meta.layout == .Packed)
            self.write("__attribute__((__packed__)) ");

        self.write(name ++ " {\n");

        inline for (meta.fields) |field| {
            self.write("   ");

            const info = @typeInfo(field.field_type);

            if (info == .Array) {
                const PrintType = comptime brk: {
                    for (static_types) |static_type| {
                        if (static_type.Type == info.Array.child) {
                            break :brk static_type.Type;
                        }
                    }

                    break :brk info.Array.child;
                };
                self.writeType(PrintType);
            } else {
                const PrintType = comptime brk: {
                    for (static_types) |static_type| {
                        if (static_type.Type == field.field_type) {
                            break :brk static_type.Type;
                        }
                    }

                    break :brk field.field_type;
                };
                self.writeType(PrintType);
            }

            self.write(" " ++ field.name);

            if (info == .Array) {
                writer.print("[{}]", .{info.Array.len}) catch unreachable;
            }

            self.write(";\n");
        }
        self.write("} " ++ name ++ ";\n\n");
    }

    pub fn gen_enum(
        self: *Self,
        comptime name: []const u8,
        comptime meta: EnumMeta,
    ) void {
        self.write("enum " ++ name ++ " {\n");

        comptime var last = 0;
        inline for (meta.fields) |field, i| {
            self.write("    " ++ field.name);

            // if field value is unexpected/custom, manually define it
            if ((i == 0 and field.value != 0) or (i > 0 and field.value > last + 1)) {
                writer.print(" = {}", .{field.value}) catch unreachable;
            }

            self.write(",\n");

            last = field.value;
        }

        self.write("};\n\n");
    }

    pub fn gen_union(
        _: *Self,
        comptime _: []const u8,
        comptime _: UnionMeta,
        comptime _: anytype,
    ) void {
        @compileError("Not implemented");
        // self.write("typedef union ");

        // self.write(name ++ " {\n");

        // inline for (meta.fields) |field, i| {
        //     self.write("   ");

        //     self.writeType(comptime FieldType);

        //     self.write(" " ++ field.name ++ ";\n");
        // }
        // self.write("} " ++ name ++ ";\n\n");
    }

    fn writeType(
        self: *Self,
        comptime T: type,
    ) void {
        const TT = comptime brk: {
            var Type = T;
            if (@typeInfo(Type) == .Optional) {
                const OtherType = std.meta.Child(Type);
                if (@typeInfo(OtherType) == .Fn) {
                    Type = OtherType;
                }
            }
            if (@typeInfo(Type) == .Pointer and !std.meta.trait.isManyItemPtr(Type)) {
                Type = @typeInfo(Type).Pointer.child;
            }

            break :brk Type;
        };

        if (comptime (isCppObject(TT)) and @hasDecl(TT, "name")) {
            if (@typeInfo(T) == .Pointer or (@hasDecl(TT, "Type") and (@TypeOf(TT.Type) == type and @typeInfo(TT.Type) == .Pointer))) {
                if (@hasDecl(TT, "is_pointer") and !TT.is_pointer) {} else if (@typeInfo(T).Pointer.is_const) {
                    write(self, "const ");
                }
            }

            const _formatted_name = comptime brk: {
                var original: [TT.name.len]u8 = undefined;
                _ = std.mem.replace(u8, TT.name, ":", "_", &original);
                break :brk original;
            };
            const formatted_name = comptime std.mem.span(&_formatted_name);

            if (@hasDecl(TT, "is_pointer") and !TT.is_pointer) {
                if (@TypeOf(TT.Type) == type) {
                    if (cTypeLabel(TT.Type)) |label| {
                        type_names.put(comptime label, formatted_name) catch unreachable;
                        if (@typeInfo(TT) == .Struct and @hasField(TT, "bytes")) {
                            size_map.put(comptime formatted_name, @as(u32, TT.shim.byte_size)) catch unreachable;
                            align_map.put(comptime formatted_name, @as(u29, TT.shim.align_size)) catch unreachable;
                        } else if (@typeInfo(TT) == .Opaque) {
                            opaque_types.insert(comptime label) catch unreachable;
                        }
                    }
                } else {
                    type_names.put(comptime TT.name, formatted_name) catch unreachable;
                    if (@typeInfo(TT) == .Struct and @hasField(TT, "bytes")) {
                        size_map.put(comptime formatted_name, @as(u32, TT.shim.byte_size)) catch unreachable;
                        align_map.put(comptime formatted_name, @as(u29, TT.shim.align_size)) catch unreachable;
                    } else if (@typeInfo(TT) == .Opaque) {
                        opaque_types.insert(comptime TT.name) catch unreachable;
                    }
                }
            } else {
                type_names.put(comptime TT.name, formatted_name) catch unreachable;
                if (@typeInfo(TT) == .Struct and @hasField(TT, "bytes")) {
                    size_map.put(comptime formatted_name, @as(u32, TT.shim.byte_size)) catch unreachable;
                    align_map.put(comptime formatted_name, @as(u29, TT.shim.align_size)) catch unreachable;
                } else if (@typeInfo(TT) == .Opaque) {
                    opaque_types.insert(comptime TT.name) catch unreachable;
                }
            }

            if (TT == T and @hasField(T, "bytes")) {
                write(self, comptime "b" ++ formatted_name);
            } else {
                write(self, comptime formatted_name);
            }

            if (@typeInfo(T) == .Pointer or (@hasDecl(TT, "Type") and (@TypeOf(TT.Type) == type and @typeInfo(TT.Type) == .Pointer))) {
                if (@hasDecl(TT, "is_pointer") and !TT.is_pointer) {} else {
                    write(self, "*");
                }
            }
            return;
        }

        if (comptime cTypeLabel(T)) |label| {
            self.write(comptime label);
        } else {
            const meta = @typeInfo(T);
            switch (meta) {
                .Pointer => |Pointer| {
                    const child = Pointer.child;
                    // if (childmeta == .Struct and childmeta.Struct.layout != .Extern) {
                    //     self.write("void");
                    // } else {
                    self.writeType(child);
                    // }
                    self.write("*");
                },
                .Optional => self.writeType(meta.Optional.child),
                .Array => @compileError("Handle goofy looking C Arrays in the calling function"),
                .Enum => |Enum| {
                    self.writeType(Enum.tag_type);
                },
                else => {
                    return self.write(comptime typeBaseName(@typeName(T)));
                },
            }
        }
    }

    fn write(_: *Self, comptime str: []const u8) void {
        _ = writer.write(str) catch {};
    }
};

const builtin = @import("builtin");
const TypeInfo = builtin.TypeInfo;
const Declaration = TypeInfo.Declaration;

const GeneratorInterface = struct {
    fn init() void {}
    fn deinit() void {}
    fn gen_func() void {}
    fn gen_struct() void {}
    fn gen_enum() void {}
    fn gen_union() void {}
};

fn validateGenerator(comptime Generator: type) void {
    comptime {
        const interface = @typeInfo(GeneratorInterface).Struct.decls;

        for (interface) |decl| {
            if (@hasDecl(Generator, decl.name) == false) {
                @compileError("Generator: '" ++
                    @typeName(Generator) ++
                    "' is missing function: " ++
                    decl.name);
            }
        }
    }
}

const NamedStruct = struct {
    name: []const u8,
    Type: type,
};

pub fn getCStruct(comptime T: type) ?NamedStruct {
    if (!std.meta.trait.isContainer(T) or (std.meta.trait.isSingleItemPtr(T) and !std.meta.trait.isContainer(std.meta.Child(T)))) {
        return null;
    }

    inline for (std.meta.declarations(T)) |decl| {
        if (std.mem.eql(u8, decl.name, "Type")) {
            switch (decl.data) {
                .Type => {
                    return NamedStruct{ .Type = T, .name = comptime typeBaseName(@typeName(T)) };
                },
                else => {},
            }
        }
    }

    return null;
}

var thenables = std.ArrayListUnmanaged([]const u8){};
pub fn HeaderGen(comptime first_import: type, comptime second_import: type, comptime fname: []const u8) type {
    return struct {
        source_file: []const u8 = fname,
        gen: C_Generator = undefined,

        const Self = @This();

        pub fn init() Self {
            return Self{};
        }

        pub fn startFile(
            comptime _: Self,
            comptime Type: type,
            comptime _: []const u8,
            file: anytype,
            other: std.fs.File,
        ) void {
            if (comptime std.meta.trait.hasDecls(Type, .{"include"})) {
                comptime var new_name = std.mem.zeroes([Type.include.len]u8);

                comptime {
                    _ = std.mem.replace(u8, Type.include, "/", "_", std.mem.span(&new_name));
                    _ = std.mem.replace(u8, &new_name, ".", "_", std.mem.span(&new_name));
                    _ = std.mem.replace(u8, &new_name, "<", "_", std.mem.span(&new_name));
                    _ = std.mem.replace(u8, &new_name, ">", "_", std.mem.span(&new_name));
                    _ = std.mem.replace(u8, &new_name, "\"", "_", std.mem.span(&new_name));
                }
                file.writeAll("\n#pragma mark - " ++ Type.name ++ "\n\n") catch unreachable;

                if (@hasDecl(Type, "include")) {
                    other.writer().print(
                        \\
                        \\#ifndef INCLUDED_{s}
                        \\#define INCLUDED_{s}
                        \\#include "{s}"
                        \\#endif
                        \\
                        \\extern "C" const size_t {s} = sizeof({s});
                        \\extern "C" const size_t {s} = alignof({s});
                        \\
                    ,
                        .{ new_name, new_name, Type.include, Type.shim.size_of_symbol, Type.name, Type.shim.align_of_symbol, Type.name },
                    ) catch unreachable;
                }
            }
        }

        pub fn processStaticExport(comptime _: Self, _: anytype, gen: *C_Generator, comptime static_export: StaticExport) void {
            const fn_meta = comptime @typeInfo(static_export.Type).Fn;
            const DeclData = @typeInfo(@TypeOf(@field(static_export.Parent, static_export.local_name)));

            gen.gen_func(
                comptime static_export.symbol_name,
                DeclData.Fn,
                comptime fn_meta,
                comptime std.mem.zeroes([]const []const u8),
                false,
            );
        }

        pub fn processThenable(comptime _: Self, _: anytype, comptime static_export: StaticExport) void {
            thenables.append(std.heap.c_allocator, comptime static_export.symbol_name) catch unreachable;
        }

        pub fn processDecl(
            comptime _: Self,
            _: anytype,
            gen: *C_Generator,
            comptime ParentType: type,
            comptime _: std.builtin.TypeInfo.Declaration,
            comptime name: []const u8,
            comptime prefix: []const u8,
        ) void {
            const DeclData = @typeInfo(@TypeOf(@field(ParentType, name)));

            switch (comptime DeclData) {
                .Type => |Type| {
                    switch (@typeInfo(Type)) {
                        .Enum => |Enum| {
                            gen.gen_enum(
                                prefix ++ "__" ++ name,
                                Enum,
                            );
                        },

                        .Fn => |func| {
                            // if (func.) {
                            // blocked by https://github.com/ziglang/zig/issues/8259
                            gen.gen_func(
                                comptime prefix ++ "__" ++ name,
                                comptime func,
                                comptime func,
                                comptime &.{},
                                false, // comptime ENABLE_REWRITE_RETURN and @typeInfo(fn_meta.return_type) == .Struct,
                            );
                        },
                        else => {},
                    }
                },
                .Fn => |func| {
                    // if (func.) {
                    // blocked by https://github.com/ziglang/zig/issues/8259
                    gen.gen_func(
                        comptime prefix ++ "__" ++ name,
                        comptime func,
                        comptime func,
                        comptime &.{},
                        comptime ENABLE_REWRITE_RETURN and @typeInfo(func.return_type) == .Struct,
                    );
                },
                else => {},
            }
        }

        pub fn exec(comptime self: Self, file: std.fs.File, impl: std.fs.File, generated: std.fs.File) void {
            const Generator = C_Generator;
            validateGenerator(Generator);
            var file_writer = file.writer();
            file_writer.print("// clang-format off\n//-- AUTOGENERATED FILE -- {d}\n", .{std.time.timestamp()}) catch unreachable;
            file.writeAll(
                \\#pragma once
                \\
                \\#include <stddef.h>
                \\#include <stdint.h>
                \\#include <stdbool.h>
                \\
                \\#ifdef __cplusplus
                \\  #define AUTO_EXTERN_C extern "C"
                \\  #define AUTO_EXTERN_C_ZIG extern "C" __attribute__((weak))
                \\#else
                \\  #define AUTO_EXTERN_C
                \\  #define AUTO_EXTERN_C_ZIG __attribute__((weak))
                \\#endif
                \\#define ZIG_DECL AUTO_EXTERN_C_ZIG
                \\#define CPP_DECL AUTO_EXTERN_C
                \\#define CPP_SIZE AUTO_EXTERN_C
                \\
                \\#ifndef __cplusplus
                \\typedef void* JSClassRef;
                \\#endif
                \\
                \\#ifdef __cplusplus
                \\#include "root.h"
                \\#include "JavaScriptCore/JSClassRef.h"
                \\#endif
                \\#include "headers-handwritten.h"
                \\
            ) catch {};

            impl.writer().print("//-- AUTOGENERATED FILE -- {d}\n// clang-format off\n", .{std.time.timestamp()}) catch unreachable;
            impl.writer().writeAll(
                \\#pragma once
                \\
                \\#include <stddef.h>
                \\#include <stdint.h>
                \\#include <stdbool.h>
                \\
                \\#include "root.h"
                \\
            ) catch {};

            var impl_second_buffer = std.ArrayList(u8).init(std.heap.c_allocator);
            var impl_second_writer = impl_second_buffer.writer();

            var impl_third_buffer = std.ArrayList(u8).init(std.heap.c_allocator);
            var impl_third_writer = impl_third_buffer.writer();

            var impl_fourth_buffer = std.ArrayList(u8).init(std.heap.c_allocator);
            var impl_fourth_writer = impl_fourth_buffer.writer();

            // var lazy_function_definitions_writer = lazy_function_definitions_buffer.writer();

            var dom_buffer = std.ArrayList(u8).init(std.heap.c_allocator);
            var dom_writer = dom_buffer.writer();

            // inline for (import.all_static_externs) |static_extern, i| {
            //     const Type = static_extern.Type;
            //     var gen = C_Generator.init(static_extern.name, @TypeOf(writer), writer);
            //     defer gen.deinit();

            //     switch (@typeInfo(Type)) {
            //         .Enum => |Enum| {
            //             gen.gen_enum(
            //                 static_extern.name,
            //                 Enum,
            //             );
            //         },
            //         .Union => |Union| {
            //             gen.gen_union(static_extern.name, Union, import.all_static_externs);
            //         },
            //         .Struct => |Struct| {
            //             gen.gen_struct(static_extern.name, Struct, import.all_static_externs);
            //         },
            //         else => {},
            //     }
            // }

            var to_get_sizes: usize = 0;

            const exclude_from_cpp = comptime [_][]const u8{ "ZigString", "ZigException" };

            const TypesToCheck: []type = comptime brk: {
                var all_types: [100]type = undefined;
                all_types[0] = first_import;
                all_types[1] = second_import;
                var counter: usize = 2;
                inline for (first_import.DOMCalls) |Type| {
                    inline for (std.meta.fieldNames(@TypeOf(Type.Class.functionDefinitions))) |static_function_name| {
                        const static_function = @field(Type.Class.functionDefinitions, static_function_name);
                        if (@TypeOf(static_function) == type and @hasDecl(static_function, "is_dom_call")) {
                            all_types[counter] = static_function;
                            counter += 1;
                        }
                    }
                }

                break :brk all_types[0..counter];
            };

            inline for (first_import.DOMCalls) |Type| {
                inline for (comptime std.meta.fieldNames(@TypeOf(Type.Class.functionDefinitions))) |static_function_name| {
                    const static_function = @field(Type.Class.functionDefinitions, static_function_name);
                    if (comptime @TypeOf(static_function) == type and @hasDecl(static_function, "is_dom_call")) {
                        dom_writer.writeAll("\n\n") catch unreachable;
                        static_function.printGenerateDOMJITSignature(@TypeOf(&dom_writer), &dom_writer) catch unreachable;
                        dom_writer.writeAll("\n\n") catch unreachable;
                    }
                }
            }

            inline for (TypesToCheck) |BaseType| {
                const all_decls = comptime std.meta.declarations(BaseType);
                inline for (all_decls) |_decls| {
                    if (comptime _decls.is_pub) {
                        @setEvalBranchQuota(99999);
                        const Type = @field(BaseType, _decls.name);
                        if (@TypeOf(Type) == type) {
                            const TypeTypeInfo: std.builtin.TypeInfo = @typeInfo(@field(BaseType, _decls.name));
                            const is_container_type = switch (TypeTypeInfo) {
                                .Opaque, .Struct, .Enum => true,
                                else => false,
                            };

                            if (is_container_type and (@hasDecl(Type, "Extern") or @hasDecl(Type, "Export") or @hasDecl(Type, "lazy_static_functions"))) {
                                const identifier = comptime std.fmt.comptimePrint("{s}_{s}", .{ Type.shim.name, Type.shim.namespace });
                                if (!bufset.contains(identifier)) {
                                    self.startFile(
                                        Type,
                                        Type.shim.name,
                                        writer,
                                        impl,
                                    );

                                    bufset.insert(identifier) catch unreachable;

                                    var gen = C_Generator.init(Type.name, @TypeOf(writer), writer);
                                    defer gen.deinit();

                                    if (@hasDecl(Type, "Extern")) {
                                        if (comptime !(std.mem.eql(u8, Type.name, exclude_from_cpp[0]) or std.mem.eql(u8, Type.name, exclude_from_cpp[1]))) {
                                            if (to_get_sizes > 0) {
                                                impl_second_writer.writeAll(", ") catch unreachable;
                                                impl_third_writer.writeAll(", ") catch unreachable;
                                                impl_fourth_writer.writeAll(", ") catch unreachable;
                                            }
                                        }

                                        const formatted_name = comptime brk: {
                                            var original: [Type.name.len]u8 = undefined;
                                            _ = std.mem.replace(u8, Type.name, ":", "_", &original);
                                            break :brk original;
                                        };

                                        if (comptime !(std.mem.eql(u8, Type.name, exclude_from_cpp[0]) or std.mem.eql(u8, Type.name, exclude_from_cpp[1]))) {
                                            impl_third_writer.print("sizeof({s})", .{comptime Type.name}) catch unreachable;
                                            impl_fourth_writer.print("alignof({s})", .{comptime Type.name}) catch unreachable;
                                            impl_second_writer.print("\"{s}\"", .{formatted_name}) catch unreachable;
                                            to_get_sizes += 1;
                                        }
                                        const ExternList = comptime brk: {
                                            const Sorder = struct {
                                                pub fn lessThan(_: @This(), lhs: []const u8, rhs: []const u8) bool {
                                                    return std.ascii.orderIgnoreCase(lhs, rhs) == std.math.Order.lt;
                                                }
                                            };
                                            var extern_list = Type.Extern;
                                            std.sort.sort([]const u8, &extern_list, Sorder{}, Sorder.lessThan);
                                            break :brk extern_list;
                                        };
                                        // impl_writer.print("  #include {s}\n", .{Type.include}) catch unreachable;
                                        inline for (&ExternList) |extern_decl| {
                                            if (@hasDecl(Type, extern_decl)) {
                                                const normalized_name = comptime brk: {
                                                    var _normalized_name: [Type.name.len]u8 = undefined;
                                                    _ = std.mem.replace(u8, Type.name, ":", "_", std.mem.span(&_normalized_name));
                                                    break :brk _normalized_name;
                                                };

                                                processDecl(
                                                    self,
                                                    writer,
                                                    &gen,
                                                    Type,
                                                    comptime std.meta.declarationInfo(Type, extern_decl),
                                                    comptime extern_decl,
                                                    comptime std.mem.span(&normalized_name),
                                                );
                                            }
                                        }
                                    }

                                    if (@hasDecl(Type, "Export")) {
                                        const ExportLIst = comptime brk: {
                                            const Sorder = struct {
                                                pub fn lessThan(_: @This(), comptime lhs: StaticExport, comptime rhs: StaticExport) bool {
                                                    return std.ascii.orderIgnoreCase(lhs.symbol_name, rhs.symbol_name) == std.math.Order.lt;
                                                }
                                            };
                                            var extern_list = Type.Export;
                                            std.sort.sort(StaticExport, &extern_list, Sorder{}, Sorder.lessThan);
                                            break :brk extern_list;
                                        };

                                        gen.direction = C_Generator.Direction.export_zig;
                                        if (ExportLIst.len > 0) {
                                            gen.write("\n#ifdef __cplusplus\n\n");
                                            inline for (ExportLIst) |static_export| {
                                                processStaticExport(
                                                    self,
                                                    file,
                                                    &gen,
                                                    comptime static_export,
                                                );
                                            }
                                            gen.write("\n#endif\n");
                                        }
                                    }

                                    // if (@hasDecl(Type, "lazy_static_functions")) {
                                    //     const ExportLIst = comptime brk: {
                                    //         const Sorder = struct {
                                    //             pub fn lessThan(_: @This(), comptime lhs: StaticExport, comptime rhs: StaticExport) bool {
                                    //                 return std.ascii.orderIgnoreCase(lhs.symbol_name, rhs.symbol_name) == std.math.Order.lt;
                                    //             }
                                    //         };
                                    //         var extern_list = Type.lazy_static_functions;
                                    //         std.sort.sort(StaticExport, &extern_list, Sorder{}, Sorder.lessThan);
                                    //         break :brk extern_list;
                                    //     };

                                    //     gen.direction = C_Generator.Direction.export_zig;
                                    //     if (ExportLIst.len > 0) {
                                    //         lazy_function_definitions_writer.writeAll("\n#pragma mark ") catch unreachable;
                                    //         lazy_function_definitions_writer.writeAll(Type.shim.name) catch unreachable;
                                    //         lazy_function_definitions_writer.writeAll("\n\n") catch unreachable;

                                    //         inline for (ExportLIst) |static_export| {
                                    //             const exp: StaticExport = static_export;
                                    //             lazy_function_definitions_writer.print("  JSC::LazyProperty<Zig::GlobalObject, Zig::JSFFIFunction> m_{s};", .{exp.symbol_name}) catch unreachable;
                                    //             lazy_function_definitions_writer.writeAll("\n") catch unreachable;

                                    //             lazy_function_definitions_writer.print(
                                    //                 "  Zig::JSFFIFunction* get__{s}(Zig::GlobalObject *globalObject) {{ return m_{s}.getInitializedOnMainThread(globalObject); }}",
                                    //                 .{ exp.symbol_name, exp.symbol_name },
                                    //             ) catch unreachable;
                                    //             lazy_function_definitions_writer.writeAll("\n") catch unreachable;

                                    //             const impl_format =
                                    //                 \\
                                    //                 \\  m_{s}.initLater(
                                    //                 \\      [](const JSC::LazyProperty<Zig::GlobalObject, Zig::JSFFIFunction>::Initializer& init) {{
                                    //                 \\          WTF::String functionName = WTF::String("{s}"_s);
                                    //                 \\          Zig::JSFFIFunction* function = Zig::JSFFIFunction::create(
                                    //                 \\               init.vm,
                                    //                 \\               init.owner,
                                    //                 \\               1,
                                    //                 \\               functionName,
                                    //                 \\               {s},
                                    //                 \\               JSC::NoIntrinsic,
                                    //                 \\               {s}
                                    //                 \\          );
                                    //                 \\          init.set(function);
                                    //                 \\      }});
                                    //                 \\
                                    //             ;

                                    //             lazy_functions_buffer_writer.print(
                                    //                 impl_format,
                                    //                 .{
                                    //                     exp.symbol_name,
                                    //                     exp.local_name,
                                    //                     exp.symbol_name,
                                    //                     exp.symbol_name,
                                    //                 },
                                    //             ) catch unreachable;

                                    //             lazy_function_visitor_writer.print(
                                    //                 \\  this->m_{s}.visit(visitor);
                                    //                 \\
                                    //             ,
                                    //                 .{exp.symbol_name},
                                    //             ) catch unreachable;
                                    //         }
                                    //         gen.write("\n");
                                    //     }
                                    // }
                                }
                            }
                        }
                    }
                }
            }

            impl.writer().print("\nconst size_t sizes[{d}] = {{", .{to_get_sizes}) catch unreachable;
            impl.writeAll(impl_third_buffer.items) catch unreachable;
            impl.writeAll("};\n") catch unreachable;
            impl.writer().print("\nconst char* names[{d}] = {{", .{to_get_sizes}) catch unreachable;
            impl.writeAll(impl_second_buffer.items) catch unreachable;
            impl.writeAll("};\n") catch unreachable;
            impl.writer().print("\nconst size_t aligns[{d}] = {{", .{to_get_sizes}) catch unreachable;
            impl.writeAll(impl_fourth_buffer.items) catch unreachable;
            impl.writeAll("};\n") catch unreachable;
            var iter = type_names.iterator();

            generated.writer().print(
                \\ #include "root.h"
                \\ 
                \\ #include <JavaScriptCore/DOMJITAbstractHeap.h>
                \\ #include "DOMJITIDLConvert.h"
                \\ #include "DOMJITIDLType.h"
                \\ #include "DOMJITIDLTypeFilter.h"
                \\ #include "DOMJITHelpers.h"
                \\ #include <JavaScriptCore/DFGAbstractHeap.h>
                \\ 
                \\ #include "JSDOMConvertBufferSource.h"
                \\ 
                \\ using namespace JSC;
                \\ using namespace WebCore;
                \\
                \\
                \\  /* -- BEGIN DOMCall DEFINITIONS -- */
                \\  {s}
                \\  /* -- END DOMCall DEFINITIONS-- */
                \\
                \\
            , .{
                dom_buffer.items,
            }) catch unreachable;

            const NamespaceMap = std.StringArrayHashMap(std.BufMap);
            var namespaces = NamespaceMap.init(std.heap.c_allocator);

            var size_iter = size_map.iterator();
            while (size_iter.next()) |size| {
                file_writer.print(" typedef struct b{s} {{ unsigned char bytes[{d}]; }} b{s};\n", .{
                    size.key_ptr.*,
                    // align_map.get(size.key_ptr.*).?,
                    size.value_ptr.*,
                    size.key_ptr.*,
                }) catch unreachable;

                file_writer.print(" typedef char* b{s}_buf;\n", .{
                    size.key_ptr.*,
                }) catch unreachable;
            }

            file_writer.writeAll("\n#ifndef __cplusplus\n") catch unreachable;
            while (iter.next()) |entry| {
                const key = entry.key_ptr.*;
                const value = entry.value_ptr.*;
                if (std.mem.indexOfScalar(u8, entry.key_ptr.*, ':')) |namespace_start| {
                    const namespace = entry.key_ptr.*[0..namespace_start];

                    if (opaque_types.contains(entry.key_ptr.*)) {
                        file_writer.print(" typedef struct {s} {s}; // {s}\n", .{
                            value,
                            value,
                            key,
                        }) catch unreachable;
                    } else {
                        file_writer.print(" typedef b{s} {s}; // {s}\n", .{
                            value,
                            value,
                            key,
                        }) catch unreachable;
                    }

                    if (!namespaces.contains(namespace)) {
                        namespaces.put(namespace, std.BufMap.init(std.heap.c_allocator)) catch unreachable;
                    }
                    const class = key[namespace_start + 2 ..];
                    namespaces.getPtr(namespace).?.put(class, value) catch unreachable;
                } else {
                    file_writer.print("  typedef {s} {s};\n", .{
                        key,
                        value,
                    }) catch unreachable;

                    impl_writer.print("  typedef {s} {s};\n", .{
                        key,
                        value,
                    }) catch unreachable;
                }
            }

            file_writer.writeAll("\n#endif\n") catch unreachable;

            file_writer.writeAll("\n#ifdef __cplusplus\n") catch unreachable;

            iter = type_names.iterator();
            var namespace_iter = namespaces.iterator();
            while (namespace_iter.next()) |map| {
                file_writer.print("  namespace {s} {{\n", .{map.key_ptr.*}) catch unreachable;
                var classes = map.value_ptr.iterator();
                while (classes.next()) |class| {
                    file_writer.print("    class {s};\n", .{class.key_ptr.*}) catch unreachable;
                }
                file_writer.writeAll("  }\n") catch unreachable;
            }

            file_writer.writeAll("\n") catch unreachable;

            file_writer.writeAll(impl_buffer.items) catch unreachable;

            iter = type_names.iterator();
            namespace_iter = namespaces.iterator();
            while (namespace_iter.next()) |map| {
                var classes = map.value_ptr.iterator();
                while (classes.next()) |class| {
                    file_writer.print("  using {s} = {s}::{s};\n", .{
                        class.value_ptr.*,
                        map.key_ptr.*,
                        class.key_ptr.*,
                    }) catch unreachable;
                }
            }

            file_writer.writeAll("\n#endif\n\n") catch unreachable;

            file.writeAll(buffer.items) catch unreachable;

            // processDecls(
            //     self,
            //     file,
            //     import,
            //     "Bindings",
            // );
        }
    };
}
