#include "core/resources/resource_list.hpp"

#include <SDL3/SDL.h>  // Para SDL_LogWarn, SDL_LogCategory, SDL_LogError

#include <algorithm>   // Para sort
#include <cstddef>     // Para size_t
#include <exception>   // Para exception
#include <filesystem>  // Para exists, path
#include <fstream>     // Para ifstream, istringstream
#include <iostream>    // Para cout
#include <sstream>     // Para istringstream
#include <stdexcept>   // Para runtime_error

#include "external/fkyaml_node.hpp"  // Para parsear YAML
#include "utils/utils.hpp"           // Para getFileName, printWithDots

namespace Resource {

// Singleton
List* List::instance = nullptr;

void List::init(const std::string& executable_path) {
    List::instance = new List(executable_path);
}

void List::destroy() {
    delete List::instance;
}

auto List::get() -> List* {
    return List::instance;
}

// Añade un elemento al mapa (función auxiliar)
void List::addToMap(const std::string& file_path, Type type, bool required, bool absolute) {
    std::string full_path = absolute ? file_path : executable_path_ + file_path;
    std::string filename = getFileName(full_path);

    // Verificar si ya existe el archivo
    if (file_list_.find(filename) != file_list_.end()) {
        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
            "Warning: Asset '%s' already exists, overwriting",
            filename.c_str());
    }

    file_list_.emplace(filename, Item{std::move(full_path), type, required});
}

// Añade un elemento a la lista
void List::add(const std::string& file_path, Type type, bool required, bool absolute) {
    addToMap(file_path, type, required, absolute);
}

// Carga recursos desde un archivo de configuración con soporte para variables
void List::loadFromFile(const std::string& config_file_path, const std::string& prefix, const std::string& system_folder) {
    std::ifstream file(config_file_path);
    if (!file.is_open()) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
            "Error: Cannot open config file: %s",
            config_file_path.c_str());
        return;
    }

    // Read entire file into string
    std::stringstream buffer;
    buffer << file.rdbuf();
    file.close();

    // Parse using loadFromString
    loadFromString(buffer.str(), prefix, system_folder);
}

// Carga recursos desde un string de configuración (para release con pack)
void List::loadFromString(const std::string& config_content, const std::string& prefix, const std::string& system_folder) {
    try {
        // Parsear YAML
        auto yaml = fkyaml::node::deserialize(config_content);

        // Verificar estructura básica
        if (!yaml.contains("assets")) {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Invalid assets.yaml format - missing 'assets' key");
            return;
        }

        const auto& assets = yaml["assets"];

        // Iterar sobre cada categoría (fonts, palettes, etc.)
        for (auto it = assets.begin(); it != assets.end(); ++it) {
            const std::string& category = it.key().get_value<std::string>();
            const auto& category_assets = it.value();

            // Verificar que es un array
            if (!category_assets.is_sequence()) {
                SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                    "Warning: Category '%s' is not a sequence, skipping",
                    category.c_str());
                continue;
            }

            // Procesar cada asset en la categoría
            for (const auto& asset : category_assets) {
                try {
                    // Verificar campos obligatorios
                    if (!asset.contains("type") || !asset.contains("path")) {
                        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                            "Warning: Asset in category '%s' missing 'type' or 'path', skipping",
                            category.c_str());
                        continue;
                    }

                    // Extraer campos
                    auto type_str = asset["type"].get_value<std::string>();
                    auto path = asset["path"].get_value<std::string>();

                    // Valores por defecto
                    bool required = true;
                    bool absolute = false;

                    // Campos opcionales
                    if (asset.contains("required")) {
                        required = asset["required"].get_value<bool>();
                    }
                    if (asset.contains("absolute")) {
                        absolute = asset["absolute"].get_value<bool>();
                    }

                    // Reemplazar variables en la ruta
                    path = replaceVariables(path, prefix, system_folder);

                    // Parsear el tipo de asset
                    Type type = parseAssetType(type_str);

                    // Añadir al mapa
                    addToMap(path, type, required, absolute);

                } catch (const std::exception& e) {
                    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                        "Error parsing asset in category '%s': %s",
                        category.c_str(),
                        e.what());
                }
            }
        }

        std::cout << "Loaded " << file_list_.size() << " assets from YAML config" << '\n';

    } catch (const fkyaml::exception& e) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
            "YAML parsing error: %s",
            e.what());
    } catch (const std::exception& e) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
            "Error loading assets: %s",
            e.what());
    }
}

// Devuelve la ruta completa a un fichero (búsqueda O(1))
auto List::get(const std::string& filename) const -> std::string {
    auto it = file_list_.find(filename);
    if (it != file_list_.end()) {
        return it->second.file;
    }

    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: file %s not found", filename.c_str());
    return "";
}

// Carga datos del archivo
auto List::loadData(const std::string& filename) const -> std::vector<uint8_t> {
    auto it = file_list_.find(filename);
    if (it != file_list_.end()) {
        std::ifstream file(it->second.file, std::ios::binary);
        if (!file.is_open()) {
            SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "Warning: Could not open file %s for data loading",
                filename.c_str());
            return {};
        }

        // Obtener tamaño del archivo
        file.seekg(0, std::ios::end);
        size_t size = file.tellg();
        file.seekg(0, std::ios::beg);

        // Leer datos
        std::vector<uint8_t> data(size);
        file.read(reinterpret_cast<char*>(data.data()), size);
        file.close();

        return data;
    }

    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: file %s not found for data loading", filename.c_str());
    return {};
}

// Verifica si un recurso existe
auto List::exists(const std::string& filename) const -> bool {
    return file_list_.find(filename) != file_list_.end();
}

// Parsea string a Type
auto List::parseAssetType(const std::string& type_str) -> Type {
    if (type_str == "DATA") {
        return Type::DATA;
    }
    if (type_str == "BITMAP") {
        return Type::BITMAP;
    }
    if (type_str == "ANIMATION") {
        return Type::ANIMATION;
    }
    if (type_str == "MUSIC") {
        return Type::MUSIC;
    }
    if (type_str == "SOUND") {
        return Type::SOUND;
    }
    if (type_str == "FONT") {
        return Type::FONT;
    }
    if (type_str == "ROOM") {
        return Type::ROOM;
    }
    if (type_str == "TILEMAP") {
        // TILEMAP está obsoleto, ahora todo es ROOM (.yaml unificado)
        return Type::ROOM;
    }
    if (type_str == "PALETTE") {
        return Type::PALETTE;
    }

    throw std::runtime_error("Unknown asset type: " + type_str);
}

// Devuelve el nombre del tipo de recurso
auto List::getTypeName(Type type) -> std::string {
    switch (type) {
        case Type::DATA:
            return "DATA";
        case Type::BITMAP:
            return "BITMAP";
        case Type::ANIMATION:
            return "ANIMATION";
        case Type::MUSIC:
            return "MUSIC";
        case Type::SOUND:
            return "SOUND";
        case Type::FONT:
            return "FONT";
        case Type::ROOM:
            return "ROOM";
        case Type::PALETTE:
            return "PALETTE";
        default:
            return "ERROR";
    }
}

// Devuelve la lista de recursos de un tipo
auto List::getListByType(Type type) const -> std::vector<std::string> {
    std::vector<std::string> list;

    for (const auto& [filename, item] : file_list_) {
        if (item.type == type) {
            list.push_back(item.file);
        }
    }

    // Ordenar alfabéticamente para garantizar orden consistente
    std::ranges::sort(list);

    return list;
}

// Reemplaza variables en las rutas
auto List::replaceVariables(const std::string& path, const std::string& prefix, const std::string& system_folder) -> std::string {
    std::string result = path;

    // Reemplazar ${PREFIX}
    size_t pos = 0;
    while ((pos = result.find("${PREFIX}", pos)) != std::string::npos) {
        result.replace(pos, 9, prefix);  // 9 = longitud de "${PREFIX}"
        pos += prefix.length();
    }

    // Reemplazar ${SYSTEM_FOLDER}
    pos = 0;
    while ((pos = result.find("${SYSTEM_FOLDER}", pos)) != std::string::npos) {
        result.replace(pos, 16, system_folder);  // 16 = longitud de "${SYSTEM_FOLDER}"
        pos += system_folder.length();
    }

    return result;
}

// Parsea las opciones de una línea de configuración
auto List::parseOptions(const std::string& options, bool& required, bool& absolute) -> void {
    if (options.empty()) {
        return;
    }

    std::istringstream iss(options);
    std::string option;

    while (std::getline(iss, option, ',')) {
        // Eliminar espacios
        option.erase(0, option.find_first_not_of(" \t"));
        option.erase(option.find_last_not_of(" \t") + 1);

        if (option == "optional") {
            required = false;
        } else if (option == "absolute") {
            absolute = true;
        }
    }
}

}  // namespace Resource
