import itertools
from collections import Counter
from typing import NamedTuple, TYPE_CHECKING

from BaseClasses import Item, ItemClassification
from .options import BossesAsChecks, VictoryCondition, ExtraOrbs

if TYPE_CHECKING:
    from . import NoitaWorld
else:
    NoitaWorld = object


class ItemData(NamedTuple):
    code: int
    group: str
    classification: ItemClassification = ItemClassification.progression
    required_num: int = 0


class NoitaItem(Item):
    game: str = "Noita"


def create_item(player: int, name: str) -> Item:
    item_data = item_table[name]
    return NoitaItem(name, item_data.classification, item_data.code, player)


def create_fixed_item_pool() -> list[str]:
    required_items: dict[str, int] = {name: data.required_num for name, data in item_table.items()}
    return list(Counter(required_items).elements())


def create_orb_items(victory_condition: VictoryCondition, extra_orbs: ExtraOrbs) -> list[str]:
    orb_count = extra_orbs.value
    if victory_condition == VictoryCondition.option_pure_ending:
        orb_count = orb_count + 11
    elif victory_condition == VictoryCondition.option_peaceful_ending:
        orb_count = orb_count + 33
    return ["Orb" for _ in range(orb_count)]


def create_spatial_awareness_item(bosses_as_checks: BossesAsChecks) -> list[str]:
    return ["Spatial Awareness Perk"] if bosses_as_checks.value >= BossesAsChecks.option_all_bosses else []


def create_kantele(victory_condition: VictoryCondition) -> list[str]:
    return ["Kantele"] if victory_condition.value >= VictoryCondition.option_pure_ending else []


def create_random_items(world: NoitaWorld, weights: dict[str, int], count: int) -> list[str]:
    filler_pool = weights.copy()
    if not world.options.bad_effects:
        filler_pool["Trap"] = 0
        filler_pool["Greed Die"] = 0

    return world.random.choices(population=list(filler_pool.keys()),
                                weights=list(filler_pool.values()),
                                k=count)


def create_all_items(world: NoitaWorld) -> None:
    player = world.player
    locations_to_fill = len(world.multiworld.get_unfilled_locations(player))

    itempool = (
        create_fixed_item_pool()
        + create_orb_items(world.options.victory_condition, world.options.extra_orbs)
        + create_spatial_awareness_item(world.options.bosses_as_checks)
        + create_kantele(world.options.victory_condition)
    )

    # if there's not enough shop-allowed items in the pool, we can encounter gen issues
    # 39 is the number of shop-valid items we need to guarantee
    if len(itempool) < 39:
        itempool += create_random_items(world, shop_only_filler_weights, 39 - len(itempool))
        # this is so that it passes tests and gens if you have minimal locations and only one player
        if world.multiworld.players == 1:
            for location in world.multiworld.get_unfilled_locations(player):
                if "Shop Item" in location.name:
                    location.item = create_item(player, itempool.pop())
            locations_to_fill = len(world.multiworld.get_unfilled_locations(player))

    itempool += create_random_items(world, filler_weights, locations_to_fill - len(itempool))
    world.multiworld.itempool += [create_item(player, name) for name in itempool]


# 110000 - 110032
item_table: dict[str, ItemData] = {
    "Trap":                                 ItemData(110000, "Traps", ItemClassification.trap),
    "Extra Max HP":                         ItemData(110001, "Pickups", ItemClassification.useful),
    "Spell Refresher":                      ItemData(110002, "Pickups", ItemClassification.filler),
    "Potion":                               ItemData(110003, "Items", ItemClassification.filler),
    "Gold (200)":                           ItemData(110004, "Gold", ItemClassification.filler),
    "Gold (1000)":                          ItemData(110005, "Gold", ItemClassification.filler),
    "Wand (Tier 1)":                        ItemData(110006, "Wands", ItemClassification.useful),
    "Wand (Tier 2)":                        ItemData(110007, "Wands", ItemClassification.useful),
    "Wand (Tier 3)":                        ItemData(110008, "Wands", ItemClassification.useful),
    "Wand (Tier 4)":                        ItemData(110009, "Wands", ItemClassification.useful),
    "Wand (Tier 5)":                        ItemData(110010, "Wands", ItemClassification.useful, 1),
    "Wand (Tier 6)":                        ItemData(110011, "Wands", ItemClassification.useful, 1),
    "Kantele":                              ItemData(110012, "Wands", ItemClassification.useful),
    "Fire Immunity Perk":                   ItemData(110013, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
    "Toxic Immunity Perk":                  ItemData(110014, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
    "Explosion Immunity Perk":              ItemData(110015, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
    "Melee Immunity Perk":                  ItemData(110016, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
    "Electricity Immunity Perk":            ItemData(110017, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
    "Tinker with Wands Everywhere Perk":    ItemData(110018, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
    "All-Seeing Eye Perk":                  ItemData(110019, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
    "Spatial Awareness Perk":               ItemData(110020, "Perks", ItemClassification.progression),
    "Extra Life Perk":                      ItemData(110021, "Repeatable Perks", ItemClassification.useful, 1),
    "Orb":                                  ItemData(110022, "Orbs", ItemClassification.progression_skip_balancing),
    "Random Potion":                        ItemData(110023, "Items", ItemClassification.filler),
    "Secret Potion":                        ItemData(110024, "Items", ItemClassification.filler),
    "Powder Pouch":                         ItemData(110025, "Items", ItemClassification.filler),
    "Chaos Die":                            ItemData(110026, "Items", ItemClassification.filler),
    "Greed Die":                            ItemData(110027, "Items", ItemClassification.trap),
    "Kammi":                                ItemData(110028, "Items", ItemClassification.filler, 1),
    "Refreshing Gourd":                     ItemData(110029, "Items", ItemClassification.filler, 1),
    "Sädekivi":                             ItemData(110030, "Items", ItemClassification.filler),
    "Broken Wand":                          ItemData(110031, "Items", ItemClassification.filler),
}

shop_only_filler_weights: dict[str, int] = {
    "Trap":             15,
    "Extra Max HP":     25,
    "Spell Refresher":  20,
    "Wand (Tier 1)":    10,
    "Wand (Tier 2)":    8,
    "Wand (Tier 3)":    7,
    "Wand (Tier 4)":    6,
    "Wand (Tier 5)":    5,
    "Wand (Tier 6)":    4,
    "Extra Life Perk":  10,
}

filler_weights: dict[str, int] = {
    **shop_only_filler_weights,
    "Gold (200)":       15,
    "Gold (1000)":      6,
    "Potion":           40,
    "Random Potion":    9,
    "Secret Potion":    10,
    "Powder Pouch":     10,
    "Chaos Die":        4,
    "Greed Die":        4,
    "Kammi":            4,
    "Refreshing Gourd": 4,
    "Sädekivi":         3,
    "Broken Wand":      10,
}


filler_items: list[str] = list(filter(lambda item: item_table[item].classification == ItemClassification.filler,
                                      item_table.keys()))
item_name_to_id: dict[str, int] = {name: data.code for name, data in item_table.items()}

item_name_groups: dict[str, set[str]] = {
    group: set(item_names) for group, item_names in itertools.groupby(item_table, lambda item: item_table[item].group)
}
