#ifndef LIGHTWEIGHT_BLYNK_H
#define LIGHTWEIGHT_BLYNK_H

#include <WiFiClient.h>
#include <ArduinoJson.h>

class LightBlynk {
private:
    WiFiClient client;
    String authToken;
    String server;
    uint16_t port;
    bool connectedState;
    unsigned long lastReconnect;
    
    // Callback functions storage
    void (*writeCallbacks[32])(String param);
    void (*readCallbacks[32])();
    
public:
    LightBlynk() : connectedState(false), lastReconnect(0) {
        for(int i = 0; i < 32; i++) {
            writeCallbacks[i] = nullptr;
            readCallbacks[i] = nullptr;
        }
    }
    
    void begin(const char* auth, const char* server = "blynk.cloud", uint16_t port = 80) {
        authToken = auth;
        this->server = server;
        this->port = port;
    }
    
    bool connect(unsigned long timeout = 10000) {
        if (connectedState && client.connected()) return true;
        
        if (!client.connect(server.c_str(), port)) {
            return false;
        }
        
        // Send login
        String login = "{\"auth\":\"" + authToken + "\"}\n";
        client.print(login);
        
        unsigned long start = millis();
        while (millis() - start < timeout) {
            if (client.available()) {
                String response = client.readStringUntil('\n');
                if (response.indexOf("200") >= 0) {
                    connectedState = true;
                    lastReconnect = millis();
                    return true;
                }
            }
            delay(100);
        }
        return false;
    }
    
    bool run() {
        if (!connectedState || !client.connected()) {
            if (millis() - lastReconnect > 5000) {
                connect();
            }
            return false;
        }
        
        // Process incoming data
        while (client.available()) {
            String line = client.readStringUntil('\n');
            processMessage(line);
        }
        
        // Heartbeat every 30 seconds
        if (millis() - lastReconnect > 30000) {
            client.print("{\"ping\":1}\n");
            lastReconnect = millis();
        }
        
        return true;
    }
    
    bool connected() {
        return connectedState && client.connected();
    }
    
    void virtualWrite(int pin, const String& value) {
        if (!connected()) return;
        
        String message = "{\"pin\":\"V" + String(pin) + "\",\"value\":\"" + value + "\"}\n";
        client.print(message);
    }
    
    void virtualWrite(int pin, float value) {
        virtualWrite(pin, String(value));
    }
    
    void virtualWrite(int pin, int value) {
        virtualWrite(pin, String(value));
    }
    
    template<typename... Args>
    void virtualWrite(int pin, Args... values) {
        // For compatibility with existing code
        virtualWrite(pin, String(values...));
    }
    
    // Callback registration
    void onWrite(int pin, void (*callback)(String param)) {
        if (pin >= 0 && pin < 32) {
            writeCallbacks[pin] = callback;
        }
    }
    
    void onRead(int pin, void (*callback)()) {
        if (pin >= 0 && pin < 32) {
            readCallbacks[pin] = callback;
        }
    }
    
private:
    void processMessage(String& message) {
        DynamicJsonDocument doc(256);
        deserializeJson(doc, message);
        
        if (doc.containsKey("pin") && doc.containsKey("value")) {
            String pinStr = doc["pin"].as<String>();
            if (pinStr.startsWith("V")) {
                int pin = pinStr.substring(1).toInt();
                String value = doc["value"].as<String>();
                
                if (writeCallbacks[pin] != nullptr) {
                    writeCallbacks[pin](value);
                }
            }
        }
    }
};

// Global instance for compatibility
extern LightBlynk Blynk;

// Macros for compatibility with existing code
#define BLYNK_WRITE(pin) \
    void blynkWrite##pin(String param); \
    class BlynkWriteRegister##pin { \
    public: \
        BlynkWriteRegister##pin() { \
            Blynk.onWrite(pin, blynkWrite##pin); \
        } \
    } blynkWriteInstance##pin; \
    void blynkWrite##pin(String param)

#define BLYNK_READ(pin) \
    void blynkRead##pin(); \
    class BlynkReadRegister##pin { \
    public: \
        BlynkReadRegister##pin() { \
            Blynk.onRead(pin, blynkRead##pin); \
        } \
    } blynkReadInstance##pin; \
    void blynkRead##pin()

#endif