#include <stdint.h>
#include <string.h>

#include "../core_runtime/core_runtime_private.h"
#include "coduo_engine_structs.h"
#include "script_memory_private.h"
#include "script_runtime_private.h"
#include "script_variable_private.h"

enum {
    SCRIPT_VARIABLE_TYPE_MASK = 0x1f,
    SCRIPT_VARIABLE_NAME_SHIFT = 8,
    SCRIPT_VARIABLE_OBJECT_KEY_BASE = 0x10000,
    SCRIPT_VARIABLE_OBJECT_KEY_LIMIT = 0x20000,
    SCRIPT_VARIABLE_ENTITY_KEY_BASE = 0x800000,
    SCRIPT_VARIABLE_ENTITY_KEY_MASK = 0xffffff,
    SCRIPT_VARIABLE_ENTITY_KEY_TEST_BIAS = 0x7e0000,
    SCRIPT_VARIABLE_ENTITY_KEY_TEST_LIMIT = 0xfe0000,
    SCRIPT_VARIABLE_PERSISTENT_PACKED_BITS_MASK = 0x60,
    SCRIPT_VARIABLE_NAME_MASK = 0xffff,
    SCRIPT_VARIABLE_PACKED_LOW_BYTE_MASK = 0xff,
    SCRIPT_VARIABLE_HASH_MULTIPLIER = 31,
    SCRIPT_VARIABLE_HASH_BUCKET_MODULO = 0xffff,
    SCRIPT_VARIABLE_HASH_BUCKET_FIRST = 1,
    SCRIPT_VARIABLE_PRIMARY_BUCKET_BITS = 0x40,
    SCRIPT_VARIABLE_COLLISION_NODE_BITS = 0x20,
    SCRIPT_VARIABLE_LIST_NODE_BITS = 0x60,
    SCRIPT_VARIABLE_OCCUPANCY_BITS_MASK = 0x60,
    SCRIPT_VARIABLE_FREE_LIST_SENTINEL = 0,
    SCRIPT_VARIABLE_THREAD_PACKED_TYPE_INDEX = 0x6c,
    SCRIPT_VARIABLE_ENTITY_PACKED_TYPE_INDEX = 0x6d,
    SCRIPT_VARIABLE_STRUCT_PACKED_TYPE_INDEX = 0x6e,
    SCRIPT_VARIABLE_ARRAY_PACKED_TYPE_INDEX = 0x6f,
    SCRIPT_VALUE_SINGLE_CHAR_BUFFER_SIZE = 2,
    SCRIPT_VALUE_SINGLE_CHAR_STRING_TYPE = 0x0e,
    SCRIPT_VALUE_VECTOR_COMPONENT_COUNT = 3,
    SCRIPT_VECTOR_ALLOC_TAG = 2,
#if UINTPTR_MAX > UINT32_MAX
    SCRIPT_VECTOR_STORAGE_PREFIX_SIZE = sizeof(float),
#else
    SCRIPT_VECTOR_STORAGE_PREFIX_SIZE = sizeof(uint16_t),
#endif
    SCRIPT_VECTOR_STORAGE_SIZE =
        SCRIPT_VECTOR_STORAGE_PREFIX_SIZE + sizeof(vec3_t)
};

/* NOT_FROM_ORIGINAL_SOURCE: names the script variable hash bucket formula. */
static uint16_t
coduomp_script_variable_hash_bucket(uint16_t parent, uint32_t name)
{
    uint32_t hash = (uint32_t)parent * SCRIPT_VARIABLE_HASH_MULTIPLIER + name;

    return (uint16_t)(
        (uint16_t)hash + (uint16_t)(hash / SCRIPT_VARIABLE_HASH_BUCKET_MODULO) +
        SCRIPT_VARIABLE_HASH_BUCKET_FIRST);
}

void InitVariables(void)
{
    uint16_t previous =
        SCRIPT_VARIABLE_FREE_LIST_SENTINEL;

    for (uint32_t handle = 1; handle < CODUO_SCRIPT_VARIABLE_NODE_COUNT;
         ++handle) {
        uint16_t current =
            (uint16_t)handle;

        script_variableNodes[current].packedTypeIndex =
            CODUO_SCRIPT_VAR_UNDEFINED;
        script_variableIndirections[current].valueIndex = current;
        script_variableNodes[previous].link08 = current;
        script_variableNodes[current].payload.halves.valueOrRefCount = previous;
        previous = current;
    }

    script_variableNodes[SCRIPT_VARIABLE_FREE_LIST_SENTINEL].packedTypeIndex =
        CODUO_SCRIPT_VAR_UNDEFINED;
    script_variableIndirections[SCRIPT_VARIABLE_FREE_LIST_SENTINEL].valueIndex =
        SCRIPT_VARIABLE_FREE_LIST_SENTINEL;
    script_variableNodes[previous].link08 = SCRIPT_VARIABLE_FREE_LIST_SENTINEL;
    script_variableNodes[SCRIPT_VARIABLE_FREE_LIST_SENTINEL].payload.halves.valueOrRefCount =
        previous;
}

void Var_Init(void)
{
    SL_Init();
    InitVariables();
    script_classMapRoot = AllocObject();
    script_entityTypeClassMapRoot = AllocObject();
}

uint32_t Scr_GetNumScriptVars(void)
{
    return 0;
}

uint16_t
FindVariableIndexInternal(uint16_t parent,
                                    uint32_t name)
{
    uint16_t bucket =
        coduomp_script_variable_hash_bucket(parent, name);
    Variable *bucketSlot =
        &script_variableIndirections[bucket];
    uint16_t nodeIndex = bucketSlot->valueIndex;
    coduo_script_variable_node_t *node = &script_variableNodes[nodeIndex];

    if ((node->packedTypeIndex & SCRIPT_VARIABLE_OCCUPANCY_BITS_MASK) !=
        SCRIPT_VARIABLE_PRIMARY_BUCKET_BITS) {
        return SCRIPT_VARIABLE_FREE_LIST_SENTINEL;
    }

    if (GetVariableName(nodeIndex) == name) {
        return bucket;
    }

    uint16_t chainLink = node->link08;
    Variable *chainSlot =
        &script_variableIndirections[chainLink];
    nodeIndex = chainSlot->valueIndex;

    while (chainSlot != bucketSlot) {
        if (GetVariableName(nodeIndex) == name) {
            return chainLink;
        }

        chainLink = script_variableNodes[nodeIndex].link08;
        chainSlot = &script_variableIndirections[chainLink];
        nodeIndex = chainSlot->valueIndex;
    }

    return SCRIPT_VARIABLE_FREE_LIST_SENTINEL;
}

uint16_t
GetVariableIndexInternal(uint16_t parent,
                                   uint32_t name)
{
    uint16_t bucket =
        coduomp_script_variable_hash_bucket(parent, name);
    Variable *bucketSlot =
        &script_variableIndirections[bucket];
    uint16_t nodeIndex = bucketSlot->valueIndex;
    coduo_script_variable_node_t *node = &script_variableNodes[nodeIndex];
    uint32_t occupancyBits =
        node->packedTypeIndex & SCRIPT_VARIABLE_OCCUPANCY_BITS_MASK;
    uint16_t childIndirection = bucket;
    qboolean makePrimaryBucketNode = qtrue;

    if (occupancyBits == SCRIPT_VARIABLE_PRIMARY_BUCKET_BITS) {
        if (GetVariableName(nodeIndex) == name) {
            return bucket;
        }

        childIndirection = node->link08;
        Variable *chainSlot =
            &script_variableIndirections[childIndirection];
        uint16_t chainNodeIndex = chainSlot->valueIndex;
        while (chainSlot != bucketSlot) {
            if (GetVariableName(chainNodeIndex) == name) {
                return childIndirection;
            }

            childIndirection = script_variableNodes[chainNodeIndex].link08;
            chainSlot = &script_variableIndirections[childIndirection];
            chainNodeIndex = chainSlot->valueIndex;
        }

        node = AllocVariable();
        childIndirection = node->link08;
        node->packedTypeIndex = SCRIPT_VARIABLE_COLLISION_NODE_BITS;
        node->link08 = script_variableNodes[bucketSlot->valueIndex].link08;
        script_variableNodes[bucketSlot->valueIndex].link08 = childIndirection;
        bucketSlot = &script_variableIndirections[childIndirection];
        makePrimaryBucketNode = qfalse;
    } else if (occupancyBits == 0) {
        uint16_t previousFree = node->payload.halves.valueOrRefCount;
        uint16_t nextFree = node->link08;

        script_variableNodes[script_variableIndirections[previousFree].valueIndex]
            .link08 = nextFree;
        script_variableNodes[script_variableIndirections[nextFree].valueIndex]
            .payload.halves.valueOrRefCount = previousFree;
    } else {
        coduo_script_variable_node_t *newNode = AllocVariable();
        uint16_t newNodeIndex =
            (uint16_t)(newNode - script_variableNodes);
        uint16_t newIndirection = newNode->link08;
        coduo_script_variable_node_t *oldNode = node;
        uint16_t oldNodeIndex = nodeIndex;
        uint16_t oldPrev = bucketSlot->previousSibling;
        uint16_t oldNext = oldNode->link0a;

        script_variableNodes[script_variableIndirections[oldPrev].valueIndex]
            .link0a = newIndirection;
        script_variableIndirections[oldNext].previousSibling = newIndirection;

        if (occupancyBits == SCRIPT_VARIABLE_COLLISION_NODE_BITS) {
            uint16_t prevCollisionNode =
                script_variableIndirections[oldNode->link08].valueIndex;

            while (script_variableNodes[prevCollisionNode].link08 != bucket) {
                prevCollisionNode =
                    script_variableIndirections
                        [script_variableNodes[prevCollisionNode].link08]
                            .valueIndex;
            }
            script_variableNodes[prevCollisionNode].link08 = newIndirection;
        } else {
            oldNode->link08 = newIndirection;
        }

        script_variableIndirections[newIndirection].previousSibling =
            bucketSlot->previousSibling;
        script_variableIndirections[newIndirection].valueIndex = oldNodeIndex;
        bucketSlot->valueIndex = newNodeIndex;
        node = newNode;
    }

    if (makePrimaryBucketNode != qfalse) {
        node->packedTypeIndex = SCRIPT_VARIABLE_PRIMARY_BUCKET_BITS;
        node->link08 = childIndirection;
    }

    coduo_script_variable_node_t *parentNode = &script_variableNodes[parent];
    uint16_t oldFirstChild = parentNode->link0a;
    node->link0a = oldFirstChild;
    script_variableIndirections[oldFirstChild].previousSibling = childIndirection;
    bucketSlot->previousSibling = parentNode->link08;
    parentNode->link0a = childIndirection;

    node->packedTypeIndex = (uint8_t)node->packedTypeIndex;
    node->packedTypeIndex |= name << SCRIPT_VARIABLE_NAME_SHIFT;

    if ((parentNode->packedTypeIndex & SCRIPT_VARIABLE_TYPE_MASK) ==
        CODUO_SCRIPT_VAR_ARRAY) {
        parentNode->payload.halves.parentHandle++;
        if (name < SCRIPT_VARIABLE_OBJECT_KEY_BASE) {
            SL_AddRefToString(
                (uint16_t)(name &
                                                 SCRIPT_VARIABLE_NAME_MASK));
        } else if (name < SCRIPT_VARIABLE_OBJECT_KEY_LIMIT) {
            AddRefToObject(
                (uint16_t)(name &
                                                 SCRIPT_VARIABLE_NAME_MASK));
        }
    }

    return childIndirection;
}

coduo_script_variable_node_t *AllocVariable(void)
{
    uint16_t freeIndirection =
        script_variableNodes[SCRIPT_VARIABLE_FREE_LIST_SENTINEL].link08;

    if (freeIndirection == SCRIPT_VARIABLE_FREE_LIST_SENTINEL) {
        Scr_TerminalError("exceeded maximum number of script variables");
    }

    uint16_t nodeIndex =
        script_variableIndirections[freeIndirection].valueIndex;
    coduo_script_variable_node_t *node = &script_variableNodes[nodeIndex];

    script_variableNodes[SCRIPT_VARIABLE_FREE_LIST_SENTINEL].link08 =
        node->link08;
    script_variableNodes[script_variableIndirections[script_variableNodes
                              [SCRIPT_VARIABLE_FREE_LIST_SENTINEL]
                                  .link08]
                             .valueIndex]
        .payload.halves.valueOrRefCount = SCRIPT_VARIABLE_FREE_LIST_SENTINEL;

    node->link08 = freeIndirection;
    node->link0a = freeIndirection;
    script_variableIndirections[freeIndirection].previousSibling = freeIndirection;

    return node;
}

uint16_t AllocValue(void)
{
    coduo_script_variable_node_t *node = AllocVariable();

    node->packedTypeIndex = SCRIPT_VARIABLE_PERSISTENT_PACKED_BITS_MASK;
    return (uint16_t)(node - script_variableNodes);
}

void MakeVariableExternal(
    Variable *slot,
    coduo_script_variable_node_t *parentNode)
{
    uint16_t indirection =
        (uint16_t)(slot - script_variableIndirections);
    uint16_t nodeIndex = slot->valueIndex;
    coduo_script_variable_node_t *node = &script_variableNodes[nodeIndex];

    if ((parentNode->packedTypeIndex & SCRIPT_VARIABLE_TYPE_MASK) ==
        CODUO_SCRIPT_VAR_ARRAY) {
        parentNode->payload.halves.parentHandle--;
        uint32_t name = GetVariableName(nodeIndex);

        if (name < SCRIPT_VARIABLE_OBJECT_KEY_BASE) {
            SL_RemoveRefToString(
                (uint16_t)(name &
                                                 SCRIPT_VARIABLE_NAME_MASK));
        } else if (name < SCRIPT_VARIABLE_OBJECT_KEY_LIMIT) {
            RemoveRefToObject(
                (uint16_t)(name &
                                                 SCRIPT_VARIABLE_NAME_MASK));
        }
    }

    if ((node->packedTypeIndex & SCRIPT_VARIABLE_OCCUPANCY_BITS_MASK) ==
        SCRIPT_VARIABLE_PRIMARY_BUCKET_BITS) {
        uint16_t replacementIndirection = node->link08;
        Variable *replacementSlot =
            &script_variableIndirections[replacementIndirection];
        uint16_t replacementNodeIndex =
            replacementSlot->valueIndex;

        if (replacementSlot != slot) {
            coduo_script_variable_node_t *replacementNode =
                &script_variableNodes[replacementNodeIndex];
            uint16_t removedPrev = slot->previousSibling;
            uint16_t removedNext = node->link0a;
            uint16_t replacementPrev =
                replacementSlot->previousSibling;

            replacementNode->packedTypeIndex &=
                ~SCRIPT_VARIABLE_OCCUPANCY_BITS_MASK;
            replacementNode->packedTypeIndex |=
                SCRIPT_VARIABLE_PRIMARY_BUCKET_BITS;

            script_variableIndirections[replacementNode->link0a].previousSibling =
                indirection;
            script_variableNodes[script_variableIndirections[replacementPrev]
                                     .valueIndex]
                .link0a = indirection;
            script_variableIndirections[removedNext].previousSibling =
                replacementIndirection;
            script_variableNodes[script_variableIndirections[removedPrev]
                                     .valueIndex]
                .link0a = replacementIndirection;

            Variable removedSlotCopy = *slot;
            *slot = *replacementSlot;
            *replacementSlot = removedSlotCopy;
            indirection = replacementIndirection;
        }
    } else {
        Variable *previousSlot = slot;
        Variable *chainSlot = previousSlot;

        do {
            previousSlot = chainSlot;
            chainSlot = &script_variableIndirections[node->link08];
            node = &script_variableNodes[chainSlot->valueIndex];
        } while (chainSlot != slot);

        script_variableNodes[previousSlot->valueIndex].link08 = node->link08;
        node = &script_variableNodes[nodeIndex];
    }

    node->packedTypeIndex &= ~SCRIPT_VARIABLE_OCCUPANCY_BITS_MASK;
    node->packedTypeIndex |= SCRIPT_VARIABLE_LIST_NODE_BITS;
    node->link08 = indirection;
}

void FreeVariable(uint16_t handle)
{
    coduo_script_variable_node_t *node = &script_variableNodes[handle];
    uint16_t indirection = node->link08;
    uint16_t prevIndirection =
        script_variableIndirections[indirection].previousSibling;
    uint16_t nextIndirection = node->link0a;

    script_variableIndirections[nextIndirection].previousSibling = prevIndirection;
    script_variableNodes[script_variableIndirections[prevIndirection].valueIndex]
        .link0a = nextIndirection;

    node->packedTypeIndex &= ~SCRIPT_VARIABLE_OCCUPANCY_BITS_MASK;
    node->link08 =
        script_variableNodes[SCRIPT_VARIABLE_FREE_LIST_SENTINEL].link08;
    node->payload.halves.valueOrRefCount = SCRIPT_VARIABLE_FREE_LIST_SENTINEL;
    script_variableNodes[script_variableIndirections
                             [script_variableNodes
                                  [SCRIPT_VARIABLE_FREE_LIST_SENTINEL]
                                      .link08]
                                 .valueIndex]
        .payload.halves.valueOrRefCount = indirection;
    script_variableNodes[SCRIPT_VARIABLE_FREE_LIST_SENTINEL].link08 =
        indirection;
}

uint16_t
AllocThread(uint16_t parent)
{
    coduo_script_variable_node_t *node = AllocVariable();

    node->packedTypeIndex = SCRIPT_VARIABLE_THREAD_PACKED_TYPE_INDEX;
    node->payload.halves.valueOrRefCount = 0;
    node->payload.halves.parentHandle = parent;

    return (uint16_t)(node - script_variableNodes);
}

float *AllocVector(void)
{
#if UINTPTR_MAX > UINT32_MAX
    /* Stock VA 0x080a6c5c allocates 14 bytes, stores the uint16 refcount at
     * the allocation base, and returns base + 2. That exact i386 layout is
     * retained below. A native float pointer at base + 2 is under-aligned,
     * however, so the 64-bit adaptation uses the same two-block MT bucket but
     * leaves two padding bytes and returns base + 4. AddRefToVector and
     * RemoveRefToVector use this shared prefix size and still free the
     * original allocation base. */
    script_memory_block_t *storage =
        MT_Alloc(SCRIPT_VECTOR_STORAGE_SIZE, SCRIPT_VECTOR_ALLOC_TAG);
    uint16_t *refCount = (uint16_t *)storage;
    uint32_t *alignedWords = (uint32_t *)storage;

    *refCount = 0;
    return (float *)(alignedWords +
                     SCRIPT_VECTOR_STORAGE_PREFIX_SIZE / sizeof(*alignedWords));
#else
    uint16_t *refCount =
        MT_Alloc(sizeof(*refCount) + sizeof(vec3_t),
                             SCRIPT_VECTOR_ALLOC_TAG);
    *refCount = 0;

    return (float *)(refCount + 1);
#endif
}

coduo_script_value_payload_t AllocVectorCopy(const vec3_t vector)
{
    float *clone = AllocVector();

    clone[0] = vector[0];
    clone[1] = vector[1];
    clone[2] = vector[2];
    return (coduo_script_value_payload_t)clone;
}

void AddRefToVector(coduo_script_value_payload_t vectorPayload)
{
    /* Stock 0x080a6ccd and 0x080a6cd9 load allocator-header fields +0x04
     * and +0x0c, respectively. They are not separate vector-pool globals.
     * Keep the original unsigned address subtraction on widened hosts. */
    if ((uintptr_t)script_memoryAllocatorHeader.reserved <=
        (uintptr_t)vectorPayload -
            (uintptr_t)script_memoryAllocatorHeader.prev) {
        uint16_t *refCount = (uint16_t *)(
            vectorPayload -
            (coduo_script_value_payload_t)SCRIPT_VECTOR_STORAGE_PREFIX_SIZE);

        /* ORIGINAL_BINARY_BUG: stock 0x080a6ce9..0x080a6cf3 increments the
         * 16-bit count modulo 65,536 without overflow protection. */
        (*refCount)++;
    }
}

void RemoveRefToVector(coduo_script_value_payload_t vectorPayload)
{
    if ((uintptr_t)script_memoryAllocatorHeader.reserved <=
        (uintptr_t)vectorPayload -
            (uintptr_t)script_memoryAllocatorHeader.prev) {
        uint16_t *refCount = (uint16_t *)(
            vectorPayload -
            (coduo_script_value_payload_t)SCRIPT_VECTOR_STORAGE_PREFIX_SIZE);

        /* ORIGINAL_BINARY_BUG: stock 0x080a6d14..0x080a6d30 tests only zero
         * before decrementing the same wrapping 16-bit count. */
        if (*refCount == 0) {
            MT_Free(refCount, SCRIPT_VECTOR_STORAGE_SIZE);
            return;
        }

        (*refCount)--;
    }
}

void AddRefToValue(coduo_script_variable_type_t type,
                        coduo_script_value_payload_t payload)
{
    if (type == CODUO_SCRIPT_VAR_VECTOR) {
        AddRefToVector(payload);
        return;
    }

    if (type < CODUO_SCRIPT_VAR_FLOAT) {
        if (type > CODUO_SCRIPT_VAR_UNDEFINED) {
            SL_AddRefToString((uint16_t)payload);
        }
        return;
    }

    if (type == CODUO_SCRIPT_VAR_OBJECT) {
        AddRefToObject((uint16_t)payload);
    }
}

void RemoveRefToValue(coduo_script_variable_type_t type,
                         coduo_script_value_payload_t payload)
{
    if (type == CODUO_SCRIPT_VAR_VECTOR) {
        RemoveRefToVector(payload);
        return;
    }

    if (type < CODUO_SCRIPT_VAR_FLOAT) {
        if (type > CODUO_SCRIPT_VAR_UNDEFINED) {
            SL_RemoveRefToString((uint16_t)payload);
        }
        return;
    }

    if (type == CODUO_SCRIPT_VAR_OBJECT) {
        RemoveRefToObject((uint16_t)payload);
    }
}

void FreeValueInternal(coduo_script_variable_node_t *node)
{
    RemoveRefToValue(
        (coduo_script_variable_type_t)(node->packedTypeIndex &
                                       SCRIPT_VARIABLE_TYPE_MASK),
        node->payload.valuePayload);
    FreeVariable(
        (uint16_t)(node - script_variableNodes));
}

void FreeValue(uint16_t handle)
{
    FreeValueInternal(&script_variableNodes[handle]);
}

qboolean IsValidArrayIndex(uint32_t name)
{
    return (name + SCRIPT_VARIABLE_ENTITY_KEY_TEST_BIAS) <
                   SCRIPT_VARIABLE_ENTITY_KEY_TEST_LIMIT
               ? qtrue
               : qfalse;
}

uint32_t GetInternalVariableIndex(uint32_t name)
{
    return (name + SCRIPT_VARIABLE_ENTITY_KEY_BASE) &
           SCRIPT_VARIABLE_ENTITY_KEY_MASK;
}

uint16_t
FindArrayVariableIndex(uint16_t parent,
                       uint32_t name)
{
    return FindVariableIndexInternal(
        parent, GetInternalVariableIndex(name));
}

uint16_t
FindArrayVariable(uint16_t parent,
                                  int32_t name)
{
    uint16_t indirection =
        FindArrayVariableIndex(parent, name);

    return script_variableIndirections[indirection].valueIndex;
}

uint16_t
GetArrayVariableIndex(uint16_t parent,
                      uint32_t name)
{
    return GetVariableIndexInternal(
        parent, GetInternalVariableIndex(name));
}

uint16_t
GetArrayVariableBySignedIndex(uint16_t parent,
                                 int32_t name)
{
    uint16_t indirection =
        GetArrayVariableIndex(parent, name);

    return script_variableIndirections[indirection].valueIndex;
}

uint16_t
GetArrayVariableByUnsignedIndex(uint16_t classRoot,
                                uint32_t name)
{
    uint16_t indirection =
        GetArrayVariableIndex(classRoot, name);

    return script_variableIndirections[indirection].valueIndex;
}

uint16_t
GetVariableField(uint16_t parent,
                             uint16_t name)
{
    uint16_t indirection =
        FindVariableIndexInternal(parent, name);

    if (indirection != SCRIPT_VARIABLE_FREE_LIST_SENTINEL) {
        return script_variableIndirections[indirection].valueIndex;
    }

    coduo_script_variable_node_t *parentNode = &script_variableNodes[parent];
    coduo_script_variable_type_t parentType =
        (coduo_script_variable_type_t)(parentNode->packedTypeIndex &
                                       SCRIPT_VARIABLE_TYPE_MASK);

    if (parentType >= CODUO_SCRIPT_VAR_DEAD_THREAD) {
        ClearVariableValue(script_tempValueHandle);
        return script_tempValueHandle;
    }

    if (parentType != CODUO_SCRIPT_VAR_ENTITY) {
        return GetVariable(parent, name);
    }

    uint16_t classRoot =
        script_entityTypeUsageRecords[GetVariableName(parent)]
            .rootHandle;
    uint16_t builtinField =
        FindArrayVariable(classRoot, name);
    if (builtinField == SCRIPT_VARIABLE_FREE_LIST_SENTINEL) {
        return GetVariable(parent, name);
    }

    coduo_script_variable_node_t *tempNode =
        &script_variableNodes[script_tempValueHandle];

    ClearVariableValue(script_tempValueHandle);
    tempNode->packedTypeIndex =
        (parentNode->packedTypeIndex &
         ~(uint32_t)SCRIPT_VARIABLE_PACKED_LOW_BYTE_MASK) |
        tempNode->packedTypeIndex | CODUO_SCRIPT_VAR_KEY_VALUE;
    tempNode->payload.halves.valueOrRefCount = parentNode->payload.halves.parentHandle;
    tempNode->payload.halves.parentHandle = script_variableNodes[builtinField].payload.halves.parentHandle;

    return script_tempValueHandle;
}

void ClearVariableField(uint16_t parent,
                                     uint16_t name)
{
    uint16_t indirection =
        FindVariableIndexInternal(parent, name);

    if (indirection != SCRIPT_VARIABLE_FREE_LIST_SENTINEL) {
        RemoveVariable(parent, name);
        return;
    }

    coduo_script_variable_node_t *parentNode = &script_variableNodes[parent];
    if ((parentNode->packedTypeIndex & SCRIPT_VARIABLE_TYPE_MASK) ==
        CODUO_SCRIPT_VAR_ENTITY) {
        uint16_t classRoot =
            script_entityTypeUsageRecords[GetVariableName(parent)]
                .rootHandle;

        if (FindArrayVariable(classRoot, name) !=
            SCRIPT_VARIABLE_FREE_LIST_SENTINEL) {
            Scr_Error(
                "cannot set entity builtin key/value to undefined");
        }
    }
}

qboolean IsFieldObject(uint16_t handle)
{
    return (script_variableNodes[handle].packedTypeIndex &
            SCRIPT_VARIABLE_TYPE_MASK) < CODUO_SCRIPT_VAR_ARRAY
               ? qtrue
               : qfalse;
}

qboolean Scr_IsThreadAlive(uint16_t handle)
{
    return (script_variableNodes[handle].packedTypeIndex &
            SCRIPT_VARIABLE_TYPE_MASK) == CODUO_SCRIPT_VAR_THREAD
               ? qtrue
               : qfalse;
}

uint16_t
CastFieldObject(VariableValue *value)
{
    if (value->type == CODUO_SCRIPT_VAR_OBJECT) {
        uint16_t object =
            (uint16_t)value->payload;

        if (IsFieldObject(object) != qfalse) {
            VariableValue objectValue;

            objectValue.payload = object;
            objectValue.type = CODUO_SCRIPT_VAR_OBJECT;
            SetVariableValue(script_tempValueHandle, &objectValue);
            return object;
        }

        Scr_Error(
            va("%s is not an object",
               script_variableTypeNames[GetVarType(object)]));
    }

    Scr_Error(
        va("%s is not an object", script_variableTypeNames[value->type]));
    return 0;
}

void EvalArray(VariableValue *index,
                               VariableValue *container)
{
    if (container->type == CODUO_SCRIPT_VAR_STRING) {
        if (index->type != CODUO_SCRIPT_VAR_INT) {
            Scr_Error(va("%s is not a string index",
                                  script_variableTypeNames[index->type]));
        }

        int32_t stringIndex = (int32_t)index->payload;
        uint16_t stringHandle =
            (uint16_t)container->payload;
        const char *string = SL_ConvertToString(stringHandle);

        if (stringIndex >= 0 && (size_t)stringIndex < strlen(string)) {
            char singleChar[SCRIPT_VALUE_SINGLE_CHAR_BUFFER_SIZE];

            singleChar[0] = string[stringIndex];
            singleChar[1] = '\0';
            index->type = CODUO_SCRIPT_VAR_STRING;
            index->payload = SL_GetStringOfLen(
                singleChar, 0, sizeof(singleChar),
                SCRIPT_VALUE_SINGLE_CHAR_STRING_TYPE);
            SL_RemoveRefToString(stringHandle);
            return;
        }

        Scr_Error(va("string index %d out of range", (int32_t)index->payload));
    }

    if (container->type == CODUO_SCRIPT_VAR_VECTOR) {
        if (index->type != CODUO_SCRIPT_VAR_INT) {
            Scr_Error(va("%s is not a vector index",
                                  script_variableTypeNames[index->type]));
        }

        if ((uint32_t)index->payload < SCRIPT_VALUE_VECTOR_COMPONENT_COUNT) {
            uint32_t componentIndex = (uint32_t)index->payload;
            const float *vector = (const float *)container->payload;

            index->payload = 0;
            memcpy(&index->payload, &vector[componentIndex],
                   sizeof(vector[componentIndex]));
            index->type = CODUO_SCRIPT_VAR_FLOAT;
            RemoveRefToVector(container->payload);
            return;
        }

        Scr_Error(va("vector index %d out of range", (int32_t)index->payload));
    }

    if (container->type != CODUO_SCRIPT_VAR_OBJECT) {
        script_errorParameterIndex = 1;
        Scr_Error(
            va("%s is not an array, string, or vector",
               script_variableTypeNames[container->type]));
        return;
    }

    uint16_t array =
        (uint16_t)container->payload;
    if (GetVarType(array) != CODUO_SCRIPT_VAR_ARRAY) {
        script_errorParameterIndex = 1;
        Scr_Error(
            va("%s is not an array",
               script_variableTypeNames[GetVarType(array)]));
    }

    uint16_t childIndirection;
    if (index->type == CODUO_SCRIPT_VAR_STRING) {
        uint16_t stringHandle =
            (uint16_t)index->payload;

        childIndirection = FindVariableIndexInternal(array,
                                                               stringHandle);
        SL_RemoveRefToString(stringHandle);
    } else if (index->type == CODUO_SCRIPT_VAR_INT) {
        int32_t intIndex = (int32_t)index->payload;

        if (IsValidArrayIndex(intIndex) == qfalse) {
            /* Native64 widens payload storage; this message uses the script int. */
            Scr_Error(va("array index %d out of range",
                         intIndex));
        }

        childIndirection =
            FindArrayVariableIndex(array, intIndex);
    } else {
        Scr_Error(va("%s is not an array index",
                              script_variableTypeNames[index->type]));
        return;
    }

    uint16_t child =
        script_variableIndirections[childIndirection].valueIndex;
    if (child == 0) {
        RemoveRefToObject(array);
        index->type = CODUO_SCRIPT_VAR_UNDEFINED;
        return;
    }

    GetVariableValue(child, index);
    AddRefToValue(index->type, index->payload);
    RemoveRefToObject(array);
}

void ClearVariableValue(uint16_t handle)
{
    coduo_script_variable_node_t *node = &script_variableNodes[handle];

    RemoveRefToValue(
        (coduo_script_variable_type_t)(node->packedTypeIndex &
                                       SCRIPT_VARIABLE_TYPE_MASK),
        node->payload.valuePayload);
    node->packedTypeIndex &= SCRIPT_VARIABLE_PERSISTENT_PACKED_BITS_MASK;
}

uint16_t AllocObject(void)
{
    coduo_script_variable_node_t *node = AllocVariable();

    node->packedTypeIndex = SCRIPT_VARIABLE_STRUCT_PACKED_TYPE_INDEX;
    node->payload.halves.valueOrRefCount = 0;
    return (uint16_t)(node - script_variableNodes);
}

uint16_t Scr_AllocArray(void)
{
    coduo_script_variable_node_t *node = AllocVariable();

    node->packedTypeIndex = SCRIPT_VARIABLE_ARRAY_PACKED_TYPE_INDEX;
    node->payload.halves.valueOrRefCount = 0;
    node->payload.halves.parentHandle = 0;
    return (uint16_t)(node - script_variableNodes);
}

uint16_t
AllocEntity(int32_t classNum,
                           uint16_t entityNum)
{
    coduo_script_variable_node_t *node = AllocVariable();

    node->packedTypeIndex =
        ((uint32_t)classNum << SCRIPT_VARIABLE_NAME_SHIFT) |
        SCRIPT_VARIABLE_ENTITY_PACKED_TYPE_INDEX;
    node->payload.halves.valueOrRefCount = 0;
    node->payload.halves.parentHandle = entityNum;
    return (uint16_t)(node - script_variableNodes);
}

uint16_t
GetVariableKeyObject(uint16_t handle)
{
    return (uint16_t)(
        script_variableNodes[handle].packedTypeIndex >>
        SCRIPT_VARIABLE_NAME_SHIFT) &
           SCRIPT_VARIABLE_NAME_MASK;
}

uint32_t GetEntityType(uint16_t handle)
{
    return script_variableNodes[handle].packedTypeIndex >>
           SCRIPT_VARIABLE_NAME_SHIFT;
}

uint16_t
GetThreadNotifyName(uint16_t handle)
{
    return (uint16_t)(
        script_variableNodes[handle].packedTypeIndex >>
        SCRIPT_VARIABLE_NAME_SHIFT) &
           SCRIPT_VARIABLE_NAME_MASK;
}

void ClearThreadNotifyName(uint16_t handle)
{
    uint16_t name = GetThreadNotifyName(handle);

    SL_RemoveRefToString(name);
    script_variableNodes[handle].packedTypeIndex =
        (uint8_t)script_variableNodes[handle].packedTypeIndex;
}

void SetThreadNotifyName(uint16_t handle,
                                  uint16_t name)
{
    SL_AddRefToString(name);
    script_variableNodes[handle].packedTypeIndex |=
        (uint32_t)name << SCRIPT_VARIABLE_NAME_SHIFT;
}

uint16_t
GetSelf(uint16_t handle)
{
    return script_variableNodes[handle].payload.halves.parentHandle;
}

void AddRefToObject(uint16_t handle)
{
    /* ORIGINAL_BINARY_BUG: stock 0x080a6bf1..0x080a6bf9 increments this
     * 16-bit ownership count modulo 65,536 without overflow protection. */
    script_variableNodes[handle].payload.halves.valueOrRefCount++;
}

void RemoveRefToObject(uint16_t handle)
{
    /* ORIGINAL_BINARY_BUG: stock 0x080a6c2d..0x080a6c55 tests only zero
     * before destroying or decrementing the same wrapping 16-bit count. */
    if (script_variableNodes[handle].payload.halves.valueOrRefCount == 0) {
        ClearObject(handle);
        FreeVariable(handle);
        return;
    }

    script_variableNodes[handle].payload.halves.valueOrRefCount--;
}

void ClearObjectInternal(uint16_t handle)
{
    Variable *slot =
        &script_variableIndirections[script_variableNodes[handle].link0a];
    uint16_t child = slot->valueIndex;

    while (child != handle) {
        MakeVariableExternal(slot,
                                              &script_variableNodes[handle]);
        slot = &script_variableIndirections[script_variableNodes[child].link0a];
        child = slot->valueIndex;
    }

    child =
        script_variableIndirections[script_variableNodes[handle].link0a]
            .valueIndex;
    while (child != handle) {
        uint16_t next =
            script_variableIndirections[script_variableNodes[child].link0a]
                .valueIndex;

        FreeValueInternal(&script_variableNodes[child]);
        child = next;
    }
}

void ClearObject(uint16_t handle)
{
    AddRefToObject(handle);
    ClearObjectInternal(handle);
    RemoveRefToObject(handle);
}

uint16_t
FindVariable(uint16_t parent, uint32_t name)
{
    uint16_t indirection =
        FindVariableIndexInternal(parent, name);

    return script_variableIndirections[indirection].valueIndex;
}

uint16_t
FindObjectVariable(uint16_t parent,
                               uint16_t child)
{
    uint16_t indirection =
        FindVariableIndexInternal(
            parent, (uint32_t)child + SCRIPT_VARIABLE_OBJECT_KEY_BASE);

    return script_variableIndirections[indirection].valueIndex;
}

uint16_t
GetVariable(uint16_t parent, uint32_t name)
{
    uint16_t indirection =
        GetVariableIndexInternal(parent, name);

    return script_variableIndirections[indirection].valueIndex;
}

uint16_t
GetObjectVariable(uint16_t parent,
                              uint16_t child)
{
    uint16_t indirection =
        GetVariableIndexInternal(
            parent, (uint32_t)child + SCRIPT_VARIABLE_OBJECT_KEY_BASE);

    return script_variableIndirections[indirection].valueIndex;
}

void RemoveVariable(uint16_t parent,
                                uint32_t name)
{
    uint16_t indirection =
        FindVariableIndexInternal(parent, name);
    uint16_t child =
        script_variableIndirections[indirection].valueIndex;

    MakeVariableExternal(&script_variableIndirections[indirection],
                                          &script_variableNodes[parent]);
    FreeValueInternal(&script_variableNodes[child]);
}

void RemoveObjectVariable(uint16_t parent,
                                      uint16_t child)
{
    RemoveVariable(parent,
                               (uint32_t)child +
                                   SCRIPT_VARIABLE_OBJECT_KEY_BASE);
}

void SafeRemoveArrayVariable(
    uint16_t parent, uint32_t name)
{
    SafeRemoveVariable(
        parent, GetInternalVariableIndex(name));
}

void RemoveArrayVariable(uint16_t parent,
                         uint32_t name)
{
    RemoveVariable(parent,
                               GetInternalVariableIndex(name));
}

void SafeRemoveVariable(uint16_t parent,
                                        uint32_t name)
{
    uint16_t indirection =
        FindVariableIndexInternal(parent, name);

    if (indirection != 0) {
        uint16_t child =
            script_variableIndirections[indirection].valueIndex;

        MakeVariableExternal(
            &script_variableIndirections[indirection],
            &script_variableNodes[parent]);
        FreeValueInternal(&script_variableNodes[child]);
    }
}

void SetNewVariableValue(uint16_t handle,
                         VariableValue *value)
{
    script_variableNodes[handle].packedTypeIndex |= value->type;
    memcpy(&script_variableNodes[handle].payload.valuePayload, &value->payload,
           sizeof(value->payload));
}

void SetVariableValue(uint16_t handle,
                      VariableValue *value)
{
    coduo_script_variable_node_t *node = &script_variableNodes[handle];

    RemoveRefToValue(
        (coduo_script_variable_type_t)(node->packedTypeIndex &
                                       SCRIPT_VARIABLE_TYPE_MASK),
        node->payload.valuePayload);
    node->packedTypeIndex &= ~SCRIPT_VARIABLE_TYPE_MASK;
    node->packedTypeIndex |= value->type;
    memcpy(&node->payload.valuePayload, &value->payload, sizeof(value->payload));
}

void SetVariableFieldValue(uint16_t handle,
                                       VariableValue *value)
{
    coduo_script_variable_node_t *node = &script_variableNodes[handle];
    coduo_script_variable_type_t type =
        (coduo_script_variable_type_t)(node->packedTypeIndex &
                                       SCRIPT_VARIABLE_TYPE_MASK);

    if (type == CODUO_SCRIPT_VAR_KEY_VALUE) {
        SetEntityFieldValue(
            GetVariableName(handle), node->payload.halves.valueOrRefCount,
            node->payload.halves.parentHandle, value);
        return;
    }

    SetVariableValue(handle, value);
}

VariableValue *
GetVariableValueAddress(uint16_t handle)
{
    return (VariableValue *)&script_variableNodes[handle];
}

uint16_t
GetArraySize(uint16_t handle)
{
    return script_variableNodes[handle].payload.halves.parentHandle;
}

uint16_t
FindNextSibling(uint16_t handle)
{
    uint16_t link = script_variableNodes[handle].link0a;
    uint16_t node =
        script_variableIndirections[link].valueIndex;

    if ((script_variableNodes[node].packedTypeIndex &
         SCRIPT_VARIABLE_TYPE_MASK) < CODUO_SCRIPT_VAR_THREAD) {
        return node;
    }

    return 0;
}

void GetVariableValue(uint16_t handle,
                              VariableValue *value)
{
    value->type = (coduo_script_variable_type_t)(
        script_variableNodes[handle].packedTypeIndex & SCRIPT_VARIABLE_TYPE_MASK);
    memcpy(&value->payload, &script_variableNodes[handle].payload.valuePayload,
           sizeof(value->payload));
}

void GetVariableFieldValue(uint16_t handle,
                                    VariableValue *value)
{
    coduo_script_variable_node_t *node = &script_variableNodes[handle];
    coduo_script_variable_type_t type =
        (coduo_script_variable_type_t)(node->packedTypeIndex &
                                       SCRIPT_VARIABLE_TYPE_MASK);

    if (type == CODUO_SCRIPT_VAR_KEY_VALUE) {
        GetEntityFieldValue(
            GetVariableName(handle), node->payload.halves.valueOrRefCount,
            node->payload.halves.parentHandle, value);

        if (value->type == CODUO_SCRIPT_VAR_OBJECT &&
            GetVarType(
                (uint16_t)value->payload) ==
                CODUO_SCRIPT_VAR_ARRAY) {
            uint16_t source =
                (uint16_t)value->payload;

            RemoveRefToObject(source);
            value->payload = Scr_AllocArray();
            CopyArray(
                source, (uint16_t)value->payload);
        }
        return;
    }

    GetVariableValue(handle, value);
    AddRefToValue(value->type, value->payload);
}

uint32_t GetVariableName(uint16_t handle)
{
    return script_variableNodes[handle].packedTypeIndex >>
           SCRIPT_VARIABLE_NAME_SHIFT;
}

uint16_t
GetObject(uint16_t handle)
{
    coduo_script_variable_node_t *node = &script_variableNodes[handle];

    if ((node->packedTypeIndex & SCRIPT_VARIABLE_TYPE_MASK) ==
        CODUO_SCRIPT_VAR_UNDEFINED) {
        node->packedTypeIndex |= CODUO_SCRIPT_VAR_OBJECT;
        node->payload.halves.valueOrRefCount = AllocObject();
    }

    return node->payload.halves.valueOrRefCount;
}

uint16_t
GetArray(uint16_t handle)
{
    coduo_script_variable_node_t *node = &script_variableNodes[handle];

    if ((node->packedTypeIndex & SCRIPT_VARIABLE_TYPE_MASK) ==
        CODUO_SCRIPT_VAR_UNDEFINED) {
        node->packedTypeIndex |= CODUO_SCRIPT_VAR_OBJECT;
        node->payload.halves.valueOrRefCount = Scr_AllocArray();
    }

    return node->payload.halves.valueOrRefCount;
}

uint16_t
FindObject(uint16_t handle)
{
    return script_variableNodes[handle].payload.halves.valueOrRefCount;
}

coduo_script_variable_type_t
GetVarType(uint16_t handle)
{
    return (coduo_script_variable_type_t)(script_variableNodes[handle]
                                              .packedTypeIndex &
                                          SCRIPT_VARIABLE_TYPE_MASK);
}

void CopyArray(uint16_t source,
                                       uint16_t dest)
{
    uint16_t child =
        script_variableIndirections[script_variableNodes[source].link0a]
            .valueIndex;

    while (child != source) {
        coduo_script_variable_node_t *childNode = &script_variableNodes[child];
        coduo_script_variable_type_t childType =
            GetVarType(child);
        uint16_t destChild =
            GetVariable(dest, GetVariableName(child));
        coduo_script_variable_node_t *destNode =
            &script_variableNodes[destChild];

        destNode->packedTypeIndex |= childType;
        if (childType == CODUO_SCRIPT_VAR_OBJECT) {
            uint16_t childObject =
                childNode->payload.halves.valueOrRefCount;

            if (GetVarType(childObject) ==
                CODUO_SCRIPT_VAR_ARRAY) {
                destNode->payload.halves.valueOrRefCount = Scr_AllocArray();
                CopyArray(childObject,
                                                  destNode->payload.halves.valueOrRefCount);
            } else {
                destNode->payload.halves.valueOrRefCount = childObject;
                AddRefToObject(childObject);
            }
        } else {
            memcpy(&destNode->payload.valuePayload, &childNode->payload.valuePayload,
                   sizeof(destNode->payload.valuePayload));
            AddRefToValue(childType, childNode->payload.valuePayload);
        }

        child =
            script_variableIndirections[script_variableNodes[child].link0a]
                .valueIndex;
    }
}

void GetSizeValue(VariableValue *value)
{
    if (value->type == CODUO_SCRIPT_VAR_OBJECT) {
        uint16_t object =
            (uint16_t)value->payload;

        value->type = CODUO_SCRIPT_VAR_INT;
        if (GetVarType(object) == CODUO_SCRIPT_VAR_ARRAY) {
            value->payload = SCRIPT_VALUE_U32_PAYLOAD(
                script_variableNodes[object].payload.halves.parentHandle);
        } else {
            value->payload = SCRIPT_VALUE_U32_PAYLOAD(1);
        }
        RemoveRefToObject(object);
        return;
    }

    if (value->type == CODUO_SCRIPT_VAR_STRING) {
        uint16_t stringHandle =
            (uint16_t)value->payload;
        const char *text = SL_ConvertToString(stringHandle);

        value->type = CODUO_SCRIPT_VAR_INT;
        value->payload = SCRIPT_VALUE_U32_PAYLOAD(strlen(text));
        SL_RemoveRefToString(stringHandle);
        return;
    }

    Scr_Error(
        va("size cannot be applied to %s", script_variableTypeNames[value->type]));
}
