"""AutoPOE2 - Stash UI Scanner (Doc 63 / SSoT 2026).

Bộ quét thị giác máy tính và WinOCR cho giao diện hòm đồ (Stash):
- Nhận diện trạng thái mở/đóng của Stash UI.
- Quét thanh Tab Bar, phân tích danh sách Stash Tabs và loại Tab.
- Thực hiện chuyển đổi Tab chính xác bằng Click chuột tuyệt đối.
- Đóng giao diện Stash an toàn bằng phím Spacebar.
"""

from __future__ import annotations

import os
import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Tuple, Union

try:
    from PIL import Image
except ImportError:
    Image = None  # type: ignore[assignment]

try:
    import winocr
except ImportError:
    winocr = None

from src.assistant_tool.stash.stash_constants import (
    StashTabType,
    TAB_NAME_MAPPINGS,
)
from src.common.win32_input import click, dismiss_modals
from src.common.vision_ocr import is_inventory_open


@dataclass
class StashTabInfo:
    """Thông tin chi tiết của một ngăn hòm đồ (Tab)."""
    index: int
    name: str
    tab_type: StashTabType
    center_x: int
    center_y: int
    bounding_box: Tuple[int, int, int, int]  # (x, y, w, h)
    is_active: bool = False


class StashUiScanner:
    """
    Bộ quét quang học và điều khiển giao diện Stash.
    Tự động co giãn tọa độ theo tỷ lệ màn hình (mặc định tham chiếu 1920x1080 -> 2560x1440).
    """

    def __init__(
        self,
        screen_w: int = 2560,
        screen_h: int = 1440,
        click_func: Optional[Callable[[int, int], Any]] = None,
    ):
        self.screen_w = screen_w
        self.screen_h = screen_h
        self.click_func = click_func
        self.scale_x = screen_w / 1920.0 if screen_w > 0 else 1.0
        self.scale_y = screen_h / 1080.0 if screen_h > 0 else 1.0

        # Vùng cắt thanh Tab Bar trên 1080p: X in [15, 750], Y in [35, 240]
        # Trên 1440p (scale 4/3): X in [20, 1000], Y in [47, 320]
        self.tab_bar_roi = (
            int(15 * self.scale_x),
            int(35 * self.scale_y),
            int(750 * self.scale_x),
            int(240 * self.scale_y),
        )

        # Bộ nhớ đệm các tabs đã nhận diện
        self.detected_tabs: List[StashTabInfo] = []
        self.active_tab_index: int = -1

    def _load_pil(self, img_input: Union[str, Image.Image]) -> Optional[Image.Image]:
        if img_input is None:
            return None
        if isinstance(img_input, str):
            if not os.path.exists(img_input):
                return None
            try:
                return Image.open(img_input).convert("RGB")
            except Exception:
                return None
        try:
            return img_input.convert("RGB")
        except Exception:
            return img_input

    def is_stash_open(self, img_input: Union[str, Image.Image]) -> bool:
        """
        Kiểm tra xem giao diện Stash có đang mở trên màn hình hay không (SSoT 2026).
        Tiêu chuẩn bất biến:
        1. Bắt buộc Inventory bên phải màn hình phải đang mở (is_inventory_open).
        2. Vùng Stash bên trái (header hoặc tab bar) phải chứa từ khóa UI Stash (STASH, CURRENCY, GEM, MAP...).
        Tránh hoàn toàn False Positive do nhãn 3D 'STASH' của hòm đồ dưới đất.
        """
        img = self._load_pil(img_input)
        if img is None or winocr is None:
            return False

        # 1. Bắt buộc: Trong POE2, mở Stash luôn tự động mở Inventory bên phải
        if not is_inventory_open(img):
            return False

        try:
            # 2. Quét vùng Stash header & tab bar ở góc trên bên trái
            crop_stash = img.crop((
                int(15 * self.scale_x),
                int(30 * self.scale_y),
                int(750 * self.scale_x),
                int(200 * self.scale_y),
            ))
            res = winocr.recognize_pil_sync(crop_stash)
            blob = " ".join(line.get("text", "") for line in res.get("lines", [])).upper()

            stash_keywords = [
                "STASH", "TASH", "CURRENCY", "CURR", "WAYSTONE", "MAP",
                "GEMS", "GEM", "RITUAL", "FRAGMENTS", "DUMP", "QUAD"
            ]
            for kw in stash_keywords:
                if kw in blob:
                    return True
        except Exception:
            pass
        return False

    def scan_stash_tabs(self, img_input: Union[str, Image.Image]) -> List[StashTabInfo]:
        """
        Quét vùng thanh Tab Bar và trích xuất danh sách các Tab Stash hiện có.
        Trả về danh sách StashTabInfo đã được sắp xếp từ trái qua phải.
        """
        img = self._load_pil(img_input)
        if img is None or winocr is None:
            return []

        tabs: List[StashTabInfo] = []
        rx1, ry1, rx2, ry2 = self.tab_bar_roi
        try:
            tab_crop = img.crop((rx1, ry1, rx2, ry2))
            res = winocr.recognize_pil_sync(tab_crop)
            lines = res.get("lines", [])

            idx = 0
            # Danh sách từ loại trừ (tiêu đề cửa sổ hoặc ký hiệu giao diện)
            noise_tokens = {"STASH", "O", "X", "E", "NEW", "", ""}

            for line in lines:
                words = line.get("words", [])
                if not words:
                    continue

                for w in words:
                    txt = w.get("text", "").strip()
                    if not txt or txt.upper() in noise_tokens or len(txt) <= 1:
                        continue

                    rect = w.get("bounding_rect", {})
                    abs_x = rx1 + rect.get("x", 0)
                    abs_y = ry1 + rect.get("y", 0)
                    abs_w = rect.get("width", 0)
                    abs_h = rect.get("height", 0)
                    center_x = int(abs_x + (abs_w // 2))
                    center_y = int(abs_y + (abs_h // 2))

                    tab_type = self._classify_tab_name(txt)

                    tab_info = StashTabInfo(
                        index=idx,
                        name=txt,
                        tab_type=tab_type,
                        center_x=center_x,
                        center_y=center_y,
                        bounding_box=(int(abs_x), int(abs_y), int(abs_w), int(abs_h)),
                        is_active=False,
                    )
                    tabs.append(tab_info)
                    idx += 1

            # Sắp xếp các tabs theo thứ tự từ trái sang phải
            tabs.sort(key=lambda t: t.center_x)
            for i, t in enumerate(tabs):
                t.index = i

            self.detected_tabs = tabs
            return tabs
        except Exception:
            return []

    def _classify_tab_name(self, name: str) -> StashTabType:
        """Phân loại StashTabType dựa trên chuỗi tên Tab."""
        cleaned = name.upper().strip()
        for kw, tab_type in TAB_NAME_MAPPINGS.items():
            if kw in cleaned:
                return tab_type
        return StashTabType.GENERAL

    def find_tab_by_type(self, tab_type: StashTabType) -> Optional[StashTabInfo]:
        """Tìm tab phù hợp nhất theo phân loại StashTabType."""
        for tab in self.detected_tabs:
            if tab.tab_type == tab_type:
                return tab
        return None

    def find_tab_by_name(self, name: str) -> Optional[StashTabInfo]:
        """Tìm tab theo chuỗi ký tự tên (không phân biệt hoa thường)."""
        lower = name.lower()
        for tab in self.detected_tabs:
            if lower in tab.name.lower():
                return tab
        return None

    def switch_to_tab(
        self,
        tab_target: Union[int, str, StashTabType, StashTabInfo],
        delay_after: float = 0.25,
    ) -> bool:
        """
        Thực hiện click chuyển sang Tab chỉ định bằng Click chuột trái có chủ đích.
        
        Args:
            tab_target: Có thể là index (0, 1..), chuỗi tên ("Currency"), StashTabType, hoặc StashTabInfo.
            delay_after: Thời gian chờ giao diện chuyển đổi xong (mặc định 250ms).
        """
        target_info: Optional[StashTabInfo] = None

        if isinstance(tab_target, StashTabInfo):
            target_info = tab_target
        elif isinstance(tab_target, int):
            if 0 <= tab_target < len(self.detected_tabs):
                target_info = self.detected_tabs[tab_target]
            else:
                # Fallback tính toán tọa độ theo tham chiếu toml
                fallback_x = int((180 + tab_target * 70) * self.scale_x)
                fallback_y = int(140 * self.scale_y)
                if self.click_func:
                    self.click_func(fallback_x, fallback_y)
                else:
                    click(fallback_x, fallback_y, button="left", hold_ms=0.04, delay_after=delay_after)
                self.active_tab_index = tab_target
                return True
        elif isinstance(tab_target, StashTabType):
            target_info = self.find_tab_by_type(tab_target)
        elif isinstance(tab_target, str):
            target_info = self.find_tab_by_name(tab_target)

        if target_info:
            if self.click_func:
                self.click_func(target_info.center_x, target_info.center_y)
            else:
                click(target_info.center_x, target_info.center_y, button="left", hold_ms=0.04, delay_after=delay_after)
            self.active_tab_index = target_info.index
            for t in self.detected_tabs:
                t.is_active = (t.index == target_info.index)
            return True

        return False

    def close_stash(self, delay_after: float = 0.2) -> None:
        """Đóng bảng giao diện Stash an toàn bằng phím Spacebar chuẩn người chơi."""
        dismiss_modals(count=1, delay_after=delay_after)
