#include "core/resources/resource.h"

#include <algorithm>
#include <filesystem>
#include <iostream>
#include <sstream>

#include "core/audio/jail_audio.hpp"
#include "core/rendering/text.h"
#include "core/rendering/texture.h"
#include "core/resources/asset.h"
#include "core/resources/resource_helper.h"
#include "game/ui/menu.h"

// Nota: Asset::get() e Input::get() se consultan en preloadAll y al construir
// los menús; no se guardan punteros en el objeto Resource.

Resource *Resource::instance_ = nullptr;

static std::string basename(const std::string &path) {
    return path.substr(path.find_last_of("\\/") + 1);
}

static std::string stem(const std::string &path) {
    std::string b = basename(path);
    size_t dot = b.find_last_of('.');
    if (dot == std::string::npos) return b;
    return b.substr(0, dot);
}

void Resource::init(SDL_Renderer *renderer) {
    if (instance_ == nullptr) {
        instance_ = new Resource(renderer);
        instance_->preloadAll();
    }
}

void Resource::destroy() {
    delete instance_;
    instance_ = nullptr;
}

Resource *Resource::get() {
    return instance_;
}

Resource::Resource(SDL_Renderer *renderer)
    : renderer_(renderer) {}

Resource::~Resource() {
    for (auto &[name, m] : menus_) delete m;
    menus_.clear();

    for (auto &[name, t] : texts_) delete t;
    texts_.clear();

    for (auto &[name, t] : textures_) delete t;
    textures_.clear();

    for (auto &[name, s] : sounds_) JA_DeleteSound(s);
    sounds_.clear();

    for (auto &[name, m] : musics_) JA_DeleteMusic(m);
    musics_.clear();
}

void Resource::preloadAll() {
    const auto &items = Asset::get()->getAll();

    // Pass 1: texturas, sonidos, músicas, animaciones (raw lines), demo, lenguajes
    for (const auto &it : items) {
        if (!ResourceHelper::shouldUseResourcePack(it.file) && it.type != t_lang) {
            // Ficheros absolutos (config.txt, score.bin, systemFolder) — no se precargan
            continue;
        }
        auto bytes = ResourceHelper::loadFile(it.file);
        if (bytes.empty()) continue;

        const std::string bname = basename(it.file);

        switch (it.type) {
            case t_bitmap: {
                auto *tex = new Texture(renderer_, bytes);
                textures_[bname] = tex;
                break;
            }
            case t_sound: {
                JA_Sound_t *s = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
                if (s) sounds_[bname] = s;
                break;
            }
            case t_music: {
                JA_Music_t *m = JA_LoadMusic(bytes.data(), (Uint32)bytes.size());
                if (m) musics_[bname] = m;
                break;
            }
            case t_data: {
                if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".ani") {
                    std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
                    std::stringstream ss(content);
                    std::vector<std::string> lines;
                    std::string line;
                    while (std::getline(ss, line)) {
                        lines.push_back(line);
                    }
                    animationLines_[bname] = std::move(lines);
                } else if (bname == "demo.bin") {
                    demoBytes_ = bytes;
                } else if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".men") {
                    // Menús: se construyen en pass 2 porque dependen de textos y sonidos
                }
                break;
            }
            case t_font:
                // Fonts: se emparejan en pass 2
                break;
            case t_lang:
                // Lenguaje: lo sigue leyendo la clase Lang a través de ResourceHelper
                break;
            default:
                break;
        }
    }

    // Pass 2: Text (fuentes emparejadas png+txt) y Menus (dependen de Text+sonidos)
    // Fuentes: construimos un Text por cada par basename.png + basename.txt
    // Acumulamos los bytes encontrados por stem (basename sin ext.)
    std::unordered_map<std::string, std::vector<uint8_t>> fontPngs;
    std::unordered_map<std::string, std::vector<uint8_t>> fontTxts;
    for (const auto &it : items) {
        if (it.type != t_font) continue;
        auto bytes = ResourceHelper::loadFile(it.file);
        if (bytes.empty()) continue;
        const std::string s = stem(it.file);
        const std::string bname = basename(it.file);
        if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".png") {
            fontPngs[s] = std::move(bytes);
        } else if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".txt") {
            fontTxts[s] = std::move(bytes);
        }
    }
    for (const auto &[s, png] : fontPngs) {
        auto itTxt = fontTxts.find(s);
        if (itTxt == fontTxts.end()) continue;
        Text *t = new Text(png, itTxt->second, renderer_);
        texts_[s] = t;
    }

    // Menus: usan aún Menu::loadFromBytes que internamente llama a asset->get() y
    // Text/JA_LoadSound por path. Funciona en modo fallback; en pack estricto
    // requiere que Menu se adapte a cargar desde ResourceHelper. Por ahora
    // lo dejamos así y será una migración del paso 7.
    for (const auto &it : items) {
        if (it.type != t_data) continue;
        const std::string bname = basename(it.file);
        if (bname.size() < 4 || bname.substr(bname.size() - 4) != ".men") continue;
        auto bytes = ResourceHelper::loadFile(it.file);
        if (bytes.empty()) continue;
        Menu *m = new Menu(renderer_, "");
        m->loadFromBytes(bytes, bname);
        const std::string s = stem(it.file);
        menus_[s] = m;
    }
}

Texture *Resource::getTexture(const std::string &name) {
    auto it = textures_.find(name);
    if (it == textures_.end()) {
        std::cerr << "Resource::getTexture: missing " << name << '\n';
        return nullptr;
    }
    return it->second;
}

JA_Sound_t *Resource::getSound(const std::string &name) {
    auto it = sounds_.find(name);
    if (it == sounds_.end()) {
        std::cerr << "Resource::getSound: missing " << name << '\n';
        return nullptr;
    }
    return it->second;
}

JA_Music_t *Resource::getMusic(const std::string &name) {
    auto it = musics_.find(name);
    if (it == musics_.end()) {
        std::cerr << "Resource::getMusic: missing " << name << '\n';
        return nullptr;
    }
    return it->second;
}

std::vector<std::string> &Resource::getAnimationLines(const std::string &name) {
    auto it = animationLines_.find(name);
    if (it == animationLines_.end()) {
        static std::vector<std::string> empty;
        std::cerr << "Resource::getAnimationLines: missing " << name << '\n';
        return empty;
    }
    return it->second;
}

Text *Resource::getText(const std::string &name) {
    auto it = texts_.find(name);
    if (it == texts_.end()) {
        std::cerr << "Resource::getText: missing " << name << '\n';
        return nullptr;
    }
    return it->second;
}

Menu *Resource::getMenu(const std::string &name) {
    auto it = menus_.find(name);
    if (it == menus_.end()) {
        std::cerr << "Resource::getMenu: missing " << name << '\n';
        return nullptr;
    }
    return it->second;
}
