#include "VisualsLoader.h"

#include <sstream>
#include <stdexcept>
#include <string>

#include <QColor>

#include "toml.hpp"
#include "BuildingType.h"

namespace
{

std::runtime_error makeError(const std::string& section, const std::string& why)
{
    return std::runtime_error("visuals.toml: [" + section + "] " + why);
}

QColor parseColor(const std::string& s, const std::string& ctx)
{
    if (s.empty() || s[0] != '#')
    {
        throw std::runtime_error("visuals.toml: invalid color '" + s + "' in " + ctx);
    }
    if (s.size() == 9)
    {
        bool ok1 = true, ok2 = true, ok3 = true, ok4 = true;
        int r = QString::fromStdString(s.substr(1, 2)).toInt(&ok1, 16);
        int g = QString::fromStdString(s.substr(3, 2)).toInt(&ok2, 16);
        int b = QString::fromStdString(s.substr(5, 2)).toInt(&ok3, 16);
        int a = QString::fromStdString(s.substr(7, 2)).toInt(&ok4, 16);
        if (!ok1 || !ok2 || !ok3 || !ok4)
        {
            throw std::runtime_error("visuals.toml: malformed color '" + s + "' in " + ctx);
        }
        return QColor(r, g, b, a);
    }
    QColor c(QString::fromStdString(s));
    if (!c.isValid())
    {
        throw std::runtime_error("visuals.toml: invalid color '" + s + "' in " + ctx);
    }
    return c;
}

std::string requireString(toml::table& tbl, const std::string& key,
                          const std::string& ctx)
{
    const std::optional<std::string> v = tbl[key].value<std::string>();
    if (!v)
    {
        throw makeError(ctx, "missing or invalid string '" + key + "'");
    }
    return *v;
}

int requireInt(toml::table& tbl, const std::string& key, const std::string& ctx)
{
    const std::optional<int64_t> v = tbl[key].value<int64_t>();
    if (!v)
    {
        throw makeError(ctx, "missing or invalid integer '" + key + "'");
    }
    return static_cast<int>(*v);
}

toml::table& requireSubtable(toml::table& tbl, const std::string& key,
                              const std::string& ctx)
{
    toml::table* sub = tbl[key].as_table();
    if (sub == nullptr)
    {
        throw makeError(ctx, "missing section '" + key + "'");
    }
    return *sub;
}

TileVisuals parseTile(toml::table& tbl, const std::string& ctx)
{
    TileVisuals v;
    v.fill = parseColor(requireString(tbl, "fill", ctx), ctx + ".fill");
    return v;
}

BuildingVisuals parseBuilding(toml::table& tbl, const std::string& ctx)
{
    BuildingVisuals v;
    v.fill    = parseColor(requireString(tbl, "fill",    ctx), ctx + ".fill");
    v.outline = parseColor(requireString(tbl, "outline", ctx), ctx + ".outline");
    v.glyph   = QString::fromStdString(requireString(tbl, "glyph", ctx));
    return v;
}

ItemVisuals parseItem(toml::table& tbl, const std::string& ctx)
{
    ItemVisuals v;
    v.fill    = parseColor(requireString(tbl, "fill",    ctx), ctx + ".fill");
    v.outline = parseColor(requireString(tbl, "outline", ctx), ctx + ".outline");
    return v;
}

ShipVisuals parseShip(toml::table& tbl, const std::string& ctx)
{
    ShipVisuals v;
    v.fill    = parseColor(requireString(tbl, "fill",    ctx), ctx + ".fill");
    v.outline = parseColor(requireString(tbl, "outline", ctx), ctx + ".outline");
    return v;
}

struct BuildingEntry
{
    const char*  key;
    BuildingType type;
};

const BuildingEntry kBuildingEntries[] = {
    { "hq",                BuildingType::Hq                },
    { "miner",             BuildingType::Miner             },
    { "smelter",           BuildingType::Smelter           },
    { "assembler",         BuildingType::Assembler         },
    { "reprocessing_plant",BuildingType::ReprocessingPlant },
    { "shipyard",          BuildingType::Shipyard          },
    { "salvage_bay",       BuildingType::SalvageBay        },
    { "belt",              BuildingType::Belt              },
    { "splitter",          BuildingType::Splitter          },
    { "tunnel_entry",      BuildingType::TunnelEntry       },
    { "tunnel_exit",       BuildingType::TunnelExit        },
};

}  // namespace


VisualsConfig VisualsLoader::load(const std::string& path)
{
    toml::table tbl;
    try
    {
        tbl = toml::parse_file(path);
    }
    catch (const toml::parse_error& e)
    {
        std::ostringstream oss;
        oss << "visuals.toml: parse error: " << e.description()
            << " at " << e.source().begin;
        throw std::runtime_error(oss.str());
    }

    VisualsConfig cfg;

    // Tiles
    {
        toml::table& tiles = requireSubtable(tbl, "tiles", "root");
        cfg.asteroid = parseTile(requireSubtable(tiles, "asteroid", "tiles"), "tiles.asteroid");
        cfg.space    = parseTile(requireSubtable(tiles, "space",    "tiles"), "tiles.space");
    }

    // Buildings ([buildings.*] sections)
    {
        toml::table& bldgs = requireSubtable(tbl, "buildings", "root");
        for (const BuildingEntry& entry : kBuildingEntries)
        {
            std::string ctx = std::string("buildings.") + entry.key;
            cfg.buildings[entry.type] = parseBuilding(
                requireSubtable(bldgs, entry.key, "buildings"), ctx);
        }
    }

    // Stations ([stations.*] → mapped as PlayerDefenceStation / EnemyDefenceStation)
    {
        toml::table& stns = requireSubtable(tbl, "stations", "root");
        cfg.buildings[BuildingType::PlayerDefenceStation] = parseBuilding(
            requireSubtable(stns, "player", "stations"), "stations.player");
        cfg.buildings[BuildingType::EnemyDefenceStation] = parseBuilding(
            requireSubtable(stns, "enemy", "stations"), "stations.enemy");
    }

    // Items (iterate all keys in [items])
    {
        toml::table& items = requireSubtable(tbl, "items", "root");
        for (toml::table::iterator it = items.begin(); it != items.end(); ++it)
        {
            std::string itemId = std::string(it->first.str());
            toml::table* sub = it->second.as_table();
            if (sub == nullptr)
            {
                throw std::runtime_error("visuals.toml: items." + itemId + " is not a table");
            }
            cfg.items[itemId] = parseItem(*sub, "items." + itemId);
        }
    }

    // Ships (dynamic keys: each key is a schematic id)
    {
        toml::table& ships = requireSubtable(tbl, "ships", "root");
        for (toml::table::iterator it = ships.begin(); it != ships.end(); ++it)
        {
            std::string schematicId = std::string(it->first.str());
            toml::table* sub = it->second.as_table();
            if (sub == nullptr)
            {
                throw std::runtime_error("visuals.toml: ships." + schematicId
                                         + " is not a table");
            }
            cfg.ships[schematicId] = parseShip(*sub, "ships." + schematicId);
        }
    }

    // Beams
    {
        toml::table& beams = requireSubtable(tbl, "beams", "root");
        cfg.beams.weaponColor  = parseColor(requireString(beams, "weapon_color",  "beams"), "beams.weapon_color");
        cfg.beams.repairColor  = parseColor(requireString(beams, "repair_color",  "beams"), "beams.repair_color");
        cfg.beams.salvageColor = parseColor(requireString(beams, "salvage_color", "beams"), "beams.salvage_color");
        cfg.beams.widthPx      = requireInt(beams, "width_px", "beams");
    }

    // Overlays
    {
        toml::table& ov = requireSubtable(tbl, "overlays", "root");
        cfg.overlays.ghostValid      = parseColor(requireString(ov, "ghost_valid",      "overlays"), "overlays.ghost_valid");
        cfg.overlays.ghostInvalid    = parseColor(requireString(ov, "ghost_invalid",    "overlays"), "overlays.ghost_invalid");
        cfg.overlays.deconstructTint    = parseColor(requireString(ov, "deconstruct_tint",    "overlays"), "overlays.deconstruct_tint");
        cfg.overlays.tileHighlight   = parseColor(requireString(ov, "tile_highlight",   "overlays"), "overlays.tile_highlight");
        cfg.overlays.selectedOutline = parseColor(requireString(ov, "selected_outline", "overlays"), "overlays.selected_outline");
        cfg.overlays.configTransfer  = parseColor(requireString(ov, "config_transfer",  "overlays"), "overlays.config_transfer");
        cfg.overlays.lockedAsteroid  = parseColor(requireString(ov, "locked_asteroid",  "overlays"), "overlays.locked_asteroid");
        cfg.overlays.nextExpansion   = parseColor(requireString(ov, "next_expansion",   "overlays"), "overlays.next_expansion");
        cfg.overlays.modalDim        = parseColor(requireString(ov, "modal_dim",        "overlays"), "overlays.modal_dim");
        cfg.overlays.tunnelPreview   = parseColor(requireString(ov, "tunnel_preview",   "overlays"), "overlays.tunnel_preview");
    }

    // Toast
    {
        toml::table& t = requireSubtable(tbl, "toast", "root");
        cfg.toast.bg       = parseColor(requireString(t, "bg", "toast"), "toast.bg");
        cfg.toast.fg       = parseColor(requireString(t, "fg", "toast"), "toast.fg");
        cfg.toast.fontSize = requireInt(t, "font_size", "toast");
    }

    // Status light (REQ-UI-STATUS-LIGHT)
    {
        toml::table& sl = requireSubtable(tbl, "status_light", "root");
        cfg.statusLight.grey    = parseColor(requireString(sl, "grey",    "status_light"), "status_light.grey");
        cfg.statusLight.green   = parseColor(requireString(sl, "green",   "status_light"), "status_light.green");
        cfg.statusLight.red     = parseColor(requireString(sl, "red",     "status_light"), "status_light.red");
        cfg.statusLight.yellow  = parseColor(requireString(sl, "yellow",  "status_light"), "status_light.yellow");
        cfg.statusLight.outline = parseColor(requireString(sl, "outline", "status_light"), "status_light.outline");
    }

    return cfg;
}
