from __future__ import annotations

from collections.abc import Iterable

from crafting import CraftOption
from models import InventoryItem, QUALITY_NAMES, level_parts
from rules import RuleBook

ALL = "Wszystkie"
EMPTY = "Brak"
LEGENDARY_ONLY = "Tylko legendarne"
NORMAL_ONLY = "Tylko zwykłe"


def _sorted(values: Iterable[str]) -> list[str]:
    return sorted(set(values), key=lambda value: value.casefold())


def _component_display(value: str, rules: RuleBook) -> str:
    return rules.display(value) if value else EMPTY


def _prefix_filter_display(value: str, rules: RuleBook) -> str:
    return rules.prefix_filter_label(value) if value else EMPTY


def _legendary_matches(is_legendary: bool, selected: str) -> bool:
    selected_values = _selection(selected)
    if not selected_values or {LEGENDARY_ONLY, NORMAL_ONLY}.issubset(selected_values):
        return True
    if LEGENDARY_ONLY in selected_values:
        return is_legendary
    if NORMAL_ONLY in selected_values:
        return not is_legendary
    return True


def _selection(value) -> set[str]:
    if value is None:
        return set()
    if isinstance(value, str):
        return set() if value in {"", ALL} else {value}
    try:
        values = {str(item) for item in value if str(item) not in {"", ALL}}
    except TypeError:
        return {str(value)}
    return values


def _matches(value: str, selected) -> bool:
    choices = _selection(selected)
    return not choices or value in choices


def inventory_filter_values(items: list[InventoryItem], rules: RuleBook) -> dict[str, list[str]]:
    return {
        "category": [ALL] + _sorted(rules.category_label(item.identity.category) for item in items),
        "prefix": [ALL] + _sorted(_prefix_filter_display(item.identity.prefix, rules) for item in items),
        "base": [ALL] + _sorted(rules.display(item.identity.base) for item in items),
        "suffix": [ALL] + _sorted(_component_display(item.identity.suffix, rules) for item in items),
        "legendary": [ALL, LEGENDARY_ONLY, NORMAL_ONLY],
        "quality": [ALL] + [name for name in QUALITY_NAMES if any(item.quality == name for item in items)],
        "upgrade": [ALL] + [f"+{value}" for value in sorted({item.upgrade for item in items})],
        "shelf": [ALL] + _sorted(item.shelf or EMPTY for item in items),
    }


def item_matches(
    item: InventoryItem,
    rules: RuleBook,
    *,
    search: str = "",
    category: str = ALL,
    prefix: str = ALL,
    base: str = ALL,
    suffix: str = ALL,
    legendary: str = ALL,
    quality: str = ALL,
    upgrade: str = ALL,
    shelf: str = ALL,
) -> bool:
    ident = item.identity
    displayed_category = rules.category_label(ident.category)
    displayed_prefix = _prefix_filter_display(ident.prefix, rules)
    surface_prefix = rules.prefix_display(ident.prefix, ident.base) if ident.prefix else EMPTY
    displayed_base = rules.display(ident.base)
    displayed_suffix = _component_display(ident.suffix, rules)
    displayed_shelf = item.shelf or EMPTY

    if not _matches(displayed_category, category):
        return False
    if not _matches(displayed_prefix, prefix):
        return False
    if not _matches(displayed_base, base):
        return False
    if not _matches(displayed_suffix, suffix):
        return False
    if not _legendary_matches(ident.legendary, legendary):
        return False
    if not _matches(item.quality, quality):
        return False
    if not _matches(f"+{item.upgrade}", upgrade):
        return False
    if not _matches(displayed_shelf, shelf):
        return False

    needle = search.casefold().strip()
    if needle:
        haystack = " ".join(
            str(value)
            for value in (
                item.full_name,
                displayed_category,
                "TAK" if ident.legendary else "NIE",
                item.quality,
                f"+{item.upgrade}",
                displayed_prefix,
                surface_prefix,
                displayed_base,
                displayed_suffix,
                displayed_shelf,
                item.item_id,
                item.parser_notes,
            )
        ).casefold()
        if needle not in haystack:
            return False
    return True


def craft_filter_values(options: list[CraftOption], rules: RuleBook) -> dict[str, list[str]]:
    qualities: set[str] = set()
    upgrades: set[int] = set()
    for option in options:
        rank, upgrade = level_parts(option.result_level)
        qualities.add(QUALITY_NAMES[rank])
        upgrades.add(upgrade)
    return {
        "category": [ALL] + _sorted(rules.category_label(option.result.category) for option in options),
        "prefix": [ALL] + _sorted(_prefix_filter_display(option.result.prefix, rules) for option in options),
        "base": [ALL] + _sorted(rules.display(option.result.base) for option in options),
        "suffix": [ALL] + _sorted(_component_display(option.result.suffix, rules) for option in options),
        "legendary": [ALL, LEGENDARY_ONLY, NORMAL_ONLY],
        "quality": [ALL] + [name for name in QUALITY_NAMES if name in qualities],
        "upgrade": [ALL] + [f"+{value}" for value in sorted(upgrades)],
    }


def craft_matches(
    option: CraftOption,
    rules: RuleBook,
    *,
    search: str = "",
    category: str = ALL,
    prefix: str = ALL,
    base: str = ALL,
    suffix: str = ALL,
    legendary: str = ALL,
    quality: str = ALL,
    upgrade: str = ALL,
) -> bool:
    ident = option.result
    displayed_category = rules.category_label(ident.category)
    displayed_prefix = _prefix_filter_display(ident.prefix, rules)
    surface_prefix = rules.prefix_display(ident.prefix, ident.base) if ident.prefix else EMPTY
    displayed_base = rules.display(ident.base)
    displayed_suffix = _component_display(ident.suffix, rules)
    rank, result_upgrade = level_parts(option.result_level)
    result_quality = QUALITY_NAMES[rank]

    if not _matches(displayed_category, category):
        return False
    if not _matches(displayed_prefix, prefix):
        return False
    if not _matches(displayed_base, base):
        return False
    if not _matches(displayed_suffix, suffix):
        return False
    if not _legendary_matches(ident.legendary, legendary):
        return False
    if not _matches(result_quality, quality):
        return False
    if not _matches(f"+{result_upgrade}", upgrade):
        return False

    needle = search.casefold().strip()
    if needle:
        haystack = " ".join(
            (
                rules.identity_display(ident),
                displayed_category,
                displayed_prefix,
                surface_prefix,
                displayed_base,
                displayed_suffix,
                result_quality,
                f"+{result_upgrade}",
                "TAK" if ident.legendary else "NIE",
                option.left.full_name,
                option.right.full_name,
            )
        ).casefold()
        if needle not in haystack:
            return False
    return True
