"""AutoPOE2 - Dynamic Resolution Scaling Engine (Doc 63 / SSoT 2026).
======================================================================
Module chuyển đổi và co giãn tọa độ màn hình động (Dynamic Coordinate Transform):
- Hỗ trợ co giãn tuyến tính chính xác giữa Single Source of Truth (1920x1080),
  1440p (2560x1440), 720p (1280x720), và chế độ Windowed thực tế (1189x891).
- Chuẩn hóa ma trận tọa độ pixel cho các phần tử UI trọng yếu:
  * Nút TRAVERSE & khung tương tác Map Device.
  * Khay cắm Waystone (Map Device Socket) & khay Waystone hàng 1 Atlas.
  * Lưới hòm đồ Stash (12x12) và túi đồ Inventory (12x5).
  * Giao diện Sàn Ngoại Hối Faustus Currency Exchange.
- Đảm bảo sai số làm tròn tối đa <= 1 pixel trên mọi độ phân giải.

Tuân thủ nghiêm ngặt:
- Rule 1: Python 3.11 Cold Path Tier 2.
- Rule 4: Module hóa dùng chung tại src/common/, trần file < 500 dòng.
- Rule 16: Google Docstrings đầy đủ.
- Bất biến: INV-SCALE-IDEMPOTENT-1PX, INV-WASD-NO-BLIND-LMB.
"""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
import math
from typing import Any, Dict, List, Optional, Tuple, Union


@dataclass(frozen=True)
class Resolution:
    """Đặc tả độ phân giải màn hình hoặc cửa sổ game.

    Attributes:
        width: Chiều rộng tính bằng pixel (> 0).
        height: Chiều cao tính bằng pixel (> 0).
    """
    width: int
    height: int

    def __post_init__(self) -> None:
        if self.width <= 0 or self.height <= 0:
            raise ValueError(f"Độ phân giải không hợp lệ: width={self.width}, height={self.height}")

    @property
    def aspect_ratio(self) -> float:
        """Tỷ lệ khung hình (width / height)."""
        return self.width / float(self.height)

    @property
    def is_standard_16_9(self) -> bool:
        """Kiểm tra xem có xấp xỉ tỷ lệ chuẩn 16:9 (1.777...) hay không."""
        return abs(self.aspect_ratio - (16.0 / 9.0)) < 0.05

    @property
    def is_windowed_aspect(self) -> bool:
        """Kiểm tra xem có phải tỷ lệ khung hình cửa sổ nhỏ (< 1.60 như 4:3, 5:4) hay không."""
        return self.aspect_ratio < 1.60

    @property
    def is_ultrawide(self) -> bool:
        """Kiểm tra màn hình siêu rộng (21:9 hoặc 32:9, aspect >= 2.30)."""
        return self.aspect_ratio >= 2.30


# Các độ phân giải tham chiếu chuẩn trong AutoPOE2
RES_1080P = Resolution(1920, 1080)             # SSoT Canonical Reference Base
RES_1440P = Resolution(2560, 1440)             # 2K QHD Standard Capture Base
RES_720P = Resolution(1280, 720)               # HD Minimum Spec
RES_4K = Resolution(3840, 2160)                 # 4K UHD High Spec
RES_WINDOWED_DEFAULT = Resolution(1189, 891)   # ClientRect thực tế PID 22632 (Aspect ~4:3)


class ScalingMode(str, Enum):
    """Chế độ co giãn tọa độ hình học."""
    STRETCH = "STRETCH"                        # Tuyến tính trực tiếp 2 trục độc lập: sx = W/W_ref, sy = H/H_ref
    UNIFORM_FIT = "UNIFORM_FIT"                # Đồng dạng giữ nguyên aspect ratio: s = min(sx, sy) căn giữa
    CENTER_ANCHORED = "CENTER_ANCHORED"        # Căn giữa theo trục X, co giãn theo chiều cao H (cho Modals)
    WINDOWED_ADAPTIVE = "WINDOWED_ADAPTIVE"    # Tự động chuyển đổi nếu aspect < 1.60 (Windowed Mode)


class CoordinateScaler:
    """Bộ chuyển đổi tọa độ động giữa các độ phân giải với sai số <= 1px."""

    def __init__(
        self,
        source_res: Resolution = RES_1080P,
        target_res: Resolution = RES_1080P,
    ) -> None:
        """Khởi tạo bộ chuyển đổi.

        Args:
            source_res: Độ phân giải gốc chứa tọa độ tham chiếu (mặc định 1080p SSoT).
            target_res: Độ phân giải đích cần chuyển đổi tới.
        """
        self.source_res = source_res
        self.target_res = target_res
        self.scale_x: float = target_res.width / float(source_res.width)
        self.scale_y: float = target_res.height / float(source_res.height)

    def transform_point(
        self,
        x: float,
        y: float,
        mode: ScalingMode = ScalingMode.STRETCH,
    ) -> Tuple[int, int]:
        """Chuyển đổi một điểm (X, Y) từ source_res sang target_res.

        Args:
            x: Tọa độ X trên source_res.
            y: Tọa độ Y trên source_res.
            mode: Chế độ co giãn (mặc định STRETCH tuyến tính).

        Returns:
            Tuple[int, int]: Tọa độ (x_target, y_target) đã làm tròn chuẩn.
        """
        if mode == ScalingMode.STRETCH:
            tx = x * self.scale_x
            ty = y * self.scale_y
            return int(round(tx)), int(round(ty))

        if mode == ScalingMode.CENTER_ANCHORED or (
            mode == ScalingMode.WINDOWED_ADAPTIVE and self.target_res.is_windowed_aspect
        ):
            # Neo vào tâm màn hình theo trục X, tỷ lệ theo trục Y
            center_src_x = self.source_res.width * 0.5
            center_tgt_x = self.target_res.width * 0.5
            offset_x = x - center_src_x
            tx = center_tgt_x + (offset_x * self.scale_y)
            ty = y * self.scale_y
            return int(round(tx)), int(round(ty))

        if mode == ScalingMode.UNIFORM_FIT:
            s = min(self.scale_x, self.scale_y)
            pad_x = (self.target_res.width - (self.source_res.width * s)) * 0.5
            pad_y = (self.target_res.height - (self.source_res.height * s)) * 0.5
            tx = pad_x + (x * s)
            ty = pad_y + (y * s)
            return int(round(tx)), int(round(ty))

        # Fallback STRETCH
        return int(round(x * self.scale_x)), int(round(y * self.scale_y))

    def transform_rect(
        self,
        x: float,
        y: float,
        w: float,
        h: float,
        mode: ScalingMode = ScalingMode.STRETCH,
    ) -> Tuple[int, int, int, int]:
        """Chuyển đổi hình chữ nhật (x, y, width, height)."""
        x1, y1 = self.transform_point(x, y, mode=mode)
        x2, y2 = self.transform_point(x + w, y + h, mode=mode)
        return x1, y1, max(1, x2 - x1), max(1, y2 - y1)

    def transform_bbox(
        self,
        x1: float,
        y1: float,
        x2: float,
        y2: float,
        mode: ScalingMode = ScalingMode.STRETCH,
    ) -> Tuple[int, int, int, int]:
        """Chuyển đổi bounding box (x1, y1, x2, y2)."""
        tx1, ty1 = self.transform_point(x1, y1, mode=mode)
        tx2, ty2 = self.transform_point(x2, y2, mode=mode)
        return tx1, ty1, tx2, ty2

    def transform_grid_cell(
        self,
        origin_x: float,
        origin_y: float,
        cell_w: float,
        cell_h: float,
        col: int,
        row: int,
        mode: ScalingMode = ScalingMode.STRETCH,
    ) -> Tuple[int, int]:
        """Tính tâm của ô lưới (col, row) trên target_res không bị tích lũy sai số làm tròn."""
        cx_src = origin_x + (col + 0.5) * cell_w
        cy_src = origin_y + (row + 0.5) * cell_h
        return self.transform_point(cx_src, cy_src, mode=mode)

    def inverse_transform_point(
        self,
        x: float,
        y: float,
        mode: ScalingMode = ScalingMode.STRETCH,
    ) -> Tuple[int, int]:
        """Biến đổi ngược lại từ target_res về source_res."""
        inv_scaler = CoordinateScaler(source_res=self.target_res, target_res=self.source_res)
        return inv_scaler.transform_point(x, y, mode=mode)


# ======================================================================
# BẢNG TỌA ĐỘ CHUẨN HÓA CỦA CÁC UI ELEMENTS (CANONICAL DEFINITIONS)
# Nguồn chân lý SSoT: 1920x1080 (Chuẩn tỷ lệ hệ thống) và 2560x1440 (Artifacts)
# ======================================================================

# Nút TRAVERSE (Modal Map Device kích hoạt cổng)
# 2560x1440: Text=(1091, 747), Frame=(1071, 752), BBox=(999, 730, 1142, 774)
# 1920x1080: Text=(818.25, 560.25), Frame=(803.25, 564.0), BBox=(749.25, 547.5, 856.5, 580.5)
CANONICAL_TRAVERSE_1080P: Dict[str, Any] = {
    "text_center": (818.25, 560.25),
    "frame_center": (803.25, 564.0),
    "bbox": (749.25, 547.5, 856.5, 580.5),
}

# Khay cắm Waystone (Map Device Socket trung tâm & Khay Waystone Hàng 1)
# 2560x1440: Socket=(1054, 735), Tray Row 1 Origin=(320, 933), Step=80
# 1920x1080: Socket=(790.5, 551.25), Tray Row 1 Origin=(240.0, 700.0), Step=60.0
CANONICAL_WAYSTONE_1080P: Dict[str, Any] = {
    "socket_center": (790.5, 551.25),
    "tray_row1_origin": (240.0, 700.0),
    "tray_slot_step": 60.0,
    "tray_slot_count": 5,
}

# Lưới Túi đồ Inventory (12 cột x 5 hàng)
# Tham chiếu 1080p SSoT Doc 63
CANONICAL_INVENTORY_1080P: Dict[str, Any] = {
    "origin_x": 1272.0,
    "origin_y": 590.0,
    "cell_w": 52.5,
    "cell_h": 52.5,
    "cols": 12,
    "rows": 5,
}

# Lưới Hòm đồ Stash Tab Thường (12 cột x 12 hàng)
# Tham chiếu 1080p SSoT Doc 63
CANONICAL_STASH_1080P: Dict[str, Any] = {
    "origin_x": 180.0,
    "origin_y": 190.0,
    "cell_w": 52.0,
    "cell_h": 52.0,
    "cols": 12,
    "rows": 12,
}

# Giao diện Sàn Ngoại Hối Faustus Currency Exchange (Phím '/')
# Tham chiếu 1080p SSoT Doc 43 / Doc 68 / Doc 69
CANONICAL_FAUSTUS_1080P: Dict[str, Any] = {
    "panel_center": (960.0, 540.0),
    "tab_i_want": (800.0, 260.0),         # Nút chọn loại tiền muốn mua
    "tab_i_have": (1120.0, 260.0),       # Nút chọn loại tiền đem bán
    "amount_input": (960.0, 680.0),      # Hộp nhập số lượng giao dịch
    "submit_order_btn": (960.0, 780.0),  # Nút xác nhận Place Order
    "close_panel_btn": (1420.0, 200.0),  # Nút 'X' đóng panel góc trên phải
}


# ======================================================================
# CÁC HÀM TIỆN ÍCH CẤP CAO DÙNG CHUNG (PUBLIC API)
# ======================================================================

def resolve_resolution(target: Union[Resolution, Tuple[int, int], Any]) -> Resolution:
    """Chuẩn hóa đối tượng độ phân giải từ nhiều nguồn (Resolution, tuple, PIL Image, HWND)."""
    if isinstance(target, Resolution):
        return target
    if isinstance(target, (tuple, list)) and len(target) >= 2:
        return Resolution(int(target[0]), int(target[1]))
    if hasattr(target, "size") and isinstance(target.size, tuple):
        return Resolution(int(target.size[0]), int(target.size[1]))
    if hasattr(target, "width") and hasattr(target, "height"):
        return Resolution(int(target.width), int(target.height))
    # Mặc định fallback SSoT 1080p
    return RES_1080P


def get_traverse_button_pos(
    target_res: Union[Resolution, Tuple[int, int]],
    use_frame_center: bool = False,
    mode: ScalingMode = ScalingMode.STRETCH,
) -> Tuple[int, int]:
    """Lấy tọa độ click tối ưu của nút TRAVERSE trên độ phân giải đích.

    Args:
        target_res: Độ phân giải màn hình đích.
        use_frame_center: True nếu muốn lấy tâm khung nút, False lấy tâm chữ TRAVERSE.
        mode: Chế độ co giãn (mặc định STRETCH tuyến tính).

    Returns:
        Tuple[int, int]: Tọa độ (x, y) với sai số <= 1px.
    """
    res = resolve_resolution(target_res)
    scaler = CoordinateScaler(source_res=RES_1080P, target_res=res)
    ref_key = "frame_center" if use_frame_center else "text_center"
    ref_x, ref_y = CANONICAL_TRAVERSE_1080P[ref_key]
    return scaler.transform_point(ref_x, ref_y, mode=mode)


def get_traverse_button_bbox(
    target_res: Union[Resolution, Tuple[int, int]],
    mode: ScalingMode = ScalingMode.STRETCH,
) -> Tuple[int, int, int, int]:
    """Lấy bounding box (x1, y1, x2, y2) của nút TRAVERSE trên độ phân giải đích."""
    res = resolve_resolution(target_res)
    scaler = CoordinateScaler(source_res=RES_1080P, target_res=res)
    bx1, by1, bx2, by2 = CANONICAL_TRAVERSE_1080P["bbox"]
    return scaler.transform_bbox(bx1, by1, bx2, by2, mode=mode)


def get_waystone_socket_pos(
    target_res: Union[Resolution, Tuple[int, int]],
    mode: ScalingMode = ScalingMode.STRETCH,
) -> Tuple[int, int]:
    """Lấy tọa độ ổ cắm Waystone trên Map Device UI."""
    res = resolve_resolution(target_res)
    scaler = CoordinateScaler(source_res=RES_1080P, target_res=res)
    sx, sy = CANONICAL_WAYSTONE_1080P["socket_center"]
    return scaler.transform_point(sx, sy, mode=mode)


def get_waystone_tray_slot_pos(
    slot_index: int,
    target_res: Union[Resolution, Tuple[int, int]],
    mode: ScalingMode = ScalingMode.STRETCH,
) -> Tuple[int, int]:
    """Lấy tọa độ ô thứ `slot_index` (0..4) trên khay Waystone Hàng 1 của Atlas."""
    res = resolve_resolution(target_res)
    scaler = CoordinateScaler(source_res=RES_1080P, target_res=res)
    ox, oy = CANONICAL_WAYSTONE_1080P["tray_row1_origin"]
    step = CANONICAL_WAYSTONE_1080P["tray_slot_step"]
    slot_x = ox + (slot_index * step)
    return scaler.transform_point(slot_x, oy, mode=mode)


def get_inventory_slot_pos(
    col: int,
    row: int,
    target_res: Union[Resolution, Tuple[int, int]],
    mode: ScalingMode = ScalingMode.STRETCH,
) -> Tuple[int, int]:
    """Lấy tọa độ tâm ô túi đồ Inventory (col: 0..11, row: 0..4).

    Đặc biệt: Khi target_res là chế độ Windowed (aspect < 1.60 như 1189x891),
    tự động áp dụng neo lề phải (Right-anchored pouch margin) chuẩn theo Doc 63.
    """
    res = resolve_resolution(target_res)
    cfg = CANONICAL_INVENTORY_1080P

    # Xử lý tối ưu riêng cho Windowed Mode (aspect < 1.60)
    if res.is_windowed_aspect:
        scale = res.width / 1920.0
        cell_size = cfg["cell_w"] * scale
        pouch_margin = 144.0 * (scale / (1189.0 / 1920.0))
        ox = res.width - pouch_margin - (cfg["cols"] * cell_size)
        oy = res.height * 0.587
        cx = ox + (col * cell_size) + (cell_size * 0.5)
        cy = oy + (row * cell_size) + (cell_size * 0.5)
        return int(round(cx)), int(round(cy))

    scaler = CoordinateScaler(source_res=RES_1080P, target_res=res)
    return scaler.transform_grid_cell(
        cfg["origin_x"], cfg["origin_y"], cfg["cell_w"], cfg["cell_h"], col, row, mode=mode
    )


def get_stash_slot_pos(
    col: int,
    row: int,
    target_res: Union[Resolution, Tuple[int, int]],
    mode: ScalingMode = ScalingMode.STRETCH,
) -> Tuple[int, int]:
    """Lấy tọa độ tâm ô hòm đồ Stash Tab Thường (col: 0..11, row: 0..11)."""
    res = resolve_resolution(target_res)
    cfg = CANONICAL_STASH_1080P
    scaler = CoordinateScaler(source_res=RES_1080P, target_res=res)
    return scaler.transform_grid_cell(
        cfg["origin_x"], cfg["origin_y"], cfg["cell_w"], cfg["cell_h"], col, row, mode=mode
    )


def get_faustus_element_pos(
    element_name: str,
    target_res: Union[Resolution, Tuple[int, int]],
    mode: ScalingMode = ScalingMode.STRETCH,
) -> Tuple[int, int]:
    """Lấy tọa độ các thành phần tương tác trên sàn ngoại hối Faustus."""
    res = resolve_resolution(target_res)
    if element_name not in CANONICAL_FAUSTUS_1080P:
        raise KeyError(f"Không tìm thấy UI element '{element_name}' trong CANONICAL_FAUSTUS_1080P")
    scaler = CoordinateScaler(source_res=RES_1080P, target_res=res)
    ref_x, ref_y = CANONICAL_FAUSTUS_1080P[element_name]
    return scaler.transform_point(ref_x, ref_y, mode=mode)


@dataclass
class ScreenPoint:
    """Tọa độ pixel trên màn hình game."""
    x: float
    y: float


@dataclass
class WasdVector:
    """Vector trạng thái 4 phím di chuyển WASD."""
    up: bool = False
    left: bool = False
    down: bool = False
    right: bool = False


def world_to_screen_isometric(
    dx: float,
    dy: float,
    dist: Optional[float] = None,
    screen_w: float = 1920.0,
    screen_h: float = 1080.0,
    margin: float = 50.0,
) -> Tuple[float, float]:
    """Chuẩn hóa phép chiếu Isometric 2D từ vector delta thế giới (dx, dy) sang pixel màn hình POE2.
    Đồng bộ 1:1 với common::WorldToScreenIsometric trong src/core/common/math2d.hpp.

    Hệ trục màn hình Windows:
    - X tăng dần sang phải màn hình.
    - Y tăng dần xuống dưới màn hình (cy là tâm màn hình).

    Phép chiếu Isometric POE2:
    - Trục X màn hình: targetX = cx + (ndx - ndy) * dist * 0.70710678
    - Trục Y màn hình: targetY = cy - (ndx + ndy) * dist * 0.45000000
      (Khi nhân vật tiến lên phía trước/bắc thế giới: ndx + ndy > 0 -> targetY < cy, tức hướng lên nửa trên màn hình).

    Args:
        dx: Độ lệch trục X thế giới (target.x - origin.x)
        dy: Độ lệch trục Y thế giới (target.y - origin.y)
        dist: Khoảng cách chiếu pixel từ tâm (nếu None sẽ tự tính bằng độ dài vector dx, dy)
        screen_w: Chiều rộng màn hình tính bằng pixel (mặc định 1920.0)
        screen_h: Chiều cao màn hình tính bằng pixel (mặc định 1080.0)
        margin: Khoảng cách an toàn tối thiểu tính từ rìa màn hình (mặc định 50.0)

    Returns:
        Tuple[float, float]: (target_x, target_y) đã clamp an toàn vào khung nhìn màn hình.
    """
    if screen_w <= 0.0:
        screen_w = 1920.0
    if screen_h <= 0.0:
        screen_h = 1080.0

    cx = screen_w * 0.5
    cy = screen_h * 0.5

    length = math.sqrt(dx * dx + dy * dy)
    ndx = (dx / length) if length > 0.001 else 0.0
    ndy = (dy / length) if length > 0.001 else 0.0

    actual_dist = dist if dist is not None else length

    target_x = cx + (ndx - ndy) * actual_dist * 0.70710678
    target_y = cy - (ndx + ndy) * actual_dist * 0.45000000

    min_x = min(margin, cx)
    max_x = max(cx, screen_w - margin)
    min_y = min(margin, cy)
    max_y = max(cy, screen_h - margin)

    target_x = max(min_x, min(target_x, max_x))
    target_y = max(min_y, min(target_y, max_y))

    return target_x, target_y


def world_to_wasd(
    dx: float,
    dy: float,
    deadzone: float = 0.01,
    threshold: float = 0.38,
) -> Tuple[bool, bool, bool, bool]:
    """Phân định 8 hướng phím WASD Isometric chuẩn src/core/common/math2d.hpp.

    Args:
        dx: Độ lệch trục X thế giới
        dy: Độ lệch trục Y thế giới
        deadzone: Ngưỡng chết tối thiểu để kích hoạt phím (mặc định 0.01)
        threshold: Ngưỡng kích hoạt hướng phím (mặc định 0.38)

    Returns:
        Tuple[bool, bool, bool, bool]: (up/W, left/A, down/S, right/D)
    """
    length = math.sqrt(dx * dx + dy * dy)
    if length <= 0.0001 or length < deadzone:
        return False, False, False, False

    ndx = dx / length
    ndy = dy / length

    inv_sqrt2 = 0.70710678
    screen_x = (ndx - ndy) * inv_sqrt2
    screen_y = (ndx + ndy) * inv_sqrt2

    up = screen_y > threshold
    down = screen_y < -threshold
    right = screen_x > threshold
    left = screen_x < -threshold

    return up, left, down, right

