#include <esp_now.h>
#include <WiFi.h>
#include <Adafruit_NeoPixel.h>


// --- Configuration ---
#define LED_PIN     10
#define LED_COUNT   10
// Removed PAUSE_TIME from here, as the timing logic will be handled by millis() in the callback.


// Structure definition (MUST MATCH THE SENDER)
typedef struct struct_message {
    char a[32]; // For status text
    int b;       // Integer (e.g., scaled reading)
    float c;     // Float (e.g., magnitude or raw float data)
    bool d;      // Boolean (Movement detected flag)
} struct_message;

struct_message myData;


// --- Object Declarations ---
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);


// Variable to track how many pixels have been lit (This is our persistent counter)
int activePixelCount = 0;

// *** NEW STATE VARIABLES FOR TIMING CONTROL ***
unsigned long lastTriggerTime = 0;
const unsigned long COOLDOWN_MS = 2000; // 2 seconds cooldown period


// --- State Saving Variables for Restoration ---
// We need to store the color of every pixel before a flash occurs.
uint32_t savedPixelColors[LED_COUNT];
bool stateSavedForFlash = false;


//=============================================================
// FUNCTION DEFINITIONS (Helpers must be defined outside setup/loop)
//=============================================================

/**
 * @brief Flashes all NeoPixels red for a specified duration and restores the previous state.
 */
void flashAllRed(unsigned long durationMs) {
    Serial.println("\n!!! RECEIVED 'TOO FAST' - FLASHING ALL RED !!!");
    
    // 1. Save Current State BEFORE flashing (Crucial Step!)
    for (int i = 0; i < LED_COUNT; i++) {
        savedPixelColors[i] = strip.getPixelColor(i);
    }
    stateSavedForFlash = true; // Mark that we have saved the state

    // --- FLASHING SEQUENCE ---
    // Set all pixels to Red
    for (int i = 0; i < LED_COUNT; i++) {
        strip.setPixelColor(i, strip.Color(255, 0, 0)); // Pure Red
    }
    strip.show();

    // Wait for the specified duration
    delay(durationMs); 

    // Turn all pixels off (This is part of the flash effect)
    for (int i = 0; i < LED_COUNT; i++) {
        strip.setPixelColor(i, strip.Color(0, 0, 0)); // Off
    }
    strip.show();

    // --- RESTORATION SEQUENCE ---
    Serial.println("Restoring previous state...");
    for (int i = 0; i < LED_COUNT; i++) {
        strip.setPixelColor(i, savedPixelColors[i]); // Restore the color
    }
    strip.show(); // Show the restored colors

    // Reset the flag and reset the counter state after restoration
    stateSavedForFlash = false; 
    // NOTE: We DO NOT reset activePixelCount here because we want the sequence to continue from where it left off.
    Serial.println("Red flash complete. State restored.");
}


/**
 * @brief Handles lighting the next pixel and cycling the state when capacity is reached.
 * NOTE: This function should only be called if the time cooldown has passed AND no red flash occurred.
 */
void activateNextPixel() {
    // 1. Check if we need to reset (Cycle condition)
    if (activePixelCount >= LED_COUNT) {
        Serial.println("--- CYCLE COMPLETE! Resetting all pixels for next cycle. ---");
        strip.clear(); // Turn ALL lights off
        strip.show();  // Update the physical LEDs
        activePixelCount = 0; // Reset the counter to start over from Pixel 0
    }


    // 2. Light up the current pixel (This runs whether we just reset or not)
    if (activePixelCount < LED_COUNT) {
        // Increment the count *before* lighting, so it reflects the index being lit
        activePixelCount++; 
        
        Serial.print("Lighting Pixel Index: ");
        Serial.println(activePixelCount - 1);


        // --- COLOR MAPPING START HERE (Using your specified colors) ---
        uint32_t color;
        int current_index = activePixelCount - 1; // The physical index being lit (0, 1, 2...)


        if (current_index == 0) {
            color = strip.Color(255, 50, 50); // Reddish
        } else if (current_index == 1) {
            color = strip.Color(255, 255, 50); // Yellowish
        } else if (current_index == 2) {
            color = strip.Color(50, 255, 50); // Green
        } else if (current_index == 3) {
            color = strip.Color(50, 255, 255); // Cyan/Light Blue
        } else if (current_index == 4) {
            color = strip.Color(50, 50, 255); // Blue
        } else if (current_index == 5) {
            color = strip.Color(255, 50, 255); // Magenta/Purple
        } else if (current_index == 6) {
            color = strip.Color(255, 255, 100); // Light Yellow/Cream
        } else if (current_index == 7) {
            color = strip.Color(250, 100, 50); // Orange/Peach
        } else if (current_index == 8) {
            color = strip.Color(100, 200, 150); // Light Teal/Mint
        } else if (current_index == 9) {
            color = strip.Color(200, 80, 10); // Brownish Orange
        } else {
             // Fallback color just in case something goes wrong
             color = strip.Color(255, 0, 0); 
        }


        strip.setPixelColor(current_index, color);
        strip.show(); // Update the physical LEDs
    }
}


/**
 * @brief Callback function executed when data is received via ESP-NOW.
 */
void OnDataRecv(const esp_now_recv_info_t *info, const uint8_t *incomingData, int len) {
    // 1. Copy the received data into our local structure variable
    memcpy(&myData, incomingData, sizeof(myData));
    
    Serial.println("\n=========================================");
    Serial.print("Packet Received from MAC: ");
    for (int i = 0; i < 6; i++) {
        Serial.printf("%02X", info->src_addr[i]);
        if (i < 5) Serial.print(":");
    }
    Serial.println();


    // Display the received data fields (for debugging)
    Serial.print("Char: ");
    Serial.println(myData.a);
    Serial.print("Int: ");
    Serial.println(myData.b);
    Serial.print("Float: ");
    Serial.println(myData.c);
    Serial.print("Bool: ");
    Serial.println(myData.d ? "TRUE" : "FALSE");


    // 2. CHECK FOR THE SPECIAL MESSAGE FIRST (Highest Priority)
    if (strcmp(myData.a, "TOO FAST") == 0) {
        // Execute the special red flash sequence for 3 seconds
        flashAllRed(100); // *** durations lights stay red ***
        
        // Reset cooldown timer after a manual override
        lastTriggerTime = millis(); 
        return; // Exit immediately to prevent running movement logic below
    }


    // 3. CHECK THE MOVEMENT FLAG AND ENFORCE COOLDOWN (Original Logic)
    if (myData.d == true) {
        unsigned long currentTime = millis();
        
        // Check if enough time has passed since the last successful trigger
        if (currentTime - lastTriggerTime >= COOLDOWN_MS) {
            Serial.println("\n*** MOVEMENT DETECTED! Cooldown elapsed. Starting timed sequence... ***");
            activateNextPixel(); // This starts the process by lighting Pixel 0
            lastTriggerTime = currentTime; // IMPORTANT: Update the timer ONLY when action is taken
        } else {
            // If movement is detected but cooldown hasn't passed, we ignore it.
            Serial.printf("Movement detected, BUT ignored. Still in cooldown period (%.1f seconds remaining).\n", 
                          (float)(COOLDOWN_MS - (millis() - lastTriggerTime)) / 1000.0);
        }
    } else {
        // If movement is NOT detected, we do nothing to the lights, 
        // allowing previously lit pixels to stay lit until a reset/cycle.
        Serial.println("Movement flag is FALSE. Lights remain as they are.");
    }
}


void setup() {
    // Initialize Serial Monitor (Use 115200 baud rate)
    Serial.begin(115200);


    // Setup the NeoPixels
    strip.begin();          
    strip.show();           // Turn OFF all pixels initially
    strip.setBrightness(50); // Set brightness to a reasonable level (0-255)


    // Initialize timing variables
    lastTriggerTime = 0; // Ensure it starts ready to receive the first trigger


    // --- ESP-NOW Initialization ---
    WiFi.mode(WIFI_STA);



    if (esp_now_init() != ESP_OK) {
        Serial.println("Error initializing ESP-NOW");
        return;
    }
    
    // Register the Receive Callback Function
    esp_now_register_recv_cb(OnDataRecv);


    Serial.println("\n=========================================");
    Serial.println("Receiver Initialized and Listening for ESP-NOW Data...");
    Serial.print("Cooldown set to: ");
    Serial.print(COOLDOWN_MS / 1000);
    Serial.println(" seconds.");
}


void loop() {
    // The loop is now empty because all logic runs asynchronously in the callback function.
    delay(20); 
}
