#include "core/system/debug.hpp"

#ifdef _DEBUG

#include <algorithm>  // Para max
#include <fstream>    // Para ifstream, ofstream
#include <memory>     // Para __shared_ptr_access, shared_ptr

#include "core/rendering/text.hpp"            // Para Text
#include "core/resources/resource_cache.hpp"  // Para Resource
#include "external/fkyaml_node.hpp"           // Para fkyaml::node
#include "game/defaults.hpp"                  // Para Defaults::Game::*
#include "utils/defines.hpp"                  // Para Tile::SIZE
#include "utils/utils.hpp"                    // Para Color, Flip::

// [SINGLETON]
Debug* Debug::debug = nullptr;

// [SINGLETON] Crearemos el objeto con esta función estática
void Debug::init() {
    Debug::debug = new Debug();
}

// [SINGLETON] Destruiremos el objeto con esta función estática
void Debug::destroy() {
    delete Debug::debug;
}

// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
auto Debug::get() -> Debug* {
    return Debug::debug;
}

// Dibuja en pantalla
void Debug::render() {
    auto text = Resource::Cache::get()->getText("aseprite");
    int y = y_;
    int w = 0;
    constexpr int DESP_Y = 7;
    const int CHAR_SIZE = text->getCharacterSize();

    // Watch window: valores persistentes (key: value)
    for (const auto& [key, value] : watches_) {
        const std::string LINE = key + ": " + value;
        text->write(x_, y, LINE);
        w = std::max(w, text->length(LINE));
        y += DESP_Y;
        if (y > 192 - CHAR_SIZE) {
            y = y_;
            x_ += w + 2;
            w = 0;
        }
    }

    // Slot one-shot: mensajes de un solo frame
    for (const auto& s : slot_) {
        text->write(x_, y, s);
        w = std::max(w, text->length(s));
        y += DESP_Y;
        if (y > 192 - CHAR_SIZE) {
            y = y_;
            x_ += w + 2;
            w = 0;
        }
    }

    y = 0;
    for (const auto& l : log_) {
        text->writeColored(x_ + 10, y, l, static_cast<Uint8>(PaletteColor::WHITE));
        y += CHAR_SIZE + 1;
    }
}

// Establece/actualiza un valor persistente en el watch window
void Debug::set(const std::string& key, const std::string& value) {
    watches_[key] = value;
}

// Elimina un valor del watch window
void Debug::unset(const std::string& key) {
    watches_.erase(key);
}

// Establece la posición donde se colocará la información de debug
void Debug::setPos(SDL_FPoint p) {
    x_ = p.x;
    y_ = p.y;
}

// Establece la ruta del archivo debug.yaml
void Debug::setDebugFile(const std::string& path) {
    debug_file_path_ = path;
}

// Convierte string a SceneManager::Scene (para debug.yaml)
static auto sceneFromString(const std::string& s) -> SceneManager::Scene {
    if (s == "LOGO") { return SceneManager::Scene::LOGO; }
    if (s == "LOADING") { return SceneManager::Scene::LOADING_SCREEN; }
    if (s == "TITLE") { return SceneManager::Scene::TITLE; }
    if (s == "CREDITS") { return SceneManager::Scene::CREDITS; }
    if (s == "DEMO") { return SceneManager::Scene::DEMO; }
    if (s == "ENDING") { return SceneManager::Scene::ENDING; }
    if (s == "ENDING2") { return SceneManager::Scene::ENDING2; }
    return SceneManager::Scene::GAME;  // Fallback seguro
}

// Convierte SceneManager::Scene a string (para debug.yaml)
static auto sceneToString(SceneManager::Scene scene) -> std::string {
    switch (scene) {
        case SceneManager::Scene::LOGO:
            return "LOGO";
        case SceneManager::Scene::LOADING_SCREEN:
            return "LOADING";
        case SceneManager::Scene::TITLE:
            return "TITLE";
        case SceneManager::Scene::CREDITS:
            return "CREDITS";
        case SceneManager::Scene::DEMO:
            return "DEMO";
        case SceneManager::Scene::ENDING:
            return "ENDING";
        case SceneManager::Scene::ENDING2:
            return "ENDING2";
        default:
            return "GAME";
    }
}

// Carga la configuración de debug desde debug.yaml
void Debug::loadFromFile() {
    // Inicializar con valores de release por defecto
    spawn_settings_.room = Defaults::Game::Room::INITIAL;
    spawn_settings_.spawn_x = Defaults::Game::Player::SPAWN_X;
    spawn_settings_.spawn_y = Defaults::Game::Player::SPAWN_Y;
    spawn_settings_.flip = Defaults::Game::Player::SPAWN_FLIP;
    initial_scene_ = SceneManager::Scene::GAME;

    std::ifstream file(debug_file_path_);
    if (!file.good()) {
        saveToFile();  // No existe: crear con valores por defecto
        return;
    }

    std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
    file.close();

    try {
        auto yaml = fkyaml::node::deserialize(content);
        if (yaml.contains("room")) {
            spawn_settings_.room = yaml["room"].get_value<std::string>();
        }
        if (yaml.contains("spawn_x")) {
            spawn_settings_.spawn_x = yaml["spawn_x"].get_value<int>() * Tile::SIZE;
        }
        if (yaml.contains("spawn_y")) {
            spawn_settings_.spawn_y = yaml["spawn_y"].get_value<int>() * Tile::SIZE;
        }
        if (yaml.contains("spawn_flip")) {
            auto s = yaml["spawn_flip"].get_value<std::string>();
            spawn_settings_.flip = (s == "right") ? Flip::RIGHT : Flip::LEFT;
        }
        if (yaml.contains("initial_scene")) {
            initial_scene_ = sceneFromString(yaml["initial_scene"].get_value<std::string>());
        }
    } catch (...) {
        // YAML inválido: resetear a defaults y sobreescribir
        spawn_settings_.room = Defaults::Game::Room::INITIAL;
        spawn_settings_.spawn_x = Defaults::Game::Player::SPAWN_X;
        spawn_settings_.spawn_y = Defaults::Game::Player::SPAWN_Y;
        spawn_settings_.flip = Defaults::Game::Player::SPAWN_FLIP;
        initial_scene_ = SceneManager::Scene::GAME;
        saveToFile();
    }
}

// Guarda la configuración de debug en debug.yaml
void Debug::saveToFile() const {
    std::ofstream file(debug_file_path_);
    if (!file.is_open()) { return; }
    file << "# JailDoctor's Dilemma - Debug Configuration\n";
    file << "# Edita para cambiar la habitacion y spawn del jugador en builds debug.\n\n";
    file << "room: \"" << spawn_settings_.room << "\"\n";
    file << "spawn_x: " << (spawn_settings_.spawn_x / Tile::SIZE) << "  # en tiles\n";
    file << "spawn_y: " << (spawn_settings_.spawn_y / Tile::SIZE) << "  # en tiles\n";
    file << "spawn_flip: " << ((spawn_settings_.flip == Flip::RIGHT) ? "right" : "left") << "\n";
    file << "initial_scene: " << sceneToString(initial_scene_) << "\n";
}

#endif  // _DEBUG