#include <UVTD/UnrealVirtualGenerator.hpp>

#include <DynamicOutput/DynamicOutput.hpp>
#include <UVTD/ConfigUtil.hpp>
#include <UVTD/Helpers.hpp>

namespace RC::UVTD
{
    auto UnrealVirtualGenerator::generate_files() -> void
    {
        // Use PDBNameInfo for version without separator (e.g., "427" from "4_27")
        const auto& pdb_name_no_underscore = pdb_info.version_no_separator;
        const auto& base_version = pdb_info.base_version;

        // Check if this PDB has suffixes that indicate a variant build
        bool has_suffixes = pdb_info.suffix_count > 0;

        // For variant builds with suffixes, we don't generate the base files
        // (those are generated by the base version PDB)
        if (has_suffixes)
        {
            Output::send(STR("PDB '{}' has suffixes - generating variant includes only\n"), pdb_info.full_name);
            return;
        }

        auto virtual_header_file = virtual_gen_header_output_path /
                                   fmt::format(STR("UnrealVirtual{}.hpp"), pdb_name_no_underscore);

        Output::send(STR("Generating file '{}'\n"), virtual_header_file.wstring());

        Output::Targets<Output::NewFileDevice> virtual_header_dumper;
        auto& virtual_header_file_device = virtual_header_dumper.get_device<Output::NewFileDevice>();
        virtual_header_file_device.set_file_name_and_path(virtual_header_file);
        virtual_header_file_device.set_formatter([](File::StringViewType string) {
            return File::StringType{string};
        });

        auto virtual_src_file = virtual_gen_source_output_path /
                                fmt::format(STR("UnrealVirtual{}.cpp"), pdb_name_no_underscore);

        Output::send(STR("Generating file '{}'\n"), virtual_src_file.wstring());

        Output::Targets<Output::NewFileDevice> virtual_src_dumper;
        auto& virtual_src_file_device = virtual_src_dumper.get_device<Output::NewFileDevice>();
        virtual_src_file_device.set_file_name_and_path(virtual_src_file);
        virtual_src_file_device.set_formatter([](File::StringViewType string) {
            return File::StringType{string};
        });

        // Generate header
        virtual_header_dumper.send(STR("#pragma once\n\n"));
        virtual_header_dumper.send(STR("#include <Unreal/VersionedContainer/UnrealVirtualImpl/UnrealVirtualBaseVC.hpp>\n\n"));
        virtual_header_dumper.send(STR("namespace RC::Unreal\n"));
        virtual_header_dumper.send(STR("{\n"));
        virtual_header_dumper.send(STR("    class UnrealVirtual{} : public UnrealVirtualBaseVC\n"), pdb_name_no_underscore);
        virtual_header_dumper.send(STR("    {\n"));
        virtual_header_dumper.send(STR("        auto set_virtual_offsets() -> void override;\n"));
        virtual_header_dumper.send(STR("    };\n"));
        virtual_header_dumper.send(STR("}\n"));

        // Generate source file
        virtual_src_dumper.send(STR("#include <Unreal/VersionedContainer/UnrealVirtualImpl/UnrealVirtual{}.hpp>\n\n"), pdb_name_no_underscore);
        virtual_src_dumper.send(STR("#include <functional>\n\n"));
        virtual_src_dumper.send(STR("// These are all the structs that have virtuals that need to have their offset set\n"));

        // Use config utility to get the includes
        const auto& virtual_generator_includes = ConfigUtil::GetVirtualGeneratorIncludes();
        for (const auto& include : virtual_generator_includes)
        {
            virtual_src_dumper.send(STR("#include <Unreal/{}.hpp>\n"), include);
        }

        virtual_src_dumper.send(STR("\n"));
        virtual_src_dumper.send(STR("namespace RC::Unreal\n"));
        virtual_src_dumper.send(STR("{\n"));
        virtual_src_dumper.send(STR("    void UnrealVirtual{}::set_virtual_offsets()\n"), pdb_name_no_underscore);
        virtual_src_dumper.send(STR("    {\n"));

        // First pass: VTable includes
        for (const auto& object_item : ConfigUtil::GetObjectItems())
        {
            const auto& class_name = object_item.name;

            auto class_it = std::find_if(
                    type_container.get_class_entries().begin(),
                    type_container.get_class_entries().end(),
                    [&class_name](const auto& entry) {
                        return entry.first == class_name || entry.second.class_name == class_name;
                    }
                    );

            if (class_it == type_container.get_class_entries().end() ||
                object_item.valid_for_vtable != ValidForVTable::Yes)
            {
                continue;
            }

            const auto& class_entry = class_it->second;

            if (!class_entry.functions.empty() && class_entry.valid_for_vtable == ValidForVTable::Yes)
            {
                virtual_src_dumper.send(STR("#include <FunctionBodies/{}_VTableOffsets_{}_FunctionBody.cpp>\n"),
                                        base_version, class_entry.class_name_clean);
            }
        }

        virtual_src_dumper.send(STR("\n"));

        // Collect known suffixes from config to generate #ifdef blocks
        const auto& suffix_defs = ConfigUtil::GetSuffixDefinitions();

        // Track suffixes for which we actually generate blocks (only those that have variant PDBs)
        std::vector<std::pair<File::StringType, SuffixDefinition>> active_suffix_defs;

        // Generate #ifdef blocks only for suffixes that have a variant PDB for this version
        for (const auto& [suffix_name, suffix_def] : suffix_defs)
        {
            if (!suffix_def.generates_variant) continue;
            if (!ConfigUtil::HasSuffixVariant(base_version, suffix_name)) continue;

            active_suffix_defs.emplace_back(suffix_name, suffix_def);

            virtual_src_dumper.send(STR("#ifdef {}\n"), suffix_def.ifdef_macro);

            for (const auto& object_item : ConfigUtil::GetObjectItems())
            {
                const auto& class_name = object_item.name;

                auto class_it = std::find_if(
                        type_container.get_class_entries().begin(),
                        type_container.get_class_entries().end(),
                        [&class_name](const auto& entry) {
                            return entry.first == class_name || entry.second.class_name == class_name;
                        }
                        );

                if (class_it == type_container.get_class_entries().end() ||
                    object_item.valid_for_member_vars != ValidForMemberVars::Yes)
                {
                    continue;
                }

                const auto& class_entry = class_it->second;
                auto class_name_clean_final = class_entry.class_name_clean;
                unify_uobject_array_if_needed(class_name_clean_final);

                if (!class_entry.variables.empty())
                {
                    virtual_src_dumper.send(STR("#include <FunctionBodies/{}_{}_MemberVariableLayout_DefaultSetter_{}.cpp>\n"),
                                            base_version,
                                            suffix_name,
                                            class_name_clean_final);
                }
            }

            virtual_src_dumper.send(STR("#else // !{}\n"), suffix_def.ifdef_macro);
        }

        // Default (no suffix) member variable includes
        for (const auto& object_item : ConfigUtil::GetObjectItems())
        {
            const auto& class_name = object_item.name;

            auto class_it = std::find_if(
                    type_container.get_class_entries().begin(),
                    type_container.get_class_entries().end(),
                    [&class_name](const auto& entry) {
                        return entry.first == class_name || entry.second.class_name == class_name;
                    }
                    );

            if (class_it == type_container.get_class_entries().end() ||
                object_item.valid_for_member_vars != ValidForMemberVars::Yes)
            {
                continue;
            }

            const auto& class_entry = class_it->second;
            auto class_name_clean_final = class_entry.class_name_clean;
            unify_uobject_array_if_needed(class_name_clean_final);

            if (!class_entry.variables.empty())
            {
                virtual_src_dumper.send(STR("#include <FunctionBodies/{}_MemberVariableLayout_DefaultSetter_{}.cpp>\n"),
                                        base_version,
                                        class_name_clean_final);
            }
        }

        // Close only the #ifdef blocks we actually generated
        for (const auto& [suffix_name, suffix_def] : active_suffix_defs)
        {
            virtual_src_dumper.send(STR("#endif // {}\n"), suffix_def.ifdef_macro);
        }

        virtual_src_dumper.send(STR("    }\n"));
        virtual_src_dumper.send(STR("}\n"));
    }

    auto UnrealVirtualGenerator::output_cleanup() -> void
    {
        // Cleanup header outputs
        if (std::filesystem::exists(virtual_gen_header_output_path))
        {
            for (const auto& item : std::filesystem::directory_iterator(virtual_gen_header_output_path))
            {
                if (item.is_directory())
                {
                    continue;
                }
                if (item.path().extension() != STR(".hpp"))
                {
                    continue;
                }

                File::delete_file(item.path());
            }
        }

        // Cleanup source outputs
        if (std::filesystem::exists(virtual_gen_source_output_path))
        {
            for (const auto& item : std::filesystem::directory_iterator(virtual_gen_source_output_path))
            {
                if (item.is_directory())
                {
                    continue;
                }
                if (item.path().extension() != STR(".cpp"))
                {
                    continue;
                }

                File::delete_file(item.path());
            }
        }
    }
} // namespace RC::UVTD
