#pragma once
#ifndef _USE_MATH_DEFINES
#define _USE_MATH_DEFINES
#endif
#include <algorithm>
#include <cmath>
#include <vector>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

// ── Log-factorial table (C++17 inline variable → single instance across TUs) ─
static constexpr int LOG_FACT_MAX = 2'000'000;
inline std::vector<double> g_log_fact;

inline void ensure_log_fact(int needed) {
    int need = std::min(needed + 1, LOG_FACT_MAX + 1);
    if ((int)g_log_fact.size() >= need) return;
    if (g_log_fact.empty()) g_log_fact.push_back(0.0);
    while ((int)g_log_fact.size() < need)
        g_log_fact.push_back(g_log_fact.back() +
                             std::log((double)g_log_fact.size()));
}

inline double lf(double x) {
    int xi = (int)x;
    if (xi >= 0 && xi <= LOG_FACT_MAX) { ensure_log_fact(xi); return g_log_fact[xi]; }
    return x * std::log(x) - x + 0.5 * std::log(2.0 * M_PI * x);
}

inline double logC_fast(double n, double k) {
    if (k <= 0.0 || k >= n) return 0.0;
    return lf(n) - lf(k) - lf(n - k);
}

// Exact Surprise in -log10 scale using precomputed log-factorial table.
inline double surprise_fast(double F, double M, double n, double p) {
    if (p < 0.0) p = 0.0;
    if (M <= 0.0 || p > M || p > n) return 0.0;
    const double log10v = std::log(10.0);
    auto log_hyper = [&](double j) {
        return (logC_fast(M, j) + logC_fast(F - M, n - j) - logC_fast(F, n)) / log10v;
    };
    double j = p, logP = log_hyper(j), minMn = (M < n) ? M : n;
    while (j < minMn) {
        j++;
        double nxt = log_hyper(j);
        if (nxt == 0.0) break;
        double common  = (nxt > logP) ? nxt : logP;
        double diffExp = (nxt > logP) ? (logP - common) : (nxt - common);
        logP = common + std::log(1.0 + std::pow(10.0, diffExp)) / log10v;
        if (nxt - logP > -4.0) break;
    }
    return -logP;
}
