/*
    * filesystem.cpp
    * Filesystem enumeration and copy/delete helpers for the embedded desktop file manager
    * Copyright (c) 2026 Daniel Hammer
*/

#include "filemanager_internal.hpp"

namespace filemanager {

static void filemanager_reset_list_state(FileManagerState* fm) {
    fm->selected = -1;
    fm->scroll_offset = 0;
    fm->scrollbar.scroll_offset = 0;
    fm->last_click_item = -1;
    fm->last_click_time = 0;
}

static void filemanager_reset_virtual_entry(FileManagerVirtualEntry* entry) {
    if (!entry) return;
    entry->kind = FM_VIRTUAL_ENTRY_NONE;
    entry->external_app_index = -1;
    montauk::memset(&entry->applet, 0, sizeof(entry->applet));
    entry->applet.kind = DESKTOP_APPLET_NONE;
    entry->applet.builtin_id = DESKTOP_BUILTIN_NONE;
    entry->icon_lg = {};
    entry->icon_sm = {};
}

static void filemanager_add_root_entry(FileManagerState* fm,
                                       const char* name,
                                       FileManagerEntryType type,
                                       int drive_index) {
    if (!fm || fm->entry_count >= 64) return;

    int idx = fm->entry_count++;
    montauk::strncpy(fm->entry_names[idx], name, 63);
    fm->entry_types[idx] = type;
    fm->entry_sizes[idx] = 0;
    fm->is_dir[idx] = true;
    fm->drive_indices[idx] = drive_index;
}

static int filemanager_add_virtual_entry(FileManagerState* fm,
                                         const char* name,
                                         const char* icon_path,
                                         FileManagerVirtualEntryKind kind) {
    if (!fm || fm->entry_count >= 64) return -1;

    int idx = fm->entry_count;
    montauk::strncpy(fm->entry_names[idx], name, 63);
    fm->entry_types[idx] = FM_ENTRY_VIRTUAL_APP;
    fm->entry_sizes[idx] = 0;
    fm->is_dir[idx] = false;
    fm->drive_indices[idx] = -1;
    filemanager_reset_virtual_entry(&fm->virtual_entries[idx]);
    fm->virtual_entries[idx].kind = kind;

    if (icon_path && icon_path[0]) {
        Color defColor = colors::ICON_COLOR;
        fm->virtual_entries[idx].icon_lg = svg_load(icon_path, 48, 48, defColor);
        fm->virtual_entries[idx].icon_sm = svg_load(icon_path, 16, 16, defColor);
    }

    fm->entry_count++;
    fm->virtual_entry_count = fm->entry_count;
    return idx;
}

static void filemanager_add_external_app_entry(FileManagerState* fm,
                                               const ExternalApp& app,
                                               int external_index) {
    int idx = filemanager_add_virtual_entry(fm, app.name, app.icon_path,
                                            FM_VIRTUAL_ENTRY_EXTERNAL_APP);
    if (idx >= 0) {
        fm->virtual_entries[idx].external_app_index = external_index;
    }
}

static void filemanager_add_system_configuration_applet_entry(
    FileManagerState* fm,
    const DesktopAppletEntry& applet) {
    int idx = filemanager_add_virtual_entry(fm, applet.label, applet.icon_path,
                                            FM_VIRTUAL_ENTRY_SYSTEM_CONFIGURATION_APPLET);
    if (idx >= 0) {
        fm->virtual_entries[idx].applet = applet;
    }
}

static void filemanager_begin_virtual_view(FileManagerState* fm, FileManagerVirtualViewKind view) {
    filemanager_free_app_icons(fm);
    fm->current_path[0] = '\0';
    fm->virtual_view = view;
    fm->entry_count = 0;
}

void filemanager_read_drives(FileManagerState* fm) {
    filemanager_free_app_icons(fm);
    fm->entry_count = 0;
    fm->virtual_view = FM_VIRTUAL_VIEW_NONE;
    fm->current_path[0] = '\0';

    // Home folder entry (if we have a valid home directory)
    DesktopState* ds = fm->desktop;
    if (ds && ds->home_dir[0] != '\0') {
        filemanager_add_root_entry(fm, "Home", FM_ENTRY_HOME, -1);
    }

    // Apps entry
    filemanager_add_root_entry(fm, "Apps", FM_ENTRY_APPS_ROOT, -1);

    // Special user folders (Documents, Desktop, Music, etc.) - only if they exist
    if (ds && ds->home_dir[0] != '\0') {
        for (int sf = 0; sf < SF_COUNT && fm->entry_count < 64; sf++) {
            char probe_path[256];
            montauk::strcpy(probe_path, ds->home_dir);
            int plen = montauk::slen(probe_path);
            if (plen > 0 && probe_path[plen - 1] != '/')
                str_append(probe_path, "/", 256);
            str_append(probe_path, sf_names[sf], 256);

            // Probe: try open to check if the directory exists
            int probe_fd = montauk::open(probe_path);
            if (probe_fd >= 0) {
                montauk::close(probe_fd);
                filemanager_add_root_entry(fm, sf_names[sf], FM_ENTRY_SPECIAL_DIR, sf);
            }
        }
    }

    // System configuration entry
    filemanager_add_root_entry(fm, "System Configuration",
                               FM_ENTRY_SYSTEM_CONFIGURATION_ROOT, -1);

    // Drive entries
    int drives[FM_MAX_DRIVES];
    int driveCount = montauk::drivelist(drives, FM_MAX_DRIVES);

    for (int di = 0; di < driveCount; di++) {
        int d = drives[di];
        char probe[8];
        if (d < 10) {
            probe[0] = '0' + d;
            probe[1] = ':';
            probe[2] = '/';
            probe[3] = '\0';
        } else {
            probe[0] = '1';
            probe[1] = '0' + (d - 10);
            probe[2] = ':';
            probe[3] = '/';
            probe[4] = '\0';
        }
        char label[64];
        montauk::strcpy(label, "Drive ");
        str_append(label, probe, 64);
        char volume_label[64];
        if (montauk::drivelabel(d, volume_label, 64) > 0 && volume_label[0] != '\0') {
            str_append(label, " [", 64);
            str_append(label, volume_label, 64);
            str_append(label, "]", 64);
        }
        filemanager_add_root_entry(fm, label, FM_ENTRY_DRIVE, d);
        if (fm->entry_count >= 64) break;
    }

    filemanager_reset_list_state(fm);
}

void filemanager_read_dir(FileManagerState* fm) {
    if (fm->current_path[0] == '\0') {
        filemanager_read_drives(fm);
        return;
    }

    filemanager_free_app_icons(fm);
    fm->virtual_view = FM_VIRTUAL_VIEW_NONE;

    const char* names[64];
    fm->entry_count = montauk::readdir(fm->current_path, names, 64);
    if (fm->entry_count < 0) fm->entry_count = 0;

    // readdir returns full paths from the VFS (e.g. "man/fetch.1" instead
    // of just "fetch.1").  Compute the prefix to strip so we get basenames.
    const char* after_drive = fm->current_path;
    for (int k = 0; after_drive[k]; k++) {
        if (after_drive[k] == ':' && after_drive[k + 1] == '/') {
            after_drive += k + 2;
            break;
        }
    }
    char prefix[256] = {0};
    int prefix_len = 0;
    if (after_drive[0] != '\0') {
        montauk::strcpy(prefix, after_drive);
        prefix_len = montauk::slen(prefix);
        if (prefix_len > 0 && prefix[prefix_len - 1] != '/') {
            prefix[prefix_len++] = '/';
            prefix[prefix_len] = '\0';
        }
    }

    for (int i = 0; i < fm->entry_count; i++) {
        const char* raw = names[i];
        // Strip directory prefix if it matches
        if (prefix_len > 0) {
            bool match = true;
            for (int k = 0; k < prefix_len; k++) {
                if (raw[k] != prefix[k]) { match = false; break; }
            }
            if (match) raw += prefix_len;
        }
        montauk::strncpy(fm->entry_names[i], raw, 128);
        int len = montauk::slen(fm->entry_names[i]);

        // Detect directory
        if (len > 0 && fm->entry_names[i][len - 1] == '/') {
            fm->is_dir[i] = true;
            fm->entry_names[i][len - 1] = '\0';
        } else {
            fm->is_dir[i] = false;
        }

        fm->entry_types[i] = detect_file_type(fm->entry_names[i], fm->is_dir[i]);

        // Get file size
        fm->entry_sizes[i] = 0;
        if (!fm->is_dir[i]) {
            char fullpath[512];
            montauk::strcpy(fullpath, fm->current_path);
            int plen = montauk::slen(fullpath);
            if (plen > 0 && fullpath[plen - 1] != '/') {
                str_append(fullpath, "/", 512);
            }
            str_append(fullpath, fm->entry_names[i], 512);
            int fd = montauk::open(fullpath);
            if (fd >= 0) {
                fm->entry_sizes[i] = (int)montauk::getsize(fd);
                montauk::close(fd);
            }
        }
    }

    // Sort: directories first, then alphabetical (case-insensitive)
    for (int i = 1; i < fm->entry_count; i++) {
        char tmp_name[128];
        int tmp_type = fm->entry_types[i];
        int tmp_size = fm->entry_sizes[i];
        bool tmp_isdir = fm->is_dir[i];
        montauk::strcpy(tmp_name, fm->entry_names[i]);

        int j = i - 1;
        while (j >= 0) {
            bool swap = false;
            if (tmp_isdir && !fm->is_dir[j]) {
                swap = true;
            } else if (tmp_isdir == fm->is_dir[j]) {
                if (str_compare_ci(tmp_name, fm->entry_names[j]) < 0) {
                    swap = true;
                }
            }
            if (!swap) break;

            montauk::strcpy(fm->entry_names[j + 1], fm->entry_names[j]);
            fm->entry_types[j + 1] = fm->entry_types[j];
            fm->entry_sizes[j + 1] = fm->entry_sizes[j];
            fm->is_dir[j + 1] = fm->is_dir[j];
            j--;
        }
        montauk::strcpy(fm->entry_names[j + 1], tmp_name);
        fm->entry_types[j + 1] = tmp_type;
        fm->entry_sizes[j + 1] = tmp_size;
        fm->is_dir[j + 1] = tmp_isdir;
    }

    filemanager_reset_list_state(fm);
}

void filemanager_free_app_icons(FileManagerState* fm) {
    if (!fm) return;

    for (int i = 0; i < fm->virtual_entry_count; i++) {
        if (fm->virtual_entries[i].icon_lg.pixels) {
            montauk::mfree(fm->virtual_entries[i].icon_lg.pixels);
            fm->virtual_entries[i].icon_lg.pixels = nullptr;
        }
        if (fm->virtual_entries[i].icon_sm.pixels) {
            montauk::mfree(fm->virtual_entries[i].icon_sm.pixels);
            fm->virtual_entries[i].icon_sm.pixels = nullptr;
        }
        filemanager_reset_virtual_entry(&fm->virtual_entries[i]);
    }
    fm->virtual_entry_count = 0;
}

void filemanager_read_virtual_view(FileManagerState* fm, FileManagerVirtualViewKind view) {
    switch (view) {
    case FM_VIRTUAL_VIEW_APPS: filemanager_read_apps(fm); break;
    case FM_VIRTUAL_VIEW_SYSTEM_CONFIGURATION:
        filemanager_read_system_configuration(fm);
        break;
    default: filemanager_read_drives(fm); break;
    }
}

void filemanager_read_apps(FileManagerState* fm) {
    filemanager_begin_virtual_view(fm, FM_VIRTUAL_VIEW_APPS);

    DesktopState* ds = fm->desktop;
    if (!ds) return;

    for (int i = 0; i < ds->external_app_count && fm->entry_count < 64; i++) {
        filemanager_add_external_app_entry(fm, ds->external_apps[i], i);
    }

    filemanager_reset_list_state(fm);
}

void filemanager_read_system_configuration(FileManagerState* fm) {
    filemanager_begin_virtual_view(fm, FM_VIRTUAL_VIEW_SYSTEM_CONFIGURATION);

    DesktopAppletEntry applets[32];
    int applet_count = desktop_list_system_configuration_applets(fm->desktop, applets, 32);
    for (int i = 0; i < applet_count && fm->entry_count < 64; i++) {
        filemanager_add_system_configuration_applet_entry(fm, applets[i]);
    }

    filemanager_reset_list_state(fm);
}

bool filemanager_delete_recursive(const char* path) {
    // Try deleting as a file (or empty directory) first
    if (montauk::fdelete(path) == 0) return true;

    // If that failed, it may be a non-empty directory -- enumerate and delete children
    const char* names[64];
    int count = montauk::readdir(path, names, 64);
    if (count < 0) return false;

    // Compute the prefix to strip (readdir returns paths relative to drive root)
    const char* after_drive = path;
    for (int k = 0; after_drive[k]; k++) {
        if (after_drive[k] == ':' && after_drive[k + 1] == '/') {
            after_drive += k + 2;
            break;
        }
    }
    char prefix[256] = {0};
    int prefix_len = 0;
    if (after_drive[0] != '\0') {
        montauk::strcpy(prefix, after_drive);
        prefix_len = montauk::slen(prefix);
        if (prefix_len > 0 && prefix[prefix_len - 1] != '/') {
            prefix[prefix_len++] = '/';
            prefix[prefix_len] = '\0';
        }
    }

    for (int i = 0; i < count; i++) {
        const char* raw = names[i];
        if (prefix_len > 0) {
            bool match = true;
            for (int k = 0; k < prefix_len; k++) {
                if (raw[k] != prefix[k]) { match = false; break; }
            }
            if (match) raw += prefix_len;
        }

        char child[512];
        montauk::strcpy(child, path);
        int plen = montauk::slen(child);
        if (plen > 0 && child[plen - 1] != '/')
            str_append(child, "/", 512);
        str_append(child, raw, 512);

        // Strip trailing slash if present (directory marker)
        int clen = montauk::slen(child);
        if (clen > 0 && child[clen - 1] == '/') child[clen - 1] = '\0';

        filemanager_delete_recursive(child);
    }

    // Now the directory should be empty -- delete it
    return montauk::fdelete(path) == 0;
}

bool filemanager_copy_file(const char* src, const char* dst) {
    if (!src || !dst || montauk::streq(src, dst)) return false;

    int sfd = montauk::open(src);
    if (sfd < 0) return false;
    uint64_t size = montauk::getsize(sfd);

    // Create destination
    int dfd = montauk::fcreate(dst);
    if (dfd < 0) {
        montauk::close(sfd);
        return false;
    }

    bool ok = true;
    if (size > 0) {
        static constexpr uint64_t COPY_CHUNK = 64 * 1024;
        uint64_t buf_size = size < COPY_CHUNK ? size : COPY_CHUNK;
        uint8_t* buf = (uint8_t*)montauk::malloc(buf_size);
        if (!buf) {
            montauk::close(sfd);
            montauk::close(dfd);
            montauk::fdelete(dst);
            return false;
        }

        uint64_t off = 0;
        while (off < size) {
            uint64_t todo64 = size - off;
            if (todo64 > buf_size) todo64 = buf_size;
            int todo = (int)todo64;
            int got = montauk::read(sfd, buf, off, todo64);
            if (got != todo) { ok = false; break; }
            int wrote = montauk::fwrite(dfd, buf, off, todo64);
            if (wrote != todo) { ok = false; break; }
            off += todo64;
        }

        montauk::mfree(buf);
    }

    montauk::close(sfd);
    montauk::close(dfd);
    if (!ok) {
        montauk::fdelete(dst);
        return false;
    }
    return true;
}

// Recursive directory copy
bool filemanager_copy_dir_recursive(const char* src, const char* dst) {
    if (!src || !dst || montauk::streq(src, dst)) return false;

    montauk::fmkdir(dst);

    const char* names[64];
    int count = montauk::readdir(src, names, 64);
    if (count < 0) return false;

    // Compute prefix to strip
    const char* after_drive = src;
    for (int k = 0; after_drive[k]; k++) {
        if (after_drive[k] == ':' && after_drive[k + 1] == '/') {
            after_drive += k + 2;
            break;
        }
    }
    char prefix[256] = {0};
    int prefix_len = 0;
    if (after_drive[0] != '\0') {
        montauk::strcpy(prefix, after_drive);
        prefix_len = montauk::slen(prefix);
        if (prefix_len > 0 && prefix[prefix_len - 1] != '/') {
            prefix[prefix_len++] = '/';
            prefix[prefix_len] = '\0';
        }
    }

    for (int i = 0; i < count; i++) {
        const char* raw = names[i];
        if (prefix_len > 0) {
            bool match = true;
            for (int k = 0; k < prefix_len; k++) {
                if (raw[k] != prefix[k]) { match = false; break; }
            }
            if (match) raw += prefix_len;
        }

        bool child_is_dir = false;
        char basename[64];
        montauk::strncpy(basename, raw, 63);
        int blen = montauk::slen(basename);
        if (blen > 0 && basename[blen - 1] == '/') {
            child_is_dir = true;
            basename[blen - 1] = '\0';
        }

        char src_child[512], dst_child[512];
        filemanager_build_fullpath(src_child, 512, src, basename);
        filemanager_build_fullpath(dst_child, 512, dst, basename);

        if (child_is_dir)
            filemanager_copy_dir_recursive(src_child, dst_child);
        else
            filemanager_copy_file(src_child, dst_child);
    }

    return true;
}

} // namespace filemanager
