#include "JsonConstrain.h"
#include <ArduinoJson.h>
#include <esp_log.h>

template <typename T>
void logConstrainedValue(const char* tag, const char* key, T) {
    // Generic fallback when no specialization provided
    ESP_LOGD(tag, "Key [%s] value set", key);
}

template <>
void logConstrainedValue<int>(const char* tag, const char* key, int value) {
    ESP_LOGD(tag, "Key [%s] value: %d", key, value);
}

template <>
void logConstrainedValue<float>(const char* tag, const char* key, float value) {
    ESP_LOGD(tag, "Key [%s] value: %f", key, value);
}

template <typename T>
void logClamping(const char* tag, const char* key, T, T, const char* condition) {
    // Generic fallback when no specialization provided
    ESP_LOGW(tag, "Key [%s] value too %s. Clamping.", key, condition);
}

template <>
void logClamping<int>(const char* tag, const char* key, int value, int limit, const char* condition) {
    ESP_LOGW(tag, "Key [%s] value too %s (%d). Clamping to [%d].", key, condition, value, limit);
}

template <>
void logClamping<float>(const char* tag, const char* key, float value, float limit, const char* condition) {
    ESP_LOGW(tag, "Key [%s] value too %s (%f). Clamping to [%f].", key, condition, value, limit);
}




template <typename T>
T jsonConstrain(const char *tag, const JsonObject &jsonObject, const char *key, T min, T max, T def) {
    // Check if the key exists and is not null
    if (!jsonObject[key].is<T>()) {
        ESP_LOGW(tag, "Key [%s] not found or null. Using default value.", key);
        return def;
    }

    // Attempt to parse the value
    JsonVariant jsonValue = jsonObject[key];

    // Check if the value type matches the expected type
    if (!jsonValue.is<T>()) {
        ESP_LOGW(tag, "Key [%s] type mismatch. Using default value.", key);
        return def;
    }

    // Get the value
    T value = jsonValue.as<T>();

    // Constrain to range
    if (value < min) {
        logClamping(tag, key, value, min, "low");
        value = min;
    } else if (value > max) {
        logClamping(tag, key, value, max, "high");
        value = max;
    }

    logConstrainedValue(tag, key, value);
    return value;
}


const char* jsonConstrainChar(const char *tag, const JsonObject &jsonObject, const char *key, const char *def) {
    if (!jsonObject[key].is<const char*>()) {
        ESP_LOGW(tag, "Key [%s] not found or null. Using default value.", key);
        return strdup(def);
    }

    String value = jsonObject[key].as<String>();
    if (value.length() == 0) {
        ESP_LOGW(tag, "Key [%s] value is empty. Using default value.", key);
        return strdup(def);
    }

    ESP_LOGD(tag, "Key [%s] value: %s", key, value.c_str());
    return strdup(value.c_str());
}


String jsonConstrainString(const char *tag, const JsonObject &jsonObject, const char *key, String def) {
    // Check if the key exists and is not null
    if (!jsonObject[key].is<String>()) {
        ESP_LOGW(tag, "Key [%s] not found or null. Using default value [%s].", key, def.c_str());
        return def;
    }

    // Extract the value as a String
    String value = jsonObject[key].as<String>();

    // Check if the value is empty
    if (value.length() == 0) {
        ESP_LOGW(tag, "Key [%s] value is empty. Using default value [%s].", key, def.c_str());
        return def;
    }

    ESP_LOGD(tag, "Key [%s] value: %s", key, value.c_str());
    return value;
}


bool jsonConstrainBool(const char *tag, const JsonObject &jsonObject, const char *key, bool def) {
    // Check if the key exists and is of type boolean
    if (!jsonObject[key].is<bool>()) {
        ESP_LOGW(tag, "Key [%s] not found or not a boolean. Using default value [%s].", key, def ? "true" : "false");
        return def;
    }

    // Retrieve and return the value
    bool value = jsonObject[key].as<bool>();
    ESP_LOGD(tag, "Key [%s] value: %s", key, value ? "true" : "false");
    return value;
}


// Explicit instantiations
template int jsonConstrain<int>(const char *, const JsonObject &, const char *, int, int, int);
template float jsonConstrain<float>(const char *, const JsonObject &, const char *, float, float, float);
