/* src/simd/mds_simd.h — runtime-dispatched SIMD API.
 *
 * Function pointers resolve to either the scalar reference implementation
 * or a native AVX2/SSE2/NEON backend selected at runtime.
 *
 * Contract: every backend MUST produce bit-identical results to the
 * scalar reference (see src/simd/mds_simd_scalar.c).
 */
#ifndef MDS_SIMD_H
#define MDS_SIMD_H

#include <stddef.h>
#include <stdint.h>

/* Pulls in the inline-keyword shim and likely/unlikely macros so SIMD
 * backends can use `static inline` under -std=c89. */
#include "../mds_compiler.h"

/* Generated by Makefile.PL: defines MDS_HAVE_{SSE2,AVX2,NEON} on hosts
 * where the compiler can build those backends. Always present. */
#include "mds_simd_config.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef struct mds_simd_ops {
    /* Structural classifier.
     * Sets a bit in out_bitmap[i/64] for every byte at offset i in
     * [in, in+len) that is one of: \n \r \t \\ ` * _ [ ] ! < > # > ~ |
     * out_bitmap must hold ceil(len/64) uint64_t words, zeroed by caller. */
    void (*classify_structural)(const char* in, size_t len, uint64_t* out_bitmap);

    /* UTF-8 validation. Returns 1 if valid, 0 otherwise. */
    int (*validate_utf8)(const char* in, size_t len);

    /* Line scanner. Fills out_offsets with byte offsets of '\n'
     * occurrences; returns the number written, or SIZE_MAX if the input
     * contains more newlines than `cap` (caller should fall back to a
     * scalar line walk in that case). On overflow, the contents of
     * out_offsets are unspecified. Input length is capped at 4 GiB
     * because offsets are uint32_t. */
    size_t (*find_newlines)(const char* in, size_t len,
                            uint32_t* out_offsets, size_t cap);

    /* Bitmap-aware structural jump. Given a precomputed
     * structural bitmap `bm` covering bytes [base .. base+bm_len), find
     * the first position at or after byte offset `p_off` (relative to
     * `base`) whose bit is set, and return the corresponding pointer.
     * If none, returns `base + bm_len`. Inputs:
     *   base     — pointer to the start of the buffer the bitmap covers
     *   bm_len   — number of bytes covered by the bitmap
     *   bm       — pointer to ceil(bm_len/64) uint64_t words
     *   p_off    — byte offset to start scanning from (0 <= p_off <= bm_len)
     * Implementations walk the bitmap with ctzll for O(set-bits) cost. */
    const char* (*next_structural_bm)(const char* base, size_t bm_len,
                                      const uint64_t* bm, size_t p_off);

    /* Legacy byte-by-byte fallback, kept for callers that
     * don't have a bitmap. Same semantics as next_structural_bm but
     * without the preprocessed bitmap. */
    const char* (*next_structural)(const char* p, const char* end);
} mds_simd_ops;

/* Name of the currently selected backend: "avx2", "sse2", "neon", or
 * "scalar". Stable string, do not free. */
const char* mds_simd_backend(void);

/* Pointer to the active ops table. Hot callers should read this once
 * and cache; for now everything goes through `mds_simd_get()`. */
const mds_simd_ops* mds_simd_get(void);

/* Force scalar backend regardless of CPU. Used by the `no_simd => 1`
 * Perl option and the MARKDOWN_SIMPLE_NO_SIMD=1 env var. Idempotent;
 * safe to call before or after mds_simd_get(). */
void mds_simd_force_scalar(int on);

/* Internal: get each concrete ops table. Returned pointer is static. */
const mds_simd_ops* mds_simd_ops_scalar(void);
#ifdef MDS_HAVE_SSE2
const mds_simd_ops* mds_simd_ops_sse2(void);
#endif
#ifdef MDS_HAVE_AVX2
const mds_simd_ops* mds_simd_ops_avx2(void);
#endif
#ifdef MDS_HAVE_NEON
const mds_simd_ops* mds_simd_ops_neon(void);
#endif

#ifdef __cplusplus
}
#endif

#endif /* MDS_SIMD_H */
