#include "types_registry.h"

#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <unordered_map>

namespace ge {

// ----------------------------------------------------------------------------
// Parser YAML minimo (subset suficiente para el ejemplo OSINT)
// ----------------------------------------------------------------------------
// Reconoce:
//   entities:
//     - name: Person
//       color: "#5B8DEF"
//       shape: circle
//       icon: ti-user
// La indentacion es por espacios; no se usa la libreria yaml-cpp para no
// arrastrar una dependencia mas. Los campos desconocidos se ignoran.

namespace {

std::string trim(std::string s) {
    auto issp = [](unsigned char c) { return std::isspace(c) || c == '\t'; };
    while (!s.empty() && issp((unsigned char)s.front())) s.erase(s.begin());
    while (!s.empty() && issp((unsigned char)s.back()))  s.pop_back();
    return s;
}

std::string strip_quotes(std::string s) {
    if (s.size() >= 2 && (s.front() == '"' || s.front() == '\'') &&
        s.back() == s.front()) {
        return s.substr(1, s.size() - 2);
    }
    return s;
}

// Parse "#RRGGBB" / "#RRGGBBAA" → ABGR8 packed (0 si invalido). El layout
// coincide con `pack_rgba8` del renderer (R en LSB, A en MSB).
uint32_t parse_color_hex(const std::string& v) {
    std::string s = trim(v);
    s = strip_quotes(s);
    if (s.empty() || s[0] != '#') return 0;
    s.erase(s.begin());
    if (s.size() != 6 && s.size() != 8) return 0;
    uint32_t value = 0;
    for (char c : s) {
        uint32_t d = 0;
        if (c >= '0' && c <= '9') d = c - '0';
        else if (c >= 'a' && c <= 'f') d = c - 'a' + 10;
        else if (c >= 'A' && c <= 'F') d = c - 'A' + 10;
        else return 0;
        value = (value << 4) | d;
    }
    uint8_t r, g, b, a;
    if (s.size() == 6) {
        r = (uint8_t)((value >> 16) & 0xFF);
        g = (uint8_t)((value >>  8) & 0xFF);
        b = (uint8_t)( value        & 0xFF);
        a = 0xFF;
    } else {
        r = (uint8_t)((value >> 24) & 0xFF);
        g = (uint8_t)((value >> 16) & 0xFF);
        b = (uint8_t)((value >>  8) & 0xFF);
        a = (uint8_t)( value        & 0xFF);
    }
    return  (uint32_t)r
          | ((uint32_t)g <<  8)
          | ((uint32_t)b << 16)
          | ((uint32_t)a << 24);
}

uint8_t parse_shape(const std::string& v) {
    std::string s = trim(v);
    s = strip_quotes(s);
    std::transform(s.begin(), s.end(), s.begin(),
                   [](unsigned char c) { return std::tolower(c); });
    if (s == "circle")         return SHAPE_CIRCLE;
    if (s == "square")         return SHAPE_SQUARE;
    if (s == "diamond")        return SHAPE_DIAMOND;
    if (s == "hex" || s == "hexagon") return SHAPE_HEX;
    if (s == "triangle")       return SHAPE_TRIANGLE;
    if (s == "rounded_square" || s == "rounded-square" || s == "rounded")
        return SHAPE_ROUNDED_SQUARE;
    return SHAPE_USE_TYPE;
}

uint8_t parse_style(const std::string& v) {
    std::string s = trim(v);
    s = strip_quotes(s);
    std::transform(s.begin(), s.end(), s.begin(),
                   [](unsigned char c) { return std::tolower(c); });
    if (s == "solid")  return EDGE_SOLID;
    if (s == "dashed") return EDGE_DASHED;
    if (s == "dotted") return EDGE_DOTTED;
    return EDGE_USE_TYPE;
}

// ----------------------------------------------------------------------------
// Inline-map parser: { key: value, key: [a, b, c], key: value }
// ----------------------------------------------------------------------------

// Split a string by ',' respecting [...] brackets. Devuelve los segmentos
// trimeados.
std::vector<std::string> split_top_level_commas(const std::string& s) {
    std::vector<std::string> out;
    int  depth = 0;
    std::string cur;
    for (char c : s) {
        if (c == '[' || c == '{') ++depth;
        else if (c == ']' || c == '}') --depth;
        if (c == ',' && depth == 0) {
            out.push_back(trim(cur));
            cur.clear();
        } else {
            cur.push_back(c);
        }
    }
    std::string t = trim(cur);
    if (!t.empty()) out.push_back(t);
    return out;
}

// Parse "[a, b, c]" → {"a","b","c"}. Soporta strings sin/con comillas.
std::vector<std::string> parse_inline_array(const std::string& s) {
    std::vector<std::string> out;
    std::string body = trim(s);
    if (body.size() < 2 || body.front() != '[' || body.back() != ']') return out;
    body = body.substr(1, body.size() - 2);
    for (auto& tok : split_top_level_commas(body)) {
        std::string v = strip_quotes(trim(tok));
        if (!v.empty()) out.push_back(v);
    }
    return out;
}

// Parse "{ name: x, type: y, required: true, values: [a,b,c] }" → FieldSpec.
// Devuelve true si parseo algo (al menos un name no vacio).
bool parse_field_inline_map(const std::string& raw, FieldSpec* out) {
    if (!out) return false;
    std::string body = trim(raw);
    if (body.size() < 2 || body.front() != '{' || body.back() != '}') return false;
    body = body.substr(1, body.size() - 2);

    *out = FieldSpec{};
    for (auto& pair : split_top_level_commas(body)) {
        auto colon = pair.find(':');
        if (colon == std::string::npos) continue;
        std::string k = trim(pair.substr(0, colon));
        std::string v = trim(pair.substr(colon + 1));
        if (k == "name") {
            out->name = strip_quotes(v);
        } else if (k == "type") {
            out->kind = field_kind_from_name(strip_quotes(v).c_str());
        } else if (k == "required") {
            std::string lv = strip_quotes(v);
            std::transform(lv.begin(), lv.end(), lv.begin(),
                            [](unsigned char c) { return std::tolower(c); });
            out->required = (lv == "true" || lv == "1" || lv == "yes");
        } else if (k == "values") {
            out->enum_values = parse_inline_array(v);
        }
    }
    return !out->name.empty();
}

// Cuenta el numero de espacios al principio de una linea. Tabs cuentan como
// 4 espacios para que el archivo siga siendo legible si alguien usa tabs.
int leading_indent(const std::string& line) {
    int n = 0;
    for (char c : line) {
        if (c == ' ') ++n;
        else if (c == '\t') n += 4;
        else break;
    }
    return n;
}

bool starts_with(const std::string& s, const char* p) {
    size_t plen = std::strlen(p);
    return s.size() >= plen && std::memcmp(s.data(), p, plen) == 0;
}

} // namespace

const char* field_kind_name(FieldKind k) {
    switch (k) {
        case FK_STRING: return "string";
        case FK_INT:    return "int";
        case FK_FLOAT:  return "float";
        case FK_BOOL:   return "bool";
        case FK_DATE:   return "date";
        case FK_URL:    return "url";
        case FK_ENUM:   return "enum";
    }
    return "string";
}

FieldKind field_kind_from_name(const char* s) {
    if (!s || !*s) return FK_STRING;
    std::string v = s;
    std::transform(v.begin(), v.end(), v.begin(),
                   [](unsigned char c) { return std::tolower(c); });
    if (v == "string" || v == "str" || v == "text") return FK_STRING;
    if (v == "int"    || v == "integer")            return FK_INT;
    if (v == "float"  || v == "double")             return FK_FLOAT;
    if (v == "bool"   || v == "boolean")            return FK_BOOL;
    if (v == "date")                                 return FK_DATE;
    if (v == "url"    || v == "uri")                return FK_URL;
    if (v == "enum")                                 return FK_ENUM;
    std::fprintf(stderr,
        "[types.yaml] tipo de campo desconocido '%s' → tratado como string\n", s);
    return FK_STRING;
}

bool types_load_yaml(const char* path, ParsedTypes* out, std::string* error_msg) {
    if (!out) return false;
    std::ifstream f(path);
    if (!f.good()) {
        if (error_msg) *error_msg = std::string("cannot open ") + path;
        return false;
    }

    enum Section { SEC_NONE, SEC_ENTITIES, SEC_RELATIONS };
    Section section = SEC_NONE;

    EntitySpec   cur_entity;
    RelationSpec cur_rel;
    bool         have_item          = false;
    int          item_indent        = -1;   // indent de "- " que abre el item
    bool         in_fields          = false; // dentro de `fields:` del entity actual

    auto flush = [&]() {
        if (!have_item) return;
        if (section == SEC_ENTITIES && !cur_entity.name.empty()) {
            out->entities.push_back(cur_entity);
        } else if (section == SEC_RELATIONS && !cur_rel.name.empty()) {
            out->relations.push_back(cur_rel);
        }
        cur_entity = EntitySpec{};
        cur_rel    = RelationSpec{};
        have_item  = false;
        in_fields  = false;
        item_indent = -1;
    };

    // Aplica un sub-key "key: value" al item actual. Si es `fields` con
    // valor vacio, activa modo fields.
    auto apply_subkey = [&](const std::string& k, const std::string& v) {
        if (k == "name") {
            if (section == SEC_ENTITIES) cur_entity.name = strip_quotes(v);
            else                          cur_rel.name    = strip_quotes(v);
        } else if (k == "color") {
            uint32_t c = parse_color_hex(v);
            if (section == SEC_ENTITIES) cur_entity.color = c;
            else                          cur_rel.color    = c;
        } else if (k == "shape" && section == SEC_ENTITIES) {
            cur_entity.shape = parse_shape(v);
        } else if (k == "icon" && section == SEC_ENTITIES) {
            std::string nm = strip_quotes(v);
            cur_entity.icon_name = nm;
            cur_entity.icon_cp   = tabler_codepoint_by_name(nm.c_str());
        } else if (k == "principal_field" && section == SEC_ENTITIES) {
            cur_entity.principal_field = strip_quotes(v);
        } else if (k == "fields" && section == SEC_ENTITIES) {
            // Sub-seccion: si el valor es vacio entramos en modo fields y
            // las siguientes lineas "- { ... }" se parsean como FieldSpec.
            // Si trae lista inline ("[...]") la parseamos aqui (raro pero
            // soportado).
            std::string vv = trim(v);
            if (vv.empty()) {
                in_fields = true;
            } else if (!vv.empty() && vv.front() == '[') {
                // No soportado en v1: lista inline de inline-maps en una
                // misma linea. Se ignora con warning.
                std::fprintf(stderr,
                    "[types.yaml] fields: en linea con [...] no soportado;"
                    " usar lista multilinea con `- { ... }`\n");
            }
        } else if (k == "style" && section == SEC_RELATIONS) {
            cur_rel.style = parse_style(v);
        }
    };

    std::string line;
    while (std::getline(f, line)) {
        // Strip comments
        auto hash = line.find('#');
        if (hash != std::string::npos) {
            // Solo si no esta dentro de comillas — en este YAML reducido
            // el unico uso comun de `#` dentro de strings es para colores
            // tipo "#5B8DEF". Asi que solo consideramos `#` como comentario
            // si la primera comilla a su izquierda no esta cerrada.
            bool in_quote = false;
            char quote = 0;
            for (size_t i = 0; i < hash; ++i) {
                char c = line[i];
                if (!in_quote && (c == '"' || c == '\'')) {
                    in_quote = true; quote = c;
                } else if (in_quote && c == quote) {
                    in_quote = false;
                }
            }
            if (!in_quote) line.erase(hash);
        }

        std::string trimmed = trim(line);
        if (trimmed.empty()) continue;

        int indent = leading_indent(line);

        // Top-level section header
        if (indent == 0 && trimmed.back() == ':') {
            flush();
            std::string head = trimmed.substr(0, trimmed.size() - 1);
            if (head == "entities")       section = SEC_ENTITIES;
            else if (head == "relations") section = SEC_RELATIONS;
            else                          section = SEC_NONE;
            continue;
        }

        if (section == SEC_NONE) continue;

        // Item nuevo a nivel del seccion (indent <= item_indent o sin item):
        // disambigua "- " entre apertura de item y entrada de fields.
        bool is_dash = starts_with(trimmed, "- ");
        bool is_field_line = in_fields && is_dash && indent > item_indent;
        bool is_new_item   = is_dash && !is_field_line;

        if (is_field_line) {
            // Parse "- { ... }" como FieldSpec
            std::string body = trim(trimmed.substr(2));
            FieldSpec fs;
            if (parse_field_inline_map(body, &fs)) {
                cur_entity.fields.push_back(std::move(fs));
            } else {
                std::fprintf(stderr,
                    "[types.yaml] field invalido (esperado inline map "
                    "{ name: ..., type: ... }): %s\n", body.c_str());
            }
            continue;
        }

        if (is_new_item) {
            flush();
            have_item   = true;
            item_indent = indent;
            in_fields   = false;
            std::string body = trim(trimmed.substr(2));
            auto colon = body.find(':');
            if (colon != std::string::npos) {
                std::string k = trim(body.substr(0, colon));
                std::string v = trim(body.substr(colon + 1));
                apply_subkey(k, v);
            }
            continue;
        }

        // Sub-key del item actual: "key: value"
        if (have_item) {
            auto colon = trimmed.find(':');
            if (colon == std::string::npos) continue;
            std::string k = trim(trimmed.substr(0, colon));
            std::string v = trim(trimmed.substr(colon + 1));
            // Sub-key del entity con indent menor o igual al item: significa
            // que salimos de in_fields (el indent de fields_items es mayor).
            if (in_fields && indent <= item_indent + 2) {
                in_fields = false;
            }
            apply_subkey(k, v);
        }
    }
    flush();
    return true;
}

// ----------------------------------------------------------------------------
// Writer: ParsedTypes -> types.yaml
// ----------------------------------------------------------------------------

namespace {

// ABGR -> "#RRGGBB" (o "#RRGGBBAA" si alpha != 0xFF). Devuelve "" si color es 0.
std::string color_to_hex(uint32_t c) {
    if (c == 0) return "";
    uint8_t r = (uint8_t)( c        & 0xFF);
    uint8_t g = (uint8_t)((c >>  8) & 0xFF);
    uint8_t b = (uint8_t)((c >> 16) & 0xFF);
    uint8_t a = (uint8_t)((c >> 24) & 0xFF);
    char buf[16];
    if (a == 0xFF) {
        std::snprintf(buf, sizeof(buf), "#%02X%02X%02X", r, g, b);
    } else {
        std::snprintf(buf, sizeof(buf), "#%02X%02X%02X%02X", r, g, b, a);
    }
    return buf;
}

const char* style_name(uint8_t s) {
    switch (s) {
        case EDGE_SOLID:  return "solid";
        case EDGE_DASHED: return "dashed";
        case EDGE_DOTTED: return "dotted";
        default:          return "";
    }
}

const char* shape_name(uint8_t s) {
    switch (s) {
        case SHAPE_CIRCLE:         return "circle";
        case SHAPE_SQUARE:         return "square";
        case SHAPE_DIAMOND:        return "diamond";
        case SHAPE_HEX:            return "hex";
        case SHAPE_TRIANGLE:       return "triangle";
        case SHAPE_ROUNDED_SQUARE: return "rounded_square";
        default:                   return "";
    }
}

// Serializa un FieldSpec como inline-map: "{ name: X, type: Y, required: true, values: [a,b,c] }".
std::string serialize_field(const FieldSpec& f) {
    std::string out = "{ name: ";
    out += f.name;
    out += ", type: ";
    out += field_kind_name(f.kind);
    if (f.required) out += ", required: true";
    if (f.kind == FK_ENUM && !f.enum_values.empty()) {
        out += ", values: [";
        for (size_t i = 0; i < f.enum_values.size(); ++i) {
            if (i) out += ", ";
            out += f.enum_values[i];
        }
        out += "]";
    }
    out += " }";
    return out;
}

} // namespace

bool types_save_yaml(const char* path, const ParsedTypes& types,
                     std::string* error_msg) {
    if (!path || !*path) {
        if (error_msg) *error_msg = "path vacio";
        return false;
    }
    std::ofstream f(path);
    if (!f.good()) {
        if (error_msg) *error_msg = std::string("cannot open ") + path;
        return false;
    }

    f << "# types.yaml — autogenerado por graph_explorer Type Editor.\n";
    f << "# Editable a mano. Round-trip con types_load_yaml/types_save_yaml.\n";
    f << "# Todos los nodos son circulo, salvo \"Table\" que es cuadrado.\n";
    f << "# Tipos de campo: string | int | float | bool | date | url | enum.\n";
    f << "\n";

    f << "entities:\n";
    for (const auto& e : types.entities) {
        f << "  - name: " << e.name << "\n";
        std::string col = color_to_hex(e.color);
        if (!col.empty()) f << "    color: \"" << col << "\"\n";
        const char* sh = shape_name(e.shape);
        if (sh && *sh) f << "    shape: " << sh << "\n";
        if (!e.icon_name.empty()) f << "    icon: " << e.icon_name << "\n";
        if (!e.principal_field.empty())
            f << "    principal_field: " << e.principal_field << "\n";
        if (!e.fields.empty()) {
            f << "    fields:\n";
            for (const auto& fs : e.fields) {
                f << "      - " << serialize_field(fs) << "\n";
            }
        }
        f << "\n";
    }

    f << "relations:\n";
    for (const auto& r : types.relations) {
        f << "  - name: " << r.name << "\n";
        std::string col = color_to_hex(r.color);
        if (!col.empty()) f << "    color: \"" << col << "\"\n";
        const char* st = style_name(r.style);
        if (st && *st) f << "    style: " << st << "\n";
        f << "\n";
    }
    return true;
}

// ----------------------------------------------------------------------------
// Mapeo nombre Tabler -> codepoint
// ----------------------------------------------------------------------------
// Tabla minima cubriendo los iconos del ejemplo OSINT y unos pocos comunes.
// Ampliable a demanda — para ampliar masivamente, mejor scriptar la
// generacion desde `tabler_codepoints_full.csv` (ver vendor/tabler-icons/).

uint16_t tabler_codepoint_by_name(const char* name) {
    if (!name || !*name) return 0;
    static const std::unordered_map<std::string, uint16_t> map = {
        // OSINT core
        {"ti-user",          0xEB4D}, // TI_USER
        {"ti-users",         0xEBF2}, // TI_USERS
        {"ti-mail",          0xEAE5}, // TI_MAIL
        {"ti-world",         0xEB54}, // TI_WORLD
        {"ti-phone",         0xEB09}, // TI_PHONE
        {"ti-building",      0xEA4F}, // TI_BUILDING
        {"ti-building-bank", 0xEBE2}, // TI_BUILDING_BANK
        {"ti-id",            0xEAC3}, // TI_ID
        {"ti-file",          0xEAA4}, // TI_FILE
        {"ti-map-pin",       0xEAE8}, // TI_MAP_PIN
        {"ti-link",          0xEADE}, // TI_LINK
        {"ti-network",       0xF09F}, // TI_NETWORK
        {"ti-server",        0xEB1F}, // TI_SERVER
        {"ti-folder",        0xEAAD}, // TI_FOLDER
        {"ti-hash",          0xEABC}, // TI_HASH
        {"ti-circle",        0xEA6B}, // TI_CIRCLE
        {"ti-square",        0xEB2C}, // TI_SQUARE
        {"ti-at",            0xEA2B}, // TI_AT
        {"ti-home",          0xEAC1}, // TI_HOME
        {"ti-database",      0xEA88}, // TI_DATABASE
        {"ti-file-text",     0xEAA2}, // TI_FILE_TEXT
    };
    auto it = map.find(name);
    if (it == map.end()) return 0;
    return it->second;
}

// ----------------------------------------------------------------------------
// Apply yaml -> graph
// ----------------------------------------------------------------------------

std::vector<uint16_t> apply_types_yaml(GraphData& graph, const ParsedTypes& types) {
    // 1) Recolectar codepoints distintos en orden de aparicion (1-based icon_id).
    std::vector<uint16_t> codepoints;
    auto find_or_add = [&](uint16_t cp) -> uint16_t {
        if (cp == 0) return 0;
        for (size_t i = 0; i < codepoints.size(); ++i) {
            if (codepoints[i] == cp) return (uint16_t)(i + 1);
        }
        codepoints.push_back(cp);
        return (uint16_t)codepoints.size();
    };

    // 2) Lookup por nombre. Buscamos tambien con normalizacion ligera
    // (lowercase trim) para que "person" matche "Person".
    auto eq_ci = [](const char* a, const std::string& b) {
        if (!a) return false;
        size_t la = std::strlen(a);
        if (la != b.size()) return false;
        for (size_t i = 0; i < la; ++i) {
            if (std::tolower((unsigned char)a[i]) !=
                std::tolower((unsigned char)b[i])) return false;
        }
        return true;
    };

    for (int i = 0; i < graph.type_count; ++i) {
        EntityType& et = graph.types[i];
        for (const auto& spec : types.entities) {
            if (!eq_ci(et.name, spec.name)) continue;
            if (spec.color != 0)            et.color = spec.color;
            if (spec.shape != SHAPE_USE_TYPE) et.shape = spec.shape;
            uint16_t icon_id = find_or_add(spec.icon_cp);
            if (icon_id != 0)               et.icon_id = icon_id;
            break;
        }
    }

    // Regla de forma: todo nodo es circulo EXCEPTO los tipos contenedores
    // "Table" (issue 0010) y "Group" (issue 0035), que son cuadrados.
    // Sobreescribe lo que diga el yaml: se aplica en cada reload, por lo
    // que ediciones futuras desde el Type Editor no rompen la convencion.
    // Tambien forzamos un tamano notablemente mayor (32 px world) para que
    // la diferencia visual con un nodo normal sea evidente.
    for (int i = 0; i < graph.type_count; ++i) {
        EntityType& et = graph.types[i];
        bool is_table = et.name && (eq_ci(et.name, std::string("Table"))
                                  || eq_ci(et.name, std::string("table")));
        bool is_group = et.name && (eq_ci(et.name, std::string("Group"))
                                  || eq_ci(et.name, std::string("group")));
        et.shape = (is_table || is_group) ? SHAPE_SQUARE : SHAPE_CIRCLE;
        if (is_table || is_group) {
            et.default_size = 8.0f;
        }
    }

    for (int i = 0; i < graph.rel_type_count; ++i) {
        RelationType& rt = graph.rel_types[i];
        for (const auto& spec : types.relations) {
            if (!eq_ci(rt.name, spec.name)) continue;
            if (spec.color != 0)            rt.color = spec.color;
            if (spec.style != EDGE_USE_TYPE) rt.style = spec.style;
            break;
        }
    }

    return codepoints;
}

IconAtlas* build_icon_atlas(const std::vector<uint16_t>& codepoints) {
    if (codepoints.empty()) return nullptr;
    return graph_icons_build(codepoints.data(), (int)codepoints.size(), 32);
}

} // namespace ge
