#include "CrowdControl.h"
#include "CrowdControlTypes.h"
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <spdlog/fmt/fmt.h>
#include "soh/ShipInit.hpp"

extern "C" {
#include <z64.h>
#include "functions.h"
extern PlayState* gPlayState;
}

void CrowdControl::Enable() {
    Network::Enable(CVarGetString(CVAR_REMOTE_CROWD_CONTROL("Host"), "127.0.0.1"),
                    CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Port"), 43384));
}

void CrowdControl::OnConnected() {
    ccThreadProcess = std::thread(&CrowdControl::ProcessActiveEffects, this);
}

void CrowdControl::OnDisconnected() {
    ccThreadProcess.join();
}

void CrowdControl::OnIncomingJson(nlohmann::json payload) {
    std::unique_ptr<Effect> incomingEffect = ParseMessage(payload);
    if (!incomingEffect) {
        return;
    }

    // If effect is not a timed effect, execute and return result.
    if (!incomingEffect->timeRemaining) {
        EffectResult result = CrowdControl::ExecuteEffect(incomingEffect.get());
        EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result);
    } else {
        // If another timed effect is already active that conflicts with the incoming effect.
        bool isConflictingEffectActive = false;
        for (const auto& effect : activeEffects) {
            if (effect != incomingEffect && effect->category == incomingEffect->category &&
                effect->id < incomingEffect->id) {
                isConflictingEffectActive = true;
                EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, EffectResult::Retry);
                break;
            }
        }

        if (!isConflictingEffectActive) {
            // Check if effect can be applied, if it can't, let CC know.
            EffectResult result = CrowdControl::CanApplyEffect(incomingEffect.get());
            if (result == EffectResult::Retry || result == EffectResult::Failure) {
                EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result);
                return;
            }

            activeEffectsMutex.lock();
            activeEffects.push_back(std::move(incomingEffect));
            activeEffectsMutex.unlock();
        }
    }
}

void CrowdControl::ProcessActiveEffects() {
    while (isEnabled) {
        // We only want to send events when status changes, on start we send Success.
        // If it fails at some point, we send Pause, and when it starts to succeed again we send Success.
        // CC uses this to pause the timer on the overlay.
        activeEffectsMutex.lock();
        auto it = activeEffects.begin();

        while (it != activeEffects.end()) {
            Effect* effect = it->get();
            EffectResult result = CrowdControl::ExecuteEffect(effect);

            if (result == EffectResult::Success) {
                // If time remaining has reached 0, we have finished the effect.
                if (effect->timeRemaining <= 0) {
                    GameInteractor::RemoveEffect(
                        *dynamic_cast<RemovableGameInteractionEffect*>(effect->giEffect.get()));
                    it = activeEffects.erase(it);
                } else {
                    // If we have a success after previously being paused, tell CC to resume timer.
                    if (effect->isPaused) {
                        effect->isPaused = false;
                        EmitMessage(effect->id, effect->timeRemaining, EffectResult::Resumed);
                        // If not paused before, subtract time from the timer and send a Success event if
                        // the result is different from the last time this was ran.
                        // Timed events are put on a thread that runs once per second.
                    } else {
                        effect->timeRemaining -= 1000;
                        if (result != effect->lastExecutionResult) {
                            effect->lastExecutionResult = result;
                            EmitMessage(effect->id, effect->timeRemaining, EffectResult::Success);
                        }
                    }
                    it++;
                }
            } else { // Timed effects only do Success or Retry
                if (!effect->isPaused && effect->timeRemaining > 0) {
                    effect->isPaused = true;
                    EmitMessage(effect->id, effect->timeRemaining, EffectResult::Paused);
                }
                it++;
            }
        }

        activeEffectsMutex.unlock();
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    SPDLOG_TRACE("[CrowdControl] Ending Process thread...");
}

void CrowdControl::EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status) {
    nlohmann::json payload;

    payload["id"] = eventId;
    payload["type"] = 0;
    payload["timeRemaining"] = timeRemaining;
    payload["status"] = status;

    SPDLOG_INFO("[CrowdControl] Sending payload:\n{}", payload.dump());

    SendJsonToRemote(payload);
}

CrowdControl::EffectResult CrowdControl::ExecuteEffect(Effect* effect) {
    if (!GameInteractor::IsPlayerInControl()) {
        return EffectResult::Retry;
    }

    GameInteractionEffectQueryResult giResult;
    if (effect->category == kEffectCatSpawnEnemy) {
        giResult = GameInteractor::RawAction::SpawnEnemyWithOffset(effect->spawnParams[0], effect->spawnParams[1],
                                                                   effect->viewerName);
    } else if (effect->category == kEffectCatSpawnActor) {
        giResult =
            GameInteractor::RawAction::SpawnActor(effect->spawnParams[0], effect->spawnParams[1], effect->viewerName);
    } else {
        giResult = GameInteractor::ApplyEffect(*effect->giEffect.get());
    }

    return TranslateGiEnum(giResult);
}

/// Checks if effect can be applied -- should not be used to check for spawn enemy effects.
CrowdControl::EffectResult CrowdControl::CanApplyEffect(Effect* effect) {
    assert(effect->category != kEffectCatSpawnEnemy || effect->category != kEffectCatSpawnActor);
    if (!GameInteractor::IsPlayerInControl()) {
        return EffectResult::Retry;
    }

    GameInteractionEffectQueryResult giResult = GameInteractor::CanApplyEffect(*effect->giEffect.get());

    return TranslateGiEnum(giResult);
}

CrowdControl::EffectResult CrowdControl::TranslateGiEnum(GameInteractionEffectQueryResult giResult) {
    // Translate GameInteractor result into CC's own enums.
    EffectResult result;
    if (giResult == GameInteractionEffectQueryResult::Possible) {
        result = EffectResult::Success;
    } else if (giResult == GameInteractionEffectQueryResult::TemporarilyNotPossible) {
        result = EffectResult::Retry;
    } else {
        result = EffectResult::Failure;
    }

    return result;
}

std::unique_ptr<CrowdControl::Effect> CrowdControl::ParseMessage(nlohmann::json dataReceived) {
    if (!dataReceived.contains("id") || !dataReceived.contains("type")) {
        SPDLOG_ERROR("[CrowdControl] Invalid payload received:\n{}", dataReceived.dump());
        return nullptr;
    }

    SPDLOG_INFO("[CrowdControl] Received payload:\n{}", dataReceived.dump());

    // "parameters" is intentionally not required: most effects (spawn enemies, teleports, status
    // effects, etc.) carry no parameters. Its absence is handled safely below, and any type error
    // is caught by the guard in Network::HandleRemoteJson.
    if (!dataReceived.contains("code") || !dataReceived.contains("viewer")) {
        // This seems to happen when the CC session ends
        SPDLOG_ERROR("[CrowdControl] Payload does not contain code or viewer, ignoring.");
        return nullptr;
    }

    auto effect = std::make_unique<Effect>();
    effect->lastExecutionResult = EffectResult::Initiate;
    effect->id = dataReceived["id"];
    effect->viewerName = dataReceived["viewer"];
    auto parameters = dataReceived["parameters"];
    uint32_t receivedParameter = 0;
    auto effectName = dataReceived["code"].get<std::string>();

    if (parameters.size() > 0) {
        receivedParameter = dataReceived["parameters"][0];
    }

    auto it = effectStringToEnum.find(effectName);
    if (it == effectStringToEnum.end()) {
        SPDLOG_ERROR("[CrowdControl] Unknown effect code: {}", effectName);
        return nullptr;
    }

    // Assign GameInteractionEffect + values to CC effect.
    // Categories are mostly used for checking for conflicting timed effects.
    switch (it->second) {

        // Spawn Enemies and Objects
        case kEffectSpawnCuccoStorm:
            effect->spawnParams[0] = ACTOR_EN_NIW;
            effect->category = kEffectCatSpawnActor;
            break;
        case kEffectSpawnLitBomb:
            effect->spawnParams[0] = ACTOR_EN_BOM;
            effect->category = kEffectCatSpawnActor;
            break;
        case kEffectSpawnExplosion:
            effect->spawnParams[0] = ACTOR_EN_BOM;
            effect->spawnParams[1] = 1;
            effect->category = kEffectCatSpawnActor;
            break;
        case kEffectSpawnArwing:
            effect->spawnParams[0] = ACTOR_EN_CLEAR_TAG;
            // Parameter for no cutscene Arwing
            effect->spawnParams[1] = 1;
            effect->category = kEffectCatSpawnEnemy;
            break;
        case kEffectSpawnDarklink:
            effect->spawnParams[0] = ACTOR_EN_TORCH2;
            effect->category = kEffectCatSpawnEnemy;
            break;
        case kEffectSpawnIronKnuckle:
            effect->spawnParams[0] = ACTOR_EN_IK;
            // Parameter for black standing Iron Knuckle
            effect->spawnParams[1] = 2;
            effect->category = kEffectCatSpawnEnemy;
            break;
        case kEffectSpawnStalfos:
            effect->spawnParams[0] = ACTOR_EN_TEST;
            // Parameter for gravity-obeying Stalfos
            effect->spawnParams[1] = 2;
            effect->category = kEffectCatSpawnEnemy;
            break;
        case kEffectSpawnFreezard:
            effect->spawnParams[0] = ACTOR_EN_FZ;
            effect->category = kEffectCatSpawnEnemy;
            break;
        case kEffectSpawnLikeLike:
            effect->spawnParams[0] = ACTOR_EN_RR;
            effect->category = kEffectCatSpawnEnemy;
            break;
        case kEffectSpawnGibdo:
            effect->spawnParams[0] = ACTOR_EN_RD;
            // Parameter for Gibdo
            effect->spawnParams[1] = 32766;
            effect->category = kEffectCatSpawnEnemy;
            break;
        case kEffectSpawnKeese:
            effect->spawnParams[0] = ACTOR_EN_FIREFLY;
            // Parameter for normal keese
            effect->spawnParams[1] = 2;
            effect->category = kEffectCatSpawnEnemy;
            break;
        case kEffectSpawnIceKeese:
            effect->spawnParams[0] = ACTOR_EN_FIREFLY;
            // Parameter for ice keese
            effect->spawnParams[1] = 4;
            effect->category = kEffectCatSpawnEnemy;
            break;
        case kEffectSpawnFireKeese:
            effect->spawnParams[0] = ACTOR_EN_FIREFLY;
            // Parameter for fire keese
            effect->spawnParams[1] = 1;
            effect->category = kEffectCatSpawnEnemy;
            break;
        case kEffectSpawnWolfos:
            effect->spawnParams[0] = ACTOR_EN_WF;
            // Match EnEncount1 wolfos spawner (0xFF00): high byte must be 0xFF so EnWf_Init does not treat
            // switchFlag 0; Flags_GetSwitch(play, 0) is true in many scenes and would instantly kill the actor.
            effect->spawnParams[1] = (0xFF << 8) | 0x00; // normal Wolfos; high byte 0xFF = no switch (vanilla encount)
            effect->category = kEffectCatSpawnEnemy;
            break;
        case kEffectSpawnWallmaster:
            effect->spawnParams[0] = ACTOR_EN_WALLMAS;
            effect->category = kEffectCatSpawnEnemy;
            break;

        // Link Modifiers
        case kEffectTakeHalfDamage:
            effect->category = kEffectCatDamageTaken;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyDefenseModifier>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = 2;
            break;
        case kEffectTakeDoubleDamage:
            effect->category = kEffectCatDamageTaken;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyDefenseModifier>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = -2;
            break;
        case kEffectOneHitKo:
            effect->category = kEffectCatDamageTaken;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::OneHitKO>();
            break;
        case kEffectInvincibility:
            effect->category = kEffectCatDamageTaken;
            effect->timeRemaining = 15000;
            effect->giEffect = std::make_unique<GameInteractionEffect::PlayerInvincibility>();
            break;
            break;
        case kEffectIncreaseSpeed:
            effect->category = kEffectCatSpeed;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyMovementSpeedMultiplier>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = 2;
            break;
        case kEffectDecreaseSpeed:
            effect->category = kEffectCatSpeed;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyMovementSpeedMultiplier>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = -2;
            break;
        case kEffectLowGravity:
            effect->category = kEffectCatGravity;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyGravity>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                GI_GRAVITY_LEVEL_LIGHT;
            break;
        case kEffectHighGravity:
            effect->category = kEffectCatGravity;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyGravity>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                GI_GRAVITY_LEVEL_HEAVY;
            break;
        case kEffectForceIronBoots:
            effect->category = kEffectCatBoots;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ForceEquipBoots>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                EQUIP_VALUE_BOOTS_IRON;
            break;
        case kEffectForceHoverBoots:
            effect->category = kEffectCatBoots;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ForceEquipBoots>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                EQUIP_VALUE_BOOTS_HOVER;
            break;
        case kEffectSlipperyFloor:
            effect->category = kEffectCatSlipperyFloor;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::SlipperyFloor>();
            break;
        case kEffectNoLedgeGrabs:
            effect->category = kEffectCatNoLedgeGrabs;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::DisableLedgeGrabs>();
            break;
        case kEffectRandomWind:
            effect->category = kEffectCatRandomWind;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::RandomWind>();
            break;
        case kEffectRandomBonks:
            effect->category = kEffectCatRandomBonks;
            effect->timeRemaining = 60000;
            effect->giEffect = std::make_unique<GameInteractionEffect::RandomBonks>();
            break;

        // Hurt or Heal Link
        case kEffectEmptyHeart:
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyHealth>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter * -1;
            break;
        case kEffectFillHeart:
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyHealth>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter;
            break;
        case kEffectKnockbackLinkWeak:
            effect->giEffect = std::make_unique<GameInteractionEffect::KnockbackPlayer>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = 1;
            break;
        case kEffectKnockbackLinkStrong:
            effect->giEffect = std::make_unique<GameInteractionEffect::KnockbackPlayer>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = 3;
            break;
        case kEffectKnockbackLinkMega:
            effect->giEffect = std::make_unique<GameInteractionEffect::KnockbackPlayer>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = 6;
            break;
        case kEffectBurnLink:
            effect->giEffect = std::make_unique<GameInteractionEffect::BurnPlayer>();
            break;
        case kEffectFreezeLink:
            effect->giEffect = std::make_unique<GameInteractionEffect::FreezePlayer>();
            break;
        case kEffectElectrocuteLink:
            effect->giEffect = std::make_unique<GameInteractionEffect::ElectrocutePlayer>();
            break;
        case kEffectKillLink:
            effect->giEffect = std::make_unique<GameInteractionEffect::SetPlayerHealth>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = 0;
            break;

        // Give Items and Consumables
        case kEffectAddHeartContainer:
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyHeartContainers>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = 1;
            break;
        case kEffectFillMagic:
            effect->giEffect = std::make_unique<GameInteractionEffect::FillMagic>();
            break;
        case kEffectAddRupees:
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyRupees>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter;
            break;
        case kEffectGiveDekuShield:
            effect->giEffect = std::make_unique<GameInteractionEffect::GiveOrTakeShield>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = ITEM_SHIELD_DEKU;
            break;
        case kEffectGiveHylianShield:
            effect->giEffect = std::make_unique<GameInteractionEffect::GiveOrTakeShield>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                ITEM_SHIELD_HYLIAN;
            break;
        case kEffectRefillSticks:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_STICK;
            break;
        case kEffectRefillNuts:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_NUT;
            break;
        case kEffectRefillBombs:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_BOMB;
            break;
        case kEffectRefillSeeds:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_SLINGSHOT;
            break;
        case kEffectRefillArrows:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_BOW;
            break;
        case kEffectRefillBombchus:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_BOMBCHU;
            break;

        // Take Items and Consumables
        case kEffectRemoveHeartContainer:
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyHeartContainers>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = -1;
            break;
        case kEffectEmptyMagic:
            effect->giEffect = std::make_unique<GameInteractionEffect::EmptyMagic>();
            break;
        case kEffectRemoveRupees:
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyRupees>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter * -1;
            break;
        case kEffectTakeDekuShield:
            effect->giEffect = std::make_unique<GameInteractionEffect::GiveOrTakeShield>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                -ITEM_SHIELD_DEKU;
            break;
        case kEffectTakeHylianShield:
            effect->giEffect = std::make_unique<GameInteractionEffect::GiveOrTakeShield>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                -ITEM_SHIELD_HYLIAN;
            break;
        case kEffectTakeSticks:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter * -1;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_STICK;
            break;
        case kEffectTakeNuts:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter * -1;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_NUT;
            break;
        case kEffectTakeBombs:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter * -1;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_BOMB;
            break;
        case kEffectTakeSeeds:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter * -1;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_SLINGSHOT;
            break;
        case kEffectTakeArrows:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter * -1;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_BOW;
            break;
        case kEffectTakeBombchus:
            effect->giEffect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                receivedParameter * -1;
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[1] = ITEM_BOMBCHU;
            break;

        // Link Size Modifiers
        case kEffectGiantLink:
            effect->category = kEffectCatLinkSize;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyLinkSize>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                GI_LINK_SIZE_GIANT;
            break;
        case kEffectMinishLink:
            effect->category = kEffectCatLinkSize;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyLinkSize>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                GI_LINK_SIZE_MINISH;
            break;
        case kEffectPaperLink:
            effect->category = kEffectCatLinkSize;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyLinkSize>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                GI_LINK_SIZE_PAPER;
            break;
        case kEffectSquishedLink:
            effect->category = kEffectCatLinkSize;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ModifyLinkSize>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                GI_LINK_SIZE_SQUISHED;
            break;
        case kEffectInvisibleLink:
            effect->category = kEffectCatLinkSize;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::InvisibleLink>();
            break;

        // Generic Effects
        case kEffectRandomBombTimer:
            effect->category = kEffectCatRandomBombFuseTimer;
            effect->timeRemaining = 60000;
            effect->giEffect = std::make_unique<GameInteractionEffect::RandomBombFuseTimer>();
            break;
        case kEffectSetTimeToDawn:
            effect->giEffect = std::make_unique<GameInteractionEffect::SetTimeOfDay>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                GI_TIMEOFDAY_DAWN;
            break;
        case kEffectSetTimeToDusk:
            effect->giEffect = std::make_unique<GameInteractionEffect::SetTimeOfDay>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                GI_TIMEOFDAY_DUSK;
            break;

        // Visual Effects
        case kEffectNoUi:
            effect->category = kEffectCatUi;
            effect->timeRemaining = 60000;
            effect->giEffect = std::make_unique<GameInteractionEffect::NoUI>();
            break;
        case kEffectRainstorm:
            effect->category = kEffectCatWeather;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::WeatherRainstorm>();
            break;
        case kEffectDebugMode:
            effect->category = kEffectCatDebugMode;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::SetCollisionViewer>();
            break;
        case kEffectRandomCosmetics:
            effect->giEffect = std::make_unique<GameInteractionEffect::RandomizeCosmetics>();
            break;

        // Controls
        case kEffectNoZButton:
            effect->category = kEffectCatNoZ;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::DisableZTargeting>();
            break;
        case kEffectReverseControls:
            effect->category = kEffectCatReverseControls;
            effect->timeRemaining = 60000;
            effect->giEffect = std::make_unique<GameInteractionEffect::ReverseControls>();
            break;
        case kEffectPacifistMode:
            effect->category = kEffectCatPacifist;
            effect->timeRemaining = 15000;
            effect->giEffect = std::make_unique<GameInteractionEffect::PacifistMode>();
            break;
        case kEffectPressRandomButtons:
            effect->category = kEffectCatRandomButtons;
            effect->timeRemaining = 30000;
            effect->giEffect = std::make_unique<GameInteractionEffect::PressRandomButton>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = 30;
            break;
        case kEffectClearCbuttons:
            effect->giEffect = std::make_unique<GameInteractionEffect::ClearAssignedButtons>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                GI_BUTTONS_CBUTTONS;
            break;
        case kEffectClearDpad:
            effect->giEffect = std::make_unique<GameInteractionEffect::ClearAssignedButtons>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] = GI_BUTTONS_DPAD;
            break;

        // Teleport Player
        case kEffectTpLinksHouse:
            effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                ENTR_LINKS_HOUSE_CHILD_SPAWN;
            break;
        case kEffectTpMinuet:
            effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                ENTR_SACRED_FOREST_MEADOW_WARP_PAD;
            break;
        case kEffectTpBolero:
            effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD;
            break;
        case kEffectTpSerenade:
            effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                ENTR_LAKE_HYLIA_WARP_PAD;
            break;
        case kEffectTpRequiem:
            effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                ENTR_DESERT_COLOSSUS_WARP_PAD;
            break;
        case kEffectTpNocturne:
            effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                ENTR_GRAVEYARD_WARP_PAD;
            break;
        case kEffectTpPrelude:
            effect->giEffect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
            dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect.get())->parameters[0] =
                ENTR_TEMPLE_OF_TIME_WARP_PAD;
            break;

        default:
            break;
    }

    return effect;
}
