#pragma once
#include "espos_core.h"
#include "fileSystemClass.h"
#include "raw_html.h"

bool rm_rf(const String &path);
bool rn_rf(const String &oldName, const String &newName);
bool makedir(const String &path);

extern bool passwdLoad();
extern bool passwdSave();
extern void passwdClear();

String editLast;

inline size_t dirSize(File dir){
    if(!dir || !dir.isDirectory()) return 0;
    size_t total = 0;
    File f = dir.openNextFile();
    while(f){
        if(f.isDirectory()){
            total += dirSize(f); // rekurzió File objektummal
        } else {
            total += f.size();
        }
        f.close();
        f = dir.openNextFile();
    }
    return total;
}

/* ===================== /list ===================== */
struct Entry {
    String name;
    bool dir;
    size_t size;
};

inline void sendFileList(AsyncWebServerRequest *r, std::vector<Entry> &v) {
    std::sort(v.begin(), v.end(), [](const Entry &a, const Entry &b) {
        if (a.dir != b.dir) return a.dir;
        return strcasecmp(a.name.c_str(), b.name.c_str()) < 0;
    });
    AsyncResponseStream *response = r->beginResponseStream("application/json");
    if (editLast != "") {
      response->print( "[{\"default\":\"" + editLast + "\"}" );
      editLast = "";
    } else {
      response->print("[");  
    }
    bool first = true;
    for (size_t i = 0; i < v.size(); i++) {
        if (v[i].name == "." ||
            v[i].name == ".." ||
            (!coreFolder   && v[i].name.equalsIgnoreCase("core")) ||
            (!systemFolder && v[i].name.equalsIgnoreCase("system")))
            continue;
        if (!first) response->print(",");
        first = false;
        response->print("{\"name\":\"");
        response->print(v[i].name);
        response->print("\",\"dir\":");
        response->print(v[i].dir ? "true" : "false");
        response->print(",\"size\":");
        response->print(v[i].size);
        response->print("}");
    }
    response->print("]");
    r->send(response);
}

inline void handleList(AsyncWebServerRequest *r) {

    String path = "/";

    if (r->hasParam("path", true)) {
        path = r->getParam("path", true)->value();
        if (!path.startsWith("/")) path = "/" + path;
    }

    std::vector<Entry> v;

#if defined(USE_SPIFFS)

    Dir dir = SPIFFS.openDir(path);

    std::vector<String> addedDirs;

    while (dir.next()) {

        String full = dir.fileName();

        if (!full.startsWith(path)) continue;

        String rel = full;

        if (path != "/") {
            rel.remove(0, path.length());
            if (rel.startsWith("/")) rel.remove(0, 1);
        } else {
            if (rel.startsWith("/")) rel.remove(0, 1);
        }

        if (!rel.length()) continue;

        int slash = rel.indexOf('/');

        // --- Fake directory ---
        if (slash >= 0) {

            String dirname = rel.substring(0, slash);

            if (dirname == "..") continue;

            if (path == "/") {
                if (!coreFolder && dirname == "core") continue;
                if (!systemFolder && dirname == "system") continue;
            }

            bool exists = false;

            for (size_t i = 0; i < addedDirs.size(); i++) {
                if (addedDirs[i] == dirname) {
                    exists = true;
                    break;
                }
            }

            if (exists) continue;

            addedDirs.push_back(dirname);

            size_t dsize = 0;

            Dir sub = SPIFFS.openDir(path == "/" ?
                                     "/" + dirname :
                                     path + "/" + dirname);

            while (sub.next()) {
                File f = sub.openFile("r");
                if (f) {
                    dsize += f.size();
                    f.close();
                }
            }

            v.push_back({dirname, true, dsize});

            continue;
        }

        File f = dir.openFile("r");

        if (!f) continue;

        size_t sz = f.size();

        if (path == "/") {
            if (!coreFolder && rel == "core") {
                f.close();
                continue;
            }

            if (!systemFolder && rel == "system") {
                f.close();
                continue;
            }
        }

        v.push_back({rel, false, sz});

        f.close();
    }

#else

    File dir = FSYS.open(path, "r");

    if (!dir || !dir.isDirectory()) {
        r->send(200, "application/json", "[]");
        return;
    }

    File x = dir.openNextFile();

    while (x) {

        String fullName = String(x.name());
        String n = fullName.substring(fullName.lastIndexOf('/') + 1);

        if (n == "..") {
            x = dir.openNextFile();
            continue;
        }

        if (path == "/") {
            if (!coreFolder && n == "core") {
                x = dir.openNextFile();
                continue;
            }

            if (!systemFolder && n == "system") {
                x = dir.openNextFile();
                continue;
            }
        }

        size_t sz = x.isDirectory() ? dirSize(x) : x.size();

        v.push_back({n, x.isDirectory(), sz});

        x = dir.openNextFile();
    }

#endif

    std::sort(v.begin(), v.end(), [](const Entry &a, const Entry &b) {

        if (a.name.equalsIgnoreCase("core")) return true;
        if (b.name.equalsIgnoreCase("core")) return false;

        if (a.name.equalsIgnoreCase("system")) return true;
        if (b.name.equalsIgnoreCase("system")) return false;

        if (a.dir != b.dir) return a.dir;

        String A = a.name;
        String B = b.name;

        A.toLowerCase();
        B.toLowerCase();

        return A < B;
    });

    String j = "[";

    if (editLast != "") {
        j += "{\"default\":\"" + editLast + "\"},";
    }

    editLast = "";

    for (size_t i = 0; i < v.size(); i++) {

      if (v[i].name != "12345") {        

        if (i) j += ",";

        j += "{\"name\":\"" + v[i].name +
             "\",\"dir\":" + (v[i].dir ? "true" : "false") +
             ",\"size\":" + String(v[i].size) + "}";
       
      }      
    }

    j += "]";

    r->send(200, "application/json", j);
}

/* ===================== /list_s ===================== */


inline void handleListSafe(AsyncWebServerRequest *r) {
    String path = "/";
    if (r->hasParam("path", true)) {
        path = r->getParam("path", true)->value();
        if (!path.startsWith("/")) path = "/" + path;
    }
    std::vector<Entry> v;
#if defined(USE_SPIFFS)
    Dir dir = SPIFFS.openDir(path);
    std::vector<String> addedDirs;
    while (dir.next()) {
        String full = dir.fileName();
        if (!full.startsWith(path)) continue;
        String rel = full;
        if (path != "/") {
            rel.remove(0, path.length());
            if (rel.startsWith("/")) rel.remove(0, 1);
        } else {
            if (rel.startsWith("/")) rel.remove(0, 1);
        }
        if (rel.length() == 0) continue;
        int slash = rel.indexOf('/');
        if (slash >= 0) {
            String dirname = rel.substring(0, slash);
            bool exists = false;
            for (size_t i = 0; i < addedDirs.size(); i++) {
                if (addedDirs[i] == dirname) {
                    exists = true;
                    break;
                }
            }
            if (!exists) {
                addedDirs.push_back(dirname);
                v.push_back({dirname, true, 0});
            }
            continue;
        }
        File f = dir.openFile("r");
        if (!f) continue;
        size_t sz = f.size();
        v.push_back({rel, false, sz});
        f.close();
    }
#else
    File dir = FSYS.open(path, "r");
    if (!dir || !dir.isDirectory()) {
        r->send(200, "application/json", "[]");
        return;
    }
    while (true) {
        File f = dir.openNextFile();
        if (!f) break;
        const char *raw = f.name();
        if (!raw) {
            f.close();
            continue;
        }
        size_t len = strlen(raw);
        bool valid = true;
        for (size_t i = 0; i < len; i++) {
            char c = raw[i];
            if (c < 32 || c > 126) {
                valid = false;
                break;
            }
        }
        if (!valid) {
            f.close();
            continue;
        }
        String full = String(raw);
        int idx = full.lastIndexOf('/');
        if (idx < 0) idx = -1;
        String name = full.substring(idx + 1);
        if (name.length() == 0) {
            f.close();
            continue;
        }
        bool isDir = f.isDirectory();
        size_t sz = isDir ? 0 : f.size();
        v.push_back({name, isDir, sz});
        f.close();
    }
    dir.close();
#endif
  sendFileList(r, v);
}



/* ===================== /flashfree ===================== */
inline void handleFlashFree(AsyncWebServerRequest *r) {
#if defined(ESP32)
    size_t total = FSYS.totalBytes();
    size_t used  = FSYS.usedBytes();
#elif defined(ESP8266)
    FSInfo info;
    FSYS.info(info);
    size_t total = info.totalBytes;
    size_t used  = info.usedBytes;
#endif
    r->send(200, "application/json",
        "{\"total\":" + String(total / 1024) +
        ",\"used\":"  + String(used  / 1024) + "}");
}


/* ===================== /upload ===================== */
inline void handleUpload(AsyncWebServerRequest *r, uint8_t *d, size_t l, size_t i, size_t t) {

    if (i == 0) {
        String name = r->getParam("name")->value();
        String path = r->getParam("path")->value();
        if (!path.endsWith("/")) path += "/";

        TarExtractor *tar = new TarExtractor();
        tar->begin(path + name);
        r->_tempObject = tar;

        if (!name.endsWith(".tar")) {
            r->_tempFile = FSYS.open(path + name, "w");
        }
    }

    TarExtractor *tar = (TarExtractor*) r->_tempObject;

    if (tar && tar->isEnabled()) {
        tar->processChunk(d, l);
    } else {
        if (r->_tempFile) r->_tempFile.write(d, l);
    }

    if (i + l == t) {
        if (tar && tar->isEnabled()) {
            delete tar;
        } else {
            if (r->_tempFile) r->_tempFile.close();
        }
    }
}

/* ===================== /download ===================== */
inline void handleDownload(AsyncWebServerRequest *r) {

    if (!r->hasParam("path", true)) {
        r->send(400, "text/plain", "Missing path");
        return;
    }

    String path = r->getParam("path", true)->value();

    path.replace("\\", "/");
    path.replace("//", "/");

    if (!path.startsWith("/"))
        path = "/" + path;

    // -------- FULL FS DOWNLOAD --------

    if (path == "/") {

        TarResponse *response = new TarResponse("/");

        response->addHeader(
            "Content-Disposition",
            "attachment; filename=\"fullFS.tar\""
        );

        r->send(response);
        return;
    }

#if defined(USE_SPIFFS)

    // SPIFFS: exists() unreliable for dirs

    File test = FSYS.open(path, "r");

    bool isDir = false;

    if (test) {
        isDir = test.isDirectory();
        test.close();
    }

    // virtual dir marker support
    if (!isDir) {

        String marker = path;

        if (!marker.endsWith("/"))
            marker += "/";

        marker += ".";

        File m = FSYS.open(marker, "r");

        if (m) {
            isDir = true;
            m.close();
        }
    }

    // -------- FILE DOWNLOAD --------

    if (!isDir) {

        File f = FSYS.open(path, "r");

        if (!f) {
            r->send(404, "text/plain", "Not found");
            return;
        }

        f.close();

        r->send(FSYS, path, "application/octet-stream", true);
        return;
    }

#else

    // LittleFS / ESP32 native

    if (!FSYS.exists(path)) {
        r->send(404, "text/plain", "Not found");
        return;
    }

    File f = FSYS.open(path, "r");

    if (!f) {
        r->send(404, "text/plain", "Open failed");
        return;
    }

    bool isDir = f.isDirectory();
    f.close();

    if (!isDir) {
        r->send(FSYS, path, "application/octet-stream", true);
        return;
    }

#endif

    // -------- DIRECTORY TAR DOWNLOAD --------

    String base = path;

    if (base.endsWith("/"))
        base.remove(base.length() - 1);

    int slash = base.lastIndexOf('/');

    if (slash >= 0)
        base = base.substring(slash + 1);

    if (base.length() == 0)
        base = "root";

    TarResponse *response = new TarResponse(path);

    response->addHeader(
        "Content-Disposition",
        "attachment; filename=\"" + base + ".tar\""
    );

    r->send(response);
}

/* ===================== /format ===================== */

inline void afterFormat(bool keep) {
    DEBUG("|   Create Folders ");
#if !defined(USE_SPIFFS)
    FSYS.mkdir("/core");
    FSYS.mkdir("/core/admin");
    FSYS.mkdir("/core/setup");
    FSYS.mkdir("/system");
    FSYS.mkdir("/system/setup");
#else
    File d;
    d = FSYS.open("/core/.", "w"); if (d) d.close();
    d = FSYS.open("/core/admin/.", "w"); if (d) d.close();
    d = FSYS.open("/core/setup/.", "w"); if (d) d.close();
    d = FSYS.open("/system/.", "w"); if (d) d.close();
    d = FSYS.open("/system/setup/.", "w"); if (d) d.close();
#endif
    if (!keep) {
        passwdClear();
        loadWifiDefaults();
        saveWifiConfig();
      } else {
      DEBUG("|   Restore Config");  
      }
    File f = FSYS.open(DEFPAGE, "w");
    if (!f) return;
    size_t len = strlen_P(index_html);
    for (size_t i = 0; i < len; i++) {
        f.write(pgm_read_byte(index_html + i));
    }
    f.close();
    wifiLed.state(0);
}

inline void handleFormat(AsyncWebServerRequest *r) {
/*
    if (!r->hasParam("confirm", true) ||
        r->getParam("confirm", true)->value() != "YES") {
        r->send(400, "application/json",
                "{\"ok\":false,\"error\":\"CONFIRM_REQUIRED\"}");
        return;
    }
*/
    // Format
    wifiLed.state(1);
    if (!FSYS.format()) {
        r->send(500, "application/json", "{\"ok\":false,\"error\":\"FORMAT_FAILED\"}");
        return;
    }
#if defined(ESP32)
    if (!FSYS.begin(true)) {
        r->send(500, "application/json", "{\"ok\":false,\"error\":\"MOUNT_FAILED_AFTER_FORMAT\"}");
        return;
    }
#elif defined(ESP8266)
    if (!FSYS.begin()) {
        r->send(500, "application/json", "{\"ok\":false,\"error\":\"MOUNT_FAILED_AFTER_FORMAT\"}");
        return;
    }
#endif
    afterFormat(true);
    r->send(200, "application/json",
            "{\"ok\":true,\"msg\":\"FS formatted and core/system created\"}");
}

inline void handleDefault(AsyncWebServerRequest *r) {
    loadWifiDefaults();
    saveWifiConfig();
    passwdLoad();
    r->send(200, "text/html", init_setup_html, wifiProcessor);
}

inline void handleSaveAll(AsyncWebServerRequest *r) {
    saveWifiConfig();
    passwdSave();
    r->send(200, "text/html", init_setup_html, wifiProcessor);
}


/* ===================== /mkdir ===================== */
inline void handleMkdir(AsyncWebServerRequest *r) {
    r->send(makedir(r->getParam("path", true)->value()) ? 200 : 500);
}

/* ===================== /delete ===================== */
inline void handleDelete(AsyncWebServerRequest *r) {
    r->send(rm_rf(r->getParam("path", true)->value()) ? 200 : 500);
}

/* ===================== /rename ===================== */
inline void handleRename(AsyncWebServerRequest *r) {
    r->send(rn_rf(
        r->getParam("old", true)->value(),
        r->getParam("new", true)->value()
    ) ? 200 : 500);
}

/* ===================== /editor.html ===================== */
inline void handleEditorSafe(AsyncWebServerRequest *r) {
    AsyncWebServerResponse* response;
    response = r->beginResponse(200, "text/html", edit_html); 
    String path = "";
    if (r->hasParam("path", true)) path = r->getParam("path", true)->value();
    response->addHeader("Set-Cookie", "editpath=" + path);
    editLast = path;
    r->send(response);
}
inline void handleEditor(AsyncWebServerRequest *r) {
    AsyncWebServerResponse* response;
    if (FSYS.exists(EDITHTML)) {
      response = r->beginResponse(FSYS, EDITHTML, "text/html");
      } else {
      response = r->beginResponse(200, "text/html", edit_html);
      }   
    String path = "";
    if (r->hasParam("path", true)) path = r->getParam("path", true)->value();
    response->addHeader("Set-Cookie", "editpath=" + path);
    editLast = path;
    r->send(response);
}

/* ===================== /style.js ===================== */
inline String styleProcessor(const String& var) {
    if (var.equals(F("STYLE_FILE"))) {
        if (FSYS.exists(STYLE_FILE)) {
            return STYLE_FILE;
        } else {
            return "";
        }
    }
    if (var.equals(F("STYLE_BG"))) return STYLE_BG;
    return String();
}

inline void handleStyle(AsyncWebServerRequest *r) {
    auto *resp = r->beginResponse(200, "application/javascript", style_html, styleProcessor);
    resp->addHeader("Cache-Control", "no-cache, must-revalidate");
    r->send(resp);
}


/* ================= REKURZÍV TÖRLÉS ================= */
inline bool rm_rf(const String &path){

    DEBUG("delete " + path);

    if(path.isEmpty() || path == "/") return false;

#if defined(USE_LITTLEFS)

    File dir = FSYS.open(path, "r");
    if(!dir) return false;

    if(!dir.isDirectory()){
        dir.close();
        return FSYS.remove(path);
    }

    std::vector<String> items;

    File f = dir.openNextFile();

    while(f){

        String name = String(f.name());

        if(!name.startsWith("/")){
            if(path.endsWith("/"))
                name = path + name;
            else
                name = path + "/" + name;
        }

        items.push_back(name);

        f.close();
        f = dir.openNextFile();
    }

    dir.close();

    for(size_t i=0;i<items.size();i++){

        File x = FSYS.open(items[i], "r");

        if(x && x.isDirectory()){
            x.close();
            rm_rf(items[i]);
        }else{
            if(x) x.close();
            FSYS.remove(items[i]);
        }
    }

    return FSYS.rmdir(path);

#else
    // ===== SPIFFS =====

    String base = path;
    if (!base.startsWith("/")) base = "/" + base;

    // 1) Ha a path egy fájl → töröljük és kész
    if (FSYS.exists(base)) {
        // SPIFFS-ben nincs isDirectory(), tehát ha létezik → fájl
        return FSYS.remove(base);
    }

    // 2) Könyvtár prefix normalizálás
    if (!base.endsWith("/")) base += "/";

    Dir dir = FSYS.openDir("/");
    bool ok = true;

    while (dir.next()) {
        String fileName = dir.fileName();

        if (!fileName.startsWith("/"))
            fileName = "/" + fileName;

        // Csak az adott könyvtárban lévő fájlokat töröljük
        if (fileName.startsWith(base)) {
            if (!FSYS.remove(fileName))
                ok = false;
        }
    }

    return ok;
#endif
}

/*=============================MKDIR========================================*/
inline bool makedir(const String &rawPath) {

    if (rawPath.isEmpty() || rawPath == "/") return false;

    // Normalize
    String path = rawPath;
    path.replace("//", "/");
    
    if (!path.startsWith("/")) path = "/" + path;

#if defined(USE_LITTLEFS)

    // Ha már létezik → OK
    {
        File f = FSYS.open(path, "r");
        if (f && f.isDirectory()) {
            f.close();
            return true;
        }
        if (f) f.close();
    }

    // Parent létrehozása
    int slash = path.lastIndexOf('/');
    if (slash > 0) {
        String parent = path.substring(0, slash);
        File p = FSYS.open(parent, "r");
        if (!p || !p.isDirectory()) {
            if (p) p.close();
            if (!makedir(parent)) return false;
        } else {
            p.close();
        }
    }
    DEBUG("mkdir " + path);  
    return FSYS.mkdir(path);

#else
    // ===== SPIFFS =====
    // SPIFFS-ben nincsenek könyvtárak → "." markerrel szimuláljuk

    // Root könyvtár → nem kell marker, mindig létezik
    if (path == "/")
        return true;

    // Parent létrehozása
    int slash = path.lastIndexOf('/');
    if (slash > 0) {
        String parent = path.substring(0, slash);
        if (!makedir(parent)) return false;
    }

    // Marker fájl neve: /dirname/.
    // Példa: path="/config/user" → marker="/config/user/."
    String marker = path;
    if (!marker.endsWith("/")) marker += "/";
    marker += ".";

    // Ha már létezik → OK
    if (FSYS.exists(marker))
        return true;

    // Marker létrehozása
    File f = FSYS.open(marker, "w");
    if (!f) return false;
    f.close();
    DEBUG("mkdir " + path);  
    return true;
#endif
}


inline bool rn_rf(const String &oldPath, const String &newPath) {

    DEBUG("rename " + oldPath + " -> " + newPath);

    if (oldPath.isEmpty() || newPath.isEmpty()) return false;
    if (oldPath == "/" || newPath == "/") return false;

#if defined(USE_LITTLEFS)
    // ===== LittleFS =====

    File f = FSYS.open(oldPath, "r");
    if (!f) return false;

    // FÁJL → sima rename
    if (!f.isDirectory()) {
        f.close();
        return FSYS.rename(oldPath, newPath);
    }

    // KÖNYVTÁR → rekurzív átnevezés
    f.close();

    // Létrehozzuk az új könyvtárat
    if (!FSYS.mkdir(newPath)) {
        // Ha már létezik, az is ok
        if (!FSYS.exists(newPath)) return false;
    }

    File dir = FSYS.open(oldPath, "r");
    File entry = dir.openNextFile();

    while (entry) {
        String name = entry.name();
        String src = name;
        String dst;

        // Új path összeállítása
        if (name.startsWith(oldPath)) {
            dst = newPath + name.substring(oldPath.length());
        } else {
            // fallback
            dst = newPath + "/" + name;
        }

        if (entry.isDirectory()) {
            entry.close();
            if (!rn_rf(src, dst)) return false;
        } else {
            entry.close();
            if (!FSYS.rename(src, dst)) return false;
        }

        entry = dir.openNextFile();
    }

    dir.close();

    // Régi könyvtár törlése
    return FSYS.rmdir(oldPath);

#else
    // ===== SPIFFS =====
    // SPIFFS-ben nincs könyvtár → minden fájl
    // A rename csak fájlokra működik

    String oldBase = oldPath;
    if (!oldBase.startsWith("/")) oldBase = "/" + oldBase;

    String newBase = newPath;
    if (!newBase.startsWith("/")) newBase = "/" + newBase;

    // Ha pontos fájl → sima rename
    if (FSYS.exists(oldBase)) {
        return FSYS.rename(oldBase, newBase);
    }

    // Könyvtár-szimuláció prefix alapján
    if (!oldBase.endsWith("/")) oldBase += "/";
    if (!newBase.endsWith("/")) newBase += "/";

    Dir dir = FSYS.openDir("/");
    bool ok = true;

    while (dir.next()) {
        String fileName = dir.fileName();
        if (!fileName.startsWith("/")) fileName = "/" + fileName;

        if (fileName.startsWith(oldBase)) {
            String suffix = fileName.substring(oldBase.length());
            String dst = newBase + suffix;

            if (!FSYS.rename(fileName, dst))
                ok = false;
        }
    }

    return ok;
#endif
}




inline String fmtSize() {
#if defined(ESP32)
    size_t total = FSYS.totalBytes();
    size_t used  = FSYS.usedBytes();
#elif defined(ESP8266)
    FSInfo info;
    FSYS.info(info);
    size_t total = info.totalBytes;
    size_t used  = info.usedBytes;
#endif
    return "|   Total: " + String(total / 1024) + "kB\n|   Free : " + String((total - used) / 1024) + "kB";
}

inline void onFormatRequired() {
    wifiLed.state(1);
    FSYS.begin();
    DEBUG("|   !!! Force format !!!");
    if (FSYS.format()) {
      afterFormat(false);
      DEBUG("|   Format OK, restarting...");
      } else {
      DEBUG("|   !!! Format FAILED !!!");
      }
    DEBUG(debugSeparator);
    delay(5000);
    ESP.restart();
    return;
}

inline void initFS(bool f, uint8_t rst, uint16_t wait) {
    DEBUG (FSTXT);
    ResetCounter::begin(rst, 511, wait, onFormatRequired); 
    if (rst) DEBUG("|   Format DownCount: ", 1);
    if (rst) DEBUG(rst - ResetCounter::get() + 1);
    if (FSYS.begin() and !f) {
      DEBUG (fmtSize());
      loadWifiConfig();
      passwdLoad();
    } else {
      DEBUG ("|   \nFSYS mount failed!");
      onFormatRequired();
      }
    if (!(FSYS.exists(SETUP_WIFI_FILE))) {
      DEBUG("|   Not found wifi.bin!!!");
      afterFormat(false);
    }
    DEBUG(debugSeparator);
}




//
