#include <WiFi.h>
#include <WebServer.h>
#include <ElegantOTA.h>
#include <Wire.h>
#include <Adafruit_INA219.h>
#include <Preferences.h>
#include <math.h>
#include "esp_system.h"

#if __has_include(<esp_arduino_version.h>)
#include <esp_arduino_version.h>
#endif

// =====================================================
// CONFIGURAZIONE INIZIALE
// =====================================================
const char* ssid = "your SSID";
const char* password = "your password";


IPAddress local_IP(192, 168, 1, 200);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8);
IPAddress secondaryDNS(1, 1, 1, 1);

String www_username = "admin";
String www_password = "123";

const uint16_t WEB_PORT = 80;

// =====================================================
// PIN E PARAMETRI
// =====================================================
#define RPWM 25
#define LPWM 26
#define BUTTON_FORWARD 14
#define BUTTON_REVERSE 27

#define PWM_FREQ 5000
#define PWM_RES 8
#define RPWM_CHANNEL 0
#define LPWM_CHANNEL 1

const float BLOCK_CURRENT = 1.0; // Ampere
const unsigned long CURRENT_START_GRACE_MS = 800;   // Ignora lo spunto iniziale
const unsigned long OVERCURRENT_CONFIRM_MS = 180;   // Conferma sovracorrente
const unsigned long CURRENT_READ_INTERVAL_MS = 100; // Lettura INA219 ogni 100 ms
const int CURRENT_ZERO_CALIBRATION_SAMPLES = 20;
const float CURRENT_FILTER_ALPHA = 0.25;

const unsigned long motionDuration = 15000;
const float ENDSTOP_TIME_RATIO = 0.80;
const int rampStep = 5;
const int rampInterval = 25;

const unsigned long BUTTON_DEBOUNCE_MS = 60;
const unsigned long BOTH_BUTTONS_RESET_MS = 2000;

enum MotorState { STOPPED, FORWARD, REVERSE };
MotorState activeDirection = STOPPED;
String statoPorta = "PORTA FERMA";
String ultimoComando = "NESSUNO";

int currentPWM = 0;
int targetPWM = 0;
unsigned long lastRampMillis = 0;
unsigned long motionStart = 0;
unsigned long overCurrentSince = 0;
unsigned long lastCurrentReadMillis = 0;
float currentAmps = 0.0;
float currentZeroOffsetAmps = 0.0;
bool currentFilterReady = false;
bool currentSensorOk = false;
bool alarmLatched = false;
bool networkServicesStarted = false;
bool wifiBeginDone = false;
unsigned long wifiConnectedSince = 0;
unsigned long lastWiFiStatusPrint = 0;
unsigned long lastLoopAlivePrint = 0;
bool restartPending = false;
unsigned long restartAtMillis = 0;

// AGGIUNTO PER GESTIONE TEMPO RESIDUO
unsigned long tempoEseguitoMS = 0;       // Tempo accumulato dall'ultimo stato di fermo totale
unsigned long corsaAttualeMassimaMS = 15000; // Il limite di tempo dinamico per il movimento attuale
MotorState ultimaDirezioneMovimento = STOPPED;

struct ButtonState {
  uint8_t pin;
  bool stableState;
  bool lastReading;
  unsigned long lastChange;
};

ButtonState openButton = { BUTTON_FORWARD, HIGH, HIGH, 0 };
ButtonState closeButton = { BUTTON_REVERSE, HIGH, HIGH, 0 };

unsigned long bothButtonsLowSince = 0;
bool bothButtonsResetDone = false;

WebServer server(WEB_PORT);
Adafruit_INA219 ina219;
Preferences preferences;

// =====================================================
// PWM COMPATIBILE ESP32 CORE 2.x / 3.x
// =====================================================

void setupPwmPin(uint8_t pin, uint8_t channel) {
  Serial.print("Inizializzo PWM su pin ");
  Serial.println(pin);
  Serial.flush();

#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR >= 3
  ledcAttach(pin, PWM_FREQ, PWM_RES);
#else
  ledcSetup(channel, PWM_FREQ, PWM_RES);
  ledcAttachPin(pin, channel);
#endif

  Serial.print("PWM pronto su pin ");
  Serial.println(pin);
  Serial.flush();
}

void writePwmPin(uint8_t pin, uint8_t channel, int duty) {
#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR >= 3
  ledcWrite(pin, duty);
#else
  ledcWrite(channel, duty);
#endif
}

// =====================================================
// LOGICA MOTORE
// =====================================================

void updateMotorHardware() {
  if (activeDirection == FORWARD) {
    writePwmPin(LPWM, LPWM_CHANNEL, 0);
    writePwmPin(RPWM, RPWM_CHANNEL, currentPWM);
  } else if (activeDirection == REVERSE) {
    writePwmPin(RPWM, RPWM_CHANNEL, 0);
    writePwmPin(LPWM, LPWM_CHANNEL, currentPWM);
  } else {
    writePwmPin(RPWM, RPWM_CHANNEL, 0);
    writePwmPin(LPWM, LPWM_CHANNEL, 0);
  }
}

// AGGIUNTO PER GESTIONE TEMPO RESIDUO (Calcola quanto tempo è passato prima dello stop)
void memorizzaTempoTrascorsco() {
  if (activeDirection != STOPPED) {
    unsigned long trascorso = millis() - motionStart;
    tempoEseguitoMS += trascorso;
    if (tempoEseguitoMS > motionDuration) {
      tempoEseguitoMS = motionDuration;
    }
    ultimaDirezioneMovimento = activeDirection;
    Serial.print("Motore fermato. Tempo parziale accumulato in questa direzione: ");
    Serial.print(tempoEseguitoMS);
    Serial.println(" ms");
  }
}

void motorStop() {
  memorizzaTempoTrascorsco(); // AGGIUNTO PER GESTIONE TEMPO RESIDUO
  targetPWM = 0;
  ultimoComando = "FERMA";
}

void motorStopInstant(const String& messaggio) {
  memorizzaTempoTrascorsco(); // AGGIUNTO PER GESTIONE TEMPO RESIDUO
  targetPWM = 0;
  currentPWM = 0;
  activeDirection = STOPPED;
  statoPorta = messaggio;
  
  // AGGIUNTO PER GESTIONE TEMPO RESIDUO: Se arriva a fine corsa reale (rilevato da tempo o corrente), resetta la memoria temporale
  if (messaggio == "PORTA APERTA" || messaggio == "PORTA CHIUSA") {
    tempoEseguitoMS = 0;
    ultimaDirezioneMovimento = STOPPED;
    corsaAttualeMassimaMS = motionDuration;
    Serial.println("Fine corsa raggiunto/presunto. Reset memoria tempi.");
  }
  
  if (messaggio == "PORTA APERTA") ultimoComando = "APRI";
  else if (messaggio == "PORTA CHIUSA") ultimoComando = "CHIUDI";
  else ultimoComando = "FERMA";
  overCurrentSince = 0;
  updateMotorHardware();
}

void triggerAlarm(const String& messaggio) {
  alarmLatched = true;
  motorStopInstant(messaggio);
}

void resetAlarm(const String& messaggio) {
  alarmLatched = false;
  overCurrentSince = 0;
  currentFilterReady = false;
  statoPorta = messaggio;
  // In caso di allarme resettato, per sicurezza consideriamo i tempi azzerati
  tempoEseguitoMS = 0;
  ultimaDirezioneMovimento = STOPPED;
  corsaAttualeMassimaMS = motionDuration;
}

bool canStartMotor() {
  if (!currentSensorOk) {
    triggerAlarm("ERRORE SENSORE CORRENTE");
    return false;
  }
  if (alarmLatched) {
    statoPorta = "ALLARME BLOCCO";
    return false;
  }
  return true;
}

void startMotor(MotorState direction, const String& message) {
  if (!canStartMotor()) return;

  // AGGIUNTO PER GESTIONE TEMPO RESIDUO (Calcolo dinamico della durata massima)
  if (ultimaDirezioneMovimento == STOPPED) {
    // La porta partiva da uno stato di fermo totale (completamente aperta o chiusa)
    corsaAttualeMassimaMS = motionDuration;
    tempoEseguitoMS = 0;
  } 
  else if (ultimaDirezioneMovimento == direction) {
    // Si preme di nuovo lo stesso comando interrotto: gira solo per il TEMPO RESIDUO
    if (motionDuration > tempoEseguitoMS) {
      corsaAttualeMassimaMS = motionDuration - tempoEseguitoMS;
    } else {
      corsaAttualeMassimaMS = 500; // Minimo di sicurezza se era già al limite
    }
    Serial.print("Stesso comando di prima. Corsa residua limitata a: ");
    Serial.print(corsaAttualeMassimaMS);
    Serial.println(" ms");
  } 
  else {
    // Si preme il comando inverso: gira solo per il TEMPO EFFETTIVAMENTE PERCORSO in precedenza
    corsaAttualeMassimaMS = tempoEseguitoMS;
    if (corsaAttualeMassimaMS < 500) corsaAttualeMassimaMS = 500; // Minimo di sicurezza
    tempoEseguitoMS = motionDuration - corsaAttualeMassimaMS; // Invertiamo il punto di partenza stimato
    Serial.print("Comando inverso. Corsa invertita limitata a: ");
    Serial.print(corsaAttualeMassimaMS);
    Serial.println(" ms");
  }

  if (activeDirection != direction) currentPWM = 0;
  activeDirection = direction;
  targetPWM = 255;
  statoPorta = message;
  ultimoComando = (direction == FORWARD) ? "APRI" : "CHIUDI";
  motionStart = millis();
  overCurrentSince = 0;
  currentFilterReady = false;
  lastRampMillis = millis();
  updateMotorHardware();
}

void motorForward() {
  startMotor(FORWARD, "IN APERTURA...");
}

void motorReverse() {
  startMotor(REVERSE, "IN CHIUSURA...");
}

void handleRamping() {
  if (millis() - lastRampMillis < rampInterval) return;
  lastRampMillis = millis();

  if (currentPWM < targetPWM) currentPWM += rampStep;
  else if (currentPWM > targetPWM) currentPWM -= rampStep;
  currentPWM = constrain(currentPWM, 0, 255);

  if (currentPWM == 0 && targetPWM == 0 && activeDirection != STOPPED) {
    if (activeDirection == FORWARD) statoPorta = "PORTA APERTA";
    else if (activeDirection == REVERSE) statoPorta = "PORTA CHIUSA";
    
    // AGGIUNTO PER GESTIONE TEMPO RESIDUO: fine della rampa di decelerazione dello stop programmato
    tempoEseguitoMS = 0;
    ultimaDirezioneMovimento = STOPPED;
    corsaAttualeMassimaMS = motionDuration;
    
    activeDirection = STOPPED;
    overCurrentSince = 0;
  }

  updateMotorHardware();
}

// =====================================================
// CORRENTE E SICUREZZA
// =====================================================

void updateCurrentReading() {
  if (!currentSensorOk) {
    currentAmps = 0.0;
    return;
  }

  unsigned long now = millis();
  if (now - lastCurrentReadMillis < CURRENT_READ_INTERVAL_MS) return;
  lastCurrentReadMillis = now;
  float measuredAmps = fabs(ina219.getCurrent_mA() / 1000.0) - currentZeroOffsetAmps;
  if (isnan(measuredAmps) || isinf(measuredAmps)) {
    triggerAlarm("ERRORE LETTURA CORRENTE");
    return;
  }
  if (measuredAmps < 0.0) measuredAmps = 0.0;

  if (!currentFilterReady) {
    currentAmps = measuredAmps;
    currentFilterReady = true;
  } else {
    currentAmps = (currentAmps * (1.0 - CURRENT_FILTER_ALPHA)) + (measuredAmps * CURRENT_FILTER_ALPHA);
  }
}

void calibrateCurrentZero() {
  if (!currentSensorOk) return;

  Serial.println("Calibrazione zero corrente INA219...");
  Serial.flush();

  float totalAmps = 0.0;
  for (int i = 0; i < CURRENT_ZERO_CALIBRATION_SAMPLES; i++) {
    totalAmps += fabs(ina219.getCurrent_mA() / 1000.0);
    delay(10);
  }

  currentZeroOffsetAmps = totalAmps / CURRENT_ZERO_CALIBRATION_SAMPLES;
  currentAmps = 0.0;
  currentFilterReady = false;

  Serial.print("Offset corrente a motore fermo: ");
  Serial.print(currentZeroOffsetAmps, 3);
  Serial.println(" A");
  Serial.flush();
}

void handleCurrentProtection() {
  if (activeDirection == STOPPED) {
    overCurrentSince = 0;
    return;
  }
  if (millis() - motionStart < CURRENT_START_GRACE_MS) {
    overCurrentSince = 0;
    return;
  }
  if (currentAmps <= BLOCK_CURRENT) {
    overCurrentSince = 0;
    return;
  }

  if (overCurrentSince == 0) overCurrentSince = millis();
  if (millis() - overCurrentSince < OVERCURRENT_CONFIRM_MS) return;
  
  // MODIFICATO PER GESTIONE TEMPO RESIDUO: Il calcolo del fine corsa ora si basa sulla corsa attuale massima dinamica
  unsigned long elapsed = millis() - motionStart;
  bool likelyEndstop = (elapsed + tempoEseguitoMS) >= (unsigned long)(motionDuration * ENDSTOP_TIME_RATIO);
  
  if (likelyEndstop) {
    if (activeDirection == FORWARD) motorStopInstant("PORTA APERTA");
    else if (activeDirection == REVERSE) motorStopInstant("PORTA CHIUSA");
  } else {
    triggerAlarm("ALLARME BLOCCO");
  }
}

// =====================================================
// PULSANTI FISICI
// =====================================================

void updateButton(ButtonState& button) {
  bool reading = digitalRead(button.pin);
  if (reading != button.lastReading) {
    button.lastChange = millis();
    button.lastReading = reading;
  }

  if ((millis() - button.lastChange) > BUTTON_DEBOUNCE_MS && reading != button.stableState) {
    button.stableState = reading;
  }
}

bool buttonPressedEdge(ButtonState& button) {
  bool oldStableState = button.stableState;
  updateButton(button);
  return oldStableState == HIGH && button.stableState == LOW;
}

void handlePhysicalButtons() {
  bool openClick = buttonPressedEdge(openButton);
  bool closeClick = buttonPressedEdge(closeButton);

  bool openPressed = (openButton.stableState == LOW);
  bool closePressed = (closeButton.stableState == LOW);
  bool bothPressed = openPressed && closePressed;
  if (alarmLatched && bothPressed) {
    if (bothButtonsLowSince == 0) bothButtonsLowSince = millis();
    if (!bothButtonsResetDone && millis() - bothButtonsLowSince >= BOTH_BUTTONS_RESET_MS) {
      resetAlarm("PORTA FERMA");
      bothButtonsResetDone = true;
    }
    return;
  }

  if (!bothPressed) {
    bothButtonsLowSince = 0;
    bothButtonsResetDone = false;
  }

  if (bothPressed && activeDirection != STOPPED) {
    motorStopInstant("FERMATA DA PULSANTI");
    return;
  }

  if (openClick) {
    if (activeDirection != STOPPED) motorStopInstant("FERMATA DA PULSANTE");
    else motorForward();
  } else if (closeClick) {
    if (activeDirection != STOPPED) motorStopInstant("FERMATA DA PULSANTE");
    else motorReverse();
  }
}

// =====================================================
// WEB SERVER
// =====================================================

bool requireAuth() {
  if (!server.authenticate(www_username.c_str(), www_password.c_str())) {
    server.requestAuthentication();
    return false;
  }
  return true;
}

String jsonEscape(const String& value) {
  String escaped = "";
  escaped.reserve(value.length() + 8);
  for (size_t i = 0; i < value.length(); i++) {
    char c = value.charAt(i);
    if (c == '\"') escaped += "\\\"";
    else if (c == '\\') escaped += "\\\\";
    else if (c == '\n') escaped += "\\n";
    else if (c == '\r') escaped += "\\r";
    else if (c == '\t') escaped += "\\t";
    else escaped += c;
  }
  return escaped;
}

String getCSS() {
  return "<style>body{font-family:sans-serif;text-align:center;background:#f4f4f4;padding:20px;}"
         ".card{background:white;margin:0 auto;width:95%;max-width:400px;padding:20px;border-radius:15px;box-shadow:0 4px 6px rgba(0,0,0,0.1);}"
         "button{width:100%;height:60px;margin:10px 0;font-size:18px;border:none;border-radius:10px;cursor:pointer;color:white;font-weight:bold;}"
         ".btn-open{background:#2ecc71}.btn-close{background:#3498db}.btn-stop{background:#e74c3c}"
         ".btn-reset{background:#f39c12}.btn-settings{background:#95a5a6;height:40px;font-size:14px}"
         ".btn-ota{background:#6c5ce7;height:40px;font-size:14px}"
         ".feedback{background:#f8f9fa;border:1px solid #ddd;border-radius:10px;padding:12px;margin:14px 0;text-align:left}"
         ".feedback b{display:block;margin-bottom:8px;text-align:center}.pillrow{display:flex;gap:6px}"
         ".pill{flex:1;text-align:center;padding:8px 4px;border-radius:8px;background:#ddd;color:#555;font-size:12px;font-weight:bold}"
         ".pill.active-open{background:#2ecc71;color:white}.pill.active-close{background:#3498db;color:white}.pill.active-stop{background:#e74c3c;color:white}"
         ".statusline{display:flex;align-items:center;justify-content:center;gap:10px;font-size:24px;font-weight:bold}"
         ".spinner{display:none;width:22px;height:22px;border:4px solid #ddd;border-radius:50%}"
         ".spinner.open{display:inline-block;border-top-color:#2ecc71;animation:spinOpen .8s linear infinite}"
         ".spinner.close{display:inline-block;border-top-color:#e74c3c;animation:spinClose .8s linear infinite}"
         "@keyframes spinOpen{to{transform:rotate(-360deg)}}@keyframes spinClose{to{transform:rotate(360deg)}}"
         "input{width:100%;height:40px;margin:10px 0;font-size:18px;text-align:center;border:1px solid #ccc;border-radius:5px;}</style>";
}

void redirectHome() {
  server.sendHeader("Location", "/");
  server.send(303);
}

String webBaseUrl() {
  String url = "http://";
  url += WiFi.localIP().toString();
  if (WEB_PORT != 80) {
    url += ":";
    url += String(WEB_PORT);
  }
  return url;
}

void handleRoot() {
  if (!requireAuth()) return;

  String icon = "&#128274;";
  String color = "#7f8c8d";
  if (statoPorta == "PORTA APERTA") {
    icon = "&#128275;";
    color = "#2ecc71";
  } else if (statoPorta == "PORTA CHIUSA") {
    icon = "&#128274;";
    color = "#3498db";
  } else if (statoPorta.startsWith("IN ")) {
    icon = "&#9881;";
    color = "#f1c40f";
  } else if (statoPorta == "ALLARME BLOCCO" || statoPorta.startsWith("ERRORE")) {
    icon = "&#9888;";
    color = "#e74c3c";
  }

  String html = "<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'>";
  html += getCSS();
  html += "<script>"
          "function updateStatus(){"
          "fetch('/status',{cache:'no-store'}).then(function(r){return r.json();}).then(function(d){"
          "document.getElementById('txtStato').innerText=d.stato;"
          "document.getElementById('txtCorrente').innerText=d.corrente.toFixed(2)+' A';"
          "document.getElementById('btnResetContainer').style.display=d.allarme?'block':'none';"
          "var s=d.stato;"
          "var spinnerClass='spinner';"
          "if(s=='IN APERTURA...')spinnerClass='spinner open';"
          "else if(s=='IN CHIUSURA...')spinnerClass='spinner close';"
          "document.getElementById('spinnerStato').className=spinnerClass;"
          "document.getElementById('pillApri').className='pill'+((s=='PORTA APERTA'||s=='IN APERTURA...')?' active-open':'');"
          "document.getElementById('pillChiudi').className='pill'+((s=='PORTA CHIUSA'||s=='IN CHIUSURA...')?' active-close':'');"
          "document.getElementById('pillFerma').className='pill'+((s.indexOf('FERMATA')>=0||s=='PORTA FERMA'||s=='ALLARME BLOCCO'||s.indexOf('ERRORE')==0)?' active-stop':'');"
          "}).catch(function(){});"
          "}"
          "window.addEventListener('load',updateStatus);"
          "setInterval(updateStatus,500);"
          "</script>";
  html += "</head><body><div class='card'><h2>PORTA SMART</h2>";
  html += "<div style='font-size:80px;margin:20px;'>" + icon + "</div>";
  html += "<div class='statusline' style='color:" + color + ";'><span id='txtStato'>" + statoPorta + "</span><span id='spinnerStato' class='spinner'></span></div>";
  html += "<p>Corrente: <span id='txtCorrente'>" + String(currentAmps, 2) + " A</span></p>";
  html += "<div class='feedback'><b>Feedback stato</b>";
  html += "<div class='pillrow'>";
  html += "<span id='pillApri' class='pill'>APERTA</span>";
  html += "<span id='pillChiudi' class='pill'>CHIUSA</span>";
  html += "<span id='pillFerma' class='pill'>FERMATA A META'</span>";
  html += "</div></div><hr>";
  html += "<a href='/f'><button class='btn-open'>APRI</button></a>";
  html += "<a href='/r'><button class='btn-close'>CHIUDI</button></a>";
  html += "<a href='/s'><button class='btn-stop'>STOP</button></a>";
  html += "<div id='btnResetContainer' style='display:" + String(alarmLatched ? "block" : "none") + ";'>";
  html += "<a href='/reset_alarm'><button class='btn-reset'>RESET ALLARME</button></a></div>";
  html += "<br><br><a href='/settings'><button class='btn-settings'>IMPOSTAZIONI PASSWORD</button></a>";
  html += "<a href='/update'><button class='btn-ota'>AGGIORNA FIRMWARE OTA</button></a>";
  html += "</div></body></html>";

  server.send(200, "text/html", html);
}

void handleStatus() {
  if (!requireAuth()) return;

  String json = "{";
  json += "\"stato\":\"" + jsonEscape(statoPorta) + "\",";
  json += "\"comando\":\"" + jsonEscape(ultimoComando) + "\",";
  json += "\"corrente\":" + String(currentAmps, 2) + ",";
  json += "\"allarme\":" + String(alarmLatched ? "true" : "false");
  json += "}";

  server.send(200, "application/json", json);
}

void handleSettings() {
  if (!requireAuth()) return;

  String html = "<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'>";
  html += getCSS();
  html += "</head><body><div class='card'><h3>CAMBIA PASSWORD</h3>";
  html += "<form action='/save_pw' method='POST'>";
  html += "Nuova Password:<br><input type='password' name='new_pw' minlength='3' required><br>";
  html += "<button type='submit' class='btn-open'>SALVA E RIAVVIA</button>";
  html += "</form><a href='/'>Indietro</a></div></body></html>";

  server.send(200, "text/html", html);
}

void handleSavePW() {
  if (!requireAuth()) return;

  if (!server.hasArg("new_pw") || server.arg("new_pw").length() < 3) {
    server.send(400, "text/plain", "Password non valida.");
    return;
  }

  www_password = server.arg("new_pw");
  preferences.begin("access", false);
  preferences.putString("pw", www_password);
  preferences.end();

  restartPending = true;
  restartAtMillis = millis() + 3000;
  String html = "<!doctype html><html><head><meta charset='utf-8'>";
  html += "<meta name='viewport' content='width=device-width,initial-scale=1'>";
  html += "<meta http-equiv='refresh' content='8;url=/'>";
  html += getCSS();
  html += "</head><body><div class='card'>";
  html += "<h3>Password salvata</h3>";
  html += "<p>L'ESP32 si riavvia ora. La pagina tornera' alla schermata principale tra pochi secondi.</p>";
  html += "<a href='/'><button class='btn-open'>TORNA ALLA HOME</button></a>";
  html += "</div></body></html>";
  server.send(200, "text/html", html);
  Serial.println("Password web salvata. Riavvio programmato tra 3 secondi.");
}

// =====================================================
// RETE
// =====================================================

void startNetworkServices() {
  if (networkServicesStarted) return;
  Serial.println("Avvio dei servizi di rete in corso...");
  Serial.print("Indirizzo web porta: ");
  Serial.println(webBaseUrl());

  server.on("/", handleRoot);
  server.on("/status", handleStatus);
  server.on("/settings", handleSettings);
  server.on("/save_pw", HTTP_POST, handleSavePW);

  server.on("/f", []() {
    if (!requireAuth()) return;
    motorForward();
    redirectHome();
  });
  server.on("/r", []() {
    if (!requireAuth()) return;
    motorReverse();
    redirectHome();
  });
  server.on("/s", []() {
    if (!requireAuth()) return;
    motorStopInstant("FERMATA A META'");
    redirectHome();
  });
  server.on("/reset_alarm", []() {
    if (!requireAuth()) return;
    resetAlarm("PORTA FERMA");
    redirectHome();
  });
  server.onNotFound([]() {
    server.send(404, "text/plain", "Pagina non trovata");
  });

  ElegantOTA.setAuth(www_username.c_str(), www_password.c_str());
  ElegantOTA.begin(&server);

  server.begin();
  networkServicesStarted = true;
  Serial.println("Web Server avviato.");
  Serial.print("Pagina OTA: ");
  Serial.print(webBaseUrl());
  Serial.println("/update");
}

void beginWiFi() {
  if (wifiBeginDone) return;

  Serial.println("Preparazione WiFi...");
  WiFi.persistent(false);
  WiFi.disconnect(true, true);
  delay(300);
  WiFi.mode(WIFI_STA);
  WiFi.setSleep(false);
  WiFi.setAutoReconnect(true);

  WiFi.setHostname("Catenaccio-ESP32");
  Serial.print("Connessione WiFi in corso a SSID: ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  wifiBeginDone = true;
  lastWiFiStatusPrint = 0;
}

void handleWiFiStartup() {
  if (!wifiBeginDone) beginWiFi();

  wl_status_t status = WiFi.status();
  unsigned long now = millis();
  if (status == WL_CONNECTED) {
    if (wifiConnectedSince == 0) {
      wifiConnectedSince = now;
      Serial.print("WiFi connesso, IP assegnato: ");
      Serial.println(WiFi.localIP());
      Serial.print("Apri dalla rete di casa: ");
      Serial.println(webBaseUrl());
      Serial.flush();
    }

    if (!networkServicesStarted) startNetworkServices();
    return;
  }

  wifiConnectedSince = 0;
  if (lastWiFiStatusPrint == 0 || now - lastWiFiStatusPrint >= 2000) {
    lastWiFiStatusPrint = now;
    Serial.print("WiFi non ancora connesso, stato=");
    Serial.println((int)status);
  }
}

// =====================================================
// SETUP E LOOP
// =====================================================

void setup() {
  Serial.begin(115200);
  delay(500);

  Serial.println();
  Serial.println("=================================");
  Serial.println("   AVVIO SISTEMA PORTA SMART");
  Serial.println("=================================");
  Serial.print("Causa ultimo reset ESP32: ");
  Serial.println((int)esp_reset_reason());

  Wire.begin(21, 22);
  Wire.setTimeOut(50);
  currentSensorOk = ina219.begin();
  if (!currentSensorOk) {
    alarmLatched = true;
    statoPorta = "ERRORE SENSORE CORRENTE";
    Serial.println("ATTENZIONE: Sensore INA219 non rilevato.");
  } else {
    statoPorta = "PORTA FERMA";
    Serial.println("Sensore INA219 inizializzato correttamente.");
  }

  preferences.begin("access", true);
  www_password = preferences.getString("pw", "123");
  preferences.end();
  Serial.println("Password web caricata.");
  Serial.flush();

  Serial.println("Metto i pin motore in stato sicuro LOW.");
  Serial.flush();
  pinMode(RPWM, OUTPUT);
  pinMode(LPWM, OUTPUT);
  digitalWrite(RPWM, LOW);
  digitalWrite(LPWM, LOW);

  Serial.println("Avvio configurazione PWM motore...");
  Serial.flush();
  setupPwmPin(RPWM, RPWM_CHANNEL);
  setupPwmPin(LPWM, LPWM_CHANNEL);
  Serial.println("Spengo uscite PWM motore...");
  Serial.flush();
  updateMotorHardware();
  Serial.println("Configurazione PWM motore completata.");
  Serial.flush();

  calibrateCurrentZero();

  pinMode(BUTTON_FORWARD, INPUT_PULLUP);
  pinMode(BUTTON_REVERSE, INPUT_PULLUP);
  openButton.stableState = digitalRead(BUTTON_FORWARD);
  openButton.lastReading = openButton.stableState;
  closeButton.stableState = digitalRead(BUTTON_REVERSE);
  closeButton.lastReading = closeButton.stableState;
  Serial.println("Pulsanti di comando pronti.");

  beginWiFi();
  Serial.println("Setup completato. Pulsanti e protezioni restano attivi anche senza WiFi.");
}

void loop() {
  unsigned long now = millis();
  if (lastLoopAlivePrint == 0 || now - lastLoopAlivePrint >= 5000) {
    lastLoopAlivePrint = now;
    Serial.print("Loop attivo, heap libero=");
    Serial.println(ESP.getFreeHeap());
  }

  handleWiFiStartup();

  if (networkServicesStarted) {
    server.handleClient();
    ElegantOTA.loop();
  }

  updateCurrentReading();
  handleCurrentProtection();
  handleRamping();
  handlePhysicalButtons();

  if (restartPending && millis() >= restartAtMillis) {
    motorStopInstant("RIAVVIO...");
    Serial.println("Riavvio ESP32 ora.");
    Serial.flush();
    delay(100);
    ESP.restart();
  }

  // MODIFICATO PER GESTIONE TEMPO RESIDUO: Il controllo del fine corsa temporale ora usa la variabile dinamica 'corsaAttualeMassimaMS'
  if (activeDirection != STOPPED && targetPWM > 0 && (millis() - motionStart >= corsaAttualeMassimaMS)) {
    if (activeDirection == FORWARD) motorStopInstant("PORTA APERTA");
    else if (activeDirection == REVERSE) motorStopInstant("PORTA CHIUSA");
  }

  delay(5);
}