#include "core/compute_ring_layout.h"

#include <algorithm>
#include <cmath>
#include <functional>  // std::hash
#include <unordered_map>

namespace fn_ring {

// ---------------------------------------------------------------------------
// Constantes internas
// ---------------------------------------------------------------------------

static constexpr float kPi      = 3.14159265358979323846f;
static constexpr float kTwoPi   = 2.0f * kPi;

// Radio minimo para ring 0 cuando ring_radii[0]==0 (evita colocar nodos en el
// origen exacto, reservado para HUD).
static constexpr float kRing0InnerMin = 30.0f;

// Umbral: si el bin tiene mas nodos de los que caben radialmente con separacion
// minima MIN_RADIAL_SPACING, activamos jitter angular.
static constexpr float kMinRadialSpacing = 18.0f;

// Amplitud maxima del jitter angular: ±0.4 del half-sector (se mantiene dentro
// del sector para no solapar el vecino).
static constexpr float kJitterMaxFraction = 0.4f;

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/// Hash deterministico FNV-1a 32 bits (estable cross-platform, sin depender de
/// std::hash<string> que puede variar entre ABI/compiladores).
static uint32_t fnv1a_32(const std::string& s) {
    uint32_t h = 2166136261u;
    for (unsigned char c : s) {
        h ^= c;
        h *= 16777619u;
    }
    return h;
}

/// Devuelve valor deterministico en [-1.0, +1.0] usando el hash del id.
static float id_jitter(const std::string& id) {
    uint32_t h = fnv1a_32(id);
    // Normalizar a [-1, +1]
    float v = static_cast<float>(h) / static_cast<float>(0xFFFFFFFFu); // [0,1]
    return v * 2.0f - 1.0f; // [-1,+1]
}

/// Devuelve el mapa canonico de status → ring.
static StatusRingMap default_status_map() {
    return {
        {"completado",           0},
        {"completed",            0},
        {"in-progress",          1},
        {"pendiente_unlocked",   2},
        {"unlocked",             2},
        {"pending",              2},
        {"pendiente",            3},
        {"locked",               3},
        {"deferred",             4},
        {"bloqueado",            4},
    };
}

/// Busca status en el mapa. Retorna -1 si no encontrado.
static int lookup_ring(const std::string& status, const StatusRingMap& smap) {
    for (auto& [k, v] : smap) {
        if (k == status) return v;
    }
    return -1;
}

/// Busca domain en el orden. Retorna n_sectors-1 si no encontrado.
static int lookup_sector(const std::string& domain,
                         const DomainOrder& order,
                         int n_sectors) {
    for (int i = 0; i < static_cast<int>(order.size()); ++i) {
        if (order[i] == domain) return i;
    }
    return n_sectors - 1;
}

// ---------------------------------------------------------------------------
// Implementacion principal
// ---------------------------------------------------------------------------

std::vector<LayoutOutput>
compute_ring_layout(const std::vector<LayoutInput>& nodes,
                    const LayoutConfig& cfg,
                    const StatusRingMap& status_map_in,
                    const DomainOrder& domain_order) {
    if (nodes.empty()) return {};

    const StatusRingMap& smap = status_map_in.empty()
                                 ? default_status_map()
                                 : status_map_in;

    const int n_rings    = static_cast<int>(cfg.ring_radii.size()) - 1;
    const int n_sectors  = cfg.n_sectors > 0 ? cfg.n_sectors : 18;
    const float sector_angle = kTwoPi / static_cast<float>(n_sectors);
    const float half_sector  = sector_angle * 0.5f;

    // Preasignar outputs con ring=-1 (descartados por defecto)
    std::vector<LayoutOutput> out(nodes.size());
    for (size_t i = 0; i < nodes.size(); ++i) {
        out[i].id = nodes[i].id;
    }

    // --- Paso 1: mapear cada nodo a (ring, sector) ---
    // indices_in_bin[ring][sector] = lista de indices en nodes[]
    using BinKey = std::pair<int,int>;
    struct BinKeyHash {
        size_t operator()(const BinKey& k) const noexcept {
            return std::hash<int>()(k.first) ^ (std::hash<int>()(k.second) << 16);
        }
    };
    std::unordered_map<BinKey, std::vector<size_t>, BinKeyHash> bins;

    for (size_t i = 0; i < nodes.size(); ++i) {
        int ring = lookup_ring(nodes[i].status, smap);
        if (ring < 0 || ring >= n_rings) {
            // Nodo descartado: ring=-1, x/y=0 (ya inicializado)
            continue;
        }
        int sector = lookup_sector(nodes[i].domain, domain_order, n_sectors);
        out[i].ring   = ring;
        out[i].sector = sector;
        bins[{ring, sector}].push_back(i);
    }

    // --- Paso 2: posicionar nodos dentro de cada bin ---
    for (auto& [key, indices] : bins) {
        const int ring   = key.first;
        const int sector = key.second;

        // Ordenar bin: recency desc, id asc (deterministico)
        std::sort(indices.begin(), indices.end(),
            [&](size_t a, size_t b) {
                float ra = nodes[a].recency;
                float rb = nodes[b].recency;
                if (ra != rb) return ra > rb;
                return nodes[a].id < nodes[b].id;
            });

        // Asignar rank_in_bin
        for (int rank = 0; rank < static_cast<int>(indices.size()); ++rank) {
            out[indices[rank]].rank_in_bin = rank;
        }

        // Radio interior y exterior del ring
        float r_inner = cfg.ring_radii[ring];
        float r_outer = cfg.ring_radii[ring + 1];

        // Caso especial: ring 0 con radio interno == 0
        if (r_inner == 0.0f) r_inner = kRing0InnerMin;

        // Aplicar padding
        float r_lo = r_inner + cfg.bin_padding;
        float r_hi = r_outer - cfg.bin_padding;
        if (r_lo > r_hi) {
            // Bin demasiado estrecho: usar el punto medio
            r_lo = r_hi = (r_inner + r_outer) * 0.5f;
        }

        const int N = static_cast<int>(indices.size());

        // Angulo central del sector (sin jitter)
        float theta_center = cfg.start_angle
                           + (static_cast<float>(sector) + 0.5f) * sector_angle;

        // Determinar si activamos jitter angular (bin sobrecargado radialmente)
        float band_height = r_hi - r_lo;
        int radial_capacity = (band_height < 1.0f)
                              ? 1
                              : std::max(1, static_cast<int>(band_height / kMinRadialSpacing));
        bool use_jitter = (N > radial_capacity);

        // Posicionar cada nodo
        for (int rank = 0; rank < N; ++rank) {
            size_t idx = indices[rank];

            // Radio: distribucion uniforme en la banda
            float r;
            if (N == 1) {
                r = (r_lo + r_hi) * 0.5f;
            } else {
                r = r_lo + (static_cast<float>(rank) + 0.5f) * (r_hi - r_lo)
                           / static_cast<float>(N);
            }

            // Sub-jitter angular deterministico si bin sobrecargado
            float jitter = 0.0f;
            if (use_jitter) {
                float raw = id_jitter(nodes[idx].id); // en [-1,+1]
                jitter = raw * half_sector * kJitterMaxFraction;
            }

            float theta = theta_center + jitter;
            out[idx].x = cfg.center_x + r * std::cos(theta);
            out[idx].y = cfg.center_y + r * std::sin(theta);
        }
    }

    return out;
}

} // namespace fn_ring
