#include "ConfigLoader.h"

#include <string>
#include <utility>

#include "toml.hpp"

#include "TomlHelpers.h"

namespace
{

// Known category→stat mappings for module stat modifier discovery.
// addedKeySuffix: unit suffix appended before "_formula" for additive modifier keys only.
// Multiplicative modifier keys are always dimensionless and carry no suffix.
struct StatEntry
{
    const char* category;
    const char* stat;
    const char* addedKeySuffix;
};

static const StatEntry kKnownStats[] = {
    {"health",   "hp",                        ""},
    {"movement", "speed",                     "_mps"},
    {"movement", "main_acceleration",         "_mpss"},
    {"movement", "maneuvering_acceleration",  "_mpss"},
    {"sensor",   "sensor_range",              "_m"},
    {"weapon",   "damage",                    ""},
    {"weapon",   "attack_range",              "_m"},
    {"weapon",   "attack_rate",               "_hz"},
    {"salvage",  "collection_range",          "_m"},
    {"salvage",  "collection_rate",           "_hz"},
    {"cargo",    "cargo_capacity",            ""},
    {"repair",   "repair_rate",               "_hz"},
    {"repair",   "repair_range",              "_m"},
};

}  // namespace

ModulesConfig ConfigLoader::loadModules(const std::string& path)
{
    const std::string file = "modules.toml";
    toml::table tbl = utility::parseFile(path, file);

    ModulesConfig cfg;

    if (!tbl.contains("module"))
    {
        return cfg;
    }

    const toml::array& arr = utility::requireArray(tbl["module"], file, "module");

    for (std::size_t i = 0; i < arr.size(); ++i)
    {
        const std::string elemPath = "module[" + std::to_string(i) + "]";
        const toml::table* st = arr[i].as_table();
        if (st == nullptr)
        {
            throw utility::makeError(file, elemPath, "not a table");
        }
        toml::table& mt = const_cast<toml::table&>(*st);

        ModuleDef def;
        def.id                      = utility::requireString(mt["id"], file, elemPath + ".id");
        def.surfaceMask             = utility::requireStringArray(mt["surface_mask"], file, elemPath + ".surface_mask");
        def.productionTimeSeconds = utility::requireDouble(
            mt["production_time_seconds"], file, elemPath + ".production_time_seconds");
        def.fillColor             = utility::requireString(mt["fill_color"], file, elemPath + ".fill_color");
        def.glyph                 = utility::requireString(mt["glyph"], file, elemPath + ".glyph");

        if (mt.contains("tooltip"))
        {
            def.tooltip = utility::requireString(mt["tooltip"], file, elemPath + ".tooltip");
        }

        // Materials
        {
            const toml::array& materials = utility::requireArray(mt["materials"], file, elemPath + ".materials");
            def.materials = utility::parseIngredients(materials, file, elemPath + ".materials");
        }

        // Stat modifiers from [module.<category>] sub-tables
        for (const StatEntry& se : kKnownStats)
        {
            if (!mt.contains(se.category))
            {
                continue;
            }
            const toml::table& catTable = utility::requireTable(mt[se.category], file,
                elemPath + "." + se.category);
            toml::table& catMt = const_cast<toml::table&>(catTable);

            const std::string addedKey      = std::string("added_") + se.stat + se.addedKeySuffix;
            const std::string multipliedKey = std::string("multiplied_") + se.stat + se.addedKeySuffix;

            if (catMt.contains(addedKey))
            {
                ModuleStatModifier mod;
                mod.stat         = se.stat;
                mod.modifierType = "additive";
                mod.value        = utility::requireDouble(catMt[addedKey], file,
                    elemPath + "." + se.category + "." + addedKey);
                def.statModifiers.push_back(std::move(mod));
            }

            if (catMt.contains(multipliedKey))
            {
                ModuleStatModifier mod;
                mod.stat         = se.stat;
                mod.modifierType = "multiplicative";
                mod.value        = utility::requireDouble(catMt[multipliedKey], file,
                    elemPath + "." + se.category + "." + multipliedKey);
                def.statModifiers.push_back(std::move(mod));
            }
        }

        // Weapon capability section: [module.weapon] with base stat formulas
        if (mt.contains("weapon"))
        {
            const std::string wPath = elemPath + ".weapon";
            const toml::table& wTable = utility::requireTable(mt["weapon"], file, wPath);
            toml::table& wMt = const_cast<toml::table&>(wTable);
            if (wMt.contains("damage") || wMt.contains("attack_range_m")
                || wMt.contains("attack_rate_hz"))
            {
                ModuleWeaponCapability cap;
                cap.damage        = static_cast<float>(utility::requireDouble(wMt["damage"],
                    file, wPath + ".damage"));
                cap.attackRange_m = static_cast<float>(utility::requireDouble(wMt["attack_range_m"],
                    file, wPath + ".attack_range_m"));
                cap.attackRate_hz = static_cast<float>(utility::requireDouble(wMt["attack_rate_hz"],
                    file, wPath + ".attack_rate_hz"));
                def.weaponCapability = std::move(cap);
            }
        }

        // Salvage capability section: [module.salvage] with base stat formulas
        if (mt.contains("salvage"))
        {
            const std::string sPath = elemPath + ".salvage";
            const toml::table& sTable = utility::requireTable(mt["salvage"], file, sPath);
            toml::table& sMt = const_cast<toml::table&>(sTable);
            if (sMt.contains("collection_range_m") || sMt.contains("cargo_capacity")
                || sMt.contains("collection_rate_hz"))
            {
                ModuleSalvageCapability cap;
                cap.collectionRange_m = static_cast<float>(utility::requireDouble(sMt["collection_range_m"],
                    file, sPath + ".collection_range_m"));
                cap.cargoCapacity     = static_cast<float>(utility::requireDouble(sMt["cargo_capacity"],
                    file, sPath + ".cargo_capacity"));
                cap.collectionRate_hz = static_cast<float>(utility::requireDouble(sMt["collection_rate_hz"],
                    file, sPath + ".collection_rate_hz"));
                def.salvageCapability = std::move(cap);
            }
        }

        // Repair capability section: [module.repair] with base stat formulas
        if (mt.contains("repair"))
        {
            const std::string rPath = elemPath + ".repair";
            const toml::table& rTable = utility::requireTable(mt["repair"], file, rPath);
            toml::table& rMt = const_cast<toml::table&>(rTable);
            if (rMt.contains("repair_rate_hz") || rMt.contains("repair_range_m"))
            {
                ModuleRepairCapability cap;
                cap.repairRate_hz   = static_cast<float>(utility::requireDouble(rMt["repair_rate_hz"],
                    file, rPath + ".repair_rate_hz"));
                cap.repairAmountHp  = static_cast<float>(utility::requireDouble(rMt["repair_amount_hp"],
                    file, rPath + ".repair_amount_hp"));
                cap.repairRange_m   = static_cast<float>(utility::requireDouble(rMt["repair_range_m"],
                    file, rPath + ".repair_range_m"));
                def.repairCapability = std::move(cap);
            }
        }

        cfg.modules.push_back(std::move(def));
    }

    return cfg;
}
