#pragma once
#include "DebugConfiguration.h"
#include <algorithm>
#include <cstdarg>
#include <iterator>
#include <stdint.h>

/// Keep a function out of line even when the compiler would rather inline it. Use on helpers that
/// hold a large object on the stack: inlining several of them into one caller makes that caller's
/// frame reserve every helper's locals at once, which on our 8 KB Arduino loopTask is enough to
/// overflow the stack (see issue #11237).
#if defined(__GNUC__)
#define NOINLINE __attribute__((noinline))
#else
#define NOINLINE
#endif

/// C++ v17+ clamp function, limits a given value to a range defined by lo and hi
template <class T> constexpr const T &clamp(const T &v, const T &lo, const T &hi)
{
    return (v < lo) ? lo : (hi < v) ? hi : v;
}

/// Return the smallest power of 2 >= n (undefined for n > 2^31)
static inline uint32_t nextPowerOf2(uint32_t n)
{
    if (n <= 1)
        return 1;
#if defined(__GNUC__)
    return 1U << (32 - __builtin_clz(n - 1));
#else
    n--;
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    return n + 1;
#endif
}

#if HAS_SCREEN
#define IF_SCREEN(X)                                                                                                             \
    if (screen) {                                                                                                                \
        X;                                                                                                                       \
    }
#else
#define IF_SCREEN(...)
#endif

#if (defined(ARCH_PORTDUINO) && !defined(STRNSTR))
#define STRNSTR
#include <string.h>
char *strnstr(const char *s, const char *find, size_t slen);
#endif

void printBytes(const char *label, const uint8_t *p, size_t numbytes);

// is the memory region filled with a single character?
bool memfll(const uint8_t *mem, uint8_t find, size_t numbytes);

// getDeviceId() fallback (see target_specific.h): copies the 6-byte factory MAC, or returns false
// on an all-zero MAC so two blank devices don't collide on an all-zero id.
bool getMacAddrDeviceId(uint8_t *deviceId);

bool isOneOf(int item, int count, ...);

const std::string vformat(const char *const zcFormat, ...);

// Get actual string length for nanopb char array fields.
size_t pb_string_length(const char *str, size_t max_len);

// Sanitize a fixed-size char buffer in-place by replacing invalid UTF-8 sequences with '?'.
// Ensures the result is null-terminated within bufSize. Returns true if any bytes were replaced.
bool sanitizeUtf8(char *buf, size_t bufSize);

// Longest User.long_name content (bytes, excluding NUL) we store or transmit.
// The wire decode buffer stays at 40 so names from senders built against the
// older 39-byte limit still parse; everything we keep or send is clamped to
// this, matching the slim NodeInfoLite storage width in deviceonly.proto.
#define MAX_LONG_NAME_BYTES 24

// Clamp a long_name buffer (at least MAX_LONG_NAME_BYTES + 1 bytes) in-place
// to MAX_LONG_NAME_BYTES bytes of content, fixing any partial UTF-8 sequence
// left at the cut.
void clampLongName(char *longName);

/// Calculate 2^n without calling pow() - used for spreading factor and other calculations
inline uint32_t pow_of_2(uint32_t n)
{
    return 1 << n;
}

/// Returns true if n is a power of two (n >= 1).
template <typename T> constexpr bool is_pow_of_2(T n)
{
    return n >= T(1) && (n & (n - T(1))) == T(0);
}

#define IS_ONE_OF(item, ...) isOneOf(item, sizeof((int[]){__VA_ARGS__}) / sizeof(int), __VA_ARGS__)
