#include "spelleffects.hpp"

#include <algorithm>

#include <components/esm3/loadmgef.hpp>
#include <components/misc/rng.hpp>
#include <components/settings/settings.hpp>
#include <components/misc/resourcehelpers.hpp>

#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"

#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/aifollow.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellresistance.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "../mwmechanics/summoning.hpp"

#include "../mwrender/animation.hpp"

#include "../mwworld/actionequip.hpp"
#include "../mwworld/actionteleport.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/player.hpp"

namespace
{
    float roll(const ESM::ActiveEffect& effect)
    {
        if(effect.mMinMagnitude == effect.mMaxMagnitude)
            return effect.mMinMagnitude;
        auto& prng = MWBase::Environment::get().getWorld()->getPrng();
        return effect.mMinMagnitude + Misc::Rng::rollDice(effect.mMaxMagnitude - effect.mMinMagnitude + 1, prng);
    }

    void modifyAiSetting(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, ESM::MagicEffect::Effects creatureEffect, MWMechanics::AiSetting setting, float magnitude, bool& invalid)
    {
        if(target == MWMechanics::getPlayer() || (effect.mEffectId == creatureEffect) == target.getClass().isNpc())
            invalid = true;
        else
        {
            auto& creatureStats = target.getClass().getCreatureStats(target);
            auto stat = creatureStats.getAiSetting(setting);
            stat.setModifier(static_cast<int>(stat.getModifier() + magnitude));
            creatureStats.setAiSetting(setting, stat);
        }
    }

    void adjustDynamicStat(const MWWorld::Ptr& target, int index, float magnitude, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false)
    {
        auto& creatureStats = target.getClass().getCreatureStats(target);
        auto stat = creatureStats.getDynamic(index);
        stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero, allowIncreaseAboveModified);
        creatureStats.setDynamic(index, stat);
    }

    void modDynamicStat(const MWWorld::Ptr& target, int index, float magnitude)
    {
        auto& creatureStats = target.getClass().getCreatureStats(target);
        auto stat = creatureStats.getDynamic(index);
        float current = stat.getCurrent();
        stat.setBase(std::max(0.f, stat.getBase() + magnitude));
        stat.setCurrent(current + magnitude);
        creatureStats.setDynamic(index, stat);
    }

    void damageAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
    {
        auto& creatureStats = target.getClass().getCreatureStats(target);
        auto attr = creatureStats.getAttribute(effect.mArg);
        if(effect.mEffectId == ESM::MagicEffect::DamageAttribute)
            magnitude = std::min(attr.getModified(), magnitude);
        attr.damage(magnitude);
        creatureStats.setAttribute(effect.mArg, attr);
    }

    void restoreAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
    {
        auto& creatureStats = target.getClass().getCreatureStats(target);
        auto attr = creatureStats.getAttribute(effect.mArg);
        attr.restore(magnitude);
        creatureStats.setAttribute(effect.mArg, attr);
    }

    void fortifyAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
    {
        auto& creatureStats = target.getClass().getCreatureStats(target);
        auto attr = creatureStats.getAttribute(effect.mArg);
        attr.setModifier(attr.getModifier() + magnitude);
        creatureStats.setAttribute(effect.mArg, attr);
    }

    void damageSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
    {
        auto& npcStats = target.getClass().getNpcStats(target);
        auto& skill = npcStats.getSkill(effect.mArg);
        if(effect.mEffectId == ESM::MagicEffect::DamageSkill)
            magnitude = std::min(skill.getModified(), magnitude);
        skill.damage(magnitude);
    }

    void restoreSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
    {
        auto& npcStats = target.getClass().getNpcStats(target);
        auto& skill = npcStats.getSkill(effect.mArg);
        skill.restore(magnitude);
    }

    void fortifySkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
    {
        auto& npcStats = target.getClass().getNpcStats(target);
        auto& skill = npcStats.getSkill(effect.mArg);
        skill.setModifier(skill.getModifier() + magnitude);
    }

    bool disintegrateSlot(const MWWorld::Ptr& ptr, int slot, float disintegrate)
    {
        MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
        MWWorld::ContainerStoreIterator item = inv.getSlot(slot);

        if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon))
        {
            if (!item->getClass().hasItemHealth(*item))
                return false;
            int charge = item->getClass().getItemHealth(*item);
            if (charge == 0)
                return false;

            // Store remainder of disintegrate amount (automatically subtracted if > 1)
            item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate));

            charge = item->getClass().getItemHealth(*item);
            charge -= std::min(static_cast<int>(disintegrate), charge);
            item->getCellRef().setCharge(charge);

            if (charge == 0)
            {
                // Will unequip the broken item and try to find a replacement
                if (ptr != MWMechanics::getPlayer())
                    inv.autoEquip(ptr);
                else
                    inv.unequipItem(*item, ptr);
            }

            return true;
        }

        return false;
    }

    int getBoundItemSlot(const MWWorld::Ptr& boundPtr)
    {
        const auto [slots, _] = boundPtr.getClass().getEquipmentSlots(boundPtr);
        if(!slots.empty())
            return slots[0];
        return -1;
    }

    void addBoundItem(const std::string& itemId, const MWWorld::Ptr& actor)
    {
        MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
        MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor);

        int slot = getBoundItemSlot(boundPtr);
        auto prevItem = slot >= 0 ? store.getSlot(slot) : store.end();

        MWWorld::ActionEquip action(boundPtr);
        action.execute(actor);

        if (actor != MWMechanics::getPlayer())
            return;

        MWWorld::Ptr newItem;
        auto it = slot >= 0 ? store.getSlot(slot) : store.end();
        // Equip can fail because beast races cannot equip boots/helmets
        if(it != store.end())
            newItem = *it;

        if (newItem.isEmpty() || boundPtr != newItem)
            return;

        MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();

        // change draw state only if the item is in player's right hand
        if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
            player.setDrawState(MWMechanics::DrawState::Weapon);

        if (prevItem != store.end())
            player.setPreviousItem(itemId, prevItem->getCellRef().getRefId());
    }

    void removeBoundItem(const std::string& itemId, const MWWorld::Ptr& actor)
    {
        MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
        auto item = std::find_if(store.begin(), store.end(), [&] (const auto& it)
        {
            return Misc::StringUtils::ciEqual(it.getCellRef().getRefId(), itemId);
        });
        if(item == store.end())
            return;
        int slot = getBoundItemSlot(*item);

        auto currentItem = store.getSlot(slot);

        bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId);

        if (actor != MWMechanics::getPlayer())
        {
            store.remove(itemId, 1, actor);

            // Equip a replacement
            if (!wasEquipped)
                return;

            auto type = currentItem->getType();
            if (type != ESM::Weapon::sRecordId && type != ESM::Armor::sRecordId && type != ESM::Clothing::sRecordId)
                return;

            if (actor.getClass().getCreatureStats(actor).isDead())
                return;

            if (!actor.getClass().hasInventoryStore(actor))
                return;

            if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
                return;

            actor.getClass().getInventoryStore(actor).autoEquip(actor);

            return;
        }

        MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
        std::string prevItemId = player.getPreviousItem(itemId);
        player.erasePreviousItem(itemId);

        if (!prevItemId.empty() && wasEquipped)
        {
            // Find previous item (or its replacement) by id.
            // we should equip previous item only if expired bound item was equipped.
            MWWorld::Ptr prevItem = store.findReplacement(prevItemId);
            if (!prevItem.isEmpty())
            {
                MWWorld::ActionEquip action(prevItem);
                action.execute(actor);
            }
        }

        store.remove(itemId, 1, actor);
    }

    bool isCorprusEffect(const MWMechanics::ActiveSpells::ActiveEffect& effect, bool harmfulOnly = false)
    {
        if(effect.mFlags & ESM::ActiveEffect::Flag_Applied && effect.mEffectId != ESM::MagicEffect::Corprus)
        {
            const auto* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectId);
            if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce && (!harmfulOnly || magicEffect->mData.mFlags & ESM::MagicEffect::Flags::Harmful))
                return true;
        }
        return false;
    }

    void absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
    {
        const auto& esmStore = MWBase::Environment::get().getWorld()->getStore();
        const ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find("VFX_Absorb");
        MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
        if (animation && !absorbStatic->mModel.empty())
        {
            const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
            animation->addEffect(
                Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel, vfs),
                ESM::MagicEffect::SpellAbsorption, false, std::string());
        }
        const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
        int spellCost = 0;
        if (spell)
        {
            spellCost = MWMechanics::calcSpellCost(*spell);
        }
        else
        {
            const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellId);
            if (enchantment)
                spellCost = MWMechanics::getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), caster);
        }

        // Magicka is increased by the cost of the spell
        auto& stats = target.getClass().getCreatureStats(target);
        auto magicka = stats.getMagicka();
        magicka.setCurrent(magicka.getCurrent() + spellCost);
        stats.setMagicka(magicka);
    }

    MWMechanics::MagicApplicationResult applyProtections(const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
        const MWMechanics::ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, const ESM::MagicEffect* magicEffect)
    {
        auto& stats = target.getClass().getCreatureStats(target);
        auto& magnitudes = stats.getMagicEffects();
        // Apply reflect and spell absorption
        if(target != caster && spellParams.getType() != ESM::ActiveSpells::Type_Enchantment && spellParams.getType() != ESM::ActiveSpells::Type_Permanent)
        {
            bool canReflect = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable) &&
                !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) && magnitudes.get(ESM::MagicEffect::Reflect).getMagnitude() > 0.f && !caster.isEmpty();
            bool canAbsorb = !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_SpellAbsorption) && magnitudes.get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f;
            if(canReflect || canAbsorb)
            {
                auto& prng = MWBase::Environment::get().getWorld()->getPrng();
                for(const auto& activeParam : stats.getActiveSpells())
                {
                    for(const auto& activeEffect : activeParam.getEffects())
                    {
                        if(!(activeEffect.mFlags & ESM::ActiveEffect::Flag_Applied))
                            continue;
                        if(activeEffect.mEffectId == ESM::MagicEffect::Reflect)
                        {
                            if(canReflect && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude)
                            {
                                return MWMechanics::MagicApplicationResult::REFLECTED;
                            }
                        }
                        else if(activeEffect.mEffectId == ESM::MagicEffect::SpellAbsorption)
                        {
                            if(canAbsorb && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude)
                            {
                                absorbSpell(spellParams.getId(), caster, target);
                                return MWMechanics::MagicApplicationResult::REMOVED;
                            }
                        }
                    }
                }
            }
        }
        // Notify the target actor they've been hit
        bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
        if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
            target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
        // Apply resistances
        if(!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances))
        {
            const ESM::Spell* spell = nullptr;
            if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
                spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellParams.getId());
            float magnitudeMult = MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes);
            if (magnitudeMult == 0)
            {
                // Fully resisted, show message
                if (target == MWMechanics::getPlayer())
                    MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
                else if (caster == MWMechanics::getPlayer())
                    MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
                return MWMechanics::MagicApplicationResult::REMOVED;
            }
            effect.mMinMagnitude *= magnitudeMult;
            effect.mMaxMagnitude *= magnitudeMult;
        }
        return MWMechanics::MagicApplicationResult::APPLIED;
    }

    static const std::map<int, std::string> sBoundItemsMap{
        {ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"},
        {ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"},
        {ESM::MagicEffect::BoundCuirass, "sMagicBoundCuirassID"},
        {ESM::MagicEffect::BoundDagger, "sMagicBoundDaggerID"},
        {ESM::MagicEffect::BoundGloves, "sMagicBoundLeftGauntletID"},
        {ESM::MagicEffect::BoundHelm, "sMagicBoundHelmID"},
        {ESM::MagicEffect::BoundLongbow, "sMagicBoundLongbowID"},
        {ESM::MagicEffect::BoundLongsword, "sMagicBoundLongswordID"},
        {ESM::MagicEffect::BoundMace, "sMagicBoundMaceID"},
        {ESM::MagicEffect::BoundShield, "sMagicBoundShieldID"},
        {ESM::MagicEffect::BoundSpear, "sMagicBoundSpearID"}
    };
}

namespace MWMechanics
{

void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, bool& receivedMagicDamage, bool& recalculateMagicka)
{
    const auto world = MWBase::Environment::get().getWorld();
    bool godmode = target == getPlayer() && world->getGodModeState();
    switch(effect.mEffectId)
    {
        case ESM::MagicEffect::CureCommonDisease:
            target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease();
            break;
        case ESM::MagicEffect::CureBlightDisease:
            target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease();
            break;
        case ESM::MagicEffect::RemoveCurse:
            target.getClass().getCreatureStats(target).getSpells().purgeCurses();
            break;
        case ESM::MagicEffect::CureCorprusDisease:
            target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Corprus);
            break;
        case ESM::MagicEffect::CurePoison:
            target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Poison);
            break;
        case ESM::MagicEffect::CureParalyzation:
            target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Paralyze);
            break;
        case ESM::MagicEffect::Dispel:
            // Dispel removes entire spells at once
            target.getClass().getCreatureStats(target).getActiveSpells().purge([magnitude=effect.mMagnitude] (const ActiveSpells::ActiveSpellParams& params)
            {
                if(params.getType() == ESM::ActiveSpells::Type_Temporary)
                {
                    const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(params.getId());
                    if (spell && spell->mData.mType == ESM::Spell::ST_Spell)
                    {
                        auto& prng = MWBase::Environment::get().getWorld()->getPrng();
                        return Misc::Rng::roll0to99(prng) < magnitude;
                    }
                }
                return false;
            }, target);
            break;
        case ESM::MagicEffect::AlmsiviIntervention:
        case ESM::MagicEffect::DivineIntervention:
            if (target != getPlayer())
                invalid = true;
            else if (world->isTeleportingEnabled())
            {
                auto marker = (effect.mEffectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker";
                world->teleportToClosestMarker(target, marker);
                if(!caster.isEmpty())
                {
                    MWRender::Animation* anim = world->getAnimation(caster);
                    anim->removeEffect(effect.mEffectId);
                    const ESM::Static* fx = world->getStore().get<ESM::Static>().search("VFX_Summon_end");
                    if (fx)
                    {
                        const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
                        anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), -1);
                    }
                }
            }
            else if (caster == getPlayer())
                MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
            break;
        case ESM::MagicEffect::Mark:
            if (target != getPlayer())
                invalid = true;
            else if (world->isTeleportingEnabled())
                world->getPlayer().markPosition(target.getCell(), target.getRefData().getPosition());
            else if (caster == getPlayer())
                MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
            break;
        case ESM::MagicEffect::Recall:
            if (target != getPlayer())
                invalid = true;
            else if (world->isTeleportingEnabled())
            {
                MWWorld::CellStore* markedCell = nullptr;
                ESM::Position markedPosition;

                world->getPlayer().getMarkedPosition(markedCell, markedPosition);
                if (markedCell)
                {
                    MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, markedPosition, false);
                    action.execute(target);
                    if(!caster.isEmpty())
                    {
                        MWRender::Animation* anim = world->getAnimation(caster);
                        anim->removeEffect(effect.mEffectId);
                    }
                }
            }
            else if (caster == getPlayer())
                MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
            break;
        case ESM::MagicEffect::CommandCreature:
        case ESM::MagicEffect::CommandHumanoid:
            if(caster.isEmpty() || !caster.getClass().isActor() || target == getPlayer() || (effect.mEffectId == ESM::MagicEffect::CommandCreature) == target.getClass().isNpc())
                invalid = true;
            else if(effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel())
            {
                MWMechanics::AiFollow package(caster, true);
                target.getClass().getCreatureStats(target).getAiSequence().stack(package, target);
            }
            break;
        case ESM::MagicEffect::ExtraSpell:
            if(target.getClass().hasInventoryStore(target))
            {
                auto& store = target.getClass().getInventoryStore(target);
                store.unequipAll(target);
            }
            else
                invalid = true;
            break;
        case ESM::MagicEffect::TurnUndead:
            if(target.getClass().isNpc() || target.get<ESM::Creature>()->mBase->mData.mType != ESM::Creature::Undead)
                invalid = true;
            else
            {
                auto& creatureStats = target.getClass().getCreatureStats(target);
                Stat<int> stat = creatureStats.getAiSetting(AiSetting::Flee);
                stat.setModifier(static_cast<int>(stat.getModifier() + effect.mMagnitude));
                creatureStats.setAiSetting(AiSetting::Flee, stat);
            }
            break;
        case ESM::MagicEffect::FrenzyCreature:
        case ESM::MagicEffect::FrenzyHumanoid:
            modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, effect.mMagnitude, invalid);
            break;
        case ESM::MagicEffect::CalmCreature:
        case ESM::MagicEffect::CalmHumanoid:
            modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, -effect.mMagnitude, invalid);
            if(!invalid && effect.mMagnitude > 0)
            {
                auto& creatureStats = target.getClass().getCreatureStats(target);
                creatureStats.getAiSequence().stopCombat();
            }
            break;
        case ESM::MagicEffect::DemoralizeCreature:
        case ESM::MagicEffect::DemoralizeHumanoid:
            modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, effect.mMagnitude, invalid);
            break;
        case ESM::MagicEffect::RallyCreature:
        case ESM::MagicEffect::RallyHumanoid:
            modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, -effect.mMagnitude, invalid);
            break;
        case ESM::MagicEffect::SummonScamp:
        case ESM::MagicEffect::SummonClannfear:
        case ESM::MagicEffect::SummonDaedroth:
        case ESM::MagicEffect::SummonDremora:
        case ESM::MagicEffect::SummonAncestralGhost:
        case ESM::MagicEffect::SummonSkeletalMinion:
        case ESM::MagicEffect::SummonBonewalker:
        case ESM::MagicEffect::SummonGreaterBonewalker:
        case ESM::MagicEffect::SummonBonelord:
        case ESM::MagicEffect::SummonWingedTwilight:
        case ESM::MagicEffect::SummonHunger:
        case ESM::MagicEffect::SummonGoldenSaint:
        case ESM::MagicEffect::SummonFlameAtronach:
        case ESM::MagicEffect::SummonFrostAtronach:
        case ESM::MagicEffect::SummonStormAtronach:
        case ESM::MagicEffect::SummonCenturionSphere:
        case ESM::MagicEffect::SummonFabricant:
        case ESM::MagicEffect::SummonWolf:
        case ESM::MagicEffect::SummonBear:
        case ESM::MagicEffect::SummonBonewolf:
        case ESM::MagicEffect::SummonCreature04:
        case ESM::MagicEffect::SummonCreature05:
            if(!target.isInCell())
                invalid = true;
            else
                effect.mArg = summonCreature(effect.mEffectId, target);
            break;
        case ESM::MagicEffect::BoundGloves:
            if(!target.getClass().hasInventoryStore(target))
            {
                invalid = true;
                break;
            }
            addBoundItem(world->getStore().get<ESM::GameSetting>().find("sMagicBoundRightGauntletID")->mValue.getString(), target);
            // left gauntlet added below
            [[fallthrough]];
        case ESM::MagicEffect::BoundDagger:
        case ESM::MagicEffect::BoundLongsword:
        case ESM::MagicEffect::BoundMace:
        case ESM::MagicEffect::BoundBattleAxe:
        case ESM::MagicEffect::BoundSpear:
        case ESM::MagicEffect::BoundLongbow:
        case ESM::MagicEffect::BoundCuirass:
        case ESM::MagicEffect::BoundHelm:
        case ESM::MagicEffect::BoundBoots:
        case ESM::MagicEffect::BoundShield:
            if(!target.getClass().hasInventoryStore(target))
                invalid = true;
            else
            {
                const std::string& item = sBoundItemsMap.at(effect.mEffectId);
                addBoundItem(world->getStore().get<ESM::GameSetting>().find(item)->mValue.getString(), target);
            }
            break;
        case ESM::MagicEffect::FireDamage:
        case ESM::MagicEffect::ShockDamage:
        case ESM::MagicEffect::FrostDamage:
        case ESM::MagicEffect::DamageHealth:
        case ESM::MagicEffect::Poison:
        case ESM::MagicEffect::DamageMagicka:
        case ESM::MagicEffect::DamageFatigue:
            if(!godmode)
            {
                int index = 0;
                if(effect.mEffectId == ESM::MagicEffect::DamageMagicka)
                    index = 1;
                else if(effect.mEffectId == ESM::MagicEffect::DamageFatigue)
                    index = 2;
                // Damage "Dynamic" abilities reduce the base value
                if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
                    modDynamicStat(target, index, -effect.mMagnitude);
                else
                {
                    static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
                    adjustDynamicStat(target, index, -effect.mMagnitude, index == 2 && uncappedDamageFatigue);
                    if(index == 0)
                        receivedMagicDamage = true;
                }
            }
            break;
        case ESM::MagicEffect::DamageAttribute:
            if(!godmode)
                damageAttribute(target, effect, effect.mMagnitude);
            break;
        case ESM::MagicEffect::DamageSkill:
            if(!target.getClass().isNpc())
                invalid = true;
            else if(!godmode)
            {
                // Damage Skill abilities reduce base skill :todd:
                if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
                {
                    auto& npcStats = target.getClass().getNpcStats(target);
                    SkillValue& skill = npcStats.getSkill(effect.mArg);
                    // Damage Skill abilities reduce base skill :todd:
                    skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f));
                }
                else
                    damageSkill(target, effect, effect.mMagnitude);
            }
            break;
        case ESM::MagicEffect::RestoreAttribute:
            restoreAttribute(target, effect, effect.mMagnitude);
            break;
        case ESM::MagicEffect::RestoreSkill:
            if(!target.getClass().isNpc())
                invalid = true;
            else
                restoreSkill(target, effect, effect.mMagnitude);
            break;
        case ESM::MagicEffect::RestoreHealth:
        case ESM::MagicEffect::RestoreMagicka:
        case ESM::MagicEffect::RestoreFatigue:
            adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::RestoreHealth, effect.mMagnitude);
            break;
        case ESM::MagicEffect::SunDamage:
            {
                // isInCell shouldn't be needed, but updateActor called during game start
                if (!target.isInCell() || !target.getCell()->isExterior() || godmode)
                    break;
                float time = world->getTimeStamp().getHour();
                float timeDiff = std::clamp(std::abs(time - 13.f), 0.f, 7.f);
                float damageScale = 1.f - timeDiff / 7.f;
                // When cloudy, the sun damage effect is halved
                static float fMagicSunBlockedMult = world->getStore().get<ESM::GameSetting>().find("fMagicSunBlockedMult")->mValue.getFloat();

                int weather = world->getCurrentWeather();
                if (weather > 1)
                    damageScale *= fMagicSunBlockedMult;
                float damage = effect.mMagnitude * damageScale;
                adjustDynamicStat(target, 0, -damage);
                if (damage > 0.f)
                    receivedMagicDamage = true;
            }
            break;
        case ESM::MagicEffect::DrainHealth:
        case ESM::MagicEffect::DrainMagicka:
        case ESM::MagicEffect::DrainFatigue:
            if(!godmode)
            {
                static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
                int index = effect.mEffectId - ESM::MagicEffect::DrainHealth;
                adjustDynamicStat(target, index, -effect.mMagnitude, uncappedDamageFatigue && index == 2);
                if(index == 0)
                    receivedMagicDamage = true;
            }
            break;
        case ESM::MagicEffect::FortifyHealth:
        case ESM::MagicEffect::FortifyMagicka:
        case ESM::MagicEffect::FortifyFatigue:
            if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
                modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude);
            else
                adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude, false, true);
            break;
        case ESM::MagicEffect::DrainAttribute:
            if(!godmode)
                damageAttribute(target, effect, effect.mMagnitude);
            break;
        case ESM::MagicEffect::FortifyAttribute:
            // Abilities affect base stats, but not for drain
            if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
            {
                auto& creatureStats = target.getClass().getCreatureStats(target);
                AttributeValue attr = creatureStats.getAttribute(effect.mArg);
                attr.setBase(attr.getBase() + effect.mMagnitude);
                creatureStats.setAttribute(effect.mArg, attr);
            }
            else
                fortifyAttribute(target, effect, effect.mMagnitude);
            break;
        case ESM::MagicEffect::DrainSkill:
            if(!target.getClass().isNpc())
                invalid = true;
            else if(!godmode)
                damageSkill(target, effect, effect.mMagnitude);
            break;
        case ESM::MagicEffect::FortifySkill:
            if(!target.getClass().isNpc())
                invalid = true;
            else if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
            {
                // Abilities affect base stats, but not for drain
                auto& npcStats = target.getClass().getNpcStats(target);
                auto& skill = npcStats.getSkill(effect.mArg);
                skill.setBase(skill.getBase() + effect.mMagnitude);
            }
            else
                fortifySkill(target, effect, effect.mMagnitude);
            break;
        case ESM::MagicEffect::FortifyMaximumMagicka:
            recalculateMagicka = true;
            break;
        case ESM::MagicEffect::AbsorbHealth:
        case ESM::MagicEffect::AbsorbMagicka:
        case ESM::MagicEffect::AbsorbFatigue:
            if(!godmode)
            {
                int index = effect.mEffectId - ESM::MagicEffect::AbsorbHealth;
                adjustDynamicStat(target, index, -effect.mMagnitude);
                if(!caster.isEmpty())
                    adjustDynamicStat(caster, index, effect.mMagnitude);
                if(index == 0)
                    receivedMagicDamage = true;
            }
            break;
        case ESM::MagicEffect::AbsorbAttribute:
            if(!godmode)
            {
                damageAttribute(target, effect, effect.mMagnitude);
                if(!caster.isEmpty())
                    fortifyAttribute(caster, effect, effect.mMagnitude);
            }
            break;
        case ESM::MagicEffect::AbsorbSkill:
            if(!target.getClass().isNpc())
                invalid = true;
            else if(!godmode)
            {
                damageSkill(target, effect, effect.mMagnitude);
                if(!caster.isEmpty())
                    fortifySkill(caster, effect, effect.mMagnitude);
            }
            break;
        case ESM::MagicEffect::DisintegrateArmor:
        {
            if (!target.getClass().hasInventoryStore(target))
            {
                invalid = true;
                break;
            }
            if (godmode)
                break;
            static const std::array<int, 9>  priorities
            {
                MWWorld::InventoryStore::Slot_CarriedLeft,
                MWWorld::InventoryStore::Slot_Cuirass,
                MWWorld::InventoryStore::Slot_LeftPauldron,
                MWWorld::InventoryStore::Slot_RightPauldron,
                MWWorld::InventoryStore::Slot_LeftGauntlet,
                MWWorld::InventoryStore::Slot_RightGauntlet,
                MWWorld::InventoryStore::Slot_Helmet,
                MWWorld::InventoryStore::Slot_Greaves,
                MWWorld::InventoryStore::Slot_Boots
            };
            for (const int priority : priorities)
            {
                if (disintegrateSlot(target, priority, effect.mMagnitude))
                    break;
            }
            break;
        }
        case ESM::MagicEffect::DisintegrateWeapon:
            if (!target.getClass().hasInventoryStore(target))
            {
                invalid = true;
                break;
            }
            if (!godmode)
                disintegrateSlot(target, MWWorld::InventoryStore::Slot_CarriedRight, effect.mMagnitude);
            break;
    }
}

bool shouldRemoveEffect(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect)
{
    const auto world = MWBase::Environment::get().getWorld();
    switch(effect.mEffectId)
    {
        case ESM::MagicEffect::Levitate:
        {
            if(!world->isLevitationEnabled())
            {
                if(target == getPlayer())
                    MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}");
                return true;
            }
            break;
        }
        case ESM::MagicEffect::Recall:
        case ESM::MagicEffect::DivineIntervention:
        case ESM::MagicEffect::AlmsiviIntervention:
        {
            return effect.mFlags & ESM::ActiveEffect::Flag_Applied;
        }
        case ESM::MagicEffect::WaterWalking:
        {
            if (target.getClass().isPureWaterCreature(target) && world->isSwimming(target))
                return true;
            if (effect.mFlags & ESM::ActiveEffect::Flag_Applied)
                break;
            if (!world->isWaterWalkingCastableOnTarget(target))
            {
                if(target == getPlayer())
                    MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidEffect}");
                return true;
            }
            break;
        }
    }
    return false;
}

MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt)
{
    const auto world = MWBase::Environment::get().getWorld();
    bool invalid = false;
    bool receivedMagicDamage = false;
    bool recalculateMagicka = false;
    if(effect.mEffectId == ESM::MagicEffect::Corprus && spellParams.shouldWorsen())
    {
        spellParams.worsen();
        for(auto& otherEffect : spellParams.getEffects())
        {
            if(isCorprusEffect(otherEffect))
                applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage, recalculateMagicka);
        }
        if(target == getPlayer())
            MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
        return MagicApplicationResult::APPLIED;
    }
    else if(shouldRemoveEffect(target, effect))
    {
        onMagicEffectRemoved(target, spellParams, effect);
        return MagicApplicationResult::REMOVED;
    }
    const auto* magicEffect = world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId);
    if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
    {
        if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce)
        {
            effect.mTimeLeft -= dt;
            return MagicApplicationResult::APPLIED;
        }
        else if(!dt)
            return MagicApplicationResult::APPLIED;
    }
    if(effect.mEffectId == ESM::MagicEffect::Lock)
    {
        if(target.getClass().canLock(target))
        {
            MWRender::Animation* animation = world->getAnimation(target);
            if(animation)
                animation->addSpellCastGlow(magicEffect);
            int magnitude = static_cast<int>(roll(effect));
            if(target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude
            {
                if(caster == getPlayer())
                    MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}");
                target.getCellRef().lock(magnitude);
            }
        }
        else
            invalid = true;
    }
    else if(effect.mEffectId == ESM::MagicEffect::Open)
    {
        if(target.getClass().canLock(target))
        {
            // Use the player instead of the caster for vanilla crime compatibility
            MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target);

            MWRender::Animation* animation = world->getAnimation(target);
            if(animation)
                animation->addSpellCastGlow(magicEffect);
            int magnitude = static_cast<int>(roll(effect));
            if(target.getCellRef().getLockLevel() <= magnitude)
            {
                if(target.getCellRef().getLockLevel() > 0)
                {
                    MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f);

                    if(caster == getPlayer())
                        MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}");
                }
                target.getCellRef().unlock();
            }
            else
            {
                MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f);
            }
        }
        else
            invalid = true;
    }
    else if(!target.getClass().isActor())
    {
        invalid = true;
    }
    else
    {
        // Morrowind.exe doesn't apply magic effects while the menu is open, we do because we like to see stats
        // updated instantly. We don't want to teleport instantly though
        if (!dt
            && (effect.mEffectId == ESM::MagicEffect::Recall
                || effect.mEffectId == ESM::MagicEffect::DivineIntervention
                || effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention))
            return MagicApplicationResult::APPLIED;
        auto& stats = target.getClass().getCreatureStats(target);
        auto& magnitudes = stats.getMagicEffects();
        if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
        {
            MagicApplicationResult result = applyProtections(target, caster, spellParams, effect, magicEffect);
            if(result != MagicApplicationResult::APPLIED)
                return result;
        }
        float oldMagnitude = 0.f;
        if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
            oldMagnitude = effect.mMagnitude;
        else
        {
            if(spellParams.getType() != ESM::ActiveSpells::Type_Enchantment)
                playEffects(target, *magicEffect, spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary);
            if(effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc() && target.getType() == ESM::Creature::sRecordId && target.get<ESM::Creature>()->mBase->mData.mSoul == 0 && caster == getPlayer())
                MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}");
        }
        float magnitude = roll(effect);
        //Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here
        effect.mMagnitude = magnitude;
        if(!(magicEffect->mData.mFlags & (ESM::MagicEffect::Flags::NoMagnitude | ESM::MagicEffect::Flags::AppliedOnce)))
        {
            if(effect.mDuration != 0)
            {
                float mult = dt;
                if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
                    mult = std::min(effect.mTimeLeft, dt);
                effect.mMagnitude *= mult;
            }
            if(effect.mMagnitude == 0)
            {
                effect.mMagnitude = oldMagnitude;
                effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
                effect.mTimeLeft -= dt;
                return MagicApplicationResult::APPLIED;
            }
        }
        if(effect.mEffectId == ESM::MagicEffect::Corprus)
            spellParams.worsen();
        else
            applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage, recalculateMagicka);
        effect.mMagnitude = magnitude;
        magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude));
    }
    effect.mTimeLeft -= dt;
    if(invalid)
    {
        effect.mTimeLeft = 0;
        effect.mFlags |= ESM::ActiveEffect::Flag_Remove;
        auto anim = world->getAnimation(target);
        if(anim)
            anim->removeEffect(effect.mEffectId);
    }
    else
        effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
    if (receivedMagicDamage && target == getPlayer())
        MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
    if(recalculateMagicka)
        target.getClass().getCreatureStats(target).recalculateMagicka();
    return MagicApplicationResult::APPLIED;
}

void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect)
{
    const auto world = MWBase::Environment::get().getWorld();
    auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects();
    bool invalid;
    switch(effect.mEffectId)
    {
        case ESM::MagicEffect::CommandCreature:
        case ESM::MagicEffect::CommandHumanoid:
            if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f)
            {
                auto& seq = target.getClass().getCreatureStats(target).getAiSequence();
                seq.erasePackageIf([&](const auto& package)
                {
                    return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast<const MWMechanics::AiFollow*>(package.get())->isCommanded();
                });
            }
            break;
        case ESM::MagicEffect::ExtraSpell:
            if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f)
                target.getClass().getInventoryStore(target).autoEquip(target);
            break;
        case ESM::MagicEffect::TurnUndead:
            {
                auto& creatureStats = target.getClass().getCreatureStats(target);
                Stat<int> stat = creatureStats.getAiSetting(AiSetting::Flee);
                stat.setModifier(static_cast<int>(stat.getModifier() - effect.mMagnitude));
                creatureStats.setAiSetting(AiSetting::Flee, stat);
            }
            break;
        case ESM::MagicEffect::FrenzyCreature:
        case ESM::MagicEffect::FrenzyHumanoid:
            modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, -effect.mMagnitude, invalid);
            break;
        case ESM::MagicEffect::CalmCreature:
        case ESM::MagicEffect::CalmHumanoid:
            modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, effect.mMagnitude, invalid);
            break;
        case ESM::MagicEffect::DemoralizeCreature:
        case ESM::MagicEffect::DemoralizeHumanoid:
            modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, -effect.mMagnitude, invalid);
            break;
        case ESM::MagicEffect::RallyCreature:
        case ESM::MagicEffect::RallyHumanoid:
            modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, effect.mMagnitude, invalid);
            break;
        case ESM::MagicEffect::SummonScamp:
        case ESM::MagicEffect::SummonClannfear:
        case ESM::MagicEffect::SummonDaedroth:
        case ESM::MagicEffect::SummonDremora:
        case ESM::MagicEffect::SummonAncestralGhost:
        case ESM::MagicEffect::SummonSkeletalMinion:
        case ESM::MagicEffect::SummonBonewalker:
        case ESM::MagicEffect::SummonGreaterBonewalker:
        case ESM::MagicEffect::SummonBonelord:
        case ESM::MagicEffect::SummonWingedTwilight:
        case ESM::MagicEffect::SummonHunger:
        case ESM::MagicEffect::SummonGoldenSaint:
        case ESM::MagicEffect::SummonFlameAtronach:
        case ESM::MagicEffect::SummonFrostAtronach:
        case ESM::MagicEffect::SummonStormAtronach:
        case ESM::MagicEffect::SummonCenturionSphere:
        case ESM::MagicEffect::SummonFabricant:
        case ESM::MagicEffect::SummonWolf:
        case ESM::MagicEffect::SummonBear:
        case ESM::MagicEffect::SummonBonewolf:
        case ESM::MagicEffect::SummonCreature04:
        case ESM::MagicEffect::SummonCreature05:
            {
                if(effect.mArg != -1)
                    MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, effect.mArg);
                auto& summons = target.getClass().getCreatureStats(target).getSummonedCreatureMap();
                auto [begin, end] = summons.equal_range(effect.mEffectId);
                for(auto it = begin; it != end; ++it)
                {
                    if(it->second == effect.mArg)
                    {
                        summons.erase(it);
                        break;
                    }
                }
            }
            break;
        case ESM::MagicEffect::BoundGloves:
            removeBoundItem(world->getStore().get<ESM::GameSetting>().find("sMagicBoundRightGauntletID")->mValue.getString(), target);
            [[fallthrough]];
        case ESM::MagicEffect::BoundDagger:
        case ESM::MagicEffect::BoundLongsword:
        case ESM::MagicEffect::BoundMace:
        case ESM::MagicEffect::BoundBattleAxe:
        case ESM::MagicEffect::BoundSpear:
        case ESM::MagicEffect::BoundLongbow:
        case ESM::MagicEffect::BoundCuirass:
        case ESM::MagicEffect::BoundHelm:
        case ESM::MagicEffect::BoundBoots:
        case ESM::MagicEffect::BoundShield:
            {
                const std::string& item = sBoundItemsMap.at(effect.mEffectId);
                removeBoundItem(world->getStore().get<ESM::GameSetting>().find(item)->mValue.getString(), target);
            }
            break;
        case ESM::MagicEffect::DrainHealth:
        case ESM::MagicEffect::DrainMagicka:
        case ESM::MagicEffect::DrainFatigue:
            adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::DrainHealth, effect.mMagnitude);
            break;
        case ESM::MagicEffect::FortifyHealth:
        case ESM::MagicEffect::FortifyMagicka:
        case ESM::MagicEffect::FortifyFatigue:
            if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
                modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude);
            else
                adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude, true);
            break;
        case ESM::MagicEffect::DrainAttribute:
            restoreAttribute(target, effect, effect.mMagnitude);
            break;
        case ESM::MagicEffect::FortifyAttribute:
            // Abilities affect base stats, but not for drain
            if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
            {
                auto& creatureStats = target.getClass().getCreatureStats(target);
                AttributeValue attr = creatureStats.getAttribute(effect.mArg);
                attr.setBase(attr.getBase() - effect.mMagnitude);
                creatureStats.setAttribute(effect.mArg, attr);
            }
            else
                fortifyAttribute(target, effect, -effect.mMagnitude);
            break;
        case ESM::MagicEffect::DrainSkill:
            restoreSkill(target, effect, effect.mMagnitude);
            break;
        case ESM::MagicEffect::FortifySkill:
            // Abilities affect base stats, but not for drain
            if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
            {
                auto& npcStats = target.getClass().getNpcStats(target);
                auto& skill = npcStats.getSkill(effect.mArg);
                skill.setBase(skill.getBase() - effect.mMagnitude);
            }
            else
                fortifySkill(target, effect, -effect.mMagnitude);
            break;
        case ESM::MagicEffect::FortifyMaximumMagicka:
            target.getClass().getCreatureStats(target).recalculateMagicka();
            break;
        case ESM::MagicEffect::AbsorbAttribute:
            {
                const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId());
                restoreAttribute(target, effect, effect.mMagnitude);
                if(!caster.isEmpty())
                    fortifyAttribute(caster, effect, -effect.mMagnitude);
            }
            break;
        case ESM::MagicEffect::AbsorbSkill:
            {
                const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId());
                restoreSkill(target, effect, effect.mMagnitude);
                if(!caster.isEmpty())
                    fortifySkill(caster, effect, -effect.mMagnitude);
            }
            break;
        case ESM::MagicEffect::Corprus:
            {
                int worsenings = spellParams.getWorsenings();
                spellParams.resetWorsenings();
                if(worsenings > 0)
                {
                    for(const auto& otherEffect : spellParams.getEffects())
                    {
                        if(isCorprusEffect(otherEffect, true))
                        {
                            for(int i = 0; i < worsenings; i++)
                                removeMagicEffect(target, spellParams, otherEffect);
                        }
                    }
                }
                //Note that we remove the effects, but keep the params
                target.getClass().getCreatureStats(target).getActiveSpells().purge([&spellParams] (const ActiveSpells::ActiveSpellParams& params, const auto&)
                {
                    return &spellParams == &params;
                }, target);
            }
            break;
    }
}

void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect)
{
    if(!(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
        return;
    auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects();
    magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMagnitude));
    removeMagicEffect(target, spellParams, effect);
    if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f)
    {
        auto anim = MWBase::Environment::get().getWorld()->getAnimation(target);
        if(anim)
            anim->removeEffect(effect.mEffectId);
    }
}

}
