#include "KingSystem/ActorSystem/actActorUtil.h"
#include <container/seadSafeArray.h>
#include "KingSystem/ActorSystem/Profiles/actRopeBase.h"
#include "KingSystem/ActorSystem/actActor.h"
#include "KingSystem/ActorSystem/actActorConstDataAccess.h"
#include "KingSystem/ActorSystem/actActorParam.h"
#include "KingSystem/ActorSystem/actBaseProcLink.h"
#include "KingSystem/ActorSystem/actInfoData.h"
#include "KingSystem/ActorSystem/actTag.h"
#include "KingSystem/GameData/gdtManager.h"
#include "KingSystem/Map/mapObject.h"
#include "KingSystem/Map/mapPlacementMgr.h"
#include "KingSystem/Resource/GeneralParamList/resGParamListObjectNpc.h"
#include "KingSystem/Resource/resResourceActorLink.h"
#include "KingSystem/Resource/resResourceGParamList.h"
#include "KingSystem/Utils/Byaml/Byaml.h"
#include "KingSystem/World/worldManager.h"

namespace ksys::act {

namespace {

ActorConstDataAccess getAccessor(BaseProcLink* link) {
    ActorConstDataAccess accessor;
    acquireActor(link, &accessor);
    return accessor;
}

ActorConstDataAccess getAccessor(Actor* actor) {
    ActorConstDataAccess accessor{actor};
    return accessor;
}

sead::SafeString sDefaultDropActor = "Item_Fruit_A";
sead::SafeArray<const char*, 6> sArrowTypes{{
    "NormalArrow",
    "BombArrow_A",
    "AncientArrow",
    "FireArrow",
    "IceArrow",
    "ElectricArrow",
}};
gdt::FlagHandle sAnimalMasterAppearanceHandle = gdt::InvalidHandle;
gdt::FlagHandle sFairyCountCheckHandle = gdt::InvalidHandle;
gdt::FlagHandle sIsGetStopTimerLv2Handle = gdt::InvalidHandle;

}  // namespace

bool hasTag(Actor* actor, const sead::SafeString& tag) {
    if (!actor)
        return false;
    return actor->getParam()->getRes().mActorLink->hasTag(tag.cstr());
}

bool hasTag(BaseProcLink* link, const sead::SafeString& tag) {
    return hasTag(getAccessor(link), tag);
}

bool hasTag(const ActorConstDataAccess& accessor, const sead::SafeString& tag) {
    return accessor.hasTag(tag.cstr());
}

bool hasTag(const sead::SafeString& actor, const sead::SafeString& tag) {
    auto* data = InfoData::instance();
    return data && data->hasTag(actor.cstr(), tag.cstr());
}

bool hasTag(Actor* actor, u32 tag) {
    return actor && actor->getParam()->getRes().mActorLink->hasTag(tag);
}

bool hasTag(BaseProcLink* link, u32 tag) {
    return hasTag(getAccessor(link), tag);
}

bool hasTag(const ActorConstDataAccess& accessor, u32 tag) {
    return accessor.hasTag(tag);
}

bool hasTag(const sead::SafeString& actor, u32 tag) {
    auto* data = InfoData::instance();
    return data && data->hasTag(actor.cstr(), tag);
}

bool hasOneTagAtLeast(Actor* actor, const sead::SafeString& tags) {
    sead::FixedSafeString<32> tag;
    for (auto it = tags.tokenBegin(","); tags.tokenEnd(",") != it; ++it) {
        it.get(&tag);
        if (hasTag(actor, tag))
            return true;
    }
    return false;
}

bool hasOneTagAtLeast(BaseProcLink* link, const sead::SafeString& tags) {
    sead::FixedSafeString<32> tag;
    for (auto it = tags.tokenBegin(","); tags.tokenEnd(",") != it; ++it) {
        it.get(&tag);
        if (hasTag(link, tag))
            return true;
    }
    return false;
}

bool hasOneTagAtLeast(const ActorConstDataAccess& accessor, const sead::SafeString& tags) {
    sead::FixedSafeString<32> tag;
    for (auto it = tags.tokenBegin(","); tags.tokenEnd(",") != it; ++it) {
        it.get(&tag);
        if (hasTag(accessor, tag))
            return true;
    }
    return false;
}

// NON_MATCHING: this version doesn't have unnecessary register moves.
bool shouldSkipSpawnWhenRaining(map::Object* obj) {
    if (obj->getFlags().isOff(map::Object::Flag::CreateNotRain))
        return false;

    if (!world::Manager::instance())
        return false;

    const auto pos = obj->getTranslate();
    return !world::Manager::instance()->isRaining(pos);
}

bool shouldSkipSpawnIfGodForestOff(map::Object* obj) {
    bool value = false;
    if (obj->getFlags().isOff(map::Object::Flag::UnderGodForestOff))
        return false;
    if (!gdt::Manager::instance()->getBool(sAnimalMasterAppearanceHandle, &value, true))
        return false;
    return value != 0;
}

bool shouldSkipSpawnGodForestActor(map::Object* obj) {
    bool value = false;
    if (obj->getFlags().isOn(map::Object::Flag::UnderGodForest) &&
        gdt::Manager::instance()->getBool(sAnimalMasterAppearanceHandle, &value, true) && !value) {
        return true;
    }
    return shouldSkipSpawnIfGodForestOff(obj);
}

static bool isFairyCountCheckEnabled() {
    bool value = false;
    return gdt::Manager::instance()->getBool(sFairyCountCheckHandle, &value, true) && value;
}

bool shouldSkipSpawnFairy(map::Object* obj) {
    const map::ActorData& actor_data = obj->getActorData();
    if (!actor_data.mFlags.isOnBit(map::ActorData::Flag::Fairy))
        return false;

    return isFairyCountCheckEnabled();
}

bool shouldSkipSpawnFairy(const sead::SafeString& actor) {
    auto* info = InfoData::instance();
    if (!info || !info->hasTag(actor.cstr(), tags::Fairy))
        return false;

    return isFairyCountCheckEnabled();
}

void initSpawnConditionGameDataFlags() {
    sFairyCountCheckHandle = gdt::Manager::instance()->getBoolHandle("FairyCountCheck");
    sAnimalMasterAppearanceHandle =
        gdt::Manager::instance()->getBoolHandle("AnimalMaster_Appearance");
    sIsGetStopTimerLv2Handle = gdt::Manager::instance()->getBoolHandle("IsGet_Obj_StopTimerLv2");
}

// TODO: remove this once IsGetStopTimerLv2 is used
// The only purpose of this function is to prevent sIsGetStopTimerLv2Handle from being optimized out
auto initSpawnConditionGameDataFlags_dummy() {
    return sIsGetStopTimerLv2Handle;
}

// NON_MATCHING: redundant branches in the original code.
bool hasAnyRevivalTag(const sead::SafeString& actor) {
    auto* info = InfoData::instance();
    al::ByamlIter iter;
    if (!info || !info->getActorIter(&iter, actor.cstr()))
        return false;

    for (auto tag : {
             tags::RevivalBloodyMoon,
             tags::RevivalRandom,
             tags::RevivalNone,
             tags::RevivalNoneForUsed,
             tags::RevivalRandomForDrop,
             tags::RevivalNoneForDrop,
             tags::RevivalUnderGodTime,
         }) {
        if (info->hasTag(iter, tag))
            return true;
    }
    return false;
}

bool hasStopTimerMiddleTag(Actor* actor) {
    return hasTag(actor, tags::StopTimerMiddle);
}

bool hasStopTimerShortTag(Actor* actor) {
    return hasTag(actor, tags::StopTimerShort);
}

// NON_MATCHING: ???
const char* arrowTypeToString(ArrowType idx) {
    return sArrowTypes[u32(idx)];
}

ArrowType arrowTypeFromString(const sead::SafeString& name) {
    for (s32 i = 0; i < sArrowTypes.size(); ++i) {
        if (name == sArrowTypes[i])
            return ArrowType(i);
    }
    return ArrowType::Invalid;
}

ZukanType getZukanType(al::ByamlIter* iter) {
    if (!iter)
        return ZukanType::Invalid;

    auto* info = InfoData::instance();

    if (info->hasTag(*iter, tags::ZukanAnimal))
        return ZukanType::Animal;

    if (info->hasTag(*iter, tags::ZukanEnemy))
        return ZukanType::Enemy;

    if (info->hasTag(*iter, tags::ZukanSozai))
        return ZukanType::Sozai;

    if (info->hasTag(*iter, tags::ZukanWeapon))
        return ZukanType::Weapon;

    if (info->hasTag(*iter, tags::ZukanOther))
        return ZukanType::Other;

    return ZukanType::Invalid;
}

ZukanType getZukanType(Actor* actor) {
    if (!actor)
        return ZukanType::Invalid;

    if (hasTag(actor, tags::ZukanAnimal))
        return ZukanType::Animal;

    if (hasTag(actor, tags::ZukanEnemy))
        return ZukanType::Enemy;

    if (hasTag(actor, tags::ZukanSozai))
        return ZukanType::Sozai;

    if (hasTag(actor, tags::ZukanWeapon))
        return ZukanType::Weapon;

    if (hasTag(actor, tags::ZukanOther))
        return ZukanType::Other;

    return ZukanType::Invalid;
}

static bool isProfile(const ActorConstDataAccess& accessor, const sead::SafeString& profile) {
    return accessor.hasProc() && accessor.getProfile() == profile;
}

bool isPlayerProfile(const ActorConstDataAccess& accessor) {
    return isProfile(accessor, "Player");
}

bool isPlayerProfile(Actor* actor) {
    return isPlayerProfile(getAccessor(actor));
}

bool isPlayerProfile(BaseProcLink* link) {
    return isPlayerProfile(getAccessor(link));
}

const sead::SafeString& getDefaultDropActor() {
    return sDefaultDropActor;
}

bool isCameraProfile(Actor* actor) {
    return isProfile(getAccessor(actor), "Camera");
}

bool isNPCProfile(const ActorConstDataAccess& accessor) {
    return isProfile(accessor, "NPC");
}

bool isNPCProfile(Actor* actor) {
    return isNPCProfile(getAccessor(actor));
}

bool isNPCProfile(BaseProcLink* link) {
    return isNPCProfile(getAccessor(link));
}

bool isNPCOffPodFromWeapon(Actor* actor) {
    if (!actor)
        return false;

    auto* gparam = actor->getParam()->getRes().mGParamList;
    if (!gparam)
        return false;

    const auto* npc_param = gparam->getNpc();
    return npc_param && npc_param->mIsOffPodFromWeapon.ref();
}

bool isDemoNPCProfile(Actor* actor) {
    return isProfile(getAccessor(actor), "DemoNPC");
}

bool isEnemyProfile(const ActorConstDataAccess& accessor) {
    if (!accessor.hasProc())
        return false;

    const auto& profile = accessor.getProfile();

    return profile == "Enemy" || profile == "GiantEnemy" || profile == "Guardian" ||
           profile == "GuardianComponent" || profile == "LastBoss" || profile == "GelEnemy" ||
           profile == "EnemySwarm" || profile == "SiteBoss" || profile == "Sandworm" ||
           profile == "Dragon";
}

bool isEnemyProfile(Actor* actor) {
    return isEnemyProfile(getAccessor(actor));
}

bool isEnemyProfile(BaseProcLink* link) {
    return isEnemyProfile(getAccessor(link));
}

bool isGuardianProfile(BaseProcLink* link) {
    const auto accessor = getAccessor(link);
    if (!accessor.hasProc())
        return false;

    const auto& profile = accessor.getProfile();
    return profile == "Guardian" || profile == "GuardianComponent";
}

bool isLargeEnemy(const ActorConstDataAccess& accessor) {
    if (!accessor.hasProc())
        return false;

    const auto& profile = accessor.getProfile();
    return profile == "GiantEnemy" || profile == "LastBoss" || profile == "SiteBoss" ||
           profile == "Sandworm" || profile == "Dragon";
}

bool isLargeEnemy(Actor* actor) {
    return isLargeEnemy(getAccessor(actor));
}

bool isGanonBeast(const ActorConstDataAccess& accessor) {
    return accessor.hasProc() && accessor.getName() == "Enemy_GanonBeast";
}

bool isGanonBeast(BaseProcLink* link) {
    return isGanonBeast(getAccessor(link));
}

bool isNotLivingCreature(Actor* actor) {
    return isNotLivingCreature(getAccessor(actor));
}

bool isNotLivingCreature(const ActorConstDataAccess& accessor) {
    if (isPlayerProfile(accessor))
        return false;
    if (isNPCProfile(accessor))
        return false;
    if (isEnemyProfile(accessor))
        return false;
    if (isHorseProfile(accessor))
        return false;
    if (isPreyOrSwarm(accessor))
        return false;
    return true;
}

bool isNotLivingCreature(BaseProcLink* link) {
    return isNotLivingCreature(getAccessor(link));
}

bool isWeaponProfile(const ActorConstDataAccess& accessor) {
    if (!accessor.hasProc())
        return false;
    return isWeaponProfile(accessor.getName());
}

bool isWeaponProfile(const sead::SafeString& actor) {
    const char* profile = nullptr;
    InfoData::instance()->getActorProfile(&profile, actor.cstr());
    return sead::SafeString(profile).startsWith("Weapon");
}

bool isWeaponProfile(Actor* actor) {
    return isWeaponProfile(getAccessor(actor));
}

bool isWeaponProfile(BaseProcLink* link) {
    return isWeaponProfile(getAccessor(link));
}

bool isWeaponOrArmor(const ActorConstDataAccess& accessor) {
    if (!accessor.hasProc())
        return false;

    const auto& name = accessor.getName();
    const char* profile_cstr = nullptr;
    InfoData::instance()->getActorProfile(&profile_cstr, name.cstr());

    const sead::SafeString profile = profile_cstr;
    return profile.startsWith("Weapon") || profile.startsWith("OptionalWeapon") ||
           profile.startsWith("Armor");
}

bool isWeaponOrArmor(Actor* actor) {
    return isWeaponOrArmor(getAccessor(actor));
}

bool isBulletProfile(const ActorConstDataAccess& accessor) {
    return isProfile(accessor, "Bullet");
}

bool isBulletProfile(Actor* actor) {
    return isBulletProfile(getAccessor(actor));
}

bool isBulletProfile(BaseProcLink* link) {
    return isBulletProfile(getAccessor(link));
}

bool isHorseProfile(const ActorConstDataAccess& accessor) {
    return isProfile(accessor, "Horse");
}

bool isHorseProfile(Actor* actor) {
    return isHorseProfile(getAccessor(actor));
}

bool isHorseProfile(BaseProcLink* link) {
    return isHorseProfile(getAccessor(link));
}

bool isGrabAttClientEnabled(void*, BaseProcLink* link) {
    return getAccessor(link).isAttClientEnabled("Grab");
}

bool isStalfosParts(BaseProcLink* link) {
    const auto accessor = getAccessor(link);
    return accessor.hasProc() && accessor.hasTag(tags::StalfosParts);
}

bool isDoor(const ActorConstDataAccess& accessor) {
    return accessor.hasProc() && accessor.hasTag(tags::Door);
}

bool isDoor(BaseProcLink* link) {
    return isDoor(getAccessor(link));
}

bool isPreyOrSwarm(const ActorConstDataAccess& accessor) {
    if (!accessor.hasProc())
        return false;
    const auto& profile = accessor.getProfile();
    return profile == "Prey" || profile == "Swarm";
}

bool isPreyOrSwarm(Actor* actor) {
    return isPreyOrSwarm(getAccessor(actor));
}

bool isPreyOrSwarm(BaseProcLink* link) {
    return isPreyOrSwarm(getAccessor(link));
}

bool isWolfOrBear(const ActorConstDataAccess& accessor) {
    if (!accessor.hasProc())
        return false;
    return accessor.hasTag(tags::AnimalTypeWolf) || accessor.hasTag(tags::AnimalTypeBear);
}

bool isWolfOrBear(Actor* actor) {
    return isWolfOrBear(getAccessor(actor));
}

bool isWolfOrBear(BaseProcLink* link) {
    return isWolfOrBear(getAccessor(link));
}

bool isRope(Actor* actor) {
    const auto accessor = getAccessor(actor);
    return accessor.hasProc() && accessor.isDerivedFrom<RopeBase>();
}

bool isRope(BaseProcLink* link) {
    const auto accessor = getAccessor(link);
    return accessor.hasProc() && accessor.isDerivedFrom<RopeBase>();
}

bool isTreeOrScaffoldOrSignboard(Actor* actor) {
    const auto accessor = getAccessor(actor);
    if (!accessor.hasProc())
        return false;

    return accessor.hasTag(tags::Tree) || accessor.hasTag(tags::Scaffold) ||
           accessor.hasTag(tags::Signboard);
}

bool isAirOctaPlatform(const sead::SafeString& name) {
    return name == "Obj_BoardWood_Square_01" || name == "Obj_BoardWood_Triangle_01" ||
           name == "FldObj_DLC_FlyShield_A_Snow_01" || name == "FldObj_DLC_FlyShield_A_Snow_02";
}

bool isAirOctaWoodPlatformDlc(const sead::SafeString& name) {
    return name == "FldObj_DLC_FlyShield_Wood_A_02" ||
           name == "FldObj_DLC_FlyShield_Wood_A_Snow_02";
}

}  // namespace ksys::act
