#include "qjs_fd.h"
#include "qjs_blob.h"
#include "jsffi.h"

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

#ifdef _WIN32
#include <io.h>
#include <direct.h>
#define mkdir(path, mode) _mkdir(path)
#define rmdir _rmdir
#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>
#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_ThrowTypeError(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)
{
    size_t len;
    ssize_t wrote;
    if (JS_IsString(val)) {
        const char *data = JS_ToCStringLen(js, &len, val);
        wrote = write(fd, data, len);
        JS_FreeCString(js, data);
    } else {
        unsigned char *data = js_get_blob_data(js, &len, val);
        wrote = write(fd, data, len);
    }
    return wrote;
}


// 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_IsString(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_ThrowReferenceError(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;
    
    ssize_t wrote = js_fd_write_helper(js, fd, argv[1]);
    if (wrote < 0)
        return JS_ThrowReferenceError(js, "write failed: %s", strerror(errno));
    
    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_ThrowReferenceError(js, "malloc failed");
    
    ssize_t bytes_read = read(fd, buf, size);
    if (bytes_read < 0) {
        free(buf);
        return JS_ThrowReferenceError(js, "read failed: %s", strerror(errno));
    }
    
    ret = js_new_blob_stoned_copy(js, buf, bytes_read);
    free(buf);
    return ret;
)

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_ThrowReferenceError(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_ThrowReferenceError(js, "getcwd failed: %s", strerror(errno));
    return JS_NewString(js, buf);
)

JSC_SCALL(fd_rmdir,
    if (rmdir(str) != 0)
        ret = JS_ThrowReferenceError(js, "could not remove directory %s: %s", str, strerror(errno));
)

JSC_SCALL(fd_mkdir,
    if (mkdir(str, 0755) != 0)
        ret = JS_ThrowReferenceError(js, "could not make directory %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_ThrowReferenceError(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_ThrowReferenceError(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_ThrowReferenceError(js, "fstat failed: %s", strerror(errno));
    
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size));
    JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode));
    JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid));
    JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid));
    JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime));
    JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime));
    JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime));
    JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink));
    JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino));
    JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev));
    JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev));
#ifndef _WIN32
    JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize));
    JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks));
#else
    JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096));
    JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512));
#endif
    
    // Add boolean properties for file type
    JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
    JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
    JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
    JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
    JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
    JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
    JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
    
    return obj;
)


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, lseek, 3),
    MIST_FUNC_DEF(fd, getcwd, 0),
    MIST_FUNC_DEF(fd, rmdir, 1),
    MIST_FUNC_DEF(fd, mkdir, 1),
    MIST_FUNC_DEF(fd, fsync, 1),
    MIST_FUNC_DEF(fd, close, 1),
    MIST_FUNC_DEF(fd, fstat, 1),
};

JSValue js_fd_use(JSContext *js) {
    JSValue mod = JS_NewObject(js);
    JS_SetPropertyFunctionList(js, mod, js_fd_funcs, countof(js_fd_funcs));
    return mod;
}