#include "core/layout_storage.h"

#include <imgui.h>
#include <sqlite3.h>

#include <chrono>
#include <cstdint>
#include <string>
#include <vector>

namespace fn_ui {

// ── handle interno ────────────────────────────────────────────────────────

struct LayoutStorage {
    sqlite3*    db = nullptr;
    std::string pending_blob;   // INI a aplicar al inicio del proximo frame
    std::string pending_name;   // nombre del layout pendiente
};

// ── helpers ───────────────────────────────────────────────────────────────

namespace {

int64_t now_unix() {
    using namespace std::chrono;
    return duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
}

bool ensure_schema(sqlite3* db) {
    // CREATE TABLE IF NOT EXISTS — no toca tablas pre-existentes con otro
    // schema (ej. ui_layouts heredada de layout_storage_sqlite).
    const char* sql =
        "CREATE TABLE IF NOT EXISTS imgui_layouts ("
        "  name       TEXT PRIMARY KEY,"
        "  ini        TEXT NOT NULL,"
        "  updated_at INTEGER NOT NULL"
        ");"
        "CREATE TABLE IF NOT EXISTS layout_meta ("
        "  key   TEXT PRIMARY KEY,"
        "  value TEXT NOT NULL"
        ");";
    char* errmsg = nullptr;
    int rc = sqlite3_exec(db, sql, nullptr, nullptr, &errmsg);
    if (errmsg) sqlite3_free(errmsg);
    return rc == SQLITE_OK;
}

bool layout_exists(sqlite3* db, const std::string& name) {
    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(db,
            "SELECT 1 FROM imgui_layouts WHERE name = ? LIMIT 1;",
            -1, &stmt, nullptr) != SQLITE_OK) return false;
    sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
    bool found = (sqlite3_step(stmt) == SQLITE_ROW);
    sqlite3_finalize(stmt);
    return found;
}

} // namespace

// ── API ───────────────────────────────────────────────────────────────────

LayoutStorage* layout_storage_open(const char* db_path) {
    if (!db_path || !*db_path) return nullptr;
    sqlite3* db = nullptr;
    int rc = sqlite3_open(db_path, &db);
    if (rc != SQLITE_OK || !db) {
        if (db) sqlite3_close(db);
        return nullptr;
    }
    if (!ensure_schema(db)) {
        sqlite3_close(db);
        return nullptr;
    }
    auto* s = new LayoutStorage{};
    s->db = db;
    return s;
}

void layout_storage_close(LayoutStorage* s) {
    if (!s) return;
    if (s->db) sqlite3_close(s->db);
    delete s;
}

std::vector<std::string> layout_storage_list(LayoutStorage* s) {
    std::vector<std::string> out;
    if (!s || !s->db) return out;
    const char* sql =
        "SELECT name FROM imgui_layouts ORDER BY updated_at DESC;";
    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(s->db, sql, -1, &stmt, nullptr) != SQLITE_OK)
        return out;
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        const unsigned char* t = sqlite3_column_text(stmt, 0);
        if (t) out.emplace_back(reinterpret_cast<const char*>(t));
    }
    sqlite3_finalize(stmt);
    return out;
}

bool layout_storage_save(LayoutStorage* s, const std::string& name) {
    if (!s || !s->db || name.empty()) return false;

    std::size_t sz = 0;
    const char* ini = ImGui::SaveIniSettingsToMemory(&sz);
    if (!ini || sz == 0) return false;

    const char* sql =
        "INSERT INTO imgui_layouts (name, ini, updated_at) "
        "VALUES (?, ?, ?) "
        "ON CONFLICT(name) DO UPDATE SET "
        "  ini        = excluded.ini, "
        "  updated_at = excluded.updated_at;";
    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(s->db, sql, -1, &stmt, nullptr) != SQLITE_OK)
        return false;
    sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, ini, static_cast<int>(sz), SQLITE_TRANSIENT);
    sqlite3_bind_int64(stmt, 3, now_unix());
    int rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    return rc == SQLITE_DONE;
}

bool layout_storage_apply(LayoutStorage* s, const std::string& name) {
    if (!s || !s->db || name.empty()) return false;
    const char* sql = "SELECT ini FROM imgui_layouts WHERE name = ?;";
    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(s->db, sql, -1, &stmt, nullptr) != SQLITE_OK)
        return false;
    sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
    bool found = false;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        const unsigned char* t = sqlite3_column_text(stmt, 0);
        int len = sqlite3_column_bytes(stmt, 0);
        if (t && len > 0) {
            s->pending_blob.assign(reinterpret_cast<const char*>(t),
                                   static_cast<std::size_t>(len));
            s->pending_name = name;
            found = true;
        }
    }
    sqlite3_finalize(stmt);
    return found;
}

std::string layout_storage_apply_pending(LayoutStorage* s) {
    if (!s || s->pending_blob.empty()) return "";
    ImGui::LoadIniSettingsFromMemory(s->pending_blob.data(),
                                     s->pending_blob.size());
    std::string applied = std::move(s->pending_name);
    s->pending_blob.clear();
    s->pending_name.clear();
    return applied;
}

bool layout_storage_delete(LayoutStorage* s, const std::string& name) {
    if (!s || !s->db || name.empty()) return false;
    const char* sql = "DELETE FROM imgui_layouts WHERE name = ?;";
    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(s->db, sql, -1, &stmt, nullptr) != SQLITE_OK)
        return false;
    sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
    int rc      = sqlite3_step(stmt);
    int changes = sqlite3_changes(s->db);
    sqlite3_finalize(stmt);
    return rc == SQLITE_DONE && changes > 0;
}

bool layout_storage_set_last_active(LayoutStorage* s, const std::string& name) {
    if (!s || !s->db) return false;
    sqlite3_stmt* stmt = nullptr;
    if (name.empty()) {
        if (sqlite3_prepare_v2(s->db,
                "DELETE FROM layout_meta WHERE key = 'last_active';",
                -1, &stmt, nullptr) != SQLITE_OK) return false;
    } else {
        if (sqlite3_prepare_v2(s->db,
                "INSERT INTO layout_meta (key, value) VALUES ('last_active', ?) "
                "ON CONFLICT(key) DO UPDATE SET value = excluded.value;",
                -1, &stmt, nullptr) != SQLITE_OK) return false;
        sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
    }
    int rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    return rc == SQLITE_DONE;
}

std::string layout_storage_get_last_active(LayoutStorage* s) {
    if (!s || !s->db) return "";
    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(s->db,
            "SELECT value FROM layout_meta WHERE key = 'last_active';",
            -1, &stmt, nullptr) != SQLITE_OK) return "";
    std::string name;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        const unsigned char* t = sqlite3_column_text(stmt, 0);
        if (t) name.assign(reinterpret_cast<const char*>(t));
    }
    sqlite3_finalize(stmt);
    if (name.empty()) return "";
    if (!layout_exists(s->db, name)) return ""; // referencia colgante: ignorar
    return name;
}

void layout_storage_make_callbacks(LayoutStorage* s, LayoutCallbacks& out) {
    out.list = [s]() {
        return layout_storage_list(s);
    };
    out.on_apply = [s, &out](const std::string& name) {
        if (layout_storage_apply(s, name)) {
            // active_name se ajustara cuando layout_storage_apply_pending
            // confirme que se aplico (la app debe tomar el valor de retorno
            // y propagarlo). Aqui ya lo dejamos optimistamente.
            out.active_name = name;
            layout_storage_set_last_active(s, name);
        }
    };
    out.on_save = [s, &out](const std::string& name) {
        if (layout_storage_save(s, name)) {
            out.active_name = name;
            layout_storage_set_last_active(s, name);
        }
    };
    out.on_delete = [s, &out](const std::string& name) {
        layout_storage_delete(s, name);
        if (out.active_name == name) {
            out.active_name.clear();
            layout_storage_set_last_active(s, "");
        }
    };
    out.on_reset = [s, &out]() {
        // Limpia el INI en memoria. La app puede ademas re-mostrar paneles.
        ImGui::LoadIniSettingsFromMemory("", 0);
        ImGuiIO& io = ImGui::GetIO();
        io.WantSaveIniSettings = true;
        out.active_name.clear();
        layout_storage_set_last_active(s, "");
    };
}

} // namespace fn_ui
