import logging
from random import Random
from typing import List, Set

from BaseClasses import Item, ItemClassification
from .item_data import StardewItemFactory, items_by_group, Group, item_table, ItemData
from ..content.feature import friendsanity
from ..content.game_content import StardewContent
from ..data.game_item import ItemTag
from ..mods.mod_data import ModNames
from ..options import StardewValleyOptions, FestivalLocations, ExcludeGingerIsland, SpecialOrderLocations, SeasonRandomization, Museumsanity, \
    ElevatorProgression, BackpackProgression, ArcadeMachineLocations, Monstersanity, Goal, \
    Chefsanity, Craftsanity, BundleRandomization, EntranceRandomization, Shipsanity, Walnutsanity, EnabledFillerBuffs, TrapDifficulty
from ..strings.ap_names.ap_option_names import BuffOptionName, WalnutsanityOptionName
from ..strings.ap_names.ap_weapon_names import APWeapon
from ..strings.ap_names.buff_names import Buff
from ..strings.ap_names.community_upgrade_names import CommunityUpgrade
from ..strings.ap_names.mods.mod_items import SVEQuestItem
from ..strings.currency_names import Currency
from ..strings.tool_names import Tool
from ..strings.wallet_item_names import Wallet

logger = logging.getLogger(__name__)

def get_too_many_items_error_message(locations_count: int, items_count: int) -> str:
    return f"There should be at least as many locations [{locations_count}] as there are mandatory items [{items_count}]"


def create_items(item_factory: StardewItemFactory, locations_count: int, items_to_exclude: List[Item],
                 options: StardewValleyOptions, content: StardewContent, random: Random) -> List[Item]:
    items = []
    unique_items = create_unique_items(item_factory, options, content, random)

    remove_items(items_to_exclude, unique_items)

    remove_items_if_no_room_for_them(unique_items, locations_count, random)

    items += unique_items
    logger.debug(f"Created {len(unique_items)} unique items")

    unique_filler_items = create_unique_filler_items(item_factory, options, random, locations_count - len(items))
    items += unique_filler_items
    logger.debug(f"Created {len(unique_filler_items)} unique filler items")

    resource_pack_items = fill_with_resource_packs_and_traps(item_factory, options, random, items + items_to_exclude, locations_count - len(items))
    items += resource_pack_items
    logger.debug(f"Created {len(resource_pack_items)} resource packs")

    return items


def remove_items(items_to_remove, items):
    for item in items_to_remove:
        if item in items:
            items.remove(item)


def remove_items_if_no_room_for_them(unique_items: List[Item], locations_count: int, random: Random):
    if len(unique_items) <= locations_count:
        return

    number_of_items_to_remove = len(unique_items) - locations_count
    removable_items = [item for item in unique_items if item.classification == ItemClassification.filler or item.classification == ItemClassification.trap]
    if len(removable_items) < number_of_items_to_remove:
        logger.debug(f"Player has more items than locations, trying to remove {number_of_items_to_remove} random non-progression items")
        removable_items = [item for item in unique_items if not item.classification & ItemClassification.progression]
    else:
        logger.debug(f"Player has more items than locations, trying to remove {number_of_items_to_remove} random filler items")
    assert len(removable_items) >= number_of_items_to_remove, get_too_many_items_error_message(locations_count, len(unique_items))
    items_to_remove = random.sample(removable_items, number_of_items_to_remove)
    remove_items(items_to_remove, unique_items)


def create_unique_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, random: Random) -> List[Item]:
    items = []

    items.extend(item_factory(item) for item in items_by_group[Group.COMMUNITY_REWARD])
    items.append(item_factory(CommunityUpgrade.movie_theater))  # It is a community reward, but we need two of them
    create_raccoons(item_factory, options, items)
    items.append(item_factory(Wallet.metal_detector))  # Always offer at least one metal detector

    create_backpack_items(item_factory, options, items)
    create_weapons(item_factory, options, items)
    items.append(item_factory("Skull Key"))
    create_elevators(item_factory, options, items)
    create_tools(item_factory, content, items)
    create_skills(item_factory, content, items)
    create_wizard_buildings(item_factory, options, items)
    create_carpenter_buildings(item_factory, content, items)
    items.append(item_factory("Railroad Boulder Removed"))
    items.append(item_factory(CommunityUpgrade.fruit_bats))
    items.append(item_factory(CommunityUpgrade.mushroom_boxes))
    items.append(item_factory("Beach Bridge"))
    create_tv_channels(item_factory, options, items)
    create_quest_rewards(item_factory, options, items)
    create_stardrops(item_factory, options, content, items)
    create_museum_items(item_factory, options, items)
    create_arcade_machine_items(item_factory, options, items)
    create_movement_buffs(item_factory, options, items)
    create_traveling_merchant_items(item_factory, items)
    items.append(item_factory("Return Scepter"))
    create_seasons(item_factory, options, items)
    create_seeds(item_factory, content, items)
    create_friendsanity_items(item_factory, options, content, items, random)
    create_festival_rewards(item_factory, options, items)
    create_special_order_board_rewards(item_factory, options, items)
    create_special_order_qi_rewards(item_factory, options, items)
    create_walnuts(item_factory, options, items)
    create_walnut_purchase_rewards(item_factory, options, items)
    create_crafting_recipes(item_factory, options, items)
    create_cooking_recipes(item_factory, options, items)
    create_shipsanity_items(item_factory, options, items)
    create_booksanity_items(item_factory, content, items)
    create_goal_items(item_factory, options, items)
    items.append(item_factory("Golden Egg"))
    items.append(item_factory(CommunityUpgrade.mr_qi_plane_ride))

    create_sve_special_items(item_factory, options, items)
    create_magic_mod_spells(item_factory, options, items)
    create_deepwoods_pendants(item_factory, options, items)
    create_archaeology_items(item_factory, options, items)

    return items


def create_raccoons(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    number_progressive_raccoons = 9
    if options.quest_locations.has_no_story_quests():
        number_progressive_raccoons = number_progressive_raccoons - 1

    items.extend(item_factory(item) for item in [CommunityUpgrade.raccoon] * number_progressive_raccoons)


def create_backpack_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if (options.backpack_progression == BackpackProgression.option_progressive or
            options.backpack_progression == BackpackProgression.option_early_progressive):
        items.extend(item_factory(item) for item in ["Progressive Backpack"] * 2)
        if ModNames.big_backpack in options.mods:
            items.append(item_factory("Progressive Backpack"))


def create_weapons(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    weapons = weapons_count(options)
    items.extend(item_factory(item) for item in [APWeapon.slingshot] * 2)
    monstersanity = options.monstersanity
    if monstersanity == Monstersanity.option_none:  # Without monstersanity, might not be enough checks to split the weapons
        items.extend(item_factory(item) for item in [APWeapon.weapon] * weapons)
        items.extend(item_factory(item) for item in [APWeapon.footwear] * 3)  # 1-2 | 3-4 | 6-7-8
        return

    items.extend(item_factory(item) for item in [APWeapon.sword] * weapons)
    items.extend(item_factory(item) for item in [APWeapon.club] * weapons)
    items.extend(item_factory(item) for item in [APWeapon.dagger] * weapons)
    items.extend(item_factory(item) for item in [APWeapon.footwear] * 4)  # 1-2 | 3-4 | 6-7-8 | 11-13
    if monstersanity == Monstersanity.option_goals or monstersanity == Monstersanity.option_one_per_category or \
            monstersanity == Monstersanity.option_short_goals or monstersanity == Monstersanity.option_very_short_goals:
        return
    if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
        rings_items = [item for item in items_by_group[Group.RING] if item.classification is not ItemClassification.filler]
    else:
        rings_items = [item for item in items_by_group[Group.RING]]
    items.extend(item_factory(item) for item in rings_items)


def create_elevators(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if options.elevator_progression == ElevatorProgression.option_vanilla:
        return

    items.extend([item_factory(item) for item in ["Progressive Mine Elevator"] * 24])
    if ModNames.deepwoods in options.mods:
        items.extend([item_factory(item) for item in ["Progressive Woods Obelisk Sigils"] * 10])
    if ModNames.skull_cavern_elevator in options.mods:
        items.extend([item_factory(item) for item in ["Progressive Skull Cavern Elevator"] * 8])


def create_tools(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
    tool_progression = content.features.tool_progression
    for tool, count in tool_progression.tool_distribution.items():
        item = item_table[tool_progression.to_progressive_item(tool)]

        # Trash can is only used in tool upgrade logic, so the last trash can is not progression because it basically does not unlock anything.
        if tool == Tool.trash_can:
            count -= 1
            items.append(item_factory(item, ItemClassification.useful))

        items.extend([item_factory(item) for _ in range(count)])


def create_skills(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
    skill_progression = content.features.skill_progression
    if not skill_progression.is_progressive:
        return

    for skill in content.skills.values():
        items.extend(item_factory(skill.level_name) for _ in skill_progression.get_randomized_level_names_by_level(skill))

        if skill_progression.is_mastery_randomized(skill):
            items.append(item_factory(skill.mastery_name))

    if skill_progression.are_masteries_shuffled:
        items.append(item_factory(Wallet.mastery_of_the_five_ways))


def create_wizard_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    useless_buildings_classification = ItemClassification.progression_skip_balancing if world_is_perfection(options) else ItemClassification.useful
    items.append(item_factory("Earth Obelisk", useless_buildings_classification))
    items.append(item_factory("Water Obelisk", useless_buildings_classification))
    items.append(item_factory("Desert Obelisk"))
    items.append(item_factory("Junimo Hut"))
    items.append(item_factory("Gold Clock", useless_buildings_classification))
    if options.exclude_ginger_island == ExcludeGingerIsland.option_false:
        items.append(item_factory("Island Obelisk"))
    if ModNames.deepwoods in options.mods:
        items.append(item_factory("Woods Obelisk"))


def create_carpenter_buildings(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
    building_progression = content.features.building_progression
    if not building_progression.is_progressive:
        return

    for building in content.farm_buildings.values():
        item_name, _ = building_progression.to_progressive_item(building.name)
        items.append(item_factory(item_name))


def create_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    create_special_quest_rewards(item_factory, options, items)
    create_help_wanted_quest_rewards(item_factory, options, items)

    create_quest_rewards_sve(item_factory, options, items)


def create_special_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if options.quest_locations.has_no_story_quests():
        return
    # items.append(item_factory("Adventurer's Guild")) # Now unlocked always!
    items.append(item_factory(Wallet.club_card))
    items.append(item_factory(Wallet.magnifying_glass))
    if ModNames.sve in options.mods:
        items.append(item_factory(Wallet.bears_knowledge))
    else:
        items.append(item_factory(Wallet.bears_knowledge, ItemClassification.useful))  # Not necessary outside of SVE
    items.append(item_factory(Wallet.iridium_snake_milk))
    items.append(item_factory("Dark Talisman"))
    if options.exclude_ginger_island == ExcludeGingerIsland.option_false:
        items.append(item_factory("Fairy Dust Recipe"))


def create_help_wanted_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if options.quest_locations <= 0:
        return

    number_help_wanted = options.quest_locations.value
    quest_per_prize_ticket = 3
    number_prize_tickets = number_help_wanted // quest_per_prize_ticket
    items.extend(item_factory(item) for item in [Currency.prize_ticket] * number_prize_tickets)


def create_stardrops(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
    stardrops_classification = get_stardrop_classification(options)
    items.append(item_factory("Stardrop", stardrops_classification))  # The Mines level 100
    items.append(item_factory("Stardrop", stardrops_classification))  # Old Master Cannoli
    items.append(item_factory("Stardrop", stardrops_classification))  # Krobus Stardrop
    if content.features.fishsanity.is_enabled:
        items.append(item_factory("Stardrop", stardrops_classification))  # Master Angler Stardrop
    if ModNames.deepwoods in options.mods:
        items.append(item_factory("Stardrop", stardrops_classification))  # Petting the Unicorn
    if content.features.friendsanity.is_enabled:
        items.append(item_factory("Stardrop", stardrops_classification))  # Spouse Stardrop


def create_museum_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    items.append(item_factory(Wallet.rusty_key))
    items.append(item_factory(Wallet.dwarvish_translation_guide))
    items.append(item_factory("Ancient Seeds Recipe"))
    items.append(item_factory("Stardrop", get_stardrop_classification(options)))
    if options.museumsanity == Museumsanity.option_none:
        return
    items.extend(item_factory(item) for item in ["Magic Rock Candy"] * 10)
    items.extend(item_factory(item) for item in ["Ancient Seeds"] * 5)
    items.append(item_factory(Wallet.metal_detector))


def create_friendsanity_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item], random: Random):
    if not content.features.friendsanity.is_enabled:
        return

    create_babies(item_factory, items, random)

    for villager in content.villagers.values():
        item_name = friendsanity.to_item_name(villager.name)

        for _ in content.features.friendsanity.get_randomized_hearts(villager):
            items.append(item_factory(item_name, ItemClassification.progression))

    need_pet = options.goal == Goal.option_grandpa_evaluation
    pet_item_classification = ItemClassification.progression_skip_balancing if need_pet else ItemClassification.useful

    for _ in content.features.friendsanity.get_pet_randomized_hearts():
        items.append(item_factory(friendsanity.pet_heart_item_name, pet_item_classification))


def create_babies(item_factory: StardewItemFactory, items: List[Item], random: Random):
    baby_items = [item for item in items_by_group[Group.BABY]]
    for i in range(2):
        chosen_baby = random.choice(baby_items)
        items.append(item_factory(chosen_baby))


def create_arcade_machine_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if options.arcade_machine_locations == ArcadeMachineLocations.option_full_shuffling:
        items.append(item_factory("JotPK: Progressive Boots"))
        items.append(item_factory("JotPK: Progressive Boots"))
        items.append(item_factory("JotPK: Progressive Gun"))
        items.append(item_factory("JotPK: Progressive Gun"))
        items.append(item_factory("JotPK: Progressive Gun"))
        items.append(item_factory("JotPK: Progressive Gun"))
        items.append(item_factory("JotPK: Progressive Ammo"))
        items.append(item_factory("JotPK: Progressive Ammo"))
        items.append(item_factory("JotPK: Progressive Ammo"))
        items.append(item_factory("JotPK: Extra Life"))
        items.append(item_factory("JotPK: Extra Life"))
        items.append(item_factory("JotPK: Increased Drop Rate"))
        items.extend(item_factory(item) for item in ["Junimo Kart: Extra Life"] * 8)


def create_movement_buffs(item_factory, options: StardewValleyOptions, items: List[Item]):
    movement_buffs: int = options.movement_buff_number.value
    items.extend(item_factory(item) for item in [Buff.movement] * movement_buffs)


def create_traveling_merchant_items(item_factory: StardewItemFactory, items: List[Item]):
    items.extend([*(item_factory(item) for item in items_by_group[Group.TRAVELING_MERCHANT_DAY]),
                  *(item_factory(item) for item in ["Traveling Merchant Stock Size"] * 6)])


def create_seasons(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if options.season_randomization == SeasonRandomization.option_disabled:
        return

    if options.season_randomization == SeasonRandomization.option_progressive:
        items.extend([item_factory(item) for item in ["Progressive Season"] * 3])
        return

    items.extend([item_factory(item) for item in items_by_group[Group.SEASON]])


def create_seeds(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
    if not content.features.cropsanity.is_enabled:
        return

    items.extend(item_factory(item_table[seed.name]) for seed in content.find_tagged_items(ItemTag.CROPSANITY_SEED))


def create_festival_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    items.append(item_factory("Deluxe Scarecrow Recipe"))
    if options.festival_locations == FestivalLocations.option_disabled:
        return

    festival_rewards = [item_factory(item) for item in items_by_group[Group.FESTIVAL] if item.classification != ItemClassification.filler]
    items.extend([*festival_rewards, item_factory("Stardrop", get_stardrop_classification(options))])


def create_walnuts(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    walnutsanity = options.walnutsanity
    if options.exclude_ginger_island == ExcludeGingerIsland.option_true or walnutsanity == Walnutsanity.preset_none:
        return

    # Give baseline walnuts just to be nice
    num_single_walnuts = 0
    num_triple_walnuts = 2
    num_penta_walnuts = 1
    # https://stardewvalleywiki.com/Golden_Walnut
    # Totals should be accurate, but distribution is slightly offset to make room for baseline walnuts
    if WalnutsanityOptionName.puzzles in walnutsanity:  # 61
        num_single_walnuts += 6  # 6
        num_triple_walnuts += 5  # 15
        num_penta_walnuts += 8  # 40
    if WalnutsanityOptionName.bushes in walnutsanity:  # 25
        num_single_walnuts += 16  # 16
        num_triple_walnuts += 3  # 9
    if WalnutsanityOptionName.dig_spots in walnutsanity:  # 18
        num_single_walnuts += 18  # 18
    if WalnutsanityOptionName.repeatables in walnutsanity:  # 33
        num_single_walnuts += 30  # 30
        num_triple_walnuts += 1  # 3

    items.extend([item_factory(item) for item in ["Golden Walnut"] * num_single_walnuts])
    items.extend([item_factory(item) for item in ["3 Golden Walnuts"] * num_triple_walnuts])
    items.extend([item_factory(item) for item in ["5 Golden Walnuts"] * num_penta_walnuts])


def create_walnut_purchase_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
        return

    items.extend([item_factory("Boat Repair"),
                  item_factory("Open Professor Snail Cave"),
                  item_factory("Ostrich Incubator Recipe"),
                  item_factory("Treehouse"),
                  *[item_factory(item) for item in items_by_group[Group.WALNUT_PURCHASE]]])


def create_special_order_board_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if options.special_order_locations & SpecialOrderLocations.option_board:
        special_order_board_items = [item for item in items_by_group[Group.SPECIAL_ORDER_BOARD]]
        items.extend([item_factory(item) for item in special_order_board_items])


def special_order_board_item_classification(item: ItemData, need_all_recipes: bool) -> ItemClassification:
    if item.classification is ItemClassification.useful:
        return ItemClassification.useful
    if item.name == "Special Order Board":
        return ItemClassification.progression
    if need_all_recipes and "Recipe" in item.name:
        return ItemClassification.progression_skip_balancing
    if item.name == "Monster Musk Recipe":
        return ItemClassification.progression_skip_balancing
    return ItemClassification.useful


def create_special_order_qi_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
        return
    qi_gem_rewards = []
    if options.bundle_randomization >= BundleRandomization.option_remixed:
        qi_gem_rewards.append("15 Qi Gems")
        qi_gem_rewards.append("15 Qi Gems")

    if options.special_order_locations & SpecialOrderLocations.value_qi:
        qi_gem_rewards.extend(["100 Qi Gems", "10 Qi Gems", "40 Qi Gems", "25 Qi Gems", "25 Qi Gems",
                               "40 Qi Gems", "20 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems"])

    qi_gem_items = [item_factory(reward) for reward in qi_gem_rewards]
    items.extend(qi_gem_items)


def create_tv_channels(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    channels = [channel for channel in items_by_group[Group.TV_CHANNEL]]
    if options.entrance_randomization == EntranceRandomization.option_disabled:
        channels = [channel for channel in channels if channel.name != "The Gateway Gazette"]
    items.extend([item_factory(item) for item in channels])


def create_crafting_recipes(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    has_craftsanity = options.craftsanity == Craftsanity.option_all
    crafting_recipes = []
    crafting_recipes.extend([recipe for recipe in items_by_group[Group.QI_CRAFTING_RECIPE]])
    if has_craftsanity:
        crafting_recipes.extend([recipe for recipe in items_by_group[Group.CRAFTSANITY]])
    crafting_recipes = remove_excluded_items(crafting_recipes, options)
    items.extend([item_factory(item) for item in crafting_recipes])


def create_cooking_recipes(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    chefsanity = options.chefsanity
    if chefsanity == Chefsanity.option_none:
        return

    chefsanity_recipes_by_name = {recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_STARTER]}  # Dictionary to not make duplicates

    if chefsanity & Chefsanity.option_queen_of_sauce:
        chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_QOS]})
    if chefsanity & Chefsanity.option_purchases:
        chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_PURCHASE]})
    if chefsanity & Chefsanity.option_friendship:
        chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_FRIENDSHIP]})
    if chefsanity & Chefsanity.option_skills:
        chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_SKILL]})

    filtered_chefsanity_recipes = remove_excluded_items(list(chefsanity_recipes_by_name.values()), options)
    items.extend([item_factory(item) for item in filtered_chefsanity_recipes])


def create_shipsanity_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    shipsanity = options.shipsanity
    if shipsanity != Shipsanity.option_everything:
        return

    items.append(item_factory(Wallet.metal_detector))


def create_booksanity_items(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
    booksanity = content.features.booksanity
    if not booksanity.is_enabled:
        return

    items.extend(item_factory(item_table[booksanity.to_item_name(book.name)]) for book in content.find_tagged_items(ItemTag.BOOK_POWER))
    progressive_lost_book = item_table[booksanity.progressive_lost_book]
    items.extend(item_factory(progressive_lost_book) for _ in content.features.booksanity.get_randomized_lost_books())


def create_goal_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    goal = options.goal
    if goal != Goal.option_perfection and goal != Goal.option_complete_collection:
        return

    items.append(item_factory(Wallet.metal_detector))


def create_archaeology_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    mods = options.mods
    if ModNames.archaeology not in mods:
        return

    items.append(item_factory(Wallet.metal_detector))


def create_filler_festival_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions) -> List[Item]:
    if options.festival_locations == FestivalLocations.option_disabled:
        return []

    return [item_factory(item) for item in items_by_group[Group.FESTIVAL] if
            item.classification == ItemClassification.filler]


def create_magic_mod_spells(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if ModNames.magic not in options.mods:
        return
    items.extend([item_factory(item) for item in items_by_group[Group.MAGIC_SPELL]])


def create_deepwoods_pendants(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if ModNames.deepwoods not in options.mods:
        return
    items.extend([item_factory(item) for item in ["Pendant of Elders", "Pendant of Community", "Pendant of Depths"]])


def create_sve_special_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if ModNames.sve not in options.mods:
        return

    items.extend([item_factory(item) for item in items_by_group[Group.MOD_WARP] if item.mod_name == ModNames.sve])


def create_quest_rewards_sve(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
    if ModNames.sve not in options.mods:
        return

    exclude_ginger_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true
    items.extend([item_factory(item) for item in SVEQuestItem.sve_always_quest_items])
    if not exclude_ginger_island:
        items.extend([item_factory(item) for item in SVEQuestItem.sve_always_quest_items_ginger_island])

    if options.quest_locations.has_no_story_quests():
        return

    items.extend([item_factory(item) for item in SVEQuestItem.sve_quest_items])
    if exclude_ginger_island:
        return
    items.extend([item_factory(item) for item in SVEQuestItem.sve_quest_items_ginger_island])


def create_unique_filler_items(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random,
                               available_item_slots: int) -> List[Item]:
    items = []

    items.extend(create_filler_festival_rewards(item_factory, options))

    if len(items) > available_item_slots:
        items = random.sample(items, available_item_slots)
    return items


def weapons_count(options: StardewValleyOptions):
    weapon_count = 5
    if ModNames.sve in options.mods:
        weapon_count += 1
    return weapon_count


def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random,
                                       items_already_added: List[Item],
                                       available_item_slots: int) -> List[Item]:
    include_traps = options.trap_difficulty != TrapDifficulty.option_no_traps
    items_already_added_names = [item.name for item in items_already_added]
    useful_resource_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK_USEFUL]
                             if pack.name not in items_already_added_names]
    trap_items = [trap for trap in items_by_group[Group.TRAP]
                  if trap.name not in items_already_added_names and
                  Group.DEPRECATED not in trap.groups and
                  (trap.mod_name is None or trap.mod_name in options.mods) and
                  options.trap_distribution[trap.name] > 0]
    player_buffs = get_allowed_player_buffs(options.enabled_filler_buffs)

    priority_filler_items = []
    priority_filler_items.extend(useful_resource_packs)
    priority_filler_items.extend(player_buffs)

    if include_traps:
        priority_filler_items.extend(trap_items)

    exclude_ginger_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true
    all_filler_packs = remove_excluded_items(get_all_filler_items(include_traps, exclude_ginger_island), options)
    all_filler_packs.extend(player_buffs)
    priority_filler_items = remove_excluded_items(priority_filler_items, options)

    number_priority_items = len(priority_filler_items)
    if available_item_slots < number_priority_items:
        chosen_priority_items = [item_factory(resource_pack) for resource_pack in
                                 random.sample(priority_filler_items, available_item_slots)]
        return chosen_priority_items

    items = []
    chosen_priority_items = [item_factory(resource_pack,
                                          ItemClassification.trap if resource_pack.classification == ItemClassification.trap else ItemClassification.useful)
                             for resource_pack in priority_filler_items]
    items.extend(chosen_priority_items)
    available_item_slots -= number_priority_items
    all_filler_packs = [filler_pack for filler_pack in all_filler_packs
                        if Group.MAXIMUM_ONE not in filler_pack.groups or
                        (filler_pack.name not in [priority_item.name for priority_item in
                                                  priority_filler_items] and filler_pack.name not in items_already_added_names)]

    filler_weights = get_filler_weights(options, all_filler_packs)

    while available_item_slots > 0:
        resource_pack = random.choices(all_filler_packs, weights=filler_weights, k=1)[0]
        exactly_2 = Group.AT_LEAST_TWO in resource_pack.groups
        while exactly_2 and available_item_slots == 1:
            resource_pack = random.choices(all_filler_packs, weights=filler_weights, k=1)[0]
            exactly_2 = Group.AT_LEAST_TWO in resource_pack.groups
        classification = ItemClassification.useful if resource_pack.classification == ItemClassification.progression else resource_pack.classification
        items.append(item_factory(resource_pack, classification))
        available_item_slots -= 1
        if exactly_2:
            items.append(item_factory(resource_pack, classification))
            available_item_slots -= 1
        if exactly_2 or Group.MAXIMUM_ONE in resource_pack.groups:
            index = all_filler_packs.index(resource_pack)
            all_filler_packs.pop(index)
            filler_weights.pop(index)

    return items


def get_filler_weights(options: StardewValleyOptions, all_filler_packs: List[ItemData]):
    weights = []
    for filler in all_filler_packs:
        if filler.name in options.trap_distribution:
            num = options.trap_distribution[filler.name]
        else:
            num = options.trap_distribution.default_weight
        weights.append(num)
    return weights


def filter_deprecated_items(items: List[ItemData]) -> List[ItemData]:
    return [item for item in items if Group.DEPRECATED not in item.groups]


def filter_ginger_island_items(exclude_island: bool, items: List[ItemData]) -> List[ItemData]:
    return [item for item in items if not exclude_island or Group.GINGER_ISLAND not in item.groups]


def filter_mod_items(mods: Set[str], items: List[ItemData]) -> List[ItemData]:
    return [item for item in items if item.mod_name is None or item.mod_name in mods]


def remove_excluded_items(items, options: StardewValleyOptions):
    return remove_excluded_items_island_mods(items, options.exclude_ginger_island == ExcludeGingerIsland.option_true, options.mods.value)


def remove_excluded_items_island_mods(items, exclude_ginger_island: bool, mods: Set[str]):
    deprecated_filter = filter_deprecated_items(items)
    ginger_island_filter = filter_ginger_island_items(exclude_ginger_island, deprecated_filter)
    mod_filter = filter_mod_items(mods, ginger_island_filter)
    return mod_filter


def generate_filler_choice_pool(options: StardewValleyOptions) -> list[str]:
    include_traps = options.trap_difficulty != TrapDifficulty.option_no_traps
    exclude_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true

    available_filler = get_all_filler_items(include_traps, exclude_island)
    available_filler = remove_limited_amount_packs(available_filler)

    return [item.name for item in available_filler]


def remove_limited_amount_packs(packs):
    return [pack for pack in packs if Group.MAXIMUM_ONE not in pack.groups and Group.AT_LEAST_TWO not in pack.groups]


def get_all_filler_items(include_traps: bool, exclude_ginger_island: bool) -> List[ItemData]:
    all_filler_items = [pack for pack in items_by_group[Group.RESOURCE_PACK]]
    all_filler_items.extend(items_by_group[Group.TRASH])
    if include_traps:
        all_filler_items.extend(items_by_group[Group.TRAP])
    all_filler_items = remove_excluded_items_island_mods(all_filler_items, exclude_ginger_island, set())
    return all_filler_items


def get_allowed_player_buffs(buff_option: EnabledFillerBuffs) -> List[ItemData]:
    allowed_buffs = []
    if BuffOptionName.luck in buff_option:
        allowed_buffs.append(item_table[Buff.luck])
    if BuffOptionName.damage in buff_option:
        allowed_buffs.append(item_table[Buff.damage])
    if BuffOptionName.defense in buff_option:
        allowed_buffs.append(item_table[Buff.defense])
    if BuffOptionName.immunity in buff_option:
        allowed_buffs.append(item_table[Buff.immunity])
    if BuffOptionName.health in buff_option:
        allowed_buffs.append(item_table[Buff.health])
    if BuffOptionName.energy in buff_option:
        allowed_buffs.append(item_table[Buff.energy])
    if BuffOptionName.bite in buff_option:
        allowed_buffs.append(item_table[Buff.bite_rate])
    if BuffOptionName.fish_trap in buff_option:
        allowed_buffs.append(item_table[Buff.fish_trap])
    if BuffOptionName.fishing_bar in buff_option:
        allowed_buffs.append(item_table[Buff.fishing_bar])
    if BuffOptionName.quality in buff_option:
        allowed_buffs.append(item_table[Buff.quality])
    if BuffOptionName.glow in buff_option:
        allowed_buffs.append(item_table[Buff.glow])
    return allowed_buffs


def get_stardrop_classification(options) -> ItemClassification:
    return ItemClassification.progression_skip_balancing if world_is_perfection(options) or world_is_stardrops(options) else ItemClassification.useful


def world_is_perfection(options) -> bool:
    return options.goal == Goal.option_perfection


def world_is_stardrops(options) -> bool:
    return options.goal == Goal.option_mystery_of_the_stardrops
