"""AutoPOE2 - Stash & Inventory Item Matrix (Doc 63 / SSoT 2026).

Quản lý ma trận tọa độ và nhận diện vật phẩm trong các ô:
- Tính toán tọa độ tâm của 60 ô túi đồ cá nhân (12 cột x 5 hàng).
- Tính toán tọa độ tâm của 144 ô lưới hòm đồ thường (12 cột x 12 hàng).
- Đo độ sáng quang học (Optical Brightness) để phát hiện ô có đồ / ô trống siêu tốc.
- Trích xuất thông tin chi tiết vật phẩm qua Clipboard (Ctrl+C) kết hợp UniversalItemParser.
"""

from __future__ import annotations

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

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

from src.assistant_tool.stash.stash_constants import (
    InventoryGridConfig,
    StashGridConfig,
    StashConfig,
)
from src.assistant_tool.stash.stash_memory_reader import StashMemoryReader, SlotMemoryData
from src.assistant_tool.item_inspector import (
    inspect_item,
    ItemInspectionReport,
    ItemCategory,
)
from src.common.win32_input import hover, press_key, VK_CONTROL, VK_C


def get_clipboard_text_win32() -> str:
    """Đọc nội dung văn bản từ Windows Clipboard một cách an toàn qua native Win32 API."""
    user32 = ctypes.windll.user32
    kernel32 = ctypes.windll.kernel32

    if not user32.OpenClipboard(0):
        return ""

    try:
        CF_UNICODETEXT = 13
        if not user32.IsClipboardFormatAvailable(CF_UNICODETEXT):
            return ""
        h_glb = user32.GetClipboardData(CF_UNICODETEXT)
        if not h_glb:
            return ""
        ptr = kernel32.GlobalLock(h_glb)
        if not ptr:
            return ""
        try:
            val = ctypes.c_wchar_p(ptr).value
            return str(val) if val else ""
        finally:
            kernel32.GlobalUnlock(h_glb)
    except Exception:
        return ""
    finally:
        user32.CloseClipboard()


def set_clipboard_text_win32(text: str) -> bool:
    """Xóa hoặc đặt văn bản vào Windows Clipboard."""
    user32 = ctypes.windll.user32
    kernel32 = ctypes.windll.kernel32

    if not user32.OpenClipboard(0):
        return False

    try:
        user32.EmptyClipboard()
        if not text:
            return True
        data = text.encode("utf-16-le") + b"\x00\x00"
        GMEM_MOVEABLE = 0x0002
        h_glb = kernel32.GlobalAlloc(GMEM_MOVEABLE, len(data))
        if not h_glb:
            return False
        ptr = kernel32.GlobalLock(h_glb)
        if ptr:
            ctypes.memmove(ptr, data, len(data))
            kernel32.GlobalUnlock(h_glb)
            CF_UNICODETEXT = 13
            user32.SetClipboardData(CF_UNICODETEXT, h_glb)
            return True
    except Exception:
        return False
    finally:
        user32.CloseClipboard()
    return False


@dataclass
class SlotItemInfo:
    """Thông tin một ô chứa vật phẩm."""
    col: int
    row: int
    center_x: int
    center_y: int
    is_occupied: bool = False
    avg_rgb: float = 0.0
    item_report: Optional[ItemInspectionReport] = None


class StashItemMatrix:
    """Bộ tính toán tọa độ lưới và quét ma trận vật phẩm trong Stash & Inventory."""

    def __init__(
        self,
        screen_w: int = 2560,
        screen_h: int = 1440,
        config: Optional[StashConfig] = None,
        memory_reader: Optional[StashMemoryReader] = None,
        is_simulated: bool = False,
    ):
        self.screen_w = screen_w
        self.screen_h = screen_h
        self.config = config or StashConfig()
        self.memory_reader = memory_reader
        self.is_simulated = is_simulated

        if self.memory_reader is None and self.config.enable_ram_reading:
            self.memory_reader = StashMemoryReader()

        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

        self.inv_cfg = InventoryGridConfig()
        self.stash_cfg = StashGridConfig()
        self._calibrated_inv_origin: Optional[Tuple[float, float, float, float]] = None

    def calibrate_from_image(self, img_input: Union[str, Image.Image]) -> bool:
        """Tự động hiệu chuẩn tọa độ lưới túi đồ từ ảnh chụp thực tế (Commercial-Grade Multi-Resolution)."""
        img = self._load_pil(img_input)
        if img is None:
            return False
        try:
            from src.common.vision_ocr import find_button_by_text
            inv_pos = find_button_by_text(img, "INVENTORY")
            if inv_pos:
                inv_x, inv_y = inv_pos
                # Độ lệch tương đối từ nhãn INVENTORY đến góc trên bên trái của lưới 12x5
                scale_w = self.screen_w / 1189.0
                scale_h = self.screen_h / 891.0
                ox = inv_x - (269.0 * scale_w)
                oy = inv_y + (465.0 * scale_h)
                cw = 33.33 * scale_w
                ch = cw
                self._calibrated_inv_origin = (ox, oy, cw, ch)
                return True
        except Exception:
            pass
        return False

    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 get_inventory_slot_center(self, col: int, row: int) -> Tuple[int, int]:
        """Tính toán tọa độ pixel tuyệt đối tâm của ô túi đồ (col: 0..11, row: 0..4).
        
        Hỗ trợ 100% độ phân giải chuẩn (1080p, 1440p, 4K) lẫn chế độ Windowed chia màn hình (4:3, 16:10).
        """
        if self._calibrated_inv_origin is not None:
            ox, oy, cw, ch = self._calibrated_inv_origin
            return int(round(ox + (col * cw) + (cw * 0.5))), int(round(oy + (row * ch) + (ch * 0.5)))

        # Nhận diện tỷ lệ màn hình phi tiêu chuẩn (ví dụ Windowed mode 4:3)
        aspect = (self.screen_w / self.screen_h) if self.screen_h > 0 else 1.777
        if aspect < 1.60:
            scale = self.screen_w / 1920.0
            cell_size = self.inv_cfg.cell_w * scale
            pouch_margin = 144.0 * (scale / 0.61927)
            ox = self.screen_w - pouch_margin - (self.inv_cfg.cols * cell_size)
            oy = self.screen_h * 0.587
            return int(round(ox + (col * cell_size) + (cell_size * 0.5))), int(round(oy + (row * cell_size) + (cell_size * 0.5)))

        raw_x = self.inv_cfg.origin_x + (col * self.inv_cfg.cell_w) + (self.inv_cfg.cell_w * 0.5)
        raw_y = self.inv_cfg.origin_y + (row * self.inv_cfg.cell_h) + (self.inv_cfg.cell_h * 0.5)
        return int(round(raw_x * self.scale_x)), int(round(raw_y * self.scale_y))

    def get_stash_slot_center(self, col: int, row: int) -> Tuple[int, int]:
        """Tính toán tọa độ pixel tuyệt đối tâm của ô hòm đồ (col: 0..11, row: 0..11)."""
        raw_x = self.stash_cfg.origin_x + (col * self.stash_cfg.cell_w) + (self.stash_cfg.cell_w * 0.5)
        raw_y = self.stash_cfg.origin_y + (row * self.stash_cfg.cell_h) + (self.stash_cfg.cell_h * 0.5)
        return int(round(raw_x * self.scale_x)), int(round(raw_y * self.scale_y))

    def measure_brightness(
        self,
        rgb_img: Image.Image,
        center_x: int,
        center_y: int,
        radius: int = 8,
    ) -> float:
        """Đo độ sáng trung bình tại tâm vùng ô pixel."""
        r = max(4, int(round(radius * self.scale_x)))
        box = (center_x - r, center_y - r, center_x + r, center_y + r)
        try:
            sample = rgb_img.crop(box)
            raw = sample.tobytes()
            if not raw:
                return 0.0
            return sum(raw) / float(len(raw))
        except Exception:
            return 0.0

    def scan_inventory_occupancy(
        self,
        img_input: Union[str, Image.Image],
    ) -> List[SlotItemInfo]:
        """
        Quét nhanh 60 ô túi đồ để xác định các ô có chứa vật phẩm.
        Trả về danh sách 60 SlotItemInfo với cờ is_occupied và avg_rgb.
        """
        img = self._load_pil(img_input)
        if img is None:
            return []

        results: List[SlotItemInfo] = []
        for row in range(self.inv_cfg.rows):
            for col in range(self.inv_cfg.cols):
                cx, cy = self.get_inventory_slot_center(col, row)
                brightness = self.measure_brightness(img, cx, cy)
                is_occ = brightness > self.config.brightness_threshold_item

                results.append(SlotItemInfo(
                    col=col,
                    row=row,
                    center_x=cx,
                    center_y=cy,
                    is_occupied=is_occ,
                    avg_rgb=brightness,
                ))

        if self.memory_reader:
            self.memory_reader.sync_with_optical_matrix(results)

        return results

    def scan_stash_occupancy(
        self,
        img_input: Union[str, Image.Image],
    ) -> List[SlotItemInfo]:
        """
        Quét nhanh 144 ô trong lưới Stash thường (12x12).
        Trả về danh sách 144 SlotItemInfo.
        """
        img = self._load_pil(img_input)
        if img is None:
            return []

        results: List[SlotItemInfo] = []
        for row in range(self.stash_cfg.rows):
            for col in range(self.stash_cfg.cols):
                cx, cy = self.get_stash_slot_center(col, row)
                brightness = self.measure_brightness(img, cx, cy)
                is_occ = brightness > self.config.brightness_threshold_item

                results.append(SlotItemInfo(
                    col=col,
                    row=row,
                    center_x=cx,
                    center_y=cy,
                    is_occupied=is_occ,
                    avg_rgb=brightness,
                ))
        return results

    def inspect_slot_item_live(
        self,
        center_x: int,
        center_y: int,
        col: Optional[int] = None,
        row: Optional[int] = None,
        delay_hover: float = 0.20,
    ) -> Optional[ItemInspectionReport]:
        """
        Di chuột tới tâm ô và trích xuất thẻ vật phẩm qua Clipboard (Ctrl+C).
        100% User Mode, không ghi bộ nhớ game, không nguy cơ ban (Doc 38).
        Tự động cập nhật thuộc tính vào RAM Cache nếu có col, row.
        """
        if self.is_simulated:
            return None

        # Xóa clipboard trước khi copy
        set_clipboard_text_win32("")

        # Di chuột tới slot và chờ tooltip game hiện lên
        hover(center_x, center_y, delay_after=delay_hover, is_simulated=self.is_simulated)

        # Gửi tổ hợp Ctrl + C chuẩn xác
        user32 = ctypes.windll.user32
        user32.keybd_event(VK_CONTROL, 0x1D, 0, 0)
        time.sleep(0.05)
        user32.keybd_event(VK_C, 0x2E, 0, 0)
        time.sleep(0.05)
        user32.keybd_event(VK_C, 0x2E, 0x0002, 0)
        user32.keybd_event(VK_CONTROL, 0x1D, 0x0002, 0)
        time.sleep(0.08)

        raw_text = get_clipboard_text_win32()
        if not raw_text or ("Item Class:" not in raw_text and "Rarity:" not in raw_text):
            return None

        report = inspect_item(raw_text)

        # Cập nhật kết quả vào RAM Memory Cache tức thời
        if report and self.memory_reader and col is not None and row is not None:
            self.memory_reader.update_slot(
                col=col,
                row=row,
                is_occupied=True,
                item_name=report.item_name or report.base_type,
                category=report.category.value,
                tier=None,
            )

        return report
