#include "valkeymodule.h"

#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/*
 * This module implements a very simple stack based scripting language.
 * It's purpose is only to test the valkey module API to implement scripting
 * engines.
 *
 * The language is called HELLO, and a program in this language is formed by
 * a list of function definitions.
 * The language supports 32-bit integer and string values, and allows to return
 * a single value from a function.
 * The language also supports error values when returned from the CALL
 * instruction.
 *
 * Example of a program:
 *
 * ```
 * FUNCTION foo  # declaration of function 'foo'
 * ARGS 0        # pushes the value in the first argument to the top of the
 *               # stack
 * RETURN        # returns the current value on the top of the stack and marks
 *               # the end of the function declaration
 *
 * RFUNCTION bar # declaration of read-only function 'bar'
 * CONSTI 432    # pushes the value 432 to the top of the stack
 * RETURN        # returns the current value on the top of the stack and marks
 *               # the end of the function declaration.
 *
 * FUNCTION baz  # declaration of function 'baz'
 * ARGS 0        # pushes the value in the first argument to the top of the
 *               # stack
 * SLEEP         # Pops the current value in the stack and sleeps for `value`
 *               # seconds
 * CONSTS x      # pushes the string 'x' to the top of the stack
 * CONSTI 0      # pushes the value 0 to the top of the stack
 * CONSTI 2      # pushes the value 2 to the top of the stack
 * CALL SET      # calls the command specified in the first argument with the
 *               # with arguments pushed to the stack on previous instructions.
 *               # The top of the stack must always have an integer value that
 *               # specifies the number of arguments to pop from the stack.
 *               # In this case, the command called will be `SET x 0`
 * RETURN        # returns the current value on the top of the stack and marks
 *               # the end of the function declaration.
 * ```
 */

/*
 * List of instructions of the HELLO language.
 */
typedef enum HelloInstKind {
    FUNCTION = 0,
    RFUNCTION,
    CONSTI,
    CONSTS,
    ARGS,
    SLEEP,
    CALL,
    RETURN,
    _NUM_INSTRUCTIONS, // Not a real instruction.
} HelloInstKind;

/*
 * String representations of the instructions above.
 */
const char *HelloInstKindStr[] = {
    "FUNCTION",
    "RFUNCTION",
    "CONSTI",
    "CONSTS",
    "ARGS",
    "SLEEP",
    "CALL",
    "RETURN",
};

/*
 * Value type used in the HELLO language.
 */
typedef enum {
    VALUE_INT,
    VALUE_STRING,
    VALUE_ERROR,
} ValueType;

/*
 * Value used in the HELLO language.
 */
typedef struct {
    ValueType type;
    union {
        uint32_t integer;
        const char *string;
    };
} Value;


/*
 * Struct that represents an instance of an instruction.
 * Instructions may have at most one parameter.
 */
typedef struct HelloInst {
    HelloInstKind kind;
    union {
        uint32_t integer;
        char *string;
    } param;
} HelloInst;

/*
 * Struct that represents an instance of a function.
 * A function is just a list of instruction instances.
 */
typedef struct HelloFunc {
    char *name;
    HelloInst instructions[256];
    uint32_t num_instructions;
    uint32_t index;
    int read_only;
} HelloFunc;

/*
 * Struct that represents an instance of an HELLO program.
 * A program is just a list of function instances.
 */
typedef struct HelloProgram {
    HelloFunc *functions[16];
    uint32_t num_functions;
} HelloProgram;


typedef struct HelloDebugCtx {
    int enabled;
    int stop_on_next_instr;
    int abort;
    const Value *stack;
    uint32_t sp;
} HelloDebugCtx;

/*
 * Struct that represents the runtime context of an HELLO program.
 */
typedef struct HelloLangCtx {
    HelloProgram *program;
    HelloDebugCtx debug;
} HelloLangCtx;


static HelloLangCtx *hello_ctx = NULL;


static Value parseValue(const char *str) {
    char *end;
    errno = 0;
    Value result;

    // Check for NULL or empty string
    if (!str || *str == '\0') {
        ValkeyModule_Log(NULL, "error", "Failed to parse argument: NULL or empty string");
        ValkeyModule_Assert(0);
        return result;
    }

    unsigned long val = strtoul(str, &end, 10);

    // Check if no digits were found
    if (end == str) {
        result.type = VALUE_STRING;
        result.string = str;
        return result;
    }

    // Check for trailing characters (non-digits after the number)
    if (*end != '\0') {
        result.type = VALUE_STRING;
        result.string = str;
        return result;
    }

    // Check for overflow
    if (errno == ERANGE || val > UINT32_MAX) {
        result.type = VALUE_STRING;
        result.string = str;
        return result;
    }

    // Success case
    result.type = VALUE_INT;
    result.integer = (uint32_t)val;
    return result;
}

/*
 * Parses the kind of instruction that the current token points to.
 */
static HelloInstKind helloLangParseInstruction(const char *token) {
    for (HelloInstKind i = 0; i < _NUM_INSTRUCTIONS; i++) {
        if (strcmp(HelloInstKindStr[i], token) == 0) {
            return i;
        }
    }
    return _NUM_INSTRUCTIONS;
}

/*
 * Parses the function param.
 */
static void helloLangParseFunction(HelloFunc *func, int read_only) {
    char *token = strtok(NULL, " \n");
    ValkeyModule_Assert(token != NULL);
    func->name = ValkeyModule_Alloc(sizeof(char) * strlen(token) + 1);
    strcpy(func->name, token);
    func->read_only = read_only;
}

/*
 * Parses an integer parameter.
 */
static void helloLangParseIntegerParam(HelloFunc *func) {
    char *token = strtok(NULL, " \n");
    Value parsed = parseValue(token);
    if (parsed.type == VALUE_INT) {
        func->instructions[func->num_instructions].param.integer = parsed.integer;
    } else {
        ValkeyModule_Log(NULL, "error", "Failed to parse integer parameter: '%s'", token);
        ValkeyModule_Assert(0); // Parse error
    }
}

/*
 * Parses the CONSTI instruction parameter.
 */
static void helloLangParseConstI(HelloFunc *func) {
    helloLangParseIntegerParam(func);
    func->num_instructions++;
}
/*
 * Parses the CONSTS instruction parameter.
 */
static void helloLangParseConstS(HelloFunc *func) {
    char *token = strtok(NULL, " \n");
    ValkeyModule_Assert(token != NULL);
    func->instructions[func->num_instructions].param.string = ValkeyModule_Alloc(sizeof(char) * strlen(token) + 1);
    strcpy(func->instructions[func->num_instructions].param.string, token);
    func->num_instructions++;
}

/*
 * Parses the ARGS instruction parameter.
 */
static void helloLangParseArgs(HelloFunc *func) {
    helloLangParseIntegerParam(func);
    func->num_instructions++;
}

static void helloLangParseCall(HelloFunc *func) {
    char *token = strtok(NULL, " \n");
    ValkeyModule_Assert(token != NULL);
    func->instructions[func->num_instructions].param.string = ValkeyModule_Alloc(sizeof(char) * strlen(token) + 1);
    strcpy(func->instructions[func->num_instructions].param.string, token);
    func->num_instructions++;
}

/*
 * Parses an HELLO program source code.
 */
static int helloLangParseCode(const char *code,
                              HelloProgram *program,
                              ValkeyModuleString **err) {
    char *_code = ValkeyModule_Alloc(sizeof(char) * strlen(code) + 1);
    strcpy(_code, code);

    HelloFunc *currentFunc = NULL;

    char *token = strtok(_code, " \n");
    while (token != NULL) {
        HelloInstKind kind = helloLangParseInstruction(token);

        if (currentFunc != NULL) {
            currentFunc->instructions[currentFunc->num_instructions].kind = kind;
        }

        switch (kind) {
        case FUNCTION:
        case RFUNCTION:
            ValkeyModule_Assert(currentFunc == NULL);
            currentFunc = ValkeyModule_Alloc(sizeof(HelloFunc));
            memset(currentFunc, 0, sizeof(HelloFunc));
            currentFunc->index = program->num_functions;
            program->functions[program->num_functions++] = currentFunc;
            helloLangParseFunction(currentFunc, kind == RFUNCTION);
            break;
        case CONSTI:
            ValkeyModule_Assert(currentFunc != NULL);
            helloLangParseConstI(currentFunc);
            break;
        case CONSTS:
            ValkeyModule_Assert(currentFunc != NULL);
            helloLangParseConstS(currentFunc);
            break;
        case ARGS:
            ValkeyModule_Assert(currentFunc != NULL);
            helloLangParseArgs(currentFunc);
            break;
        case SLEEP:
            ValkeyModule_Assert(currentFunc != NULL);
            currentFunc->num_instructions++;
            break;
        case CALL:
            ValkeyModule_Assert(currentFunc != NULL);
            helloLangParseCall(currentFunc);
            break;
        case RETURN:
            ValkeyModule_Assert(currentFunc != NULL);
            currentFunc->num_instructions++;
            currentFunc = NULL;
            break;
        default:
            *err = ValkeyModule_CreateStringPrintf(NULL, "Failed to parse instruction: '%s'", token);
            ValkeyModule_Free(_code);
            return -1;
        }

        token = strtok(NULL, " \n");
    }

    ValkeyModule_Free(_code);

    return 0;
}

static ValkeyModuleScriptingEngineExecutionState executeSleepInst(ValkeyModuleScriptingEngineServerRuntimeCtx *server_ctx,
                                                                  uint32_t seconds) {
    uint32_t elapsed_milliseconds = 0;
    ValkeyModuleScriptingEngineExecutionState state = VMSE_STATE_EXECUTING;
    while(1) {
        state = ValkeyModule_GetFunctionExecutionState(server_ctx);
        if (state != VMSE_STATE_EXECUTING) {
            break;
        }

        if (elapsed_milliseconds >= (seconds * 1000)) {
            break;
        }

        usleep(1000);
        elapsed_milliseconds++;
    }

    return state;
}

static void executeCallInst(ValkeyModuleCtx *module_ctx,
                            const char *cmd_name,
                            Value *stack,
                            int *sp) {
    ValkeyModule_Assert(stack != NULL);
    ValkeyModule_Assert(sp != NULL);
    ValkeyModuleCallReply *rep = NULL;
    errno = 0;
    ValkeyModule_Assert(*sp > 0);
    Value numargs = stack[--(*sp)];
    ValkeyModule_Assert(numargs.type == VALUE_INT);
    ValkeyModule_Assert(numargs.integer <= (uint32_t)(*sp));
    if (numargs.integer > 0) {
        ValkeyModuleString **cmd_args = ValkeyModule_Alloc(sizeof(ValkeyModuleString *) * numargs.integer);
        int bp = *sp - numargs.integer;
        for (uint32_t i = 0; i < numargs.integer; i++) {
            if (stack[bp + i].type == VALUE_INT) {
                cmd_args[i] = ValkeyModule_CreateStringPrintf(NULL, "%u", stack[bp + i].integer);
            } else {
                cmd_args[i] = ValkeyModule_CreateStringPrintf(NULL, "%s", stack[bp + i].string);
            }
            (*sp)--;
        }
        rep = ValkeyModule_Call(module_ctx, cmd_name, "ESCXv", cmd_args, numargs.integer);
        for (uint32_t i = 0; i < numargs.integer; i++) {
            ValkeyModule_FreeString(NULL, cmd_args[i]);
        }
        ValkeyModule_Free(cmd_args);
    } else {
        rep = ValkeyModule_Call(module_ctx, cmd_name, "ESCX");
    }
    ValkeyModule_Assert(rep != NULL);
    int type = ValkeyModule_CallReplyType(rep);

    ValkeyModuleString *response_str = ValkeyModule_CreateStringFromCallReply(rep);
    const char *resp_cstr = ValkeyModule_StringPtrLen(response_str, NULL);

    if (type == VALKEYMODULE_REPLY_ERROR) {
        stack[*sp].type = VALUE_ERROR;
        stack[(*sp)++].string = resp_cstr;
    } else if (type == VALKEYMODULE_REPLY_ARRAY_NULL) {
        stack[(*sp)].type = VALUE_STRING;
        stack[(*sp)++].string = "(null array)";
    } else if (type == VALKEYMODULE_REPLY_NULL) {
        stack[(*sp)].type = VALUE_STRING;
        stack[(*sp)++].string = "(null string)";
    } else {
        if (response_str != NULL) {
            stack[(*sp)++] = parseValue(resp_cstr);
        } else {
            stack[(*sp)].type = VALUE_STRING;
            stack[(*sp)++].string = "OK";
        }
    }

    ValkeyModule_FreeCallReply(rep);
}

static void helloDebuggerLogCurrentInstr(uint32_t pc, HelloInst *instr) {
    ValkeyModuleString *msg = NULL;
    switch (instr->kind) {
    case CONSTI:
    case ARGS:
        msg = ValkeyModule_CreateStringPrintf(NULL, ">>> %3u: %s %u", pc, HelloInstKindStr[instr->kind], instr->param.integer);
        break;
    case CONSTS:
    case CALL:
        msg = ValkeyModule_CreateStringPrintf(NULL, ">>> %3u: %s %s", pc, HelloInstKindStr[instr->kind], instr->param.string);
        break;
    case SLEEP:
    case RETURN:
        msg = ValkeyModule_CreateStringPrintf(NULL, ">>> %3u: %s", pc, HelloInstKindStr[instr->kind]);
        break;
    case FUNCTION:
    case RFUNCTION:
    case _NUM_INSTRUCTIONS:
        ValkeyModule_Assert(0);
    }

    ValkeyModule_ScriptingEngineDebuggerLog(msg, 0);
}

static int helloDebuggerInstrHook(uint32_t pc, HelloInst *instr) {
    helloDebuggerLogCurrentInstr(pc, instr);
    ValkeyModule_ScriptingEngineDebuggerFlushLogs();

    int client_disconnected = 0;
    ValkeyModuleString *err;
    ValkeyModule_ScriptingEngineDebuggerProcessCommands(&client_disconnected, &err);

    if (err) {
        ValkeyModule_ScriptingEngineDebuggerLog(err, 0);
        goto error;
    } else if (client_disconnected) {
        ValkeyModuleString *msg = ValkeyModule_CreateStringPrintf(NULL, "ERROR: Client socket disconnected");
        ValkeyModule_ScriptingEngineDebuggerLog(msg, 0);
        goto error;
    }

    return 1;

error:
    ValkeyModule_ScriptingEngineDebuggerFlushLogs();
    return 0;
}

typedef enum {
    FINISHED,
    KILLED,
    ABORTED,
} HelloExecutionState;

/*
 * Executes an HELLO function.
 */
static HelloExecutionState executeHelloLangFunction(ValkeyModuleCtx *module_ctx,
                                                    ValkeyModuleScriptingEngineServerRuntimeCtx *server_ctx,
                                                    HelloDebugCtx *debug_ctx,
                                                    HelloFunc *func,
                                                    ValkeyModuleString **args,
                                                    int nargs,
                                                    Value *result) {
    ValkeyModule_Assert(result != NULL);
    Value stack[64];
    int sp = 0;

    for (uint32_t pc = 0; pc < func->num_instructions; pc++) {
        HelloInst instr = func->instructions[pc];
        if (debug_ctx->enabled && debug_ctx->stop_on_next_instr) {
            debug_ctx->stack = stack;
            debug_ctx->sp = sp;
            if (!helloDebuggerInstrHook(pc, &instr)) {
                return ABORTED;
            }
            if (debug_ctx->abort) {
                return ABORTED;
            }
        }
        switch (instr.kind) {
        case CONSTI: {
            stack[sp].type = VALUE_INT;
            stack[sp++].integer = instr.param.integer;
            break;
        }
        case CONSTS: {
            stack[sp].type = VALUE_STRING;
            stack[sp++].string = instr.param.string;
            break;
        }
        case ARGS: {
            uint32_t idx = instr.param.integer;
            ValkeyModule_Assert(idx < (uint32_t)nargs);
            size_t len;
            const char *argStr = ValkeyModule_StringPtrLen(args[idx], &len);
            stack[sp++] = parseValue(argStr);
            break;
	    }
        case SLEEP: {
            ValkeyModule_Assert(sp > 0);
            Value sleepVal = stack[--sp];
            ValkeyModule_Assert(sleepVal.type == VALUE_INT);
            if (executeSleepInst(server_ctx, sleepVal.integer) == VMSE_STATE_KILLED) {
                return KILLED;
            }
            break;
	    }
        case CALL: {
            const char *cmd_name = instr.param.string;
            executeCallInst(module_ctx, cmd_name, stack, &sp);
            break;
        }
        case RETURN: {
            ValkeyModule_Assert(sp > 0);
            Value returnVal = stack[--sp];
            ValkeyModule_Assert(sp == 0);
            *result = returnVal;
            return FINISHED;
	    }
        case FUNCTION:
        case RFUNCTION:
        case _NUM_INSTRUCTIONS:
            ValkeyModule_Assert(0);
        }
    }

    ValkeyModule_Assert(0);
    return ABORTED;
}

static ValkeyModuleScriptingEngineMemoryInfo engineGetMemoryInfo(ValkeyModuleCtx *module_ctx,
                                                                 ValkeyModuleScriptingEngineCtx *engine_ctx,
                                                                 ValkeyModuleScriptingEngineSubsystemType type) {
    VALKEYMODULE_NOT_USED(module_ctx);
    VALKEYMODULE_NOT_USED(type);
    HelloLangCtx *ctx = (HelloLangCtx *)engine_ctx;
    ValkeyModuleScriptingEngineMemoryInfo mem_info = {
        .version = VALKEYMODULE_SCRIPTING_ENGINE_ABI_MEMORY_INFO_VERSION
    };

    if (ctx->program != NULL) {
        mem_info.used_memory += ValkeyModule_MallocSize(ctx->program);

        for (uint32_t i = 0; i < ctx->program->num_functions; i++) {
            HelloFunc *func = ctx->program->functions[i];
            if (func != NULL) {
                mem_info.used_memory += ValkeyModule_MallocSize(func);
                mem_info.used_memory += ValkeyModule_MallocSize(func->name);
            }
        }
    }

    mem_info.engine_memory_overhead = ValkeyModule_MallocSize(ctx);
    if (ctx->program != NULL) {
        mem_info.engine_memory_overhead += ValkeyModule_MallocSize(ctx->program);
    }

    return mem_info;
}

static size_t engineFunctionMemoryOverhead(ValkeyModuleCtx *module_ctx,
                                           ValkeyModuleScriptingEngineCompiledFunction *compiled_function) {
    VALKEYMODULE_NOT_USED(module_ctx);
    HelloFunc *func = (HelloFunc *)compiled_function->function;
    return ValkeyModule_MallocSize(func->name);
}

static void engineFreeFunction(ValkeyModuleCtx *module_ctx,
			                   ValkeyModuleScriptingEngineCtx *engine_ctx,
                               ValkeyModuleScriptingEngineSubsystemType type,
                               ValkeyModuleScriptingEngineCompiledFunction *compiled_function) {
    VALKEYMODULE_NOT_USED(module_ctx);
    VALKEYMODULE_NOT_USED(type);
    HelloLangCtx *ctx = (HelloLangCtx *)engine_ctx;
    HelloFunc *func = (HelloFunc *)compiled_function->function;

    for (uint32_t i = 0; i < func->num_instructions; i++) {
        HelloInst instr = func->instructions[i];
        if (instr.kind == CONSTS || instr.kind == CALL) {
            ValkeyModule_Free(instr.param.string);
        }
    }

    ctx->program->functions[func->index] = NULL;
    ValkeyModule_Free(func->name);
    func->name = NULL;
    ValkeyModule_Free(func);
    ValkeyModule_Free(compiled_function->name);
    ValkeyModule_Free(compiled_function);
}

static ValkeyModuleScriptingEngineCompiledFunction **createHelloLangEngine(ValkeyModuleCtx *module_ctx,
                                                                           ValkeyModuleScriptingEngineCtx *engine_ctx,
                                                                           ValkeyModuleScriptingEngineSubsystemType type,
                                                                           const char *code,
                                                                           size_t code_len,
                                                                           size_t timeout,
                                                                           size_t *out_num_compiled_functions,
                                                                           ValkeyModuleString **err) {
    VALKEYMODULE_NOT_USED(module_ctx);
    VALKEYMODULE_NOT_USED(code_len);
    VALKEYMODULE_NOT_USED(type);
    VALKEYMODULE_NOT_USED(timeout);
    VALKEYMODULE_NOT_USED(err);

    HelloLangCtx *ctx = (HelloLangCtx *)engine_ctx;

    if (ctx->program == NULL) {
        ctx->program = ValkeyModule_Alloc(sizeof(HelloProgram));
        memset(ctx->program, 0, sizeof(HelloProgram));
    } else {
        ctx->program->num_functions = 0;
    }

    int ret = helloLangParseCode(code, ctx->program, err);
    if (ret < 0) {
        for (uint32_t i = 0; i < ctx->program->num_functions; i++) {
            HelloFunc *func = ctx->program->functions[i];
            ValkeyModule_Free(func->name);
            ValkeyModule_Free(func);
            ctx->program->functions[i] = NULL;
        }
        ctx->program->num_functions = 0;
        return NULL;
    }

    ValkeyModuleScriptingEngineCompiledFunction **compiled_functions =
        ValkeyModule_Alloc(sizeof(ValkeyModuleScriptingEngineCompiledFunction *) * ctx->program->num_functions);

    for (uint32_t i = 0; i < ctx->program->num_functions; i++) {
        HelloFunc *func = ctx->program->functions[i];

        ValkeyModuleScriptingEngineCompiledFunction *cfunc =
            ValkeyModule_Alloc(sizeof(ValkeyModuleScriptingEngineCompiledFunction));
        *cfunc = (ValkeyModuleScriptingEngineCompiledFunction) {
            .version = VALKEYMODULE_SCRIPTING_ENGINE_ABI_COMPILED_FUNCTION_VERSION,
            .name = ValkeyModule_CreateString(NULL, func->name, strlen(func->name)),
            .function = func,
            .desc = NULL,
            .f_flags = func->read_only ? VMSE_SCRIPT_FLAG_NO_WRITES : 0,
        };

        compiled_functions[i] = cfunc;
    }

    *out_num_compiled_functions = ctx->program->num_functions;

    return compiled_functions;
}

static void
callHelloLangFunction(ValkeyModuleCtx *module_ctx,
                      ValkeyModuleScriptingEngineCtx *engine_ctx,
                      ValkeyModuleScriptingEngineServerRuntimeCtx *server_ctx,
                      ValkeyModuleScriptingEngineCompiledFunction *compiled_function,
                      ValkeyModuleScriptingEngineSubsystemType type,
                      ValkeyModuleString **keys, size_t nkeys,
                      ValkeyModuleString **args, size_t nargs) {
    VALKEYMODULE_NOT_USED(keys);
    VALKEYMODULE_NOT_USED(nkeys);

    ValkeyModule_Assert(type == VMSE_EVAL || type == VMSE_FUNCTION);

    ValkeyModule_AutoMemory(module_ctx);

    HelloLangCtx *ctx = (HelloLangCtx *)engine_ctx;
    HelloFunc *func = (HelloFunc *)compiled_function->function;
    Value result;
    HelloExecutionState state = executeHelloLangFunction(
        module_ctx,
        server_ctx,
        &ctx->debug,
        func,
        args,
        nargs,
        &result);
    ValkeyModule_Assert(state == KILLED || state == FINISHED || state == ABORTED);

    if (state == KILLED) {
        if (type == VMSE_EVAL) {
            ValkeyModule_ReplyWithCustomErrorFormat(module_ctx, 1, "ERR Script killed by user with SCRIPT KILL.");
            return;
        }
        if (type == VMSE_FUNCTION) {
            ValkeyModule_ReplyWithCustomErrorFormat(module_ctx, 1, "ERR Script killed by user with FUNCTION KILL");
            return;
        }
    }
    else if (state == ABORTED) {
        ValkeyModule_ReplyWithCustomErrorFormat(module_ctx, 1, "ERR execution aborted during debugging session");
    }
    else {
        if (result.type == VALUE_INT) {
            ValkeyModule_Log(module_ctx, "info", "Function '%s' executed, returning %u", func->name, result.integer);
            ValkeyModule_ReplyWithLongLong(module_ctx, result.integer);
        } else if (result.type == VALUE_STRING) {
            ValkeyModule_Log(module_ctx, "info", "Function '%s' executed, returning string '%s'", func->name, result.string);
            ValkeyModule_ReplyWithCString(module_ctx, result.string);
        } else {
            ValkeyModule_Assert(result.type == VALUE_ERROR);
            ValkeyModule_Log(module_ctx, "info", "Function '%s' executed, returning error '%s'", func->name, result.string);
            ValkeyModule_ReplyWithCustomErrorFormat(module_ctx, 0, "%s",result.string);
        }
    }
}

static ValkeyModuleScriptingEngineCallableLazyEnvReset *helloResetEvalEnv(ValkeyModuleCtx *module_ctx,
                                                                          ValkeyModuleScriptingEngineCtx *engine_ctx,
                                                                          int async) {
    VALKEYMODULE_NOT_USED(module_ctx);
    VALKEYMODULE_NOT_USED(engine_ctx);
    VALKEYMODULE_NOT_USED(async);
    return NULL;
}

static ValkeyModuleScriptingEngineCallableLazyEnvReset *helloResetEnv(ValkeyModuleCtx *module_ctx,
                                                                      ValkeyModuleScriptingEngineCtx *engine_ctx,
                                                                      ValkeyModuleScriptingEngineSubsystemType type,
                                                                      int async) {
    VALKEYMODULE_NOT_USED(module_ctx);
    VALKEYMODULE_NOT_USED(engine_ctx);
    VALKEYMODULE_NOT_USED(type);
    VALKEYMODULE_NOT_USED(async);
    return NULL;
}

static int helloDebuggerStepCommand(ValkeyModuleString **argv, size_t argc, void *context) {
    VALKEYMODULE_NOT_USED(argv);
    VALKEYMODULE_NOT_USED(argc);
    HelloDebugCtx *ctx = context;
    ctx->stop_on_next_instr = 1;
    return 0;
}

static int helloDebuggerContinueCommand(ValkeyModuleString **argv, size_t argc, void *context) {
    VALKEYMODULE_NOT_USED(argv);
    VALKEYMODULE_NOT_USED(argc);
    HelloDebugCtx *ctx = context;
    ctx->stop_on_next_instr = 0;
    return 0;
}

static int helloDebuggerStackCommand(ValkeyModuleString **argv, size_t argc, void *context) {
    HelloDebugCtx *ctx = context;
    ValkeyModuleString *msg = NULL;

    if (argc > 1) {
        long long n;
        ValkeyModule_StringToLongLong(argv[1], &n);
        uint32_t index = (uint32_t)n;

        if (index >= ctx->sp) {
            ValkeyModuleString *msg = ValkeyModule_CreateStringPrintf(NULL, "Index out of range. Current stack size: %u", ctx->sp);
            ValkeyModule_ScriptingEngineDebuggerLog(msg, 0);
        }
        else {
            Value stackVal = ctx->stack[ctx->sp - index - 1];
            if (stackVal.type == VALUE_INT) {
                msg = ValkeyModule_CreateStringPrintf(NULL, "[%u] %d", index, stackVal.integer);
            } else {
                msg = ValkeyModule_CreateStringPrintf(NULL, "[%u] \"%s\"", index, stackVal.string);
            }
            ValkeyModule_ScriptingEngineDebuggerLog(msg, 0);
        }
    }
    else {
        msg = ValkeyModule_CreateStringPrintf(NULL, "Stack contents:");
        if (ctx->sp == 0) {
            msg = ValkeyModule_CreateStringPrintf(NULL, "[empty]");
            ValkeyModule_ScriptingEngineDebuggerLog(msg, 0);
        }
        else {
            ValkeyModule_ScriptingEngineDebuggerLog(msg, 0);
            for (uint32_t i=0; i < ctx->sp; i++) {
                Value stackVal = ctx->stack[ctx->sp - i - 1];
                if (stackVal.type == VALUE_INT) {
                    if (i == 0) {
                        msg = ValkeyModule_CreateStringPrintf(NULL, "top -> [%u] %d", i, stackVal.integer);
                    } else {
                        msg = ValkeyModule_CreateStringPrintf(NULL, "       [%u] %d", i, stackVal.integer);
                    }
                } else {
                    if (i == 0) {
                        msg = ValkeyModule_CreateStringPrintf(NULL, "top -> [%u] \"%s\"", i, stackVal.string);
                    } else {
                        msg = ValkeyModule_CreateStringPrintf(NULL, "       [%u] \"%s\"", i, stackVal.string);
                    }
                }
                ValkeyModule_ScriptingEngineDebuggerLog(msg, 0);
            }
        }
    }

    ValkeyModule_ScriptingEngineDebuggerFlushLogs();
    return 1;
}

static int helloDebuggerAbortCommand(ValkeyModuleString **argv, size_t argc, void *context) {
    VALKEYMODULE_NOT_USED(argv);
    VALKEYMODULE_NOT_USED(argc);
    HelloDebugCtx *ctx = context;
    ctx->abort = 1;
    return 0;
}

#define COMMAND_COUNT (4)

static ValkeyModuleScriptingEngineDebuggerCommandParam stack_params[1] = {
    {
        .name = "index",
        .optional = 1
    }
};

static ValkeyModuleScriptingEngineDebuggerCommand helloDebuggerCommands[COMMAND_COUNT] = {
    VALKEYMODULE_SCRIPTING_ENGINE_DEBUGGER_COMMAND("step", 1, NULL, 0, "Execute current instruction.", 0 ,helloDebuggerStepCommand),
    VALKEYMODULE_SCRIPTING_ENGINE_DEBUGGER_COMMAND("continue", 1, NULL, 0, "Continue normal execution.", 0, helloDebuggerContinueCommand),
    VALKEYMODULE_SCRIPTING_ENGINE_DEBUGGER_COMMAND("stack", 2, stack_params, 1, "Print stack contents. If index is specified, print only the value at index. Indexes start at 0 (top = 0).", 0, helloDebuggerStackCommand),
    VALKEYMODULE_SCRIPTING_ENGINE_DEBUGGER_COMMAND("abort", 1, NULL, 0, "Abort execution.", 0, helloDebuggerAbortCommand),
};

static ValkeyModuleScriptingEngineDebuggerEnableRet helloDebuggerEnable(ValkeyModuleCtx *module_ctx,
                                                                        ValkeyModuleScriptingEngineCtx *engine_ctx,
                                                                        ValkeyModuleScriptingEngineSubsystemType type,
                                                                        const ValkeyModuleScriptingEngineDebuggerCommand **commands,
                                                                        size_t *commands_len) {
    VALKEYMODULE_NOT_USED(module_ctx);
    VALKEYMODULE_NOT_USED(type);

    HelloLangCtx *ctx = (HelloLangCtx *)engine_ctx;
    ctx->debug = (HelloDebugCtx) {.enabled = 1};
    *commands = helloDebuggerCommands;
    *commands_len = COMMAND_COUNT;

    for (int i=0; i < COMMAND_COUNT; i++) {
        helloDebuggerCommands[i].context = &ctx->debug;
    }
    return VMSE_DEBUG_ENABLED;
}

static void helloDebuggerDisable(ValkeyModuleCtx *module_ctx,
                                 ValkeyModuleScriptingEngineCtx *engine_ctx,
                                 ValkeyModuleScriptingEngineSubsystemType type) {
    VALKEYMODULE_NOT_USED(module_ctx);
    VALKEYMODULE_NOT_USED(type);

    HelloLangCtx *ctx = (HelloLangCtx *)engine_ctx;
    ctx->debug = (HelloDebugCtx){0};

}

static void helloDebuggerStart(ValkeyModuleCtx *module_ctx,
                               ValkeyModuleScriptingEngineCtx *engine_ctx,
                               ValkeyModuleScriptingEngineSubsystemType type,
                               ValkeyModuleString *code) {
    VALKEYMODULE_NOT_USED(module_ctx);
    VALKEYMODULE_NOT_USED(type);
    VALKEYMODULE_NOT_USED(code);

    HelloLangCtx *ctx = (HelloLangCtx *)engine_ctx;
    ctx->debug.stop_on_next_instr = 1;
}

static void helloDebuggerEnd(ValkeyModuleCtx *module_ctx,
                               ValkeyModuleScriptingEngineCtx *engine_ctx,
                               ValkeyModuleScriptingEngineSubsystemType type) {
    VALKEYMODULE_NOT_USED(module_ctx);
    VALKEYMODULE_NOT_USED(type);

    HelloLangCtx *ctx = (HelloLangCtx *)engine_ctx;
    ctx->debug.stop_on_next_instr = 0;
    ctx->debug.abort = 0;
    ctx->debug.stack = NULL;
    ctx->debug.sp = 0;
}

int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx,
                        ValkeyModuleString **argv,
                        int argc) {
    VALKEYMODULE_NOT_USED(argv);
    VALKEYMODULE_NOT_USED(argc);

    if (ValkeyModule_Init(ctx, "helloengine", 1, VALKEYMODULE_APIVER_1) == VALKEYMODULE_ERR) {
        return VALKEYMODULE_ERR;
    }

    unsigned long long abi_version = VALKEYMODULE_SCRIPTING_ENGINE_ABI_VERSION;
    if (argc > 0) {
        if (ValkeyModule_StringToULongLong(argv[0], &abi_version) == VALKEYMODULE_ERR) {
            const char *arg_str = ValkeyModule_StringPtrLen(argv[0], NULL);
            ValkeyModule_Log(ctx, "error", "Invalid ABI version: %s", arg_str);
            return VALKEYMODULE_ERR;
        }
        else {
            const char *arg_str = ValkeyModule_StringPtrLen(argv[0], NULL);
            ValkeyModule_Log(ctx, "info", "initializing Hello scripting engine with ABI version: %s", arg_str);
        }
    }

    hello_ctx = ValkeyModule_Alloc(sizeof(HelloLangCtx));
    hello_ctx->program = NULL;
    hello_ctx->debug.enabled = 0;


    ValkeyModuleScriptingEngineMethodsV3 methodsV3;
    ValkeyModuleScriptingEngineMethodsV4 methodsV4;

    if (abi_version <= 2) {
        methodsV3 = (ValkeyModuleScriptingEngineMethodsV3) {
            .version = abi_version,
            .compile_code = createHelloLangEngine,
            .free_function = engineFreeFunction,
            .call_function = callHelloLangFunction,
            .get_function_memory_overhead = engineFunctionMemoryOverhead,
            .reset_eval_env_v2 = helloResetEvalEnv,
            .get_memory_info = engineGetMemoryInfo,
        };
    } else if (abi_version <= 3) {
        methodsV3 = (ValkeyModuleScriptingEngineMethodsV3) {
            .version = abi_version,
            .compile_code = createHelloLangEngine,
            .free_function = engineFreeFunction,
            .call_function = callHelloLangFunction,
            .get_function_memory_overhead = engineFunctionMemoryOverhead,
            .reset_env = helloResetEnv,
            .get_memory_info = engineGetMemoryInfo,
        };
    } else {
        methodsV4 = (ValkeyModuleScriptingEngineMethodsV4) {
            .version = abi_version,
            .compile_code = createHelloLangEngine,
            .free_function = engineFreeFunction,
            .call_function = callHelloLangFunction,
            .get_function_memory_overhead = engineFunctionMemoryOverhead,
            .reset_env = helloResetEnv,
            .get_memory_info = engineGetMemoryInfo,
            .debugger_enable = helloDebuggerEnable,
            .debugger_disable = helloDebuggerDisable,
            .debugger_start = helloDebuggerStart,
            .debugger_end = helloDebuggerEnd,
        };
    }

    ValkeyModuleScriptingEngineMethods *methods = abi_version <= 3 ?
        (ValkeyModuleScriptingEngineMethods *)&methodsV3 :
        (ValkeyModuleScriptingEngineMethods *)&methodsV4;

    ValkeyModule_RegisterScriptingEngine(ctx,
                                         "HELLO",
                                         hello_ctx,
                                         methods);

    return VALKEYMODULE_OK;
}

int ValkeyModule_OnUnload(ValkeyModuleCtx *ctx) {
    if (ValkeyModule_UnregisterScriptingEngine(ctx, "HELLO") != VALKEYMODULE_OK) {
        ValkeyModule_Log(ctx, "error", "Failed to unregister engine");
        return VALKEYMODULE_ERR;
    }

    ValkeyModule_Free(hello_ctx->program);
    hello_ctx->program = NULL;
    ValkeyModule_Free(hello_ctx);
    hello_ctx = NULL;

    return VALKEYMODULE_OK;
}
