/* eg_cosine_batch.h — stable Adapter interface over batch-cosine-similarity
 * BACKEND STRATEGIES. This header is the ONE thing call sites (el_runtime.c,
 * vindex_bench.c, ...) talk to. Plain C11, safe to #include on every
 * platform — the symbols declared here always exist and always link,
 * regardless of what backend actually runs underneath. Zero #ifdef at call
 * sites: which concrete strategy executes (ggml/Metal, hand-rolled Metal, or
 * the always-false CPU fallback) is resolved once, lazily, inside
 * eg_cosine_batch.c's factory — see eg_cosine_batch_strategy.h for that.
 *
 * This supersedes eg_metal_cosine.h (PR #114's single hand-rolled-Metal-only
 * bridge). The contract is UNCHANGED — same shapes, same sentinel, same
 * never-partial guarantee, same "caller must always be prepared to fall back
 * to its own scalar per-node loop" rule — only the name changed, because the
 * thing behind it is no longer "the Metal bridge," it is "whichever batch-
 * cosine strategy the factory picked." eg_metal_cosine.h's original doc
 * comments (byte-for-byte, this file is the direct descendant) are preserved
 * below since they remain the precise spec any strategy must honor.
 *
 * On ANY failure at ANY step — no compute device, compile/init error, alloc
 * failure, bad args — every function here returns false and writes nothing.
 * Out-params are either fully populated or left completely untouched, never
 * partial. Callers MUST always be prepared to fall back to their own scalar
 * per-node CPU loop unconditionally. These functions must never crash, throw,
 * or hang the calling process — several call sites run inside a long-lived
 * daemon's request-handling hot path.
 */
#ifndef EG_COSINE_BATCH_H
#define EG_COSINE_BATCH_H

#include <stdint.h>
#include <stdbool.h>

#ifdef __cplusplus
extern "C" {
#endif

/* Batched cosine similarity: one query vector against `n` node vectors.
 *
 *   query      — qdim floats, the query embedding. Raw/unnormalized.
 *   qdim       — query dimensionality (e.g. 768 for nomic-embed-text).
 *   node_ptrs  — array of n pointers, node_ptrs[i] pointing at a (possibly
 *                differently-owned, possibly NULL) float vector for node i.
 *                NOT required to be contiguous — every strategy performs the
 *                gather into a packed row-major matrix internally, exactly
 *                mirroring how EngramNode.emb is one malloc per node.
 *   node_dims  — array of n ints, node_dims[i] = that node's real emb_dim
 *                (0 or mismatched vs qdim => that node scores -2.0, matching
 *                eg_cosine's null/dim-mismatch/zero-norm sentinel exactly).
 *   n          — number of nodes.
 *   out_scores — caller-owned array of n doubles; out_scores[i] is filled
 *                with the cosine similarity of node i against query, or
 *                -2.0 for a null/dim-mismatched/zero-norm node — bit-for-bit
 *                the same contract as eg_cosine(node_ptrs[i], query, qdim).
 *
 * Returns true iff a real backend strategy ran and out_scores was fully
 * populated. Returns false (out_scores left untouched) on ANY failure or
 * unavailability — no compute device, compile/init failure, allocation
 * failure, n<=0, qdim<=0, null query/node_ptrs/node_dims/out_scores.
 */
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);

/* True iff a real (non-CPU-fallback) strategy is available right now (cheap
 * after the first call — cached). Purely informational (e.g. a startup log
 * line or /api/stats field); callers should still treat a false return from
 * eg_cosine_batch()/eg_cosine_batch_multi() itself as the authoritative
 * fallback signal, not this function. */
bool eg_cosine_batch_available(void);

/* Which concrete strategy is currently selected — "ggml", "metal-hand",
 * or "cpu-fallback". Purely informational/diagnostic, same spirit as
 * eg_cosine_batch_available(). Never NULL. */
const char* eg_cosine_batch_strategy_name(void);

/* Multi-query batched cosine: nq query vectors against the SAME n node
 * vectors, in one call. A real strategy uploads/prepares the node population
 * ONCE and reuses it for every query, instead of nq separate
 * eg_cosine_batch() calls each paying the full gather+upload cost — PR #114
 * measured this necessary: at N~=13.7k/dim=768, repeating the single-query
 * call per query was slower than the CPU baseline; batching queries together
 * is what makes a GPU-backed path a real win at this shape. Use this
 * whenever multiple queries will run against an unchanged (or
 * rarely-changing) node population; use eg_cosine_batch() for a genuinely
 * one-off comparison.
 *
 *   queries    — nq*qdim floats, row-major (query i at queries+i*qdim).
 *   out_scores — caller-owned nq*n doubles, row-major
 *                (out_scores[i*n+j] = cosine(queries[i], node j)), same
 *                -2.0 sentinel semantics as eg_cosine_batch().
 *
 * Returns true iff a real strategy ran and out_scores was fully populated
 * (all nq*n entries); false (untouched) on any failure/unavailability. */
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);

#ifdef __cplusplus
}
#endif

#endif /* EG_COSINE_BATCH_H */
