void updateMinHeap() {
  lastFreeHeap = ESP.getFreeHeap();
  if (lastFreeHeap < minFreeHeap) minFreeHeap = lastFreeHeap;
}

void logHealth(const char* reason) {
  updateMinHeap();
  Serial.print(F("[")); Serial.print(millis()/1000); Serial.print(F("s] "));
  Serial.print(reason);
  Serial.print(F(" | heap=")); Serial.print(lastFreeHeap);
  Serial.print(F(" min=")); Serial.print(minFreeHeap);
  Serial.print(F(" | WiFi=")); Serial.print(WiFi.isConnected() ? F("OK") : F("--"));
  if (WiFi.isConnected()) { Serial.print(F(" ")); Serial.print(WiFi.localIP()); Serial.print(F(" RSSI=")); Serial.print(WiFi.RSSI()); }
  Serial.print(F(" | MQTT=")); Serial.print(mqtt.connected() ? F("OK") : F("--"));
  Serial.print(F(" | DHT=")); Serial.println(lastDhtStatus);
}

void logHealthTimed() {
  if (millis() - lastLog > 30000) {
    lastLog = millis();
    logHealth("status");
  }
}

void updateDisplay();

void setupMqtt() {
  if (cfg.mqttTls) {
    secureClient.setInsecure();
    secureClient.setBufferSizes(512, 512);
    mqtt.setClient(secureClient);
  } else {
    mqtt.setClient(plainClient);
  }
  mqtt.setBufferSize(256);
  mqtt.setServer(cfg.mqttHost, cfg.mqttPort);
  mqtt.setCallback([](char* topic, byte* payload, unsigned int length){
    String msg; for (unsigned int i=0;i<length;i++) msg += (char)payload[i]; msg.trim();
    if (String(topic) == String(cfg.topicLed)) {
      ledState = (msg == "1" || msg == "ON" || msg == "on" || msg == "true");
      digitalWrite(LED_PIN, ledState ? HIGH : LOW);
    }
  });
}

void connectMqttNonBlocking() {
  if (!WiFi.isConnected() || mqtt.connected() || millis() - lastMqttAttempt < 5000) return;
  lastMqttAttempt = millis();
  String id = String(cfg.hostname) + "-" + String(ESP.getChipId(), HEX);
  bool ok;
  if (strlen(cfg.mqttUser) > 0) ok = mqtt.connect(id.c_str(), cfg.mqttUser, cfg.mqttPassword);
  else ok = mqtt.connect(id.c_str());
  if (ok) { mqtt.subscribe(cfg.topicLed); lastMqttStatus = "connected"; updateDisplay(); logHealth("mqtt connected"); }
  else { lastMqttStatus = "connect failed"; logHealth("mqtt failed"); }
}

void connectWifiOrPortal() {
  if (strlen(cfg.wifiSsid) == 0) {
    portalMode = true;
  } else {
    WiFi.mode(WIFI_STA);
    WiFi.hostname(cfg.hostname);
    WiFi.begin(cfg.wifiSsid, cfg.wifiPassword);
    unsigned long start = millis();
    while (WiFi.status() != WL_CONNECTED && millis() - start < 15000) { delay(250); }
    portalMode = WiFi.status() != WL_CONNECTED;
  }
  if (portalMode) {
    logHealth("wifi portal");
    WiFi.mode(WIFI_AP_STA);
    WiFi.softAP(String(APP_NAME).substring(0,12).c_str());
    dnsServer.start(53, "*", WiFi.softAPIP());
  }
  if (!portalMode) logHealth(WiFi.isConnected() ? "wifi connected" : "wifi failed");
}

void updateDisplay() {
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);
  display.setCursor(0,0);
  display.print(cfg.sensorName);
  display.setCursor(0,10);
  display.print(WiFi.isConnected() ? "WiFi OK" : "WiFi --");
  display.print("  ");
  display.print(mqtt.connected() ? "MQTT OK" : "MQTT --");
  display.setTextSize(2);
  display.setCursor(0,26);
  if (isnan(lastTemp)) display.print("--.-"); else display.print(lastTemp,1);
  display.print((char)247); display.print("C");
  display.setCursor(0,46);
  if (isnan(lastHum)) display.print("--.-"); else display.print(lastHum,1);
  display.print(" %");
  display.display();
}

void readAndPublish() {
  if (millis() - lastPublish < cfg.publishIntervalMs) return;
  lastPublish = millis();
  delay(dht.getMinimumSamplingPeriod());
  float h = dht.getHumidity();
  float t = dht.getTemperature();
  bool dhtOk = false;
  // Simpelt filter: ignorer helt urealistiske eller fejlbehæftede målinger.
  if (!isnan(t) && t > -30.0 && t < 80.0) { lastTemp = t; dhtOk = true; }
  if (!isnan(h) && h >= 0.0 && h <= 100.0) { lastHum = h; dhtOk = true; }
  lastDhtStatus = dhtOk ? "OK" : "fejl";
  if (mqtt.connected() && !isnan(lastTemp) && !isnan(lastHum)) {
    char tb[16]; char hb[16];
    dtostrf(lastTemp, 0, 1, tb);
    dtostrf(lastHum,  0, 1, hb);
    mqtt.publish(cfg.topicTemp, tb, cfg.retained);
    mqtt.publish(cfg.topicHum,  hb, cfg.retained);
  }
  updateDisplay();
  logHealth("publish");
}

