"""AutoPOE2 - Thư viện điều khiển Nhập liệu Win32 Dùng Chung (Win32 Input Foundation).

Cung cấp Single Source of Truth (SSoT) cho toàn bộ thao tác chuột và bàn phím:
- Click chuột trái, chuột phải, Ctrl+Click, Hover với tọa độ tuyệt đối.
- Nhấn phím đơn, tổ hợp phím với dwell time tự nhiên.
- Di chuyển 8 hướng WASD isometric đồng thời (True simultaneous keydown).
- Tích hợp Focus Interlock bảo vệ desktop người dùng.
- Bảng mã phím chuẩn ảo (VK) và mã quét phần cứng (Scan Code).

Tuân thủ nghiêm ngặt các bất biến kiến trúc:
- INV-INPUT-ABS-MOUSE: Tọa độ chuột tuyệt đối.
- INV-WASD-NO-BLIND-LMB: Không click mù LMB trong chế độ WASD.
- INV-KEY-NO-F-FOR-INTERACT: Phím F là Skill 8, cấm dùng cho tương tác vật lý.
- INV-KEY-NO-U-FOR-MAP-DEVICE: Phím U chỉ mở Campaign World Map, cấm dùng ở Hideout.
- INV-WASD-MIN-DWELL: Dwell di chuyển tối thiểu 300ms, cấm micro-tap < 150ms.
"""

from __future__ import annotations

import ctypes
from ctypes import wintypes
import math
import os
import time
from typing import Any, Optional, Sequence, Tuple, Union

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

from src.common.win32_window import is_poe2_window_focused, find_poe2_window, ensure_window_focus

# Cờ toàn cục cho phép cưỡng chế trạng thái mô phỏng Focus (phục vụ Unit Testing / Headless)
_GLOBAL_SIMULATED_FOCUS: Optional[bool] = None


def set_input_simulation_mode(enabled: Optional[bool]) -> None:
    """Thiết lập chế độ mô phỏng Focus cho tầng Win32 Input.
    
    Args:
        enabled: True để luôn cho phép input (giả lập game đang focused),
                 False để luôn chặn input (giả lập game mất focus),
                 None để kiểm tra thực tế qua GetForegroundWindow().
    """
    global _GLOBAL_SIMULATED_FOCUS
    _GLOBAL_SIMULATED_FOCUS = enabled


def get_input_simulation_mode() -> Optional[bool]:
    """Lấy trạng thái chế độ mô phỏng Focus hiện tại."""
    return _GLOBAL_SIMULATED_FOCUS


def _is_physical_allowed(is_simulated: bool = False, hwnd: Optional[int] = None) -> bool:
    """Kiểm tra điều kiện thực tế để được phép gửi phím/chuột phần cứng (Hardware Safety Guarantee).
    
    Quy tắc bất biến INV-FOCUS-INTERLOCK-HARDWARE-SAFETY:
    - Nếu đang ở chế độ mô phỏng (is_simulated=True hoặc _GLOBAL_SIMULATED_FOCUS is not None):
      TUYỆT ĐỐI CẤM gửi bất kỳ tín hiệu phần cứng nào ra OS Windows (0% leakage, 0 pixel displacement).
    - Chỉ cho phép gửi tín hiệu phần cứng khi game thật đang thực sự giữ Foreground Focus.
    """
    if is_simulated or _GLOBAL_SIMULATED_FOCUS is not None:
        return False
    target = hwnd if (hwnd and hwnd > 0) else None
    if target is None:
        try:
            from src.common.win32_window import find_poe2_window
            target = find_poe2_window()
        except Exception:
            target = None
    return is_poe2_window_focused(hwnd=target)


def _is_focus_allowed(is_simulated: bool = False, hwnd: Optional[int] = None) -> bool:
    """Kiểm tra điều kiện Focus Interlock logic (cho phép FSM advance trong tests/simulation).
    
    Returns:
        bool: True nếu thao tác input logic được chấp thuận (trong simulation hoặc khi game focused).
    """
    if is_simulated:
        return True
    if _GLOBAL_SIMULATED_FOCUS is not None:
        return bool(_GLOBAL_SIMULATED_FOCUS)
    target = hwnd if (hwnd and hwnd > 0) else None
    if target is None:
        try:
            from src.common.win32_window import find_poe2_window
            target = find_poe2_window()
        except Exception:
            target = None
    return is_poe2_window_focused(hwnd=target)

# ==========================================================
# BẢNG MÃ PHÍM ẢO (VIRTUAL KEY CODES) & SCAN CODES
# ==========================================================
VK_LBUTTON = 0x01
VK_RBUTTON = 0x02
VK_MBUTTON = 0x04

VK_BACK = 0x08
VK_TAB = 0x09
VK_RETURN = 0x0D
VK_SHIFT = 0x10
VK_CONTROL = 0x11
VK_MENU = 0x12  # Alt
VK_PAUSE = 0x13
VK_ESCAPE = 0x1B
VK_SPACE = 0x20

# Phím điều hướng & di chuyển
VK_LEFT = 0x25
VK_UP = 0x26
VK_RIGHT = 0x27
VK_DOWN = 0x28

# Phím chữ cái (Thường dùng trong POE2)
VK_A = 0x41
VK_B = 0x42
VK_C = 0x43
VK_D = 0x44
VK_E = 0x45
VK_F = 0x46  # Skill 8 (CẤM dùng để tương tác thế giới)
VK_G = 0x47  # Gem Panel
VK_I = 0x49  # Inventory
VK_M = 0x4D
VK_O = 0x4F
VK_P = 0x50  # Passive Skill Tree
VK_Q = 0x51
VK_R = 0x52
VK_S = 0x53
VK_T = 0x54
VK_U = 0x55  # Campaign World Map (CẤM dùng ở Hideout Map Device)
VK_W = 0x57

# Phím số hàng trên
VK_0 = 0x30
VK_1 = 0x31
VK_2 = 0x32
VK_3 = 0x33
VK_4 = 0x34
VK_5 = 0x35

# Phím chức năng
VK_F1 = 0x70
VK_F2 = 0x71
VK_F3 = 0x72
VK_F4 = 0x73
VK_F5 = 0x74
VK_F6 = 0x75
VK_F7 = 0x76
VK_F8 = 0x77  # Toggle Autonomous Mode
VK_F9 = 0x78  # Telemetry Capture
VK_F10 = 0x79 # Tactical Brain
VK_F11 = 0x7A
VK_F12 = 0x7B

# Dấu chấm (Town Portal trong POE2) và dấu gạch chéo (Chợ Currency Exchange)
VK_OEM_PERIOD = 0xBE  # '.' Town Portal
VK_OEM_2 = 0xBF       # '/' Currency Exchange

# Scan codes phần cứng (Hardware Scan Codes)
SC_ESCAPE = 0x01
SC_1 = 0x02
SC_2 = 0x03
SC_3 = 0x04
SC_4 = 0x05
SC_5 = 0x06
SC_Q = 0x10
SC_W = 0x11
SC_E = 0x12
SC_R = 0x13
SC_T = 0x14
SC_A = 0x1E
SC_S = 0x1F
SC_D = 0x20
SC_F = 0x21
SC_I = 0x17
SC_SPACE = 0x39
SC_CONTROL = 0x1D
SC_SHIFT = 0x2A

# Hằng số sự kiện Win32
MOUSEEVENTF_MOVE = 0x0001
MOUSEEVENTF_LEFTDOWN = 0x0002
MOUSEEVENTF_LEFTUP = 0x0004
MOUSEEVENTF_RIGHTDOWN = 0x0008
MOUSEEVENTF_RIGHTUP = 0x0010
MOUSEEVENTF_MIDDLEDOWN = 0x0020
MOUSEEVENTF_MIDDLEUP = 0x0040
MOUSEEVENTF_WHEEL = 0x0800
MOUSEEVENTF_ABSOLUTE = 0x8000

KEYEVENTF_EXTENDEDKEY = 0x0001
KEYEVENTF_KEYUP = 0x0002
KEYEVENTF_SCANCODE = 0x0008

# Hằng số thông điệp Win32 Message Queue (PostMessage / SendMessage)
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_MOUSEMOVE = 0x0200
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP = 0x0208
MK_LBUTTON = 0x0001
MK_RBUTTON = 0x0002
MK_SHIFT = 0x0004
MK_CONTROL = 0x0008
MK_MBUTTON = 0x0010

_user32 = ctypes.windll.user32


class KEYBDINPUT(ctypes.Structure):
    _fields_ = [
        ("wVk", wintypes.WORD),
        ("wScan", wintypes.WORD),
        ("dwFlags", wintypes.DWORD),
        ("time", wintypes.DWORD),
        ("dwExtraInfo", ctypes.c_ulonglong if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_ulong),
    ]


class MOUSEINPUT(ctypes.Structure):
    _fields_ = [
        ("dx", wintypes.LONG),
        ("dy", wintypes.LONG),
        ("mouseData", wintypes.DWORD),
        ("dwFlags", wintypes.DWORD),
        ("time", wintypes.DWORD),
        ("dwExtraInfo", ctypes.c_ulonglong if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_ulong),
    ]


class HARDWAREINPUT(ctypes.Structure):
    _fields_ = [
        ("uMsg", wintypes.DWORD),
        ("wParamL", wintypes.WORD),
        ("wParamH", wintypes.WORD),
    ]


class INPUT_UNION(ctypes.Union):
    _fields_ = [
        ("mi", MOUSEINPUT),
        ("ki", KEYBDINPUT),
        ("hi", HARDWAREINPUT),
    ]


class INPUT(ctypes.Structure):
    _fields_ = [
        ("type", wintypes.DWORD),
        ("_pad", wintypes.DWORD) if ctypes.sizeof(ctypes.c_void_p) == 8 else ("type", wintypes.DWORD),
        ("i", INPUT_UNION),
    ] if ctypes.sizeof(ctypes.c_void_p) == 8 else [
        ("type", wintypes.DWORD),
        ("i", INPUT_UNION),
    ]


INPUT_KEYBOARD = 1

if os.name == "nt":
    _user32.SendInput.argtypes = [wintypes.UINT, ctypes.c_void_p, ctypes.c_int]
    _user32.SendInput.restype = wintypes.UINT


def _send_input_keys(
    keys: Sequence[Tuple[int, int]],
    is_up: bool = False,
    is_simulated: bool = False,
    hwnd: Optional[int] = None,
) -> None:
    if not keys or os.name != "nt":
        return
    try:
        from src.common.win32_window import attach_to_default_desktop
        attach_to_default_desktop()
    except Exception:
        pass
    # Khóa an toàn INV-FOCUS-INTERLOCK-HARDWARE-SAFETY:
    # KeyUp (is_up=True) luôn được phép gửi để giải phóng phần cứng chống kẹt phím.
    # KeyDown (is_up=False) BẮT BUỘC phải thỏa mãn _is_physical_allowed, tuyệt đối cấm gửi khi unfocused hoặc simulation!
    if not is_up and not _is_physical_allowed(is_simulated, hwnd=hwnd):
        return
    try:
        n = len(keys)
        # Bơm phím đồng thời Dual-Stream:
        # Event 1: Hardware Scan Code Event (KEYEVENTF_SCANCODE) cho DirectX 12 RawInput
        # Event 2: Virtual Key Event cho POE2 KeyMap (move_up=87, move_left=65) & GetAsyncKeyState
        input_arr = (INPUT * (n * 2))()
        for i, (vk, sc) in enumerate(keys):
            # Scan Code stream (DirectX 12 RawInput)
            sc_flags = KEYEVENTF_SCANCODE | (KEYEVENTF_KEYUP if is_up else 0)
            input_arr[i * 2].type = INPUT_KEYBOARD
            input_arr[i * 2].i.ki.wVk = 0
            input_arr[i * 2].i.ki.wScan = sc
            input_arr[i * 2].i.ki.dwFlags = sc_flags
            input_arr[i * 2].i.ki.time = 0

            # Virtual Key stream (Action Keys / GetAsyncKeyState)
            vk_flags = KEYEVENTF_KEYUP if is_up else 0
            input_arr[i * 2 + 1].type = INPUT_KEYBOARD
            input_arr[i * 2 + 1].i.ki.wVk = vk
            input_arr[i * 2 + 1].i.ki.wScan = sc
            input_arr[i * 2 + 1].i.ki.dwFlags = vk_flags
            input_arr[i * 2 + 1].i.ki.time = 0

        ret = _user32.SendInput(n * 2, input_arr, ctypes.sizeof(INPUT))
        if ret == 0:
            # Fallback sang keybd_event nếu SendInput bị chặn do UIPI
            for vk, sc in keys:
                flags_vk = KEYEVENTF_KEYUP if is_up else 0
                _user32.keybd_event(vk, sc, flags_vk, 0)
    except Exception:
        pass


class POINT(ctypes.Structure):
    _fields_ = [("x", wintypes.LONG), ("y", wintypes.LONG)]


def get_cursor_pos() -> Tuple[int, int]:
    """Lấy tọa độ con trỏ chuột hiện tại trên màn hình desktop."""
    pt = POINT()
    _user32.GetCursorPos(ctypes.byref(pt))
    return int(pt.x), int(pt.y)


def set_cursor_pos(
    x: int,
    y: int,
    is_simulated: bool = False,
    hwnd: Optional[int] = None,
) -> bool:
    """Đặt con trỏ chuột vào vị trí pixel tuyệt đối (x, y).
    
    Khóa an toàn: Từ chối di chuyển chuột nếu game không có Foreground Focus
    để không cướp chuột của người dùng đang làm việc (0 pixel displacement).
    """
    if not _is_physical_allowed(is_simulated, hwnd=hwnd):
        return False
    if os.name == "nt":
        try:
            from src.common.win32_window import attach_to_default_desktop
            attach_to_default_desktop()
        except Exception:
            pass
    return bool(_user32.SetCursorPos(int(round(x)), int(round(y))))


def hover(
    x: int,
    y: int,
    delay_after: float = 0.05,
    is_simulated: bool = False,
    hwnd: Optional[int] = None,
) -> bool:
    """Di chuyển chuột tới pixel tuyệt đối mà KHÔNG bấm phím nào (INV-WASD-NO-BLIND-LMB)."""
    if not _is_focus_allowed(is_simulated, hwnd=hwnd):
        return False
    if not _is_physical_allowed(is_simulated, hwnd=hwnd):
        if is_simulated or _GLOBAL_SIMULATED_FOCUS is not None:
            return True
        return False
    set_cursor_pos(x, y, is_simulated=is_simulated, hwnd=hwnd)
    if delay_after > 0:
        time.sleep(delay_after)
    return True


def click(
    x: int,
    y: int,
    button: str = "left",
    hold_ms: float = 0.05,
    delay_after: float = 0.1,
    is_simulated: bool = False,
    hwnd: Optional[int] = None,
) -> bool:
    """Di chuyển chuột tới (x, y) và thực hiện click có chủ đích.
    
    Bảo vệ Focus Interlock: Từ chối click nếu game không giữ Foreground focus.
    
    @param x: Tọa độ màn hình X
    @param y: Tọa độ màn hình Y
    @param button: 'left', 'right', hoặc 'middle'
    @param hold_ms: Thời gian giữ chuột tính theo giây (mặc định 50ms)
    @param delay_after: Thời gian chờ sau khi nhả chuột (mặc định 100ms)
    @param is_simulated: Bỏ qua kiểm tra focus cho môi trường kiểm thử
    @return: True nếu click thành công, False nếu bị chặn do mất focus.
    """
    if not _is_focus_allowed(is_simulated, hwnd=hwnd):
        return False
    if not _is_physical_allowed(is_simulated, hwnd=hwnd):
        if is_simulated or _GLOBAL_SIMULATED_FOCUS is not None:
            return True
        return False

    set_cursor_pos(x, y, is_simulated=is_simulated, hwnd=hwnd)
    time.sleep(0.04)

    if button == "left":
        down_flag, up_flag = MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP
    elif button == "right":
        down_flag, up_flag = MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP
    elif button == "middle":
        down_flag, up_flag = MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP
    else:
        down_flag, up_flag = MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP

    _user32.mouse_event(down_flag, 0, 0, 0, 0)
    if hold_ms > 0:
        time.sleep(hold_ms)
    _user32.mouse_event(up_flag, 0, 0, 0, 0)

    if delay_after > 0:
        time.sleep(delay_after)
    return True


def drag(
    start_x: int,
    start_y: int,
    end_x: int,
    end_y: int,
    steps: int = 20,
    duration: float = 0.4,
    button: str = "left",
    is_simulated: bool = False,
    hwnd: Optional[int] = None,
) -> bool:
    """Kéo chuột từ (start_x, start_y) đến (end_x, end_y) với nội suy mượt.
    
    Bảo vệ Focus Interlock: Từ chối drag nếu game không giữ Foreground focus.
    Phục vụ kéo cuộn bản đồ Atlas (Atlas Panning) hoặc di chuyển vật phẩm.
    """
    if not _is_focus_allowed(is_simulated, hwnd=hwnd):
        return False
    if not _is_physical_allowed(is_simulated, hwnd=hwnd):
        if is_simulated or _GLOBAL_SIMULATED_FOCUS is not None:
            return True
        return False

    if button == "left":
        down_flag, up_flag = MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP
    elif button == "right":
        down_flag, up_flag = MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP
    elif button == "middle":
        down_flag, up_flag = MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP
    else:
        down_flag, up_flag = MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP

    set_cursor_pos(start_x, start_y)
    time.sleep(0.08)
    _user32.mouse_event(down_flag, 0, 0, 0, 0)
    time.sleep(0.06)

    cur_x, cur_y = start_x, start_y
    step_interval = max(0.005, duration / max(1, steps))
    for i in range(1, steps + 1):
        if not _is_physical_allowed(is_simulated, hwnd=hwnd):
            _user32.mouse_event(up_flag, 0, 0, 0, 0)
            return False
        t = i / float(steps)
        cx = int(round(start_x + (end_x - start_x) * t))
        cy = int(round(start_y + (end_y - start_y) * t))
        dx = cx - cur_x
        dy = cy - cur_y
        cur_x, cur_y = cx, cy
        if dx != 0 or dy != 0:
            _user32.mouse_event(MOUSEEVENTF_MOVE, dx, dy, 0, 0)
        set_cursor_pos(cx, cy, is_simulated=is_simulated, hwnd=hwnd)
        time.sleep(step_interval)

    time.sleep(0.06)
    _user32.mouse_event(up_flag, 0, 0, 0, 0)
    time.sleep(0.1)
    return True


def emergency_release_wasd_keys(is_simulated: bool = False, hwnd: Optional[int] = None) -> None:
    """Cưỡng chế nhả toàn bộ phím WASD và Modifier để ngăn chặn trôi dạt nhân vật khi mất focus (INV-FOCUS-INTERLOCK-HARDWARE-SAFETY)."""
    if os.name == "nt":
        try:
            from src.common.win32_window import attach_to_default_desktop
            attach_to_default_desktop()
        except Exception:
            pass
    wasd_keys = [
        (VK_W, SC_W), (VK_A, SC_A), (VK_S, SC_S), (VK_D, SC_D),
        (VK_CONTROL, SC_CONTROL), (VK_SHIFT, SC_SHIFT), (VK_MENU, 0x38), (VK_SPACE, SC_SPACE),
    ]
    if _is_physical_allowed(is_simulated=is_simulated, hwnd=hwnd):
        _send_input_keys(wasd_keys, is_up=True, is_simulated=is_simulated, hwnd=hwnd)
        for vk, sc in wasd_keys:
            try:
                _user32.keybd_event(vk, sc, KEYEVENTF_KEYUP, 0)
                _user32.keybd_event(0, sc, KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP, 0)
            except Exception:
                pass
    if hwnd and _user32.IsWindow(hwnd):
        for vk, sc in wasd_keys:
            try:
                _user32.PostMessageW(hwnd, WM_KEYUP, vk, (sc << 16) | 0xC0000001)
            except Exception:
                pass


def ctrl_click(
    x: int,
    y: int,
    hold_ms: float = 0.05,
    delay_after: float = 0.1,
    is_simulated: bool = False,
    hwnd: Optional[int] = None,
) -> bool:
    """Thực hiện Ctrl+Click (thao tác nhanh trong túi đồ / hòm đồ / Map Device).
    
    Bảo vệ Focus Interlock: Từ chối nếu game không giữ Foreground focus.
    """
    if not _is_focus_allowed(is_simulated, hwnd=hwnd):
        return False
    if hwnd is None and not is_simulated:
        from src.common.win32_window import find_poe2_window
        hwnd = find_poe2_window()
    allow_physical = _is_physical_allowed(is_simulated, hwnd=hwnd)
    if allow_physical:
        _user32.keybd_event(VK_CONTROL, SC_CONTROL, 0, 0)
        time.sleep(0.04)
    res = click(x, y, button="left", hold_ms=hold_ms, delay_after=0.06, is_simulated=is_simulated, hwnd=hwnd)
    if allow_physical:
        _user32.keybd_event(VK_CONTROL, SC_CONTROL, KEYEVENTF_KEYUP, 0)
    if hwnd:
        from src.common.win32_window import get_client_rect
        cl, ct, _, _ = get_client_rect(hwnd)
        post_message_ctrl_click(hwnd, x - cl, y - ct, hold_ms=hold_ms, delay_after=0.04)
    if delay_after > 0:
        time.sleep(delay_after)
    return res


def press_key(
    vk: int,
    scan: int = 0,
    hold_ms: float = 0.05,
    delay_after: float = 0.1,
    is_simulated: bool = False,
    hwnd: Optional[int] = None,
) -> bool:
    """Nhấn và nhả một phím Win32 với dwell time an toàn.
    
    Bảo vệ Focus Interlock: Từ chối bơm phím nếu game không giữ Foreground focus.
    """
    if not _is_focus_allowed(is_simulated, hwnd=hwnd):
        return False
    if hwnd is None and not is_simulated:
        from src.common.win32_window import find_poe2_window
        hwnd = find_poe2_window()
    allow_physical = _is_physical_allowed(is_simulated, hwnd=hwnd)
    actual_scan = scan if scan > 0 else int(_user32.MapVirtualKeyW(vk, 0))
    if allow_physical:
        _send_input_keys([(vk, actual_scan)], is_up=False, is_simulated=is_simulated, hwnd=hwnd)
        _user32.keybd_event(vk, actual_scan, 0, 0)
    if hwnd:
        _user32.PostMessageW(hwnd, WM_KEYDOWN, vk, (actual_scan << 16) | 1)
    if hold_ms > 0:
        time.sleep(hold_ms)
    if allow_physical:
        _send_input_keys([(vk, actual_scan)], is_up=True, is_simulated=is_simulated, hwnd=hwnd)
        _user32.keybd_event(vk, actual_scan, KEYEVENTF_KEYUP, 0)
    if hwnd:
        _user32.PostMessageW(hwnd, WM_KEYUP, vk, (actual_scan << 16) | 0xC0000001)
    if delay_after > 0:
        time.sleep(delay_after)
    return True


def dismiss_modals(
    count_or_img: Union[int, str, Any, None] = 1,
    delay_after: float = 0.2,
    is_simulated: bool = False,
    img: Optional[Union[str, Any]] = None,
) -> None:
    """Đóng các bảng giao diện (modals/panels) bằng phím Spacebar / ESC chuẩn người chơi POE2.

    Tích hợp Fast-Path is_center_modal_present (< 0.1ms):
    Nếu phát hiện modal dialog / menu che trung tâm màn hình, gửi ESC để giải phóng tức thời.
    Sau đó gửi phím Spacebar để đóng các panel còn tồn đọng.
    """
    if isinstance(count_or_img, (str, Image.Image if Image is not None else ())) or (
        not isinstance(count_or_img, int) and count_or_img is not None
    ):
        target_img = count_or_img
        count = 1
    else:
        target_img = img
        count = count_or_img if isinstance(count_or_img, int) else 1

    if target_img is not None:
        try:
            from src.common.vision_ocr import is_center_modal_present

            if is_center_modal_present(target_img):
                press_key(VK_ESCAPE, SC_ESCAPE, hold_ms=0.04, delay_after=0.15, is_simulated=is_simulated)
        except Exception:
            pass

    for _ in range(count):
        press_key(VK_SPACE, SC_SPACE, hold_ms=0.04, delay_after=0.08, is_simulated=is_simulated)
    if delay_after > 0:
        time.sleep(delay_after)


def send_wasd_direction(
    dx: float,
    dy: float,
    dwell_ms: float = 0.35,
    threshold: float = 0.38,
    is_simulated: bool = False,
    hwnd: Optional[int] = None,
) -> bool:
    """Gửi phím di chuyển 8 hướng WASD Isometric đồng thời (True simultaneous keydown).
    
    Tuân thủ INV-WASD-MIN-DWELL: Dwell tối thiểu >= 300ms, cấm micro-tap < 150ms.
    Chiếu vectơ chênh lệch thế giới sang góc nhìn Isometric 45 độ.
    
    Bảo vệ Focus Interlock: Nếu cửa sổ POE2 không có focus, TỪ CHỐI BƠM PHÍM và cưỡng chế
    nhả toàn bộ phím WASD để ngăn chặn trôi dạt nhân vật và không gõ phím ra desktop.
    
    @return: True nếu di chuyển thành công, False nếu bị chặn do mất focus.
    """
    if os.name == "nt":
        try:
            from src.common.win32_window import attach_to_default_desktop
            attach_to_default_desktop()
        except Exception:
            pass

    if not _is_focus_allowed(is_simulated, hwnd=hwnd):
        emergency_release_wasd_keys(is_simulated=is_simulated, hwnd=hwnd)
        return False

    if hwnd is None and not is_simulated:
        from src.common.win32_window import find_poe2_window
        hwnd = find_poe2_window()

    dist = math.sqrt(dx * dx + dy * dy)
    if dist < 0.001:
        return True

    ndx = dx / dist
    ndy = dy / dist
    inv_sqrt2 = 0.70710678
    screen_x = (ndx - ndy) * inv_sqrt2
    screen_y = (ndx + ndy) * inv_sqrt2

    keys = []
    if screen_y > threshold:
        keys.append((VK_W, SC_W))
    elif screen_y < -threshold:
        keys.append((VK_S, SC_S))

    if screen_x > threshold:
        keys.append((VK_D, SC_D))
    elif screen_x < -threshold:
        keys.append((VK_A, SC_A))

    allow_physical = _is_physical_allowed(is_simulated, hwnd=hwnd)
    success = True
    try:
        # Khởi đầu chu kỳ di chuyển: Dual-stream SendInput (Scan Code + Virtual Key) kết hợp keybd_event kép
        actual_dwell = max(0.30, float(dwell_ms))
        if allow_physical:
            _send_input_keys(keys, is_up=False, is_simulated=is_simulated, hwnd=hwnd)
            for vk, sc in keys:
                _user32.keybd_event(vk, sc, 0, 0)
                _user32.keybd_event(0, sc, KEYEVENTF_SCANCODE, 0)

        if hwnd and _user32.IsWindow(hwnd):
            for vk, sc in keys:
                _user32.PostMessageW(hwnd, WM_KEYDOWN, vk, (sc << 16) | 1)

        # Cơ chế True Continuous Single-Hold (INV-WASD-MIN-DWELL >= 300ms):
        # Trạng thái phím đè liên tục không ngắt quãng (Uninterrupted Key Pressed State)
        # để bộ tích phân gia tốc (velocity integrator) của POE2 DirectX 12 cho phép nhân vật bước chạy thực sự.
        # Vòng lặp giám sát focus liên tục mỗi 15-20ms; xả phím khẩn cấp nếu mất focus.
        t_end = time.time() + actual_dwell
        last_pm_time = time.time()
        while time.time() < t_end:
            remaining = t_end - time.time()
            if remaining <= 0:
                break
            if not is_poe2_window_focused(hwnd=hwnd):
                # Game thực sự mất focus sang ứng dụng khác -> khẩn cấp xả phím và dừng
                emergency_release_wasd_keys(is_simulated=is_simulated, hwnd=hwnd)
                allow_physical = False
                success = True
                break

            now = time.time()
            if hwnd and _user32.IsWindow(hwnd) and (now - last_pm_time >= 0.045):
                for vk, sc in keys:
                    _user32.PostMessageW(hwnd, WM_KEYDOWN, vk, (sc << 16) | 1)
                last_pm_time = now

            time.sleep(min(0.02, remaining))
    finally:
        # Bảo hiểm tuyệt đối: Nhả sạch toàn bộ phím phòng ngừa (Hardware Safety Guarantee)
        if allow_physical:
            _send_input_keys(keys, is_up=True, is_simulated=is_simulated, hwnd=hwnd)
            for vk, sc in keys:
                try:
                    _user32.keybd_event(vk, sc, KEYEVENTF_KEYUP, 0)
                    _user32.keybd_event(0, sc, KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP, 0)
                except Exception:
                    pass
        if hwnd and _user32.IsWindow(hwnd):
            for vk, sc in keys:
                try:
                    _user32.PostMessageW(hwnd, WM_KEYUP, vk, (sc << 16) | 0xC0000001)
                except Exception:
                    pass

    time.sleep(0.04)
    return success


def mouse_wheel(delta_clicks: int, delay_after: float = 0.05) -> None:
    """Cuộn chuột giữa để phóng to/thu nhỏ camera (1 click = 120 units)."""
    wheel_amount = delta_clicks * 120
    _user32.mouse_event(MOUSEEVENTF_WHEEL, 0, 0, wheel_amount, 0)
    if delay_after > 0:
        time.sleep(delay_after)


def emergency_f8_pause_and_release_all() -> None:
    """[INV-F8-MUTES-BRAIN]
    Kích hoạt F8 tạm dừng toàn bộ hệ thống tự hành và cưỡng chế nhả tất cả các phím W, A, S, D, Space, Ctrl, LMB, RMB.
    """
    # 1. Nhả toàn bộ phím WASD, modifier và hotkeys
    for vk, sc in [
        (VK_W, SC_W),
        (VK_A, SC_A),
        (VK_S, SC_S),
        (VK_D, SC_D),
        (VK_CONTROL, SC_CONTROL),
        (VK_SPACE, SC_SPACE),
    ]:
        try:
            _user32.keybd_event(vk, sc, KEYEVENTF_KEYUP, 0)
            _user32.keybd_event(0, sc, KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP, 0)
        except Exception:
            pass
    # 2. Nhả chuột
    _user32.mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
    _user32.mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0)
    # 3. Gửi phím F8 (Scan code 0x42)
    press_key(VK_F8, 0x42, hold_ms=0.08, delay_after=0.1)


# ==========================================================
# CÁC HÀM TƯƠNG TÁC NỀN KHÔNG CHIẾM CHUỘT (NON-HIJACKING BACKGROUND INPUT)
# ==========================================================

def client_to_screen_pos(hwnd: int, client_x: int, client_y: int) -> Tuple[int, int]:
    """Chuyển đổi tọa độ từ Client Area sang tọa độ Desktop tuyệt đối (cho Physical Mouse)."""
    if not hwnd or not _user32.IsWindow(hwnd):
        return int(round(client_x)), int(round(client_y))
    pt = POINT(int(round(client_x)), int(round(client_y)))
    if _user32.ClientToScreen(hwnd, ctypes.byref(pt)):
        return int(pt.x), int(pt.y)
    return int(round(client_x)), int(round(client_y))


def post_message_hover(
    hwnd: int,
    client_x: int,
    client_y: int,
    delay_after: float = 0.04,
) -> bool:
    """Di chuyển chuột ảo ngầm tới tọa độ Client mà KHÔNG di chuyển con trỏ chuột thật."""
    if not hwnd or not _user32.IsWindow(hwnd):
        return False
    lParam = ((int(round(client_y)) & 0xFFFF) << 16) | (int(round(client_x)) & 0xFFFF)
    _user32.PostMessageW(hwnd, WM_MOUSEMOVE, 0, lParam)
    if delay_after > 0:
        time.sleep(delay_after)
    return True


def post_message_click(
    hwnd: int,
    client_x: int,
    client_y: int,
    button: str = "left",
    hold_ms: float = 0.04,
    delay_after: float = 0.06,
) -> bool:
    """Gửi click chuột ảo ngầm trực tiếp tới HWND cửa sổ game không cướp chuột người dùng."""
    if not hwnd or not _user32.IsWindow(hwnd):
        return False
    lParam = ((int(round(client_y)) & 0xFFFF) << 16) | (int(round(client_x)) & 0xFFFF)
    _user32.PostMessageW(hwnd, WM_MOUSEMOVE, 0, lParam)
    time.sleep(0.02)

    if button == "left":
        down_msg, up_msg, mk_btn = WM_LBUTTONDOWN, WM_LBUTTONUP, MK_LBUTTON
    elif button == "right":
        down_msg, up_msg, mk_btn = WM_RBUTTONDOWN, WM_RBUTTONUP, MK_RBUTTON
    elif button == "middle":
        down_msg, up_msg, mk_btn = WM_MBUTTONDOWN, WM_MBUTTONUP, MK_MBUTTON
    else:
        down_msg, up_msg, mk_btn = WM_LBUTTONDOWN, WM_LBUTTONUP, MK_LBUTTON

    _user32.PostMessageW(hwnd, down_msg, mk_btn, lParam)
    if hold_ms > 0:
        time.sleep(hold_ms)
    _user32.PostMessageW(hwnd, up_msg, 0, lParam)

    if delay_after > 0:
        time.sleep(delay_after)
    return True


def post_message_ctrl_click(
    hwnd: int,
    client_x: int,
    client_y: int,
    hold_ms: float = 0.04,
    delay_after: float = 0.06,
) -> bool:
    """Gửi Ctrl+Click ảo ngầm tới HWND game (thao tác nhanh cất/rút đồ Stash & Inventory).
    
    Tự động kích hoạt trạng thái phím VK_CONTROL qua keybd_event trong thời gian cực ngắn (~40ms)
    để đảm bảo GetAsyncKeyState của game client nhận diện được Quick Move mà không dịch chuyển con trỏ chuột.
    """
    if not hwnd or not _user32.IsWindow(hwnd):
        return False
    lParam = ((int(round(client_y)) & 0xFFFF) << 16) | (int(round(client_x)) & 0xFFFF)

    # 1. Kích hoạt trạng thái phím VK_CONTROL (chỉ dùng keybd_event nếu game đang có Focus để không ảnh hưởng người dùng)
    need_physical_ctrl = is_poe2_window_focused(hwnd=hwnd)
    if need_physical_ctrl:
        _user32.keybd_event(VK_CONTROL, SC_CONTROL, 0, 0)
    _user32.PostMessageW(hwnd, WM_KEYDOWN, VK_CONTROL, (SC_CONTROL << 16) | 1)
    time.sleep(0.02)

    try:
        # 2. Gửi WM_MOUSEMOVE kèm cờ MK_CONTROL
        _user32.PostMessageW(hwnd, WM_MOUSEMOVE, MK_CONTROL, lParam)
        time.sleep(0.02)

        # 3. Gửi WM_LBUTTONDOWN kèm cờ MK_LBUTTON | MK_CONTROL
        _user32.PostMessageW(hwnd, WM_LBUTTONDOWN, MK_LBUTTON | MK_CONTROL, lParam)
        if hold_ms > 0:
            time.sleep(hold_ms)
        _user32.PostMessageW(hwnd, WM_LBUTTONUP, MK_CONTROL, lParam)
        time.sleep(0.02)
    finally:
        # 4. Nhả phím VK_CONTROL tuyệt đối an toàn
        if need_physical_ctrl:
            _user32.keybd_event(VK_CONTROL, SC_CONTROL, KEYEVENTF_KEYUP, 0)
        _user32.PostMessageW(hwnd, WM_KEYUP, VK_CONTROL, (SC_CONTROL << 16) | 0xC0000001)

    if delay_after > 0:
        time.sleep(delay_after)
    return True


def post_message_drag(
    hwnd: int,
    start_x: int,
    start_y: int,
    end_x: int,
    end_y: int,
    steps: int = 15,
    duration: float = 0.3,
    button: str = "left",
) -> bool:
    """Gửi drag chuột ảo ngầm tới HWND game qua PostMessageW không chiếm chuột thật."""
    if not hwnd or not _user32.IsWindow(hwnd):
        return False
    down_msg = WM_LBUTTONDOWN if button == "left" else WM_RBUTTONDOWN
    up_msg = WM_LBUTTONUP if button == "left" else WM_RBUTTONUP
    mk_btn = MK_LBUTTON if button == "left" else MK_RBUTTON

    start_lparam = ((int(round(start_y)) & 0xFFFF) << 16) | (int(round(start_x)) & 0xFFFF)
    _user32.PostMessageW(hwnd, WM_MOUSEMOVE, 0, start_lparam)
    time.sleep(0.02)
    _user32.PostMessageW(hwnd, down_msg, mk_btn, start_lparam)
    time.sleep(0.02)

    actual_steps = max(2, steps)
    step_delay = max(0.005, duration / actual_steps)
    for i in range(1, actual_steps + 1):
        alpha = i / float(actual_steps)
        curr_x = int(round(start_x + (end_x - start_x) * alpha))
        curr_y = int(round(start_y + (end_y - start_y) * alpha))
        move_lparam = ((curr_y & 0xFFFF) << 16) | (curr_x & 0xFFFF)
        _user32.PostMessageW(hwnd, WM_MOUSEMOVE, mk_btn, move_lparam)
        time.sleep(step_delay)

    end_lparam = ((int(round(end_y)) & 0xFFFF) << 16) | (int(round(end_x)) & 0xFFFF)
    _user32.PostMessageW(hwnd, up_msg, 0, end_lparam)
    time.sleep(0.04)
    return True


def post_message_key(
    hwnd: int,
    vk_code: int,
    scan_code: int = 0,
    dwell_ms: float = 0.05,
    delay_after: float = 0.05,
) -> bool:
    """Gửi phím bấm ảo ngầm tới HWND game không làm gián đoạn người dùng."""
    if not hwnd or not _user32.IsWindow(hwnd):
        return False
    sc = scan_code or _user32.MapVirtualKeyW(vk_code, 0)
    down_lparam = (sc << 16) | 1
    up_lparam = (sc << 16) | 0xC0000001
    _user32.PostMessageW(hwnd, WM_KEYDOWN, vk_code, down_lparam)
    if dwell_ms > 0:
        time.sleep(dwell_ms)
    _user32.PostMessageW(hwnd, WM_KEYUP, vk_code, up_lparam)
    if delay_after > 0:
        time.sleep(delay_after)
    return True


