String fmtUptime() {
  unsigned long sec = millis() / 1000;
  unsigned int days = sec / 86400; sec %= 86400;
  unsigned int hours = sec / 3600; sec %= 3600;
  unsigned int mins = sec / 60;
  String s;
  if (days) { s += String(days); s += "d "; }
  if (hours < 10) s += "0"; s += String(hours); s += ":";
  if (mins < 10) s += "0"; s += String(mins);
  return s;
}

String currentLang() {
  String l = String(cfg.language); l.toUpperCase();
  if (l == "EN") l = "GB";
  if (l == "DE") l = "D";
  return l;
}

String txt(const char* key) {
  String l = currentLang();
  String k = String(key);
  if (l == "GB") {
    if (k=="status") return "Status"; if (k=="arrival") return "Arrival"; if (k=="wifi") return "WiFi"; if (k=="mqtt") return "MQTT"; if (k=="system") return "System"; if (k=="firmware") return "Firmware"; if (k=="info") return "Info";
    if (k=="connected") return "Connected"; if (k=="notConnected") return "Not connected"; if (k=="ip") return "IP address"; if (k=="freeheap") return "Free RAM / heap"; if (k=="quickActions") return "Quick actions"; if (k=="updateStatus") return "Update status"; if (k=="restart") return "Restart"; if (k=="resetConfig") return "Reset configuration"; if (k=="save") return "Save"; if (k=="saveRestart") return "Save and restart"; if (k=="password") return "Password"; if (k=="user") return "User"; if (k=="brokerHost") return "Broker host"; if (k=="useTls") return "Use TLS / secure";
    if (k=="languageCode") return "Language code"; if (k=="retained") return "Retained MQTT messages"; if (k=="saved") return "Saved."; if (k=="mqttSaved") return "MQTT saved."; if (k=="arrivalSaved") return "Arrival settings saved."; if (k=="firmwareUpload") return "Firmware upload"; if (k=="firmwareHelp") return "Select a compiled .bin file from Arduino IDE and upload it directly to NodeMCU."; if (k=="about") return "Arrival board with OLED display, MQTT and web configuration.";
    if (k=="boardName") return "Board name"; if (k=="trackCount") return "Number of tracks"; if (k=="rowTopic") return "Row topic"; if (k=="homeStation") return "Home station"; if (k=="displayInterval") return "Display interval"; if (k=="seconds") return "seconds"; if (k=="latest") return "Latest MQTT message";
  }
  if (l == "D") {
    if (k=="status") return "Status"; if (k=="arrival") return "Ankunft"; if (k=="wifi") return "WiFi"; if (k=="mqtt") return "MQTT"; if (k=="system") return "System"; if (k=="firmware") return "Firmware"; if (k=="info") return "Info";
    if (k=="connected") return "Verbunden"; if (k=="notConnected") return "Nicht verbunden"; if (k=="ip") return "IP-Adresse"; if (k=="freeheap") return "Freier RAM / Heap"; if (k=="quickActions") return "Schnellaktionen"; if (k=="updateStatus") return "Status aktualisieren"; if (k=="restart") return "Neustart"; if (k=="resetConfig") return "Konfiguration zurücksetzen"; if (k=="save") return "Speichern"; if (k=="saveRestart") return "Speichern und neu starten"; if (k=="password") return "Passwort"; if (k=="user") return "Benutzer"; if (k=="brokerHost") return "Broker Host"; if (k=="useTls") return "TLS / secure verwenden";
    if (k=="languageCode") return "Sprachcode"; if (k=="retained") return "Retained MQTT-Nachrichten"; if (k=="saved") return "Gespeichert."; if (k=="mqttSaved") return "MQTT gespeichert."; if (k=="arrivalSaved") return "Ankunft-Einstellungen gespeichert."; if (k=="firmwareUpload") return "Firmware hochladen"; if (k=="firmwareHelp") return "Wähle eine kompilierte .bin-Datei aus der Arduino IDE und lade sie direkt auf den NodeMCU."; if (k=="about") return "Ankunftstafel mit OLED-Display, MQTT und Web-Konfiguration.";
    if (k=="boardName") return "Name der Tafel"; if (k=="trackCount") return "Anzahl Gleise"; if (k=="rowTopic") return "Zeilen-Topic"; if (k=="homeStation") return "Heimatbahnhof"; if (k=="displayInterval") return "Display-Intervall"; if (k=="seconds") return "Sekunden"; if (k=="latest") return "Letzte MQTT-Nachricht";
  }
  if (k=="status") return "Status"; if (k=="arrival") return "Ankomst"; if (k=="wifi") return "WiFi"; if (k=="mqtt") return "MQTT"; if (k=="system") return "System"; if (k=="firmware") return "Firmware"; if (k=="info") return "Info";
  if (k=="connected") return "Tilsluttet"; if (k=="notConnected") return "Ikke forbundet"; if (k=="ip") return "IP-adresse"; if (k=="freeheap") return "Fri RAM / heap"; if (k=="quickActions") return "Hurtige handlinger"; if (k=="updateStatus") return "Opdater status"; if (k=="restart") return "Genstart"; if (k=="resetConfig") return "Nulstil konfiguration"; if (k=="save") return "Gem"; if (k=="saveRestart") return "Gem og genstart"; if (k=="password") return "Password"; if (k=="user") return "Bruger"; if (k=="brokerHost") return "Broker host"; if (k=="useTls") return "Brug TLS / secure";
  if (k=="languageCode") return "Sprogkode"; if (k=="retained") return "Retained MQTT beskeder"; if (k=="saved") return "Gemt."; if (k=="mqttSaved") return "MQTT er gemt."; if (k=="arrivalSaved") return "Ankomst er gemt."; if (k=="firmwareUpload") return "Firmware upload"; if (k=="firmwareHelp") return "Vælg en kompileret .bin-fil fra Arduino IDE og upload den direkte til NodeMCU."; if (k=="about") return "Ankomsttavle med OLED-visning, MQTT og web-konfiguration.";
  if (k=="boardName") return "Navn på tavle"; if (k=="trackCount") return "Antal spor"; if (k=="rowTopic") return "Række-topic"; if (k=="homeStation") return "Hjemmestation"; if (k=="displayInterval") return "Display interval"; if (k=="seconds") return "sekunder"; if (k=="latest") return "Seneste MQTT-besked";
  return String(key);
}

String htmlEscape(const String& s) { String r=s; r.replace("&","&amp;"); r.replace("<","&lt;"); r.replace(">","&gt;"); r.replace("\"","&quot;"); return r; }
void strlcpySafe(char* dst, const String& src, size_t len) { src.substring(0, len - 1).toCharArray(dst, len); }
String statusText(bool ok) { return ok ? String("<span class='pill ok'>OK</span>") : String("<span class='pill bad'>--</span>"); }

void setDefaults() {
  memset(&cfg, 0, sizeof(cfg));
  cfg.magic = CONFIG_MAGIC;
  strlcpy(cfg.hostname, "ankomsttavle", sizeof(cfg.hostname));
  strlcpy(cfg.language, "DK", sizeof(cfg.language));
  strlcpy(cfg.boardName, "Kaelderkoebing Ankomst", sizeof(cfg.boardName));
  cfg.mqttPort = 8883;
  cfg.mqttTls = true;
  cfg.retained = true;
  cfg.trackCount = 6;
  cfg.displayIntervalMs = 5000;
  strlcpy(cfg.rowTopic, "WDP/Custom/Rejseplanen/Row", sizeof(cfg.rowTopic));
  strlcpy(cfg.homeStation, "HBF", sizeof(cfg.homeStation));
}

void loadConfig() {
  EEPROM.begin(EEPROM_SIZE);
  EEPROM.get(0, cfg);
  if (cfg.magic != CONFIG_MAGIC || cfg.trackCount < 1 || cfg.trackCount > MAX_TRACKS || cfg.displayIntervalMs < 1000 || cfg.displayIntervalMs > 60000) {
    setDefaults(); EEPROM.put(0, cfg); EEPROM.commit();
  }
}
void saveConfig() { cfg.magic = CONFIG_MAGIC; EEPROM.put(0, cfg); EEPROM.commit(); }

String rowTopic() { return String(cfg.rowTopic); }
