from ..Class.itemClass import KH2Item
from ..List.inventory import (
    ability,
    form,
    growth,
    magic,
    misc,
    proof,
    storyunlock,
    summon,
    synth,
    keyblade,
)
from ..List.inventory.form import DriveForm
from ..List.inventory.growth import GrowthType
from ..List.inventory.item import InventoryItem
from ..List.location.graph import RequirementFunction


class ItemPlacementHelpers:

    @staticmethod
    def need_fire_blizzard_thunder(inventory: list[int]) -> bool:
        return (
            magic.Fire.id in inventory
            and magic.Blizzard.id in inventory
            and magic.Thunder.id in inventory
        )

    @staticmethod
    def need_1_magnet(inventory: list[int]) -> bool:
        return inventory.count(magic.Magnet.id) >= 1

    @staticmethod
    def need_2_magnet(inventory: list[int]) -> bool:
        return inventory.count(magic.Magnet.id) >= 2

    @staticmethod
    def need_3_thunders(inventory: list[int]) -> bool:
        return inventory.count(magic.Thunder.id) == 3

    @staticmethod
    def count_growth(inventory: list[int], growth_type: GrowthType) -> int:
        ids = [
            item.id for item in growth.all_growth() if item.growth_type == growth_type
        ]
        count = 0
        for item in inventory:
            if item in ids:
                count += 1
        return count

    @staticmethod
    def need_growths(inventory: list[int]) -> bool:
        return (
            ItemPlacementHelpers.count_growth(inventory, GrowthType.HIGH_JUMP) >= 3
            and ItemPlacementHelpers.count_growth(inventory, GrowthType.QUICK_RUN) >= 3
            and ItemPlacementHelpers.count_growth(inventory, GrowthType.AERIAL_DODGE)
            >= 3
            and ItemPlacementHelpers.count_growth(inventory, GrowthType.GLIDE) >= 3
        )

    @staticmethod
    def need_proof_connection(inventory: list[int]) -> bool:
        return proof.ProofOfConnection.id in inventory

    @staticmethod
    def need_proof_peace(inventory: list[int]) -> bool:
        return proof.ProofOfPeace.id in inventory

    @staticmethod
    def has_valor_form(inventory: list[int]) -> bool:
        return form.ValorForm.id in inventory

    @staticmethod
    def has_wisdom_form(inventory: list[int]) -> bool:
        return form.WisdomForm.id in inventory

    @staticmethod
    def has_limit_form(inventory: list[int]) -> bool:
        return form.LimitForm.id in inventory

    @staticmethod
    def has_master_form(inventory: list[int]) -> bool:
        return form.MasterForm.id in inventory

    @staticmethod
    def has_final_form(inventory: list[int]) -> bool:
        return form.FinalForm.id in inventory

    @staticmethod
    def can_level_valor(inventory: list[int]) -> bool:
        return True

    @staticmethod
    def can_level_wisdom(inventory: list[int]) -> bool:
        return True

    @staticmethod
    def can_level_limit(inventory: list[int]) -> bool:
        return True

    @staticmethod
    def can_level_master(inventory: list[int]) -> bool:
        return ItemPlacementHelpers.ht1_check(
            inventory
        ) or ItemPlacementHelpers.hb2_check(inventory)

    @staticmethod
    def can_level_final(inventory: list[int]) -> bool:
        return (
            ItemPlacementHelpers.twtnw_roxas_check(inventory)
            or ItemPlacementHelpers.pr2_check(inventory)
            or ItemPlacementHelpers.bc2_check(inventory)
            or ItemPlacementHelpers.lod2_check(inventory)
            or ItemPlacementHelpers.tt3_check(inventory)
        )

    @staticmethod
    def has_auto_valor(inventory: list[int]) -> bool:
        return ability.AutoValor.id in inventory

    @staticmethod
    def has_auto_wisdom(inventory: list[int]) -> bool:
        return ability.AutoWisdom.id in inventory

    @staticmethod
    def has_auto_limit(inventory: list[int]) -> bool:
        return ability.AutoLimitForm.id in inventory

    @staticmethod
    def has_auto_master(inventory: list[int]) -> bool:
        return ability.AutoMaster.id in inventory

    @staticmethod
    def has_auto_final(inventory: list[int]) -> bool:
        return ability.AutoFinal.id in inventory

    @staticmethod
    def count_forms(inventory: list[int]) -> int:
        count = 0
        if ItemPlacementHelpers.has_valor_form(inventory):
            count += 1
        if ItemPlacementHelpers.has_wisdom_form(inventory):
            count += 1
        if ItemPlacementHelpers.has_limit_form(inventory):
            count += 1
        if ItemPlacementHelpers.has_master_form(inventory):
            count += 1
        if ItemPlacementHelpers.has_final_form(inventory):
            count += 1
        return count

    @staticmethod
    def need_forms(inventory: list[int]) -> bool:
        return ItemPlacementHelpers.count_forms(inventory) == 5

    @staticmethod
    def need_summons(inventory: list[int]) -> bool:
        return (
            summon.LampCharm.id in inventory
            and summon.FeatherCharm.id in inventory
            and summon.UkuleleCharm.id in inventory
            and summon.BaseballCharm.id in inventory
        )

    @staticmethod
    def count_pages(inventory: list[int]) -> int:
        return inventory.count(misc.TornPages.id)

    @staticmethod
    def need_torn_pages(count: int) -> RequirementFunction:
        return lambda inventory: ItemPlacementHelpers.count_pages(inventory) >= count

    @staticmethod
    def need_proofs(inventory: list[int]) -> bool:
        return (
            proof.ProofOfConnection.id in inventory
            and proof.ProofOfNonexistence.id in inventory
            and proof.ProofOfPeace.id in inventory
        )

    @staticmethod
    def need_promise_charm(inventory: list[int]) -> bool:
        return misc.PromiseCharm.id in inventory

    @staticmethod
    def make_need_objectives_lambda(num_objectives_needed: int):
        return (
            lambda inventory: inventory.count(misc.ObjectiveItem.id)
            >= num_objectives_needed
        )

    @staticmethod
    def make_form_lambda(drive_form: DriveForm, form_level: int) -> RequirementFunction:
        if drive_form == form.ValorForm:
            return (
                lambda inventory: ItemPlacementHelpers.has_valor_form(inventory)
                and ItemPlacementHelpers.can_level_valor(inventory)
                and ItemPlacementHelpers.count_forms(inventory) >= form_level - 2
            )
        if drive_form == form.WisdomForm:
            return (
                lambda inventory: ItemPlacementHelpers.has_wisdom_form(inventory)
                and ItemPlacementHelpers.can_level_wisdom(inventory)
                and ItemPlacementHelpers.count_forms(inventory) >= form_level - 2
            )
        if drive_form == form.LimitForm:
            return (
                lambda inventory: ItemPlacementHelpers.has_limit_form(inventory)
                and ItemPlacementHelpers.can_level_limit(inventory)
                and ItemPlacementHelpers.count_forms(inventory) >= form_level - 2
            )
        if drive_form == form.MasterForm:
            return (
                lambda inventory: ItemPlacementHelpers.has_master_form(inventory)
                and ItemPlacementHelpers.can_level_master(inventory)
                and ItemPlacementHelpers.count_forms(inventory) >= form_level - 2
            )
        if drive_form == form.FinalForm:
            return (
                lambda inventory: ItemPlacementHelpers.has_final_form(inventory)
                and ItemPlacementHelpers.can_level_final(inventory)
                and ItemPlacementHelpers.count_forms(inventory) >= form_level - 2
            )
        return lambda inventory: False

    @staticmethod
    def make_form_lambda_nightmare(
        drive_form: DriveForm, form_level: int
    ) -> RequirementFunction:
        def count_auto_forms(inventory: list[int]) -> int:
            count = 0
            if ItemPlacementHelpers.has_auto_valor(inventory):
                count += 1
            if ItemPlacementHelpers.has_auto_wisdom(inventory):
                count += 1
            if ItemPlacementHelpers.has_auto_limit(inventory):
                count += 1
            if ItemPlacementHelpers.has_auto_master(inventory):
                count += 1
            return count

        def final_possible_but_not_obtained(inventory: list[int]) -> bool:
            return (
                ItemPlacementHelpers.has_valor_form(inventory)
                or ItemPlacementHelpers.has_wisdom_form(inventory)
                or ItemPlacementHelpers.has_limit_form(inventory)
                or ItemPlacementHelpers.has_master_form(inventory)
                or count_auto_forms(inventory) >= 1
            ) and not ItemPlacementHelpers.has_final_form(inventory)

        def max_form_level(inventory: list[int]) -> int:
            base_count = ItemPlacementHelpers.count_forms(inventory) + 2
            if final_possible_but_not_obtained(inventory):
                return base_count + 1
            else:
                return base_count

        if drive_form == form.ValorForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_valor_form(inventory)
                    or ItemPlacementHelpers.has_auto_valor(inventory)
                )
                and ItemPlacementHelpers.can_level_valor(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.WisdomForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_wisdom_form(inventory)
                    or ItemPlacementHelpers.has_auto_wisdom(inventory)
                )
                and ItemPlacementHelpers.can_level_wisdom(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.LimitForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_limit_form(inventory)
                    or ItemPlacementHelpers.has_auto_limit(inventory)
                )
                and ItemPlacementHelpers.can_level_limit(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.MasterForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_master_form(inventory)
                    or ItemPlacementHelpers.has_auto_master(inventory)
                )
                and ItemPlacementHelpers.can_level_master(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.FinalForm:

            def have_final_form(inventory: list[int]) -> bool:
                return ItemPlacementHelpers.has_final_form(
                    inventory
                ) or final_possible_but_not_obtained(inventory)

            if form_level == 2:
                return lambda inventory: (
                    have_final_form(inventory)
                    or ItemPlacementHelpers.has_auto_final(inventory)
                ) and ItemPlacementHelpers.can_level_final(inventory)
            else:
                return (
                    lambda inventory: have_final_form(inventory)
                    and max_form_level(inventory) >= form_level
                    and ItemPlacementHelpers.can_level_final(inventory)
                )

        return lambda inventory: False

    @staticmethod
    def make_form_lambda_nightmare_no_anti(
        drive_form: DriveForm, form_level: int
    ) -> RequirementFunction:
        def max_form_level(inventory: list[int]) -> int:
            base_count = ItemPlacementHelpers.count_forms(inventory) + 2
            return base_count

        if drive_form == form.ValorForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_valor_form(inventory)
                    or ItemPlacementHelpers.has_auto_valor(inventory)
                )
                and ItemPlacementHelpers.can_level_valor(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.WisdomForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_wisdom_form(inventory)
                    or ItemPlacementHelpers.has_auto_wisdom(inventory)
                )
                and ItemPlacementHelpers.can_level_wisdom(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.LimitForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_limit_form(inventory)
                    or ItemPlacementHelpers.has_auto_limit(inventory)
                )
                and ItemPlacementHelpers.can_level_limit(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.MasterForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_master_form(inventory)
                    or ItemPlacementHelpers.has_auto_master(inventory)
                )
                and ItemPlacementHelpers.can_level_master(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.FinalForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_final_form(inventory)
                    or ItemPlacementHelpers.has_auto_final(inventory)
                )
                and ItemPlacementHelpers.can_level_final(inventory)
                and max_form_level(inventory) >= form_level
            )

        return lambda inventory: False

    @staticmethod
    def make_form_lambda_nightmare_no_final(
        drive_form: DriveForm, form_level: int
    ) -> RequirementFunction:
        def final_possible_but_not_obtained(_: list[int]) -> bool:
            return False

        def max_form_level(inventory: list[int]) -> int:
            base_count = ItemPlacementHelpers.count_forms(inventory) + 2
            if final_possible_but_not_obtained(inventory):
                return base_count + 1
            else:
                return base_count

        if drive_form == form.ValorForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_valor_form(inventory)
                    or ItemPlacementHelpers.has_auto_valor(inventory)
                )
                and ItemPlacementHelpers.can_level_valor(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.WisdomForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_wisdom_form(inventory)
                    or ItemPlacementHelpers.has_auto_wisdom(inventory)
                )
                and ItemPlacementHelpers.can_level_wisdom(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.LimitForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_limit_form(inventory)
                    or ItemPlacementHelpers.has_auto_limit(inventory)
                )
                and ItemPlacementHelpers.can_level_limit(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.MasterForm:
            return (
                lambda inventory: (
                    ItemPlacementHelpers.has_master_form(inventory)
                    or ItemPlacementHelpers.has_auto_master(inventory)
                )
                and ItemPlacementHelpers.can_level_master(inventory)
                and max_form_level(inventory) >= form_level
            )
        if drive_form == form.FinalForm:

            def have_final_form(inventory: list[int]) -> bool:
                return final_possible_but_not_obtained(
                    inventory
                ) or ItemPlacementHelpers.has_final_form(inventory)

            if form_level == 2:
                return lambda inventory: (
                    have_final_form(inventory)
                    or ItemPlacementHelpers.has_auto_final(inventory)
                ) and ItemPlacementHelpers.can_level_final(inventory)
            else:
                return (
                    lambda inventory: have_final_form(inventory)
                    and max_form_level(inventory) >= form_level
                    and ItemPlacementHelpers.can_level_final(inventory)
                )

        return lambda inventory: False

    @staticmethod
    def get_number_visit_unlocks(inventory: list[int]) -> int:
        visit_unlock_ids = [u.id for u in storyunlock.all_story_unlocks()]
        running_total = 0
        for id in visit_unlock_ids:
            running_total += inventory.count(id)
        return running_total

    @staticmethod
    def make_level_group_check(group_index) -> RequirementFunction:
        thresholds = [
            1,
            3,
            6,
            9,
            12,
            15,
            18,
            21,
            24,
        ]  # modified from AP so that max level could in theory have a visit unlock
        if group_index < len(thresholds):
            return (
                lambda inv: ItemPlacementHelpers.get_number_visit_unlocks(inv)
                >= thresholds[group_index]
            )
        else:
            return (
                lambda inv: ItemPlacementHelpers.get_number_visit_unlocks(inv)
                >= thresholds[-1]
            )

    @staticmethod
    def stt_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.NaminesSketches.id) == 1

    @staticmethod
    def dc1_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.RoyalSummons.id) >= 1

    @staticmethod
    def dc2_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.RoyalSummons.id) == 2

    @staticmethod
    def oc1_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.BattlefieldsOfWar.id) >= 1

    @staticmethod
    def oc2_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.BattlefieldsOfWar.id) == 2

    @staticmethod
    def lod1_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.SwordOfTheAncestor.id) >= 1

    @staticmethod
    def lod2_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.SwordOfTheAncestor.id) == 2

    @staticmethod
    def bc1_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.BeastsClaw.id) >= 1

    @staticmethod
    def bc2_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.BeastsClaw.id) == 2

    @staticmethod
    def ht1_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.BoneFist.id) >= 1

    @staticmethod
    def ht2_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.BoneFist.id) == 2

    @staticmethod
    def pl1_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.ProudFang.id) >= 1

    @staticmethod
    def pl2_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.ProudFang.id) == 2

    @staticmethod
    def pr1_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.SkillAndCrossbones.id) >= 1

    @staticmethod
    def pr2_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.SkillAndCrossbones.id) == 2

    @staticmethod
    def ag1_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.Scimitar.id) >= 1

    @staticmethod
    def ag2_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.Scimitar.id) == 2

    @staticmethod
    def twtnw_roxas_check(inventory: list[int]) -> bool:
        return (
            inventory.count(storyunlock.WayToTheDawn.id) >= 1
            and ItemPlacementHelpers.get_number_visit_unlocks(inventory) > 10
        )

    @staticmethod
    def twtnw_post_saix_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.WayToTheDawn.id) == 2

    @staticmethod
    def sp1_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.IdentityDisk.id) >= 1

    @staticmethod
    def sp2_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.IdentityDisk.id) == 2

    @staticmethod
    def tt1_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.IceCream.id) >= 1

    @staticmethod
    def tt2_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.IceCream.id) >= 2

    @staticmethod
    def tt3_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.IceCream.id) == 3

    @staticmethod
    def hb1_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.MembershipCard.id) >= 1

    @staticmethod
    def hb2_check(inventory: list[int]) -> bool:
        return inventory.count(storyunlock.MembershipCard.id) == 2

    @staticmethod
    def make_synth_requirement(synth_item: KH2Item) -> RequirementFunction:
        # making lambdas for the different synth items
        default_access_lambda: RequirementFunction = lambda inventory: True
        synth_req_map: dict[InventoryItem, RequirementFunction] = {
            synth.BlazingShard: lambda inv: ItemPlacementHelpers.dc2_check(inv)
            or ItemPlacementHelpers.bc2_check(inv)
            or ItemPlacementHelpers.oc2_check(inv),
            synth.BlazingStone: lambda inv: ItemPlacementHelpers.lod2_check(inv)
            or ItemPlacementHelpers.sp2_check(inv)
            or ItemPlacementHelpers.oc2_check(inv),
            synth.BlazingGem: ItemPlacementHelpers.ag1_check,
            synth.BlazingCrystal: lambda inv: ItemPlacementHelpers.oc2_check(inv)
            or ItemPlacementHelpers.hb2_check(inv)
            or ItemPlacementHelpers.bc2_check(inv)
            or ItemPlacementHelpers.ag2_check(inv),
            # shadows are guaranteed anytime in DC/TR
            synth.DarkShard: ItemPlacementHelpers.dc1_check,
            # LoD1 and 2 both have them
            synth.DarkStone: ItemPlacementHelpers.lod1_check,
            # BC1 and 2 have them
            synth.DarkGem: ItemPlacementHelpers.bc1_check,
            # PR1 has them, but could be locked out
            synth.DarkCrystal: lambda inv: ItemPlacementHelpers.lod2_check(inv),
            # TWTNW has them post Xemnas
            synth.DenseShard: lambda inv: ItemPlacementHelpers.twtnw_post_saix_check(
                inv
            )
            or ItemPlacementHelpers.bc2_check(inv)
            or ItemPlacementHelpers.tt3_check(inv),
            # TWTNW has them post Xemnas
            synth.DenseStone: lambda inv: ItemPlacementHelpers.lod2_check(inv)
            or ItemPlacementHelpers.twtnw_roxas_check(inv),
            # TWTNW has them post Xemnas
            synth.DenseGem: lambda inv: ItemPlacementHelpers.tt3_check(inv)
            or ItemPlacementHelpers.stt_check(inv)
            or ItemPlacementHelpers.twtnw_roxas_check(inv),
            # TWTNW has them post Xemnas
            synth.DenseCrystal: lambda inv: ItemPlacementHelpers.tt3_check(inv)
            or ItemPlacementHelpers.twtnw_roxas_check(inv)
            or ItemPlacementHelpers.pl2_check(inv),
            # BC ones can disappear
            synth.FrostShard: lambda inv: ItemPlacementHelpers.lod2_check(inv)
            or ItemPlacementHelpers.ag2_check(inv)
            or ItemPlacementHelpers.pr2_check(inv),
            # DC/TR
            synth.FrostStone: ItemPlacementHelpers.dc2_check,
            # AG1 and 2 both have them
            synth.FrostGem: ItemPlacementHelpers.ag1_check,
            # PL1 and 2 both have them
            synth.FrostCrystal: ItemPlacementHelpers.pl1_check,
            # LoD1 and 2 both have them
            synth.LightningShard: lambda inv: ItemPlacementHelpers.lod1_check(inv)
            or ItemPlacementHelpers.dc1_check(inv)
            or ItemPlacementHelpers.pl2_check(inv)
            or ItemPlacementHelpers.pr2_check(inv)
            or ItemPlacementHelpers.ag2_check(inv),
            # HT and SP ones can become unavailable
            synth.LightningStone: lambda inv: ItemPlacementHelpers.oc2_check(inv)
            or ItemPlacementHelpers.lod2_check(inv)
            or ItemPlacementHelpers.ag2_check(inv),
            synth.LightningGem: lambda inv: ItemPlacementHelpers.hb2_check(inv)
            or ItemPlacementHelpers.bc2_check(inv)
            or ItemPlacementHelpers.pr2_check(inv)
            or ItemPlacementHelpers.ht2_check(inv)
            or ItemPlacementHelpers.pl2_check(inv)
            or ItemPlacementHelpers.sp2_check(inv),
            # SP1 and 2 both have them
            synth.LightningCrystal: ItemPlacementHelpers.sp1_check,
            # OC1 can be locked out
            synth.LucidShard: lambda inv: ItemPlacementHelpers.pr2_check(inv)
            or ItemPlacementHelpers.ht2_check(inv),
            # HT1 and 2 have them
            synth.LucidStone: ItemPlacementHelpers.ht1_check,
            # SP1 and 2 both have them
            synth.LucidGem: lambda inv: ItemPlacementHelpers.hb2_check(inv)
            or ItemPlacementHelpers.sp1_check(inv),
            synth.LucidCrystal: lambda inv: ItemPlacementHelpers.lod2_check(inv)
            or ItemPlacementHelpers.bc2_check(inv)
            or ItemPlacementHelpers.oc2_check(inv)
            or ItemPlacementHelpers.pr2_check(inv)
            or ItemPlacementHelpers.ag2_check(inv)
            or ItemPlacementHelpers.ht2_check(inv)
            or ItemPlacementHelpers.pl2_check(inv)
            or ItemPlacementHelpers.hb2_check(inv),
            # BC1 and 2 both have them, also TR
            synth.PowerShard: lambda inv: ItemPlacementHelpers.bc1_check(inv)
            or ItemPlacementHelpers.dc2_check(inv)
            or ItemPlacementHelpers.ht2_check(inv),
            # AG1 and 2 both have them
            synth.PowerStone: ItemPlacementHelpers.ag1_check,
            # PL1 and 2 both have them
            synth.PowerGem: ItemPlacementHelpers.pl1_check,
            synth.PowerCrystal: lambda inv: ItemPlacementHelpers.hb2_check(inv)
            or ItemPlacementHelpers.bc2_check(inv)
            or ItemPlacementHelpers.oc2_check(inv)
            or ItemPlacementHelpers.pr2_check(inv),
            # TWTNW always has them in Luxord and Saix rooms
            synth.TwilightShard: lambda inv: ItemPlacementHelpers.twtnw_roxas_check(inv)
            or ItemPlacementHelpers.pr2_check(inv)
            or ItemPlacementHelpers.tt3_check(inv)
            or ItemPlacementHelpers.lod2_check(inv),
            # TWTNW always has them
            synth.TwilightStone: lambda inv: ItemPlacementHelpers.tt3_check(inv)
            or ItemPlacementHelpers.twtnw_roxas_check(inv)
            or ItemPlacementHelpers.hb2_check(inv),
            # TWTNW always has them
            synth.TwilightGem: lambda inv: ItemPlacementHelpers.twtnw_roxas_check(inv)
            or ItemPlacementHelpers.tt3_check(inv),
            # TWTNW always has them
            synth.TwilightCrystal: ItemPlacementHelpers.twtnw_roxas_check,
        }

        item = synth_item.item
        if item in synth_req_map:
            return synth_req_map[item]

        # default returned lambda, doesn't allow for acquiring the item, failsafe
        return lambda inventory: False

    @staticmethod
    def need_stt_keyblade(inventory: list[int]) -> bool:
        return keyblade.BondOfFlame.id in inventory

    @staticmethod
    def need_tt_keyblade(inventory: list[int]) -> bool:
        return keyblade.Oathkeeper.id in inventory

    @staticmethod
    def need_hb_keyblade(inventory: list[int]) -> bool:
        return keyblade.SleepingLion.id in inventory

    @staticmethod
    def need_cor_keyblade(inventory: list[int]) -> bool:
        return keyblade.WinnersProof.id in inventory

    @staticmethod
    def need_lod_keyblade(inventory: list[int]) -> bool:
        return keyblade.HiddenDragon.id in inventory

    @staticmethod
    def need_bc_keyblade(inventory: list[int]) -> bool:
        return keyblade.RumblingRose.id in inventory

    @staticmethod
    def need_oc_keyblade(inventory: list[int]) -> bool:
        return keyblade.HerosCrest.id in inventory

    @staticmethod
    def need_dc_keyblade(inventory: list[int]) -> bool:
        return keyblade.Monochrome.id in inventory

    @staticmethod
    def need_pr_keyblade(inventory: list[int]) -> bool:
        return keyblade.FollowTheWind.id in inventory

    @staticmethod
    def need_ag_keyblade(inventory: list[int]) -> bool:
        return keyblade.WishingLamp.id in inventory

    @staticmethod
    def need_ht_keyblade(inventory: list[int]) -> bool:
        return keyblade.DecisivePumpkin.id in inventory

    @staticmethod
    def need_pl_keyblade(inventory: list[int]) -> bool:
        return keyblade.CircleOfLife.id in inventory

    @staticmethod
    def need_sp_keyblade(inventory: list[int]) -> bool:
        return keyblade.PhotonDebugger.id in inventory

    @staticmethod
    def need_twtnw_keyblade(inventory: list[int]) -> bool:
        return keyblade.TwoBecomeOne.id in inventory

    @staticmethod
    def need_haw_keyblade(inventory: list[int]) -> bool:
        return keyblade.SweetMemories.id in inventory
