/*
 * Zelda64Jewel v1.3 - Production Edition (ENHANCED)
 * Copyright (c) 2025 TheRND2
 * 
 * FEATURES:
 * - Proper screen blanking when changing GIFs
 * - Reduced motion sensitivity (higher threshold values)
 * - Optimized GIF loading to reduce lag
 * - Better frame timing and memory management
 * - Enhanced audio system with 3x faster startup
 * - Hidden secret system with 1/15 unlock chance
 * - Silent GIF switching for smooth operation
 * - Production ready - no development endpoints
 */

#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <SPIFFS.h>
#include <Wire.h>
#include "Arduino_GFX_Library.h"
#include "pin_config.h"
#include "SensorQMI8658.hpp"
#include "HWCDC.h"
#include "GifClass.h"

const char *ssid = "Zelda64Jewel";
const char *password = "triforce";

// Color definitions
#define BLACK 0x0000
#define WHITE 0xFFFF
#define GREEN 0x07E0
#define YELLOW 0xFFE0
#define RED   0xF800

// Note frequencies for Zelda sounds
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
#define NOTE_CS5 554
#define NOTE_D5  587
#define NOTE_DS5 622
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_FS5 740
#define NOTE_G5  784
#define NOTE_GS5 831
#define NOTE_A5  880
#define NOTE_AS5 932
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_D6  1175
#define NOTE_E6  1319
#define NOTE_F6  1397
#define NOTE_FS6 1480
#define NOTE_G6  1568
#define NOTE_GS6 1661
#define REST     0

// Development/Testing flags - DISABLED FOR PRODUCTION
// #define ENABLE_SECRET_TESTING    // Enables /force_secret endpoint
// #define ENABLE_SECRET_RESET      // Enables /reset_secret endpoint
// #define RESET_SECRET_ON_BOOT     // Uncomment to reset secret on every boot

HWCDC USBSerial;
Arduino_DataBus *bus = new Arduino_ESP32SPI(LCD_DC, LCD_CS, LCD_SCK, LCD_MOSI);
Arduino_GFX *gfx = new Arduino_ST7789(bus, LCD_RST, 0, true, LCD_WIDTH, LCD_HEIGHT, 0, 20, 0, 0);

WebServer server(80);

// Motion sensor - FIXED: Increased thresholds and default sensitivity
SensorQMI8658 qmi;
IMUdata acc, gyr;
IMUdata prevAcc = {0, 0, 0};
IMUdata prevGyr = {0, 0, 0};
unsigned long lastMotionTime = 0;
unsigned long lastMotionCheck = 0;
const unsigned long MOTION_COOLDOWN_MS = 2000;
const unsigned long MOTION_CHECK_INTERVAL = 100;
float MOTION_THRESHOLD = 8.0;      // INCREASED from 4.0 to 8.0
float GYRO_THRESHOLD = 6.0;        // INCREASED from 3.0 to 6.0
float motionSensitivity = 2.0;     // INCREASED from 1.0 to 2.0 (less sensitive by default)
bool motionEnabled = true;
bool sensorInitialized = false;

// Audio features
const int BUZZER_PIN = 42;
bool buzzerEnabled = false;
int actualBuzzerPin = -1;
bool isSoundPlaying = false;

// Secret system
bool secretUnlocked = false;
const char* SECRET_GIF_HIDDEN = "/.2716057.gif";  // Hidden filename
const char* SECRET_GIF_VISIBLE = "/2716057.gif";  // Visible filename
const char* SECRET_FLAG_FILE = "/secret_unlocked.txt";

// ESP32 LEDC (PWM) functions for buzzer
void setupBuzzer() {
  const int buzzerPins[] = {46, 45, 48, 47, 5, 4, 18, 19, 3, 8, 9, 10, 11, 12};
  const int numPins = sizeof(buzzerPins) / sizeof(buzzerPins[0]);
  
  buzzerEnabled = false;
  actualBuzzerPin = -1;
  
  if (BUZZER_PIN == -2) {
    USBSerial.println("Buzzer disabled by configuration");
    return;
  }
  
  if (BUZZER_PIN >= 0) {
    USBSerial.println("Testing buzzer on GPIO " + String(BUZZER_PIN) + "...");
    
    if (ledcAttach(BUZZER_PIN, 1000, 8)) {
      ledcWrite(BUZZER_PIN, 128);
      delay(100);
      ledcWrite(BUZZER_PIN, 0);
      
      actualBuzzerPin = BUZZER_PIN;
      buzzerEnabled = true;
      USBSerial.println("Buzzer initialized on GPIO " + String(BUZZER_PIN));
    } else {
      USBSerial.println("Failed to attach buzzer on GPIO " + String(BUZZER_PIN));
    }
  } else if (BUZZER_PIN == -1) {
    USBSerial.println("Auto-detecting buzzer pin...");
    USBSerial.println("Listen for beeps and note which GPIO works:");
    
    for (int i = 0; i < numPins; i++) {
      int pin = buzzerPins[i];
      USBSerial.print("Testing GPIO " + String(pin) + "... ");
      
      pinMode(pin, OUTPUT);
      
      if (ledcAttach(pin, 1000, 8)) {
        ledcWrite(pin, 128);
        delay(200);
        ledcWrite(pin, 0);
        ledcDetach(pin);
        USBSerial.println("done");
      } else {
        USBSerial.println("failed to attach");
      }
      delay(300);
    }
    USBSerial.println("Auto-detect complete. Set BUZZER_PIN to the GPIO that made sound.");
  }
}

void buzzerTone(int frequency, int duration = 0) {
  if (!buzzerEnabled || actualBuzzerPin < 0) return;
  
  if (frequency > 0) {
    ledcChangeFrequency(actualBuzzerPin, frequency, 8);
    ledcWrite(actualBuzzerPin, 128);
    if (duration > 0) {
      delay(duration);
      buzzerNoTone();
    }
  } else {
    buzzerNoTone();
  }
}

void buzzerNoTone() {
  if (!buzzerEnabled || actualBuzzerPin < 0) return;
  ledcWrite(actualBuzzerPin, 0);
}

// Enhanced Zelda sound functions
void playBuzzerTone(int frequency, int duration) {
  if (!buzzerEnabled || actualBuzzerPin < 0) return;
  
  if (frequency > 0) {
    buzzerTone(frequency, duration);
    delay(duration);
    buzzerNoTone();
  } else {
    delay(duration);
  }
  delay(30);
}

void playSecretFound() {
  if (!buzzerEnabled || isSoundPlaying) return;
  isSoundPlaying = true;
  
  USBSerial.println("🎵 PLAYING SECRET FOUND! 🎵");
  
  // Secret sequence: G, F#, D#, A, G#, E, G#, C
  playBuzzerTone(NOTE_G5, 125);
  playBuzzerTone(NOTE_FS5, 125);
  playBuzzerTone(NOTE_DS5, 125);
  playBuzzerTone(NOTE_A5, 125);
  playBuzzerTone(NOTE_GS5, 125);
  playBuzzerTone(NOTE_E5, 125);
  playBuzzerTone(NOTE_GS5, 125);
  playBuzzerTone(NOTE_C6, 125);
  
  isSoundPlaying = false;
}

void playTriforce() {
  if (!buzzerEnabled || isSoundPlaying) return;
  isSoundPlaying = true;
  
  USBSerial.println("Playing Triforce sound");
  
  // 3x faster (200% speed increase)
  playBuzzerTone(NOTE_F4, 100);
  playBuzzerTone(NOTE_A4, 100);
  playBuzzerTone(NOTE_C5, 100);
  playBuzzerTone(NOTE_F5, 200);
  playBuzzerTone(NOTE_F5, 50);
  playBuzzerTone(NOTE_E5, 50);
  playBuzzerTone(NOTE_D5, 50);
  playBuzzerTone(NOTE_C5, 200);
  
  isSoundPlaying = false;
}

// Secret system functions
bool checkSecretUnlocked() {
  if (SPIFFS.exists(SECRET_FLAG_FILE)) {
    secretUnlocked = true;
    USBSerial.println("Secret already unlocked from previous session");
    return true;
  }
  return false;
}

void unlockSecretGif() {
  if (secretUnlocked) {
    USBSerial.println("Secret already unlocked!");
    return;
  }
  
  USBSerial.println("===============================================");
  USBSerial.println("🎉 SECRET UNLOCKED! 🎉");
  USBSerial.println("Hidden GIF 2716057.gif is now available!");
  USBSerial.println("===============================================");
  
  if (SPIFFS.exists(SECRET_GIF_HIDDEN)) {
    File hiddenFile = SPIFFS.open(SECRET_GIF_HIDDEN, "r");
    File visibleFile = SPIFFS.open(SECRET_GIF_VISIBLE, "w");
    
    if (hiddenFile && visibleFile) {
      while (hiddenFile.available()) {
        visibleFile.write(hiddenFile.read());
      }
      hiddenFile.close();
      visibleFile.close();
      USBSerial.println("Secret GIF copied to visible location");
    } else {
      USBSerial.println("Warning: Hidden secret GIF not found - you may need to upload 2716057.gif manually");
    }
  }
  
  File flagFile = SPIFFS.open(SECRET_FLAG_FILE, "w");
  if (flagFile) {
    flagFile.println("SECRET_UNLOCKED");
    flagFile.println("Unlocked on startup with secret sound");
    flagFile.close();
  }
  
  secretUnlocked = true;
}

bool isSecretGif(const String& filename) {
  return filename == "2716057.gif";
}

// GIF player variables
static GifClass gifClass;
File gifFile;
uint8_t *gifBuf = nullptr;
int32_t gifSize = 0;
gd_GIF *gif = nullptr;
int16_t x1_gif = 0;
int16_t y1_gif = 0;

// GIF management
#define MAX_GIFS 50
String gifArray[MAX_GIFS];
uint8_t gifArraySize = 0;
uint8_t selectedGIFIndex = 0;
bool gifChanged = false;
unsigned long lastFrameTime = 0;
unsigned long lastGifScan = 0;
bool fileOperationInProgress = false;

// Upload tracking
size_t uploadTotalSize = 0;
size_t uploadCurrentSize = 0;
String uploadingFilename = "";

// Navigation
volatile int8_t webNavigateDirection = 0;

// Include the webpage
#include "webpage.h"

// Helper functions
bool isSystemFile(const String& filename) {
  return filename.startsWith(".") || 
         filename == "default_gif.txt" || 
         filename == "secret_unlocked.txt" ||
         filename == "System Volume Information";
}

size_t getSpiffsFreeSpace() {
  return SPIFFS.totalBytes() - SPIFFS.usedBytes();
}

size_t getSpiffsTotalSpace() {
  return SPIFFS.totalBytes();
}

void cleanupGIF() {
  if (gif) {
    gifClass.gd_close_gif(gif);
    gif = nullptr;
  }
  if (gifBuf) {
    free(gifBuf);
    gifBuf = nullptr;
  }
  if (gifFile) {
    gifFile.close();
  }
}

void listGIFs() {
  USBSerial.println("Scanning for GIFs...");
  uint8_t oldGifArraySize = gifArraySize;
  gifArraySize = 0;
  
  File root = SPIFFS.open("/");
  if (!root) {
    USBSerial.println("Failed to open root directory");
    return;
  }
  
  while (gifArraySize < MAX_GIFS) {
    File file = root.openNextFile();
    if (!file) break;
    
    String name = file.name();
    if (name.startsWith("/")) name = name.substring(1);
    
    if (isSystemFile(name)) {
      file.close();
      continue;
    }
    
    // Hide secret GIF if not unlocked
    if (name == "2716057.gif" && !secretUnlocked) {
      file.close();
      continue;
    }
    
    if ((name.endsWith(".GIF") || name.endsWith(".gif")) && !file.isDirectory()) {
      size_t fileSize = file.size();
      if (fileSize >= 100 && fileSize <= 2000000) {
        // Validate GIF header
        uint8_t header[6];
        file.seek(0);
        size_t bytesRead = file.read(header, 6);
        
        if (bytesRead == 6 && 
            header[0] == 'G' && header[1] == 'I' && header[2] == 'F' &&
            (memcmp(&header[3], "87a", 3) == 0 || memcmp(&header[3], "89a", 3) == 0)) {
          
          gifArray[gifArraySize++] = name;
          USBSerial.printf("Found GIF: %s (%zu bytes)\n", name.c_str(), fileSize);
        }
      }
    }
    file.close();
  }
  root.close();
  
  // Validate selectedGIFIndex
  if (gifArraySize == 0) {
    selectedGIFIndex = 0;
  } else if (selectedGIFIndex >= gifArraySize) {
    selectedGIFIndex = 0;
    gifChanged = true;
  }
  
  USBSerial.printf("Found %d valid GIFs total (was %d)\n", gifArraySize, oldGifArraySize);
}

bool loadGIF(String filename) {
  if (fileOperationInProgress) {
    return false;
  }
  
  USBSerial.println("Loading: " + filename);
  
  // FIXED: Always clear screen first when changing GIFs
  gfx->fillScreen(BLACK);
  
  // Only cleanup if we have something loaded
  if (gif || gifBuf) {
    cleanupGIF();
  }
  
  // Quick memory check
  if (ESP.getFreeHeap() < 40000) {
    USBSerial.println("Low memory");
    return false;
  }
  
  String fullPath = "/" + filename;
  gifFile = SPIFFS.open(fullPath, "r");
  
  if (!gifFile) {
    USBSerial.println("Cannot open file");
    return false;
  }

  // Quick header check
  uint8_t header[3];
  if (gifFile.read(header, 3) != 3 || header[0] != 'G' || header[1] != 'I' || header[2] != 'F') {
    gifFile.close();
    return false;
  }
  
  gifFile.seek(0);
  gif = gifClass.gd_open_gif(&gifFile);
  if (!gif) {
    gifFile.close();
    return false;
  }

  // Size check
  if (gif->width > 280 || gif->height > 240) {
    gifClass.gd_close_gif(gif);
    gif = nullptr;
    gifFile.close();
    return false;
  }

  x1_gif = (gfx->width() - gif->width) / 2;
  y1_gif = (gfx->height() - gif->height) / 2;
  gifSize = gif->width * gif->height;

  gifBuf = (uint8_t *)malloc(gifSize);
  if (!gifBuf) {
    gifClass.gd_close_gif(gif);
    gif = nullptr;
    gifFile.close();
    return false;
  }

  USBSerial.printf("GIF ready: %dx%d\n", gif->width, gif->height);
  return true;
}

// FIXED: Optimized frame display function to reduce lag
void displayGIFFrame() {
  if (!gif || !gifBuf || fileOperationInProgress) return;
  
  int32_t res = gifClass.gd_get_frame(gif, gifBuf);
  
  if (res > 0) {
    // Display frame - optimized positioning check
    if (x1_gif >= 0 && y1_gif >= 0 && 
        x1_gif + gif->width <= gfx->width() && 
        y1_gif + gif->height <= gfx->height()) {
      gfx->drawIndexedBitmap(x1_gif, y1_gif, gifBuf, gif->palette->colors, 
                            gif->width, gif->height);
    }
    
    // FIXED: Optimized delay calculation
    uint16_t delay_ms = 30; // Increased minimum delay from 20 to 30ms
    if (gif->gce.delay > 0) {
      delay_ms = gif->gce.delay * 10;
      delay_ms = delay_ms * 0.6; // Changed from 50% to 60% speed (slightly slower)
      if (delay_ms < 30) delay_ms = 30; // Increased minimum from 10 to 30ms
      if (delay_ms > 100) delay_ms = 100; // Increased maximum from 60 to 100ms
    }
    
    lastFrameTime = millis() + delay_ms;
    
  } else if (res == 0) {
    // End of GIF - restart without cleanup to avoid flicker
    gifClass.gd_rewind(gif);
    // Immediately try to get the first frame
    res = gifClass.gd_get_frame(gif, gifBuf);
    if (res > 0) {
      gfx->drawIndexedBitmap(x1_gif, y1_gif, gifBuf, gif->palette->colors, 
                            gif->width, gif->height);
      lastFrameTime = millis(); // No pause - continuous loop
    }
  } else {
    // Error reading frame
    USBSerial.printf("GIF frame error: %d\n", res);
    cleanupGIF();
    gifChanged = true;
  }
}

void showStatus() {
  gfx->fillScreen(BLACK);
  
  // For round display, content should be more centered
  gfx->setTextColor(GREEN);
  gfx->setTextSize(1);
  
  if (gifArraySize == 0) {
    gfx->setCursor(60, 80);
    gfx->setTextSize(2);
    gfx->println("Zelda64Jewel");
    gfx->setTextSize(1);
    gfx->setCursor(80, 110);
    gfx->setTextColor(YELLOW);
    gfx->println("No GIFs found!");
    gfx->setCursor(60, 130);
    gfx->setTextColor(GREEN);
    gfx->println("Upload via web:");
    gfx->setTextColor(WHITE);
    gfx->setCursor(40, 150);
    gfx->println("SSID: " + String(ssid));
    gfx->setCursor(40, 165);
    gfx->println("Pass: " + String(password));
    gfx->setCursor(40, 180);
    gfx->println("IP: " + WiFi.softAPIP().toString());
    gfx->setCursor(60, 200);
    gfx->setTextColor(YELLOW);
    if (motionEnabled) {
      gfx->printf("Motion: ON (%.1fx)", motionSensitivity);
    } else {
      gfx->println("Motion: OFF");
    }
  } else if (fileOperationInProgress) {
    gfx->setCursor(70, 100);
    gfx->println("File operation");
    gfx->setCursor(70, 115);
    gfx->println("in progress...");
    if (uploadingFilename.length() > 0) {
      gfx->setCursor(40, 140);
      gfx->setTextColor(YELLOW);
      String displayName = uploadingFilename;
      if (displayName.length() > 20) {
        displayName = displayName.substring(0, 17) + "...";
      }
      gfx->println(displayName);
      if (uploadTotalSize > 0) {
        int percent = (uploadCurrentSize * 100) / uploadTotalSize;
        gfx->setCursor(110, 160);
        gfx->printf("%d%%", percent);
      }
    }
  }
  // Removed "Loading GIF..." text section completely
}

void checkMotion() {
  if (!sensorInitialized || !motionEnabled || fileOperationInProgress) return;
  if (millis() - lastMotionCheck < MOTION_CHECK_INTERVAL) return;
  
  lastMotionCheck = millis();
  
  if (qmi.getDataReady()) {
    if (qmi.getAccelerometer(acc.x, acc.y, acc.z) && qmi.getGyroscope(gyr.x, gyr.y, gyr.z)) {
      
      // Skip first reading
      if (prevAcc.x == 0 && prevAcc.y == 0 && prevAcc.z == 0) {
        prevAcc = acc;
        prevGyr = gyr;
        return;
      }
      
      float accDelta = sqrt(pow(acc.x - prevAcc.x, 2) + pow(acc.y - prevAcc.y, 2) + pow(acc.z - prevAcc.z, 2));
      float gyrDelta = sqrt(pow(gyr.x - prevGyr.x, 2) + pow(gyr.y - prevGyr.y, 2) + pow(gyr.z - prevGyr.z, 2));
      
      // FIXED: Apply sensitivity multiplier correctly (higher value = less sensitive)
      float adjustedAccThreshold = MOTION_THRESHOLD * motionSensitivity;
      float adjustedGyrThreshold = GYRO_THRESHOLD * motionSensitivity;
      
      unsigned long currentTime = millis();
      bool motionDetected = (accDelta > adjustedAccThreshold || gyrDelta > adjustedGyrThreshold);
      
      if (motionDetected && 
          (currentTime - lastMotionTime) > MOTION_COOLDOWN_MS &&
          gifArraySize > 1) {
        
        selectedGIFIndex = (selectedGIFIndex + 1) % gifArraySize;
        gifChanged = true;
        lastMotionTime = currentTime;
        
        // NO SOUND - removed playSecretSound() call
        
        USBSerial.printf("Motion! Next GIF: %s (sensitivity: %.1fx, acc: %.2f, gyr: %.2f)\n", 
                        gifArray[selectedGIFIndex].c_str(), motionSensitivity, accDelta, gyrDelta);
      }
      
      prevAcc = acc;
      prevGyr = gyr;
    }
  }
}

void playStartupSound() {
  if (!buzzerEnabled) {
    USBSerial.println("Buzzer not available");
    return;
  }
  
  // 1 in 15 chance for secret sound (6.67% chance)
  randomSeed(esp_random());
  int secretChance = random(1, 16); // 1-15
  
  if (secretChance == 7) { // Lucky number 7 triggers secret
    USBSerial.println("===============================================");
    USBSerial.println("🎵 RARE SECRET STARTUP! 🎵");
    USBSerial.println("You found a secret! (1/15 chance)");
    USBSerial.println("===============================================");
    
    delay(500);
    playSecretFound();
    delay(500);
    unlockSecretGif();
    
  } else {
    playTriforce();
    // Don't reveal secret system until unlocked
    if (secretUnlocked) {
      USBSerial.println("Normal startup (" + String(secretChance) + "/15 - need 7 for secret)");
    } else {
      USBSerial.println("Triforce startup sound played!");
    }
  }
}

void testBuzzer() {
  if (!buzzerEnabled) {
    USBSerial.println("Buzzer not available");
    return;
  }
  
  USBSerial.println("Playing buzzer test with Zelda sounds...");
  
  USBSerial.println("Test 1: Quick beeps");
  for (int i = 0; i < 3; i++) {
    buzzerTone(1000, 100);
    delay(150);
  }
  delay(500);
  
  USBSerial.println("Test 2: Triforce");
  playTriforce();
  delay(500);
  
  USBSerial.println("Test 3: Secret Found");
  playSecretFound();
  
  USBSerial.println("Zelda buzzer test complete!");
}

void setupDisplay() {
  USBSerial.println("Setting up display...");
  
  if (!gfx->begin()) {
    USBSerial.println("GFX init failed!");
    while (1) delay(1000);
  }
  
  gfx->fillScreen(BLACK);
  pinMode(LCD_BL, OUTPUT);
  pinMode(38, OUTPUT);
  digitalWrite(LCD_BL, HIGH);
  digitalWrite(38, HIGH);
  
  gfx->setRotation(1);
  gfx->setTextSize(1);
  
  // Quick splash screen
  gfx->setTextColor(GREEN);
  gfx->setCursor(80, 110);
  gfx->setTextSize(2);
  gfx->println("Zelda64Jewel");
  gfx->setTextSize(1);
  gfx->setCursor(110, 140);
  gfx->println("v1.3 PRODUCTION");
  delay(500);
  gfx->fillScreen(BLACK);
  USBSerial.println("Display ready");
}

void setupSensor() {
  USBSerial.println("Setting up motion sensor...");
  
  if (qmi.begin(Wire, QMI8658_L_SLAVE_ADDRESS, IIC_SDA, IIC_SCL)) {
    qmi.configAccelerometer(SensorQMI8658::ACC_RANGE_4G, SensorQMI8658::ACC_ODR_1000Hz, SensorQMI8658::LPF_MODE_0, true);
    qmi.configGyroscope(SensorQMI8658::GYR_RANGE_64DPS, SensorQMI8658::GYR_ODR_896_8Hz, SensorQMI8658::LPF_MODE_3, true);
    qmi.enableAccelerometer();
    qmi.enableGyroscope();
    sensorInitialized = true;
    USBSerial.printf("QMI8658 initialized - Motion thresholds: ACC=%.1f, GYR=%.1f, Sensitivity=%.1fx\n", 
                     MOTION_THRESHOLD, GYRO_THRESHOLD, motionSensitivity);
  } else {
    USBSerial.println("QMI8658 init failed - motion detection disabled");
    sensorInitialized = false;
  }
}

void setupWiFi() {
  USBSerial.println("Starting WiFi AP...");
  
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
  delay(500);
  
  WiFi.mode(WIFI_AP);
  delay(500);
  
  if (!WiFi.softAP(ssid, password)) {
    USBSerial.println("WiFi AP failed!");
    return;
  }
  
  delay(2000);
  USBSerial.println("AP: " + String(ssid));
  USBSerial.println("IP: " + WiFi.softAPIP().toString());
}

void handleFileUpload() {
  HTTPUpload& upload = server.upload();
  static File uploadFile;
  static bool uploadError = false;
  
  if (upload.status == UPLOAD_FILE_START) {
    uploadError = false;
    fileOperationInProgress = true;
    uploadTotalSize = server.clientContentLength();
    uploadCurrentSize = 0;
    uploadingFilename = upload.filename;
    
    USBSerial.println("Upload start: " + uploadingFilename);
    
    // Validate filename
    if (uploadingFilename.length() == 0 || uploadingFilename.length() > 60) {
      USBSerial.println("Invalid filename");
      uploadError = true;
      return;
    }
    
    // Check space
    size_t freeSpace = getSpiffsFreeSpace();
    if (uploadTotalSize > (freeSpace - 102400)) {
      USBSerial.println("Insufficient space");
      uploadError = true;
      return;
    }
    
    // Clear display for upload status
    showStatus();
    
    String fullPath = "/" + uploadingFilename;
    if (SPIFFS.exists(fullPath)) {
      SPIFFS.remove(fullPath);
    }
    
    uploadFile = SPIFFS.open(fullPath, "w");
    if (!uploadFile) {
      USBSerial.println("Failed to create file");
      uploadError = true;
    }
    
  } else if (upload.status == UPLOAD_FILE_WRITE) {
    if (!uploadError && uploadFile) {
      size_t written = uploadFile.write(upload.buf, upload.currentSize);
      if (written != upload.currentSize) {
        USBSerial.println("Write error");
        uploadError = true;
      } else {
        uploadCurrentSize += upload.currentSize;
        // Update display periodically
        if (uploadCurrentSize % 10240 == 0 || uploadCurrentSize == uploadTotalSize) {
          showStatus();
        }
      }
    }
    
  } else if (upload.status == UPLOAD_FILE_END) {
    if (uploadFile) {
      uploadFile.close();
    }
    
    if (!uploadError) {
      USBSerial.printf("Upload complete: %s (%d bytes)\n", uploadingFilename.c_str(), upload.totalSize);
      
      delay(1000);
      listGIFs();
      
      // Try to select the newly uploaded GIF
      for (uint8_t i = 0; i < gifArraySize; i++) {
        if (gifArray[i] == uploadingFilename) {
          selectedGIFIndex = i;
          gifChanged = true;
          USBSerial.println("Selected uploaded GIF");
          break;
        }
      }
    } else {
      // Remove failed upload
      String fullPath = "/" + uploadingFilename;
      SPIFFS.remove(fullPath);
      USBSerial.println("Upload failed, file removed");
    }
    
    fileOperationInProgress = false;
    uploadTotalSize = 0;
    uploadCurrentSize = 0;
    uploadingFilename = "";
  }
}

void setupWebServer() {
  USBSerial.println("Setting up web server...");
  
  // Main page
  server.on("/", HTTP_GET, []() {
    server.send_P(200, "text/html", PAGE_HTML);
  });
  
  // Status endpoint
  server.on("/status", HTTP_GET, []() {
    String json = "{";
    json += "\"freeSpace\":" + String(getSpiffsFreeSpace()) + ",";
    json += "\"totalSpace\":" + String(getSpiffsTotalSpace()) + ",";
    json += "\"uploadProgress\":" + String(uploadTotalSize > 0 ? (uploadCurrentSize * 100) / uploadTotalSize : 0) + ",";
    json += "\"uploadFilename\":\"" + uploadingFilename + "\",";
    json += "\"uploadInProgress\":" + String(fileOperationInProgress ? "true" : "false") + ",";
    json += "\"motionEnabled\":" + String(motionEnabled ? "true" : "false") + ",";
    json += "\"motionSensitivity\":" + String(motionSensitivity, 1) + ",";
    json += "\"secretUnlocked\":" + String(secretUnlocked ? "true" : "false");
    json += "}";
    server.send(200, "application/json", json);
  });
  
  // List files
  server.on("/list", HTTP_GET, []() {
    if (fileOperationInProgress) {
      server.send(503, "text/plain", "File operation in progress");
      return;
    }
    
    String output = "[";
    bool first = true;
    
    File root = SPIFFS.open("/");
    if (root) {
      while (File file = root.openNextFile()) {
        String name = file.name();
        if (name.startsWith("/")) name = name.substring(1);
        
        if (isSystemFile(name)) {
          file.close();
          continue;
        }
        
        // Hide secret GIF if not unlocked
        if (isSecretGif(name) && !secretUnlocked) {
          file.close();
          continue;
        }
        
        if (!first) output += ",";
        
        bool isValidGif = false;
        bool isCurrent = false;
        for (uint8_t i = 0; i < gifArraySize; i++) {
          if (gifArray[i] == name) {
            isValidGif = true;
            if (i == selectedGIFIndex) isCurrent = true;
            break;
          }
        }
        
        output += "{\"name\":\"" + name + "\",\"size\":" + String(file.size()) + 
                 ",\"valid\":" + (isValidGif ? "true" : "false") + 
                 ",\"current\":" + (isCurrent ? "true" : "false") + "}";
        first = false;
        file.close();
      }
      root.close();
    }
    
    output += "]";
    server.send(200, "application/json", output);
  });
  
  // Upload
  server.on("/upload", HTTP_POST, 
    []() {
      server.send(200, "text/plain", "Upload complete");
    },
    handleFileUpload
  );
  
  // Delete
  server.on("/delete", HTTP_POST, []() {
    if (server.hasArg("file")) {
      String filename = server.arg("file");
      
      if (fileOperationInProgress) {
        server.send(503, "text/plain", "Another operation in progress");
        return;
      }
      
      fileOperationInProgress = true;
      String fullPath = "/" + filename;
      
      bool wasCurrentGif = false;
      String cleanName = filename;
      
      if (gifArraySize > 0 && selectedGIFIndex < gifArraySize && gifArray[selectedGIFIndex] == cleanName) {
        wasCurrentGif = true;
      }
      
      if (SPIFFS.exists(fullPath)) {
        if (SPIFFS.remove(fullPath)) {
          USBSerial.println("Deleted: " + filename);
          delay(500);
          listGIFs();
          
          if (wasCurrentGif && gifArraySize > 0) {
            selectedGIFIndex = 0;
            gifChanged = true;
          }
          
          server.send(200, "text/plain", "Deleted " + filename);
        } else {
          server.send(500, "text/plain", "Delete failed");
        }
      } else {
        server.send(404, "text/plain", "File not found");
      }
      
      fileOperationInProgress = false;
    } else {
      server.send(400, "text/plain", "Missing file parameter");
    }
  });
  
  // Navigation - NO SOUNDS
  server.on("/next", HTTP_POST, []() {
    if (gifArraySize > 1 && !fileOperationInProgress) {
      selectedGIFIndex = (selectedGIFIndex + 1) % gifArraySize;
      gifChanged = true;
      // NO SOUND - removed playSecretSound()
      server.send(200, "text/plain", "Next GIF");
      USBSerial.println("Next: " + gifArray[selectedGIFIndex]);
    } else {
      server.send(503, "text/plain", fileOperationInProgress ? "File operation in progress" : "No other GIFs");
    }
  });
  
  server.on("/prev", HTTP_POST, []() {
    if (gifArraySize > 1 && !fileOperationInProgress) {
      selectedGIFIndex = (selectedGIFIndex == 0) ? gifArraySize - 1 : selectedGIFIndex - 1;
      gifChanged = true;
      // NO SOUND - removed playSecretSound()
      server.send(200, "text/plain", "Previous GIF");
      USBSerial.println("Previous: " + gifArray[selectedGIFIndex]);
    } else {
      server.send(503, "text/plain", fileOperationInProgress ? "File operation in progress" : "No other GIFs");
    }
  });
  
  // Make default
  server.on("/make_current_default", HTTP_POST, []() {
    if (server.hasArg("filename")) {
      String filename = server.arg("filename");
      
      File f = SPIFFS.open("/default_gif.txt", "w");
      if (f) {
        f.println(filename);
        f.close();
        server.send(200, "text/plain", "Set as default");
      } else {
        server.send(500, "text/plain", "Failed to save");
      }
    } else {
      server.send(400, "text/plain", "Missing filename");
    }
  });
  
  // Preview
  server.on("/preview", HTTP_GET, []() {
    if (!server.hasArg("file")) {
      server.send(400, "text/plain", "Missing file parameter");
      return;
    }
    
    String filename = server.arg("file");
    String fullPath = "/" + filename;
    
    if (fileOperationInProgress) {
      server.send(503, "text/plain", "File operation in progress");
      return;
    }
    
    if (SPIFFS.exists(fullPath)) {
      File file = SPIFFS.open(fullPath, "r");
      if (file) {
        server.streamFile(file, "image/gif");
        file.close();
      } else {
        server.send(500, "text/plain", "Cannot open file");
      }
    } else {
      server.send(404, "text/plain", "Not found");
    }
  });
  
  // Motion control endpoints
  server.on("/motion_toggle", HTTP_POST, []() {
    if (server.hasArg("enabled")) {
      String value = server.arg("enabled");
      motionEnabled = (value == "true" || value == "1");
      USBSerial.printf("Motion detection %s\n", motionEnabled ? "enabled" : "disabled");
      server.send(200, "text/plain", motionEnabled ? "Motion detection enabled" : "Motion detection disabled");
    } else {
      server.send(400, "text/plain", "Missing enabled parameter");
    }
  });
  
  server.on("/motion_sensitivity", HTTP_POST, []() {
    if (server.hasArg("sensitivity")) {
      float newSensitivity = server.arg("sensitivity").toFloat();
      if (newSensitivity >= 0.1 && newSensitivity <= 3.0) {
        motionSensitivity = newSensitivity;
        USBSerial.printf("Motion sensitivity set to %.1fx (ACC threshold: %.1f, GYR threshold: %.1f)\n", 
                        motionSensitivity, MOTION_THRESHOLD * motionSensitivity, GYRO_THRESHOLD * motionSensitivity);
        server.send(200, "text/plain", "Sensitivity updated to " + String(motionSensitivity, 1) + "x");
      } else {
        server.send(400, "text/plain", "Sensitivity must be between 0.1 and 3.0");
      }
    } else {
      server.send(400, "text/plain", "Missing sensitivity parameter");
    }
  });
  
  server.on("/test_buzzer", HTTP_POST, []() {
    if (buzzerEnabled) {
      testBuzzer();
      server.send(200, "text/plain", "Buzzer test complete - did you hear the beeps?");
    } else {
      server.send(503, "text/plain", "Buzzer not available on this device");
    }
  });
  
  server.onNotFound([]() {
    server.send(404, "text/plain", "Not Found");
  });
  
  server.begin();
  USBSerial.println("Web server started on " + WiFi.softAPIP().toString());
}

void setup() {
  USBSerial.begin(115200);
  delay(1000);
  
  USBSerial.println("===============================================");
  USBSerial.println("Zelda64Jewel v1.3 PRODUCTION - Combined Edition");
  USBSerial.println("- Enhanced audio system with 3x faster startup");
  USBSerial.println("- Optimized GIF loading and display");
  USBSerial.println("- Silent GIF switching for smooth operation");
  USBSerial.println("- Production ready with hidden secret system");
  
  setupDisplay();
  
  // Initialize SPIFFS
  USBSerial.println("Initializing SPIFFS...");
  if (!SPIFFS.begin(false)) {
    USBSerial.println("SPIFFS needs formatting...");
    gfx->fillScreen(BLACK);
    gfx->setCursor(10, 10);
    gfx->setTextColor(YELLOW);
    gfx->println("Formatting storage...");
    gfx->println("Please wait...");
    
    if (!SPIFFS.begin(true)) {
      USBSerial.println("SPIFFS Mount Failed");
      gfx->fillScreen(RED);
      gfx->setCursor(10, 10);
      gfx->setTextColor(WHITE);
      gfx->println("SPIFFS Error");
      while (1) delay(1000);
    }
  }
  
  USBSerial.println("SPIFFS mounted");
  USBSerial.printf("Total: %d, Used: %d\n", SPIFFS.totalBytes(), SPIFFS.usedBytes());
  
  // Check if secret was previously unlocked
  checkSecretUnlocked();
  
  // Setup WiFi and web server
  setupWiFi();
  setupWebServer();
  
  // Setup motion sensor
  setupSensor();
  
  // Setup buzzer
  setupBuzzer();
  if (buzzerEnabled && actualBuzzerPin >= 0) {
    tone(actualBuzzerPin, 1000, 50);
    delay(60);
    USBSerial.println("Buzzer ready on GPIO " + String(actualBuzzerPin));
  }
  
  // List GIFs
  USBSerial.println("Listing GIFs...");
  listGIFs();
  
  // Load default GIF if set
  File f = SPIFFS.open("/default_gif.txt", "r");
  if (f) {
    String defaultName = f.readStringUntil('\n');
    defaultName.trim();
    for (uint8_t i = 0; i < gifArraySize; i++) {
      if (gifArray[i] == defaultName) {
        selectedGIFIndex = i;
        USBSerial.println("Default GIF: " + defaultName);
        break;
      }
    }
    f.close();
  }
  
  // Initial display
  if (gifArraySize > 0) {
    // Pre-load first GIF immediately
    if (loadGIF(gifArray[selectedGIFIndex])) {
      // Display first frame right away
      if (gif && gifBuf) {
        int32_t res = gifClass.gd_get_frame(gif, gifBuf);
        if (res > 0) {
          gfx->drawIndexedBitmap(x1_gif, y1_gif, gifBuf, gif->palette->colors, 
                                gif->width, gif->height);
        }
      }
      lastFrameTime = millis(); // Start immediately
    }
  } else {
    showStatus();
  }
  
  // Play startup sound with secret chance
  if (buzzerEnabled) {
    playStartupSound();
  }
  
  USBSerial.println("===============================================");
  USBSerial.println("Zelda64Jewel v1.3 PRODUCTION - Waveshare ESP32-S3-LCD-1.69 Rev2");
  USBSerial.println("Web interface: http://" + WiFi.softAPIP().toString());
  USBSerial.printf("Motion: %s (Sensitivity: %.1fx, ACC: %.1f, GYR: %.1f)\n", 
                   motionEnabled ? "Enabled" : "Disabled", motionSensitivity, 
                   MOTION_THRESHOLD * motionSensitivity, GYRO_THRESHOLD * motionSensitivity);
  USBSerial.println("Buzzer: " + String(buzzerEnabled ? "Ready on GPIO " + String(actualBuzzerPin) : "Not available"));
  if (secretUnlocked) {
    USBSerial.println("Secret System: UNLOCKED");
  }
  USBSerial.println("GIF Switching: Silent operation");
  USBSerial.println("Production Mode: All development features disabled");
  USBSerial.println("===============================================");
  
  USBSerial.printf("Free heap: %d\n", ESP.getFreeHeap());
}

void loop() {
  // Handle web requests
  server.handleClient();
  
  // Handle motion detection (in main loop for stability)
  if (!fileOperationInProgress) {
    checkMotion();
  }
  
  // Handle navigation requests
  if (webNavigateDirection != 0 && !fileOperationInProgress) {
    if (gifArraySize > 1) {
      if (webNavigateDirection > 0) {
        selectedGIFIndex = (selectedGIFIndex + 1) % gifArraySize;
      } else {
        selectedGIFIndex = (selectedGIFIndex == 0) ? gifArraySize - 1 : selectedGIFIndex - 1;
      }
      gifChanged = true;
      USBSerial.printf("Web nav to GIF %d: %s\n", selectedGIFIndex, gifArray[selectedGIFIndex].c_str());
    }
    webNavigateDirection = 0;
  }
  
  // Handle GIF changes - FIXED: Proper screen clearing and loading
  if (gifChanged && !fileOperationInProgress) {
    if (gifArraySize > 0 && selectedGIFIndex < gifArraySize) {
      // No loading message - go straight to loading GIF
      
      if (loadGIF(gifArray[selectedGIFIndex])) {
        lastFrameTime = millis(); // Start immediately
        // Immediately display first frame
        if (gif && gifBuf) {
          int32_t res = gifClass.gd_get_frame(gif, gifBuf);
          if (res > 0) {
            gfx->drawIndexedBitmap(x1_gif, y1_gif, gifBuf, gif->palette->colors, 
                                  gif->width, gif->height);
          }
        }
      } else {
        gfx->fillScreen(BLACK);
        showStatus();
        delay(500);
      }
    } else {
      showStatus();
    }
    gifChanged = false;
  }
  
  // Display GIF frames
  if (gif && gifBuf && !fileOperationInProgress && millis() >= lastFrameTime) {
    displayGIFFrame();
  }
  
  // Show status if no GIFs loaded
  if (!gif && !fileOperationInProgress && gifArraySize == 0) {
    static unsigned long lastStatusUpdate = 0;
    if (millis() - lastStatusUpdate > 5000) {
      showStatus();
      lastStatusUpdate = millis();
    }
  }
  
  // Periodic GIF rescan (every 30 seconds)
  if (!fileOperationInProgress && millis() - lastGifScan > 30000) {
    uint8_t oldSize = gifArraySize;
    listGIFs();
    if (oldSize != gifArraySize) {
      USBSerial.printf("GIF count changed: %d -> %d\n", oldSize, gifArraySize);
    }
    lastGifScan = millis();
  }
  
  // Status reporting
  static unsigned long lastStatus = 0;
  if (millis() - lastStatus > 60000) {
    if (secretUnlocked) {
      USBSerial.printf("Status: heap=%d, clients=%d, GIFs=%d, current=%s, motion_thresh=%.1f/%.1f, secret=UNLOCKED\n", 
                       ESP.getFreeHeap(), 
                       WiFi.softAPgetStationNum(),
                       gifArraySize,
                       (gifArraySize > 0 && selectedGIFIndex < gifArraySize) ? gifArray[selectedGIFIndex].c_str() : "none",
                       MOTION_THRESHOLD * motionSensitivity,
                       GYRO_THRESHOLD * motionSensitivity);
    } else {
      USBSerial.printf("Status: heap=%d, clients=%d, GIFs=%d, current=%s, motion_thresh=%.1f/%.1f\n", 
                       ESP.getFreeHeap(), 
                       WiFi.softAPgetStationNum(),
                       gifArraySize,
                       (gifArraySize > 0 && selectedGIFIndex < gifArraySize) ? gifArray[selectedGIFIndex].c_str() : "none",
                       MOTION_THRESHOLD * motionSensitivity,
                       GYRO_THRESHOLD * motionSensitivity);
    }
    lastStatus = millis();
  }
  
  // Minimal delay to prevent watchdog
  delay(1);
}
