#include "core/resources/resource.h"

#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 auto basename(const std::string &path) -> std::string {
    return path.substr(path.find_last_of("\\/") + 1);
}

static auto stem(const std::string &path) -> std::string {
    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;
}

auto Resource::get() -> Resource * {
    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() {
    preloadResources();
    preloadFonts();
    preloadMenus();
}

// Pass 1: texturas, sonidos, músicas y datos (animaciones / demo / menús)
void Resource::preloadResources() {
    const auto &items = Asset::get()->getAll();

    for (const auto &it : items) {
        if (!ResourceHelper::shouldUseResourcePack(it.file) && it.type != Asset::Type::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 BASE_NAME = basename(it.file);

        switch (it.type) {
            case Asset::Type::BITMAP: {
                auto *tex = new Texture(renderer_, bytes);
                textures_[BASE_NAME] = tex;
                break;
            }
            case Asset::Type::SOUND: {
                Ja::Sound *s = Ja::loadSound(bytes.data(), (uint32_t)bytes.size());
                if (s != nullptr) {
                    sounds_[BASE_NAME] = s;
                }
                break;
            }
            case Asset::Type::MUSIC: {
                Ja::Music *m = Ja::loadMusic(bytes.data(), (Uint32)bytes.size());
                if (m != nullptr) {
                    musics_[BASE_NAME] = m;
                }
                break;
            }
            case Asset::Type::DATA:
                loadDataAsset(BASE_NAME, bytes);
                break;

            case Asset::Type::FONT:  // Fonts: se emparejan en pass 2
            case Asset::Type::LANG:  // Lenguaje: lo sigue leyendo la clase Lang via ResourceHelper
            default:
                break;
        }
    }
}

// Despacha un asset Asset::Type::DATA en función de la extensión / nombre
void Resource::loadDataAsset(const std::string &bname, const std::vector<uint8_t> &bytes) {
    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)) {
            // Normalitza CRLF perquè loadFromVector compari línies amb literals
            // ("[animation]", "[/animation]") sense \r residual.
            if (!line.empty() && line.back() == '\r') {
                line.pop_back();
            }
            lines.push_back(line);
        }
        animation_lines_[bname] = std::move(lines);
    } else if (bname == "demo.bin") {
        demo_bytes_ = bytes;
    }
    // Menús (.men): se construyen en pass 2 porque dependen de textos y sonidos
}

// Pass 2a: construye Text por cada par basename.png + basename.txt
void Resource::preloadFonts() {
    const auto &items = Asset::get()->getAll();

    std::unordered_map<std::string, std::vector<uint8_t>> font_pngs;
    std::unordered_map<std::string, std::vector<uint8_t>> font_txts;
    for (const auto &it : items) {
        if (it.type != Asset::Type::FONT) {
            continue;
        }
        auto bytes = ResourceHelper::loadFile(it.file);
        if (bytes.empty()) {
            continue;
        }
        const std::string S = stem(it.file);
        const std::string BASE_NAME = basename(it.file);
        if (BASE_NAME.size() >= 4 && BASE_NAME.substr(BASE_NAME.size() - 4) == ".png") {
            font_pngs[S] = std::move(bytes);
        } else if (BASE_NAME.size() >= 4 && BASE_NAME.substr(BASE_NAME.size() - 4) == ".txt") {
            font_txts[S] = std::move(bytes);
        }
    }
    for (const auto &[s, png] : font_pngs) {
        auto it_txt = font_txts.find(s);
        if (it_txt == font_txts.end()) {
            continue;
        }
        Text *t = new Text(png, it_txt->second, renderer_);
        texts_[s] = t;
    }
}

// Pass 2b: construye los Menu (dependen de Text+sonidos cargados antes)
//
// NOTA: Menu::loadFromBytes aún llama internamente 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. Migración pendiente.
void Resource::preloadMenus() {
    const auto &items = Asset::get()->getAll();

    for (const auto &it : items) {
        if (it.type != Asset::Type::DATA) {
            continue;
        }
        const std::string BASE_NAME = basename(it.file);
        if (BASE_NAME.size() < 4 || BASE_NAME.substr(BASE_NAME.size() - 4) != ".men") {
            continue;
        }
        auto bytes = ResourceHelper::loadFile(it.file);
        if (bytes.empty()) {
            continue;
        }
        Menu *m = new Menu(renderer_, "");
        m->loadFromBytes(bytes, BASE_NAME);
        const std::string S = stem(it.file);
        menus_[S] = m;
    }
}

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

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

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

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

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

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