/* eg_cosine_batch.c — the Factory. Implements the stable public interface
 * declared in eg_cosine_batch.h by selecting ONE concrete
 * EgCosineBatchStrategy (eg_cosine_batch_strategy.h) and dispatching every
 * call to it. This is the ONLY file that branches on EG_HAVE_STRATEGY_*
 * (build-time: which strategy .c/.m files were actually compiled in for
 * this platform) — call sites never see those macros.
 *
 * Selection is lazy (first call) and cached — mirrors the lazy-init caching
 * every individual strategy already does internally, so there is no added
 * per-call cost after the first.
 *
 * Selection mechanism (env var + build-time + runtime capability probe, all
 * three, exactly as directed):
 *   - BUILD-TIME decides which strategies exist to choose from at all: a
 *     Darwin build compiles+links the ggml strategy and the hand-rolled
 *     Metal strategy (EG_HAVE_STRATEGY_GGML / EG_HAVE_STRATEGY_METAL_HAND
 *     both defined); a non-Darwin build compiles neither, matching PR #114's
 *     original Linux behavior exactly (CPU-fallback only, no Objective-C
 *     compiler or Metal frameworks required).
 *   - RUNTIME CAPABILITY PROBE: each candidate strategy's own available()
 *     does the real, cheap-after-first-call check (device present, backend
 *     plugin loaded, pipeline compiled) — never assumed from build-time
 *     alone. A build that HAS the ggml strategy compiled in but is running
 *     on hardware/software where it can't actually initialize (backend
 *     plugin missing, no GPU) correctly falls through to the next candidate.
 *   - ENV VAR gives explicit, debuggable override for either axis:
 *       EL_COSINE_BATCH_STRATEGY = "ggml" | "metal" | "cpu" | unset/"auto"
 *         forces a specific strategy (falling back to cpu if the forced one
 *         isn't actually available), or leaves the default auto-preference
 *         order in place.
 *       EL_METAL_COSINE = 0/n/N/f/F   (back-compat with PR #114's vindex_bench
 *         gate) disables ALL GPU-backed strategies outright, same as before.
 *
 * DEFAULT preference order when nothing is forced: ggml, then hand-rolled
 * Metal, then CPU fallback — first candidate whose available() reports true
 * wins. This is what makes "stop hand-rolling GPU kernels, use ggml" real
 * rather than nominal: ggml is what actually runs by default on this
 * machine today (see the PR body for the measured numbers backing that).
 */
#include "eg_cosine_batch.h"
#include "eg_cosine_batch_strategy.h"

#include <stdlib.h>
#include <string.h>

static bool g_selected = false;
static const EgCosineBatchStrategy* g_active = NULL;

static bool eg_env_truthy_off(const char* v) {
    return v && (v[0]=='0' || v[0]=='n' || v[0]=='N' || v[0]=='f' || v[0]=='F');
}

static const EgCosineBatchStrategy* eg_select_strategy(void) {
    if (g_selected) return g_active;
    g_selected = true;

    const EgCosineBatchStrategy* cpu = eg_cosine_batch_strategy_cpu();
    const char* force = getenv("EL_COSINE_BATCH_STRATEGY");
    const char* legacy_off = getenv("EL_METAL_COSINE");

    if (eg_env_truthy_off(legacy_off)) { g_active = cpu; return g_active; }

    if (force && strcmp(force, "cpu") == 0) { g_active = cpu; return g_active; }

    if (force && strcmp(force, "ggml") == 0) {
#ifdef EG_HAVE_STRATEGY_GGML
        const EgCosineBatchStrategy* s = eg_cosine_batch_strategy_ggml();
        if (s->available()) { g_active = s; return g_active; }
#endif
        g_active = cpu; return g_active;
    }

    if (force && strcmp(force, "metal") == 0) {
#ifdef EG_HAVE_STRATEGY_METAL_HAND
        const EgCosineBatchStrategy* s = eg_cosine_batch_strategy_metal_hand();
        if (s->available()) { g_active = s; return g_active; }
#endif
        g_active = cpu; return g_active;
    }

    /* auto (unset, or any other value): ggml -> metal-hand -> cpu, first
     * available wins. */
#ifdef EG_HAVE_STRATEGY_GGML
    {
        const EgCosineBatchStrategy* s = eg_cosine_batch_strategy_ggml();
        if (s->available()) { g_active = s; return g_active; }
    }
#endif
#ifdef EG_HAVE_STRATEGY_METAL_HAND
    {
        const EgCosineBatchStrategy* s = eg_cosine_batch_strategy_metal_hand();
        if (s->available()) { g_active = s; return g_active; }
    }
#endif
    g_active = cpu;
    return g_active;
}

bool eg_cosine_batch_available(void) {
    return eg_select_strategy()->available();
}

const char* eg_cosine_batch_strategy_name(void) {
    return eg_select_strategy()->name;
}

bool eg_cosine_batch(const float* query, int32_t qdim,
                      const float* const* node_ptrs,
                      const int32_t* node_dims,
                      int32_t n,
                      double* out_scores) {
    return eg_select_strategy()->batch(query, qdim, node_ptrs, node_dims, n, out_scores);
}

bool eg_cosine_batch_multi(const float* queries, int32_t qdim, int32_t nq,
                            const float* const* node_ptrs,
                            const int32_t* node_dims,
                            int32_t n,
                            double* out_scores) {
    return eg_select_strategy()->batch_multi(queries, qdim, nq, node_ptrs, node_dims, n, out_scores);
}
