"""Action-Bar-Slot-Erkennung mit Item- und Zauber-Datenbank.

Ordnet benutzbare Items (Traenke) ihrer Item-ID und Heilzauber ihren
Slot-Icons zu und erkennt per Template-Matching, was in welchem
Action-Bar-Slot liegt. Damit muss keine Hotkey-Belegung im Client
gepflegt werden: Der Bot klickt den erkannten Slot direkt an
("Hotkeys syncen"-Button in der Healing-Seite).

ponytail: Aktuell alle Heal-/Mana-Traenke (Stand 15.25 inkl. Superior/
Distilled), die klassischen Heilzauber und die Combo-Angriffszauber.
Neue Eintraege = eine Zeile
plus 32x32-Icon unter images/items|<id>.png bzw. images/spells|<name>.png.
Grenzen: nur die erste Action Bar (linker Pfeil-Anker) wird gescannt;
Zauber-Icons stammen aus der 38x38-Spellbook-Grafik (herunterskaliert),
Live-Abgleich mit dem Client-Rendering steht noch aus — bei Nicht-Treffer
greift der Chat-Zauber-Fallback (Upgrade-Trigger: echtes Slot-Icon aus
Client-Screenshot uebernehmen).
"""
import pathlib

import cv2

from src.shared.typings import GrayImage
from src.utils.image import loadFromRGBToGray

from .locators import getLeftArrowsPosition


_itemsPath = pathlib.Path(__file__).parent.resolve() / 'images' / 'items'
_spellsPath = pathlib.Path(__file__).parent.resolve() / 'images' / 'spells'

# Item-ID -> Anzeigename. IDs sind die echten Tibia-Item-IDs, damit
# BBot-Importe (die IDs liefern) direkt verknüpft werden können.
# Icons liegen als <id>.png unter images/items (32x32-Item-Sprites).
SLOT_ITEMS = {
    7876: 'Small Health Potion',
    266: 'Health Potion',
    236: 'Strong Health Potion',
    239: 'Great Health Potion',
    23375: 'Supreme Health Potion',
    7643: 'Ultimate Health Potion',
    268: 'Mana Potion',
    237: 'Strong Mana Potion',
    238: 'Great Mana Potion',
    53162: 'Superior Mana Potion',
    53163: 'Distilled Superior Mana Potion',
    53164: 'Distilled Ultimate Mana Potion',
    7642: 'Great Spirit Potion',
    23374: 'Ultimate Spirit Potion',
    35563: 'Magic Shield Potion',
    3031: 'gold coin',
    3035: 'platinum coin',
    3360: 'crystal coin',
    3304: 'Crowbar',
    5460: 'Helmet of the Deep',
    8822: 'Ghost Charm',
    8826: 'Ghost Residue',
    8827: 'Charged Ghost Charm',
    8829: 'Food Matrix Crystal',
    3003: 'Rope',
    3457: 'Shovel',
}

# Werkzeuge, die auf ein Feld angewandt werden (Loch graben, Seil hoch).
# Sie werden getrennt erkannt, weil ihre Vorlagen aus dem echten Client
# stammen und deshalb einen anderen Bildbereich brauchen - siehe
# TOOL_REGION.
SLOT_TOOLS = {
    3003: 'Rope',
    3457: 'Shovel',
}

HEALTH_POTION_IDS = (7876, 266, 236, 239, 23375, 7643)
MANA_POTION_IDS = (268, 237, 238, 53162, 53163, 53164, 7642, 23374)

# Zauber (Zauberworte) mit Slot-Icon unter images/spells/<name>.png:
# klassische Heilzauber + die Combo-Angriffs-/Buff-Zauber.
SLOT_SPELLS = (
    'exura', 'exura gran', 'exura vita',
    'exura ico', 'exura infir ico', 'exura med ico', 'exura gran ico',
    'exura san', 'exura sio', 'exura gran mas res',
    'utura', 'utura gran',
    'exori', 'exori gran', 'exori mas', 'exori min',
    'utito tempo', 'utamo tempo', 'exeta res', 'exeta amp res',
    'exevo gran mas vis', 'utamo vita',
)

SLOT_COUNT = 12
_MATCH_CONFIDENCE = 0.75
# Anzahl-Ziffern ueberlagern den unteren Icon-Rand — nur der obere
# Icon-Bereich ist stabil genug fuer den Vergleich.
_ICON_TOP_ROWS = 24


def _loadIcons(path, names):
    return {
        name: loadFromRGBToGray(str(path / f'{name}.png'))[:_ICON_TOP_ROWS]
        for name in names
    }


# Bildbereich eines Slots, in dem NUR das Item steht.
#
# Der Client blendet das Tastenkuerzel oben rechts ein ("F10") und die
# Stueckzahl unten rechts ("1"). Beides gehoert nicht zum Item und wuerde
# die Vorlage an genau eine Taste binden. Uebrig bleibt der Bereich
# darunter und links davon. Gemessen an der echten Leiste: Mit diesem
# Ausschnitt trifft jede Werkzeug-Vorlage ihren eigenen Slot mit 1.00 und
# jeden fremden Slot mit hoechstens 0.16 - eine sehr klare Trennung.
TOOL_REGION = (9, 32, 1, 24)
_TR_OBEN, _TR_UNTEN, _TR_LINKS, _TR_RECHTS = TOOL_REGION


def _toolRegion(image: GrayImage) -> GrayImage:
    return image[_TR_OBEN:_TR_UNTEN, _TR_LINKS:_TR_RECHTS]


def _loadToolIcons(path, ids):
    return {
        itemId: _toolRegion(loadFromRGBToGray(str(path / f'{itemId}.png')))
        for itemId in ids
    }


SLOT_ITEM_ICONS = _loadIcons(_itemsPath, SLOT_ITEMS.keys())
SLOT_SPELL_ICONS = _loadIcons(_spellsPath, SLOT_SPELLS)
SLOT_TOOL_ICONS = _loadToolIcons(_itemsPath, SLOT_TOOLS)

_FULL_ITEM_ICONS = {}


def getItemIcon(itemId):
    """Volles 32x32-Icon fuer Rucksack-Erkennung (lazy, None bei Fehlen)."""
    if itemId not in _FULL_ITEM_ICONS:
        path = _itemsPath / f'{itemId}.png'
        if not path.exists():
            return None
        _FULL_ITEM_ICONS[itemId] = loadFromRGBToGray(str(path))
    return _FULL_ITEM_ICONS[itemId]


def getSlotImage(screenshot: GrayImage, slot: int):
    """Schneidet das 34x34-Slot-Bild (1-basiert) aus; None ohne Action Bar."""
    arrowsPos = getLeftArrowsPosition(screenshot)
    if arrowsPos is None:
        return None
    x0 = arrowsPos[0] + arrowsPos[2] + (slot * 2) + ((slot - 1) * 34)
    return screenshot[arrowsPos[1]:arrowsPos[1] + 34, x0:x0 + 34]


def getSlotCenter(screenshot: GrayImage, slot: int):
    """Fenster-Koordinate der Slot-Mitte fuer Mausklicks."""
    arrowsPos = getLeftArrowsPosition(screenshot)
    if arrowsPos is None:
        return None
    x0 = arrowsPos[0] + arrowsPos[2] + (slot * 2) + ((slot - 1) * 34)
    return (x0 + 17, arrowsPos[1] + 17)


def _slotIsEmpty(slotImage: GrayImage) -> bool:
    return (
        slotImage[1, 2] == 54 and slotImage[1, 4] == 54
        and slotImage[1, 6] == 54 and slotImage[1, 8] == 54
        and slotImage[1, 10] == 54
    )


def matchSlotImage(slotImage: GrayImage, icons, confidence=_MATCH_CONFIDENCE):
    """Bester Schluessel aus `icons` fuer ein Slot-Bild oder None."""
    region = slotImage[:_ICON_TOP_ROWS + 2]
    best_key = None
    best_score = confidence
    for key, icon in icons.items():
        score = cv2.matchTemplate(region, icon, cv2.TM_CCOEFF_NORMED).max()
        if score > best_score:
            best_key = key
            best_score = score
    return best_key


def matchScore(region: GrayImage, icon: GrayImage) -> float:
    """Hoechster Template-Score des Icons in der Region (0..1)."""
    if (region.shape[0] < icon.shape[0] or region.shape[1] < icon.shape[1]):
        return 0.0
    return float(cv2.matchTemplate(region, icon, cv2.TM_CCOEFF_NORMED).max())


def matchSlotItem(slotImage: GrayImage):
    """Beste Item-ID fuer ein Slot-Bild oder None (Score-Schwelle)."""
    return matchSlotImage(slotImage, SLOT_ITEM_ICONS)


def _iterFilledSlots(screenshot: GrayImage):
    arrowsPos = getLeftArrowsPosition(screenshot)
    if arrowsPos is None:
        return None
    filled = []
    for slot in range(1, SLOT_COUNT + 1):
        x0 = arrowsPos[0] + arrowsPos[2] + (slot * 2) + ((slot - 1) * 34)
        slotImage = screenshot[arrowsPos[1]:arrowsPos[1] + 34, x0:x0 + 34]
        if slotImage.shape[0] < 34 or slotImage.shape[1] < 34 or _slotIsEmpty(slotImage):
            continue
        filled.append((slot, slotImage))
    return filled


def _detect(screenshot: GrayImage, icons):
    filled = _iterFilledSlots(screenshot)
    if filled is None:
        return None
    detected = {}
    for slot, slotImage in filled:
        key = matchSlotImage(slotImage, icons)
        if key is not None:
            detected[key] = slot
    return detected


def detectSlotItems(screenshot: GrayImage):
    """Erkennt belegte Item-Slots: {item_id: slot}. None ohne Action Bar."""
    return _detect(screenshot, SLOT_ITEM_ICONS)


def detectSlotSpells(screenshot: GrayImage):
    """Erkennt Zauber-Slots: {zauberworte: slot}. None ohne Action Bar."""
    return _detect(screenshot, SLOT_SPELL_ICONS)


def detectSlotTools(screenshot: GrayImage):
    """Erkennt Werkzeug-Slots (Seil, Schaufel): {item_id: slot}.

    Eigener Weg statt detectSlotItems, weil die Vorlagen aus dem echten
    Client stammen und nur im Bereich ohne Tastenkuerzel und Stueckzahl
    verglichen werden duerfen (TOOL_REGION).
    """
    filled = _iterFilledSlots(screenshot)
    if filled is None:
        return None
    detected = {}
    for slot, slotImage in filled:
        region = slotImage[
            max(0, _TR_OBEN - 2):_TR_UNTEN + 1,
            max(0, _TR_LINKS - 2):_TR_RECHTS + 2,
        ]
        bester, bestwert = None, _MATCH_CONFIDENCE
        for itemId, icon in SLOT_TOOL_ICONS.items():
            if region.shape[0] < icon.shape[0] or region.shape[1] < icon.shape[1]:
                continue
            wert = float(cv2.matchTemplate(
                region, icon, cv2.TM_CCOEFF_NORMED).max())
            if wert > bestwert:
                bester, bestwert = itemId, wert
        if bester is not None:
            detected[bester] = slot
    return detected


def slotItemsByName(detected):
    """Lesbare Darstellung des Scan-Ergebnisses fuer die UI."""
    return [
        f'{SLOT_ITEMS[item_id]} (Slot {slot})'
        for item_id, slot in sorted(detected.items(), key=lambda entry: entry[1])
    ]
