#include "cell.h"

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <limits.h>

#ifdef _WIN32
#include <io.h>
#include <direct.h>
#include <windows.h>
#define mkdir(path, mode) _mkdir(path)
#define rmdir _rmdir
#define unlink _unlink
#define getcwd _getcwd
#define PATH_MAX _MAX_PATH
#define fsync(fd) _commit(fd)
#define S_ISLNK(m) 0
#define S_ISSOCK(m) 0
#else
#include <unistd.h>
#include <dirent.h>
#include <sys/mman.h>
#endif

// Helper to convert JS value to file descriptor
static int js2fd(JSContext *ctx, JSValueConst val)
{
    int fd;
    if (JS_ToInt32(ctx, &fd, val) < 0) {
        JS_RaiseDisrupt(ctx, "Expected file descriptor number");
        return -1;
    }
    return fd;
}

// Helper function for writing
static ssize_t js_fd_write_helper(JSContext *js, int fd, JSValue val)
{

}


// POSIX FILE DESCRIPTOR FUNCTIONS
JSC_SCALL(fd_open,
    int flags = O_RDWR | O_CREAT;
    mode_t mode = 0644;
    
    // Parse optional flags argument
    if (argc > 1 && JS_IsText(argv[1])) {
        const char *flag_str = JS_ToCString(js, argv[1]);
        flags = 0;
        
        if (strchr(flag_str, 'r')) flags |= O_RDONLY;
        if (strchr(flag_str, 'w')) flags |= O_WRONLY | O_CREAT | O_TRUNC;
        if (strchr(flag_str, 'a')) flags |= O_WRONLY | O_CREAT | O_APPEND;
        if (strchr(flag_str, '+')) {
            flags &= ~(O_RDONLY | O_WRONLY);
            flags |= O_RDWR;
        }
        
        JS_FreeCString(js, flag_str);
    }
    
    int fd = open(str, flags, mode);
    if (fd < 0)
        ret = JS_RaiseDisrupt(js, "open failed: %s", strerror(errno));
    else
        ret = JS_NewInt32(js, fd);
)

JSC_CCALL(fd_write,
    int fd = js2fd(js, argv[0]);
    if (fd < 0) return JS_EXCEPTION;
    
    size_t len;
    ssize_t wrote;
    if (JS_IsText(argv[1])) {
        const char *data = JS_ToCStringLen(js, &len, argv[1]);
        if (!data) return JS_EXCEPTION;
        wrote = write(fd, data, len);
        JS_FreeCString(js, data);
    } else {
        void *data = js_get_blob_data(js, &len, argv[1]);
        if (data == -1)
            return JS_EXCEPTION;
        wrote = write(fd, data, len);
    }

    return JS_NewInt64(js, wrote);
)

JSC_CCALL(fd_read,
    int fd = js2fd(js, argv[0]);
    if (fd < 0) return JS_EXCEPTION;
    
    size_t size = 4096;
    if (argc > 1)
        size = js2number(js, argv[1]);
    
    void *buf = malloc(size);
    if (!buf)
        return JS_RaiseDisrupt(js, "malloc failed");
    
    ssize_t bytes_read = read(fd, buf, size);
    if (bytes_read < 0) {
        free(buf);
        return JS_RaiseDisrupt(js, "read failed: %s", strerror(errno));
    }
    
    ret = js_new_blob_stoned_copy(js, buf, bytes_read);
    free(buf);
    return ret;
)

JSC_SCALL(fd_slurp,
    struct stat st;
    if (stat(str, &st) != 0)
        return JS_RaiseDisrupt(js, "stat failed: %s", strerror(errno));
    
    if (!S_ISREG(st.st_mode))
        return JS_RaiseDisrupt(js, "path is not a regular file");
    
    size_t size = st.st_size;
    if (size == 0)
        return js_new_blob_stoned_copy(js, NULL, 0);
    
#ifndef _WIN32
    int fd = open(str, O_RDONLY);
    if (fd < 0)
        return JS_RaiseDisrupt(js, "open failed: %s", strerror(errno));
    
    void *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (data == MAP_FAILED) {
        close(fd);
        return JS_RaiseDisrupt(js, "mmap failed: %s", strerror(errno));
    }
    ret = js_new_blob_stoned_copy(js, data, size);
    munmap(data, size);
    close(fd);
#else
    // Windows: use memory mapping for optimal performance
    HANDLE hFile = CreateFileA(str, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
        return JS_RaiseDisrupt(js, "CreateFile failed: %lu", GetLastError());
    
    HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (hMapping == NULL) {
        CloseHandle(hFile);
        return JS_RaiseDisrupt(js, "CreateFileMapping failed: %lu", GetLastError());
    }
    
    void *data = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
    if (data == NULL) {
        CloseHandle(hMapping);
        CloseHandle(hFile);
        return JS_RaiseDisrupt(js, "MapViewOfFile failed: %lu", GetLastError());
    }
    
    ret = js_new_blob_stoned_copy(js, data, size);
    UnmapViewOfFile(data);
    CloseHandle(hMapping);
    CloseHandle(hFile);
#endif
)

JSC_CCALL(fd_lseek,
    int fd = js2fd(js, argv[0]);
    if (fd < 0) return JS_EXCEPTION;
    
    off_t offset = js2number(js, argv[1]);
    int whence = SEEK_SET;
    
    if (argc > 2) {
        const char *whence_str = JS_ToCString(js, argv[2]);
        if (strcmp(whence_str, "cur") == 0) whence = SEEK_CUR;
        else if (strcmp(whence_str, "end") == 0) whence = SEEK_END;
        JS_FreeCString(js, whence_str);
    }
    
    off_t new_pos = lseek(fd, offset, whence);
    if (new_pos < 0)
        return JS_RaiseDisrupt(js, "lseek failed: %s", strerror(errno));
    
    return JS_NewInt64(js, new_pos);
)

JSC_CCALL(fd_getcwd,
    char buf[PATH_MAX];
    if (getcwd(buf, sizeof(buf)) == NULL)
        return JS_RaiseDisrupt(js, "getcwd failed: %s", strerror(errno));
    return JS_NewString(js, buf);
)

JSC_SCALL(fd_rmdir,
    int recursive = 0;
    if (argc > 1)
        recursive = JS_ToBool(js, argv[1]);
    
    if (recursive) {
        // Recursively remove directory contents first
#ifdef _WIN32
        WIN32_FIND_DATAA ffd;
        char search_path[PATH_MAX];
        snprintf(search_path, sizeof(search_path), "%s\\*", str);
        HANDLE hFind = FindFirstFileA(search_path, &ffd);
        if (hFind != INVALID_HANDLE_VALUE) {
            do {
                if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0)
                    continue;
                char full_path[PATH_MAX];
                snprintf(full_path, sizeof(full_path), "%s\\%s", str, ffd.cFileName);
                if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                    JSValue args[2] = { JS_NewString(js, full_path), JS_TRUE };
                    JSValue result = js_fd_rmdir(js, JS_NULL, 2, args);
                    JS_FreeValue(js, args[0]);
                    if (JS_IsException(result)) {
                        FindClose(hFind);
                        return result;
                    }
                    JS_FreeValue(js, result);
                } else {
                    if (unlink(full_path) != 0) {
                        FindClose(hFind);
                        ret = JS_RaiseDisrupt(js, "could not remove file %s: %s", full_path, strerror(errno));
                        return ret;
                    }
                }
            } while (FindNextFileA(hFind, &ffd));
            FindClose(hFind);
        }
#else
        DIR *dir = opendir(str);
        if (dir) {
            struct dirent *entry;
            while ((entry = readdir(dir)) != NULL) {
                if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
                    continue;
                char full_path[PATH_MAX];
                snprintf(full_path, sizeof(full_path), "%s/%s", str, entry->d_name);
                struct stat st;
                if (lstat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
                    JSValue args[2] = { JS_NewString(js, full_path), JS_TRUE };
                    JSValue result = js_fd_rmdir(js, JS_NULL, 2, args);
                    JS_FreeValue(js, args[0]);
                    if (JS_IsException(result)) {
                        closedir(dir);
                        return result;
                    }
                    JS_FreeValue(js, result);
                } else {
                    if (unlink(full_path) != 0) {
                        closedir(dir);
                        ret = JS_RaiseDisrupt(js, "could not remove file %s: %s", full_path, strerror(errno));
                        return ret;
                    }
                }
            }
            closedir(dir);
        }
#endif
    }
    
    if (rmdir(str) != 0)
        ret = JS_RaiseDisrupt(js, "could not remove directory %s: %s", str, strerror(errno));
)

JSC_SCALL(fd_mkdir,
    if (mkdir(str, 0755) != 0)
        ret = JS_RaiseDisrupt(js, "could not make directory %s: %s", str, strerror(errno));
)

JSC_SCALL(fd_mv,
    if (argc < 2)
        ret = JS_RaiseDisrupt(js, "fd.mv requires 2 arguments: old path and new path");
    else if (!JS_IsText(argv[1]))
        ret = JS_RaiseDisrupt(js, "second argument must be a string (new path)");
    else {
        const char *new_path = JS_ToCString(js, argv[1]);
        if (rename(str, new_path) != 0)
            ret = JS_RaiseDisrupt(js, "could not rename %s to %s: %s", str, new_path, strerror(errno));
        JS_FreeCString(js, new_path);
    }
)

JSC_SCALL(fd_symlink,
    if (argc < 2)
        ret = JS_RaiseDisrupt(js, "fd.symlink requires 2 arguments: target and link path");
    else if (!JS_IsText(argv[1]))
        ret = JS_RaiseDisrupt(js, "second argument must be a string (link path)");
    else {
        const char *link_path = JS_ToCString(js, argv[1]);
#ifdef _WIN32
        if (!CreateSymbolicLinkA(link_path, str, SYMBOLIC_LINK_FLAG_DIRECTORY)) {
             // Try file
             if (!CreateSymbolicLinkA(link_path, str, 0)) {
                 ret = JS_RaiseDisrupt(js, "could not create symlink %s -> %s: %lu", link_path, str, GetLastError());
             }
        }
#else
        if (symlink(str, link_path) != 0)
            ret = JS_RaiseDisrupt(js, "could not create symlink %s -> %s: %s", link_path, str, strerror(errno));
#endif
        JS_FreeCString(js, link_path);
    }
)

JSC_SCALL(fd_unlink,
#ifdef _WIN32
    // On Windows, try to remove read-only attribute first
    DWORD attrs = GetFileAttributesA(str);
    if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_READONLY)) {
        SetFileAttributesA(str, attrs & ~FILE_ATTRIBUTE_READONLY);
    }
    if (!DeleteFileA(str)) {
        DWORD err = GetLastError();
        if (err == ERROR_ACCESS_DENIED) {
            // Might be a directory, try RemoveDirectory
            if (!RemoveDirectoryA(str))
                ret = JS_RaiseDisrupt(js, "could not remove %s: error %lu", str, GetLastError());
        } else {
            ret = JS_RaiseDisrupt(js, "could not remove file %s: error %lu", str, err);
        }
    }
#else
    if (unlink(str) != 0)
        ret = JS_RaiseDisrupt(js, "could not remove file %s: %s", str, strerror(errno));
#endif
)

// Helper function for recursive removal
static int remove_recursive(const char *path) {
    struct stat st;
#ifdef _WIN32
    if (stat(path, &st) != 0)
#else
    if (lstat(path, &st) != 0)
#endif
        return -1;
    
    if (S_ISDIR(st.st_mode)) {
        // Directory: remove contents first
#ifdef _WIN32
        WIN32_FIND_DATA ffd;
        char search_path[PATH_MAX];
        snprintf(search_path, sizeof(search_path), "%s\\*", path);
        HANDLE hFind = FindFirstFile(search_path, &ffd);
        if (hFind != INVALID_HANDLE_VALUE) {
            do {
                if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
                char child_path[PATH_MAX];
                snprintf(child_path, sizeof(child_path), "%s\\%s", path, ffd.cFileName);
                if (remove_recursive(child_path) != 0) {
                    FindClose(hFind);
                    return -1;
                }
            } while (FindNextFile(hFind, &ffd) != 0);
            FindClose(hFind);
        }
#else
        DIR *d = opendir(path);
        if (d) {
            struct dirent *dir;
            while ((dir = readdir(d)) != NULL) {
                if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue;
                char child_path[PATH_MAX];
                snprintf(child_path, sizeof(child_path), "%s/%s", path, dir->d_name);
                if (remove_recursive(child_path) != 0) {
                    closedir(d);
                    return -1;
                }
            }
            closedir(d);
        }
#endif
        // Remove the now-empty directory
        return rmdir(path);
    } else {
        // File: just unlink
        return unlink(path);
    }
}

JSC_SCALL(fd_rm,
    if (remove_recursive(str) != 0)
        ret = JS_RaiseDisrupt(js, "could not remove %s: %s", str, strerror(errno));
)

JSC_CCALL(fd_fsync,
    int fd = js2fd(js, argv[0]);
    if (fd < 0) return JS_EXCEPTION;
    
    if (fsync(fd) != 0)
        return JS_RaiseDisrupt(js, "fsync failed: %s", strerror(errno));
    
    return JS_NULL;
)

JSC_CCALL(fd_close,
    int fd = js2fd(js, argv[0]);
    if (fd < 0) return JS_EXCEPTION;
    
    if (close(fd) != 0)
        return JS_RaiseDisrupt(js, "close failed: %s", strerror(errno));
    
    return JS_NULL;
)

JSC_CCALL(fd_fstat,
    int fd = js2fd(js, argv[0]);
    if (fd < 0) return JS_EXCEPTION;

    struct stat st;
    if (fstat(fd, &st) != 0)
        return JS_RaiseDisrupt(js, "fstat failed: %s", strerror(errno));

    JS_FRAME(js);
    JS_ROOT(obj, JS_NewObject(js));
    JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size));
    JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode));
    JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid));
    JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid));
    JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime));
    JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime));
    JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime));
    JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink));
    JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino));
    JS_SetPropertyStr(js, obj.val, "dev", JS_NewInt32(js, st.st_dev));
    JS_SetPropertyStr(js, obj.val, "rdev", JS_NewInt32(js, st.st_rdev));
#ifndef _WIN32
    JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize));
    JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks));
#else
    JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096));
    JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512));
#endif
    JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
    JS_RETURN(obj.val);
)

JSC_CCALL(fd_stat,
    const char *path = JS_ToCString(js, argv[0]);
    if (!path) return JS_EXCEPTION;

    struct stat st;
    if (stat(path, &st) != 0) {
        JS_FreeCString(js, path);
        return JS_NewObject(js);
    }

    JS_FRAME(js);
    JS_ROOT(obj, JS_NewObject(js));
    JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size));
    JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode));
    JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid));
    JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid));
    JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime));
    JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime));
    JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime));
    JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink));
    JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino));
    JS_SetPropertyStr(js, obj.val, "dev", JS_NewInt32(js, st.st_dev));
    JS_SetPropertyStr(js, obj.val, "rdev", JS_NewInt32(js, st.st_rdev));
#ifndef _WIN32
    JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize));
    JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks));
#else
    JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096));
    JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512));
#endif
    JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
    JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
    JS_FreeCString(js, path);
    JS_RETURN(obj.val);
)

JSC_SCALL(fd_readdir,
    JS_FRAME(js);
#ifdef _WIN32
    WIN32_FIND_DATA ffd;
    char path[PATH_MAX];
    snprintf(path, sizeof(path), "%s\\*", str);
    HANDLE hFind = FindFirstFile(path, &ffd);
    if (hFind == INVALID_HANDLE_VALUE) {
        ret = JS_RaiseDisrupt(js, "FindFirstFile failed for %s", path);
    } else {
        JS_ROOT(arr, JS_NewArray(js));
        do {
            if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
            JS_ArrayPush(js, &arr.val, JS_NewString(js, ffd.cFileName));
        } while (FindNextFile(hFind, &ffd) != 0);
        FindClose(hFind);
        ret = arr.val;
    }
#else
    DIR *d;
    struct dirent *dir;
    d = opendir(str);
    if (d) {
        JS_ROOT(arr, JS_NewArray(js));
        while ((dir = readdir(d)) != NULL) {
            if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue;
            JS_ArrayPush(js, &arr.val, JS_NewString(js, dir->d_name));
        }
        closedir(d);
        ret = arr.val;
    } else {
        ret = JS_RaiseDisrupt(js, "opendir failed for %s: %s", str, strerror(errno));
    }
#endif
    JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
)

JSC_CCALL(fd_is_file,
    const char *path = JS_ToCString(js, argv[0]);
    if (!path) return JS_EXCEPTION;
    
    struct stat st;
    if (stat(path, &st) != 0) {
        JS_FreeCString(js, path);
        return JS_NewBool(js, 0);
    }
    
    JS_FreeCString(js, path);
    return JS_NewBool(js, S_ISREG(st.st_mode));
)

JSC_CCALL(fd_is_dir,
    const char *path = JS_ToCString(js, argv[0]);
    if (!path) return JS_EXCEPTION;
    
    struct stat st;
    if (stat(path, &st) != 0) {
        JS_FreeCString(js, path);
        return JS_NewBool(js, 0);
    }
    
    JS_FreeCString(js, path);
    return JS_NewBool(js, S_ISDIR(st.st_mode));
)

JSC_CCALL(fd_slurpwrite,
    size_t len;
    const char *data = js_get_blob_data(js, &len, argv[1]);

    if (!data && len > 0)
        return JS_EXCEPTION;
    
    const char *str = JS_ToCString(js, argv[0]);

    if (!str) return JS_EXCEPTION;
    int fd = open(str, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0)  {
      ret = JS_RaiseDisrupt(js, "open failed for %s: %s", str, strerror(errno));
      JS_FreeCString(js, str);
      return ret;
    }

    ssize_t written = write(fd, data, len);
    close(fd);

    if (written != (ssize_t)len) {
      ret = JS_RaiseDisrupt(js, "write failed for %s: %s", str, strerror(errno));
      JS_FreeCString(js, str);
      return ret;
    }

    JS_FreeCString(js, str);
    
    return JS_NULL;
)

JSC_CCALL(fd_slurpappend,
    size_t len;
    const char *data = js_get_blob_data(js, &len, argv[1]);

    if (!data && len > 0)
        return JS_EXCEPTION;

    const char *str = JS_ToCString(js, argv[0]);

    if (!str) return JS_EXCEPTION;
    int fd = open(str, O_WRONLY | O_CREAT | O_APPEND, 0644);
    if (fd < 0) {
      ret = JS_RaiseDisrupt(js, "open failed for %s: %s", str, strerror(errno));
      JS_FreeCString(js, str);
      return ret;
    }

    ssize_t written = write(fd, data, len);
    close(fd);

    if (written != (ssize_t)len) {
      ret = JS_RaiseDisrupt(js, "write failed for %s: %s", str, strerror(errno));
      JS_FreeCString(js, str);
      return ret;
    }

    JS_FreeCString(js, str);

    return JS_NULL;
)

// Helper function for recursive enumeration
static void visit_directory(JSContext *js, JSValue *results, int *result_count, const char *curr_path, const char *rel_prefix, int recurse) {
    if (!curr_path) return;

#ifdef _WIN32
    WIN32_FIND_DATA ffd;
    char search_path[PATH_MAX];
    snprintf(search_path, sizeof(search_path), "%s\\*", curr_path);
    HANDLE hFind = FindFirstFile(search_path, &ffd);
    if (hFind != INVALID_HANDLE_VALUE) {
        do {
            if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
            char item_rel[PATH_MAX];
            if (rel_prefix && strlen(rel_prefix) > 0) {
                snprintf(item_rel, sizeof(item_rel), "%s/%s", rel_prefix, ffd.cFileName);
            } else {
                strcpy(item_rel, ffd.cFileName);
            }
            JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));

            if (recurse) {
                struct stat st;
                char child_path[PATH_MAX];
                snprintf(child_path, sizeof(child_path), "%s\\%s", curr_path, ffd.cFileName);
                if (stat(child_path, &st) == 0 && S_ISDIR(st.st_mode)) {
                    visit_directory(js, results, result_count, child_path, item_rel, recurse);
                }
            }
        } while (FindNextFile(hFind, &ffd) != 0);
        FindClose(hFind);
    }
#else
    DIR *d = opendir(curr_path);
    if (d) {
        struct dirent *dir;
        while ((dir = readdir(d)) != NULL) {
            if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue;
            char item_rel[PATH_MAX];
            if (rel_prefix && strlen(rel_prefix) > 0) {
                snprintf(item_rel, sizeof(item_rel), "%s/%s", rel_prefix, dir->d_name);
            } else {
                strcpy(item_rel, dir->d_name);
            }
            JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));

            if (recurse) {
                struct stat st;
                char child_path[PATH_MAX];
                snprintf(child_path, sizeof(child_path), "%s/%s", curr_path, dir->d_name);
                if (stat(child_path, &st) == 0 && S_ISDIR(st.st_mode)) {
                    visit_directory(js, results, result_count, child_path, item_rel, recurse);
                }
            }
        }
        closedir(d);
    }
#endif
}

JSC_SCALL(fd_enumerate,
    const char *path = str;
    if (!path) path = ".";
    int recurse = 0;

    if (argc > 1)
        recurse = JS_ToBool(js, argv[1]);

    JS_FRAME(js);
    JS_ROOT(arr, JS_NewArray(js));
    int result_count = 0;

    struct stat st;
    if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
        visit_directory(js, &arr.val, &result_count, path, "", recurse);

    ret = arr.val;
    JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
)

JSC_CCALL(fd_realpath,
    const char *path = JS_ToCString(js, argv[0]);
    if (!path) return JS_EXCEPTION;
    
#ifdef _WIN32
    char resolved[PATH_MAX];
    DWORD len = GetFullPathNameA(path, PATH_MAX, resolved, NULL);
    if (len == 0 || len >= PATH_MAX) {
        JSValue err = JS_RaiseDisrupt(js, "realpath failed for %s: %s", path, strerror(errno));
        JS_FreeCString(js, path);
        return err;
    }
    JS_FreeCString(js, path);
    return JS_NewString(js, resolved);
#else
    char *resolved = realpath(path, NULL);
    if (!resolved) {
        JSValue err = JS_RaiseDisrupt(js, "realpath failed for %s: %s", path, strerror(errno));
        JS_FreeCString(js, path);
        return err;
    }
    JS_FreeCString(js, path);
    JSValue result = JS_NewString(js, resolved);
    free(resolved);
    return result;
#endif
)

JSC_CCALL(fd_is_link,
    const char *path = JS_ToCString(js, argv[0]);
    if (!path) return JS_EXCEPTION;
    
#ifdef _WIN32
    DWORD attrs = GetFileAttributesA(path);
    JS_FreeCString(js, path);
    int is_link = (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_REPARSE_POINT));
    return JS_NewBool(js, is_link);
#else
    struct stat st;
    int is_link = (lstat(path, &st) == 0 && S_ISLNK(st.st_mode));
    JS_FreeCString(js, path);
    return JS_NewBool(js, is_link);
#endif
)

JSC_CCALL(fd_readlink,
    const char *path = JS_ToCString(js, argv[0]);
    if (!path) return JS_EXCEPTION;
    
#ifdef _WIN32
    JS_FreeCString(js, path);
    return JS_RaiseDisrupt(js, "readlink not supported on Windows");
#else
    char buf[PATH_MAX];
    ssize_t len = readlink(path, buf, sizeof(buf) - 1);
    JS_FreeCString(js, path);
    if (len < 0) {
        return JS_RaiseDisrupt(js, "readlink failed: %s", strerror(errno));
    }
    buf[len] = '\0';
    return JS_NewString(js, buf);
#endif
)

static const JSCFunctionListEntry js_fd_funcs[] = {
    MIST_FUNC_DEF(fd, open, 2),
    MIST_FUNC_DEF(fd, write, 2),
    MIST_FUNC_DEF(fd, read, 2),
    MIST_FUNC_DEF(fd, slurp, 1),
    MIST_FUNC_DEF(fd, slurpwrite, 2),
    MIST_FUNC_DEF(fd, slurpappend, 2),
    MIST_FUNC_DEF(fd, lseek, 3),
    MIST_FUNC_DEF(fd, getcwd, 0),
    MIST_FUNC_DEF(fd, rmdir, 2),
    MIST_FUNC_DEF(fd, unlink, 1),
    MIST_FUNC_DEF(fd, mkdir, 1),
    MIST_FUNC_DEF(fd, mv, 2),
    MIST_FUNC_DEF(fd, rm, 1),
    MIST_FUNC_DEF(fd, fsync, 1),
    MIST_FUNC_DEF(fd, close, 1),
    MIST_FUNC_DEF(fd, stat, 1),
    MIST_FUNC_DEF(fd, fstat, 1),
    MIST_FUNC_DEF(fd, readdir, 1),
    MIST_FUNC_DEF(fd, is_file, 1),
    MIST_FUNC_DEF(fd, is_dir, 1),
    MIST_FUNC_DEF(fd, is_link, 1),
    MIST_FUNC_DEF(fd, enumerate, 2),
    MIST_FUNC_DEF(fd, symlink, 2),
    MIST_FUNC_DEF(fd, realpath, 1),
    MIST_FUNC_DEF(fd, readlink, 1),
};

JSValue js_core_internal_fd_use(JSContext *js) {
    JS_FRAME(js);
    JS_ROOT(mod, JS_NewObject(js));
    JS_SetPropertyFunctionList(js, mod.val, js_fd_funcs, countof(js_fd_funcs));
    JS_RETURN(mod.val);
}
