import ctypes
import platform
from dataclasses import dataclass
from enum import IntEnum
from typing import Callable, List, Tuple, Union, Dict, Optional

import numpy
from strenum import StrEnum  # For Python 3.9/3.10

MaaBool = ctypes.c_uint8
MaaSize = ctypes.c_size_t
MaaNullSize = MaaSize(-1)

MaaId = ctypes.c_int64
MaaCtrlId = MaaId
MaaResId = MaaId
MaaTaskId = MaaId
MaaRecoId = MaaId
MaaActId = MaaId
MaaNodeId = MaaId
MaaSinkId = MaaId
MaaInvalidId = MaaId(0)

MaaStringBufferHandle = ctypes.c_void_p
MaaImageBufferHandle = ctypes.c_void_p
MaaRectHandle = ctypes.c_void_p
MaaStringListBufferHandle = ctypes.c_void_p
MaaImageListBufferHandle = ctypes.c_void_p

MaaResourceHandle = ctypes.c_void_p
MaaControllerHandle = ctypes.c_void_p
MaaTaskerHandle = ctypes.c_void_p
MaaContextHandle = ctypes.c_void_p

MaaStatus = ctypes.c_int32


class MaaStatusEnum(IntEnum):
    invalid = 0
    pending = 1000
    running = 2000
    succeeded = 3000
    failed = 4000


MaaLoggingLevel = ctypes.c_int32


MaaOptionValueSize = ctypes.c_uint64
MaaOptionValue = ctypes.c_void_p

MaaOption = ctypes.c_int32
MaaGlobalOption = MaaOption
MaaCtrlOption = MaaOption
MaaResOption = MaaOption


class MaaGlobalOptionEnum(IntEnum):
    Invalid = 0

    # Log dir
    #
    # value: string, eg: "C:\\Users\\Administrator\\Desktop\\log"; val_size: string length
    LogDir = 1

    # Whether to save draw
    #
    # value: bool, eg: true; val_size: sizeof(bool)
    SaveDraw = 2

    # Deprecated
    # Dump all screenshots and actions
    #
    # Recording will evaluate to true if any of this or MaaCtrlOptionEnum::MaaCtrlOption_Recording
    # is true. value: bool, eg: true; val_size: sizeof(bool)
    # Recording = 3

    # The level of log output to stdout
    #
    # value, val_size: sizeof(MaaLoggingLevel)
    # default value is MaaLoggingLevel_Error
    StdoutLevel = 4

    # Whether to show hit draw
    #
    # value: bool, eg: true; val_size: sizeof(bool)
    ShowHitDraw = 5

    # Whether to debug
    #
    # value: bool, eg: true; val_size: sizeof(bool)
    DebugMode = 6

    # Whether to save screenshot on error
    #
    # value: bool, eg: true; val_size: sizeof(bool)
    SaveOnError = 7

    # Image quality for draw images
    #
    # value: int, eg: 85; val_size: sizeof(int)
    # default value is 85, range: [0, 100]
    DrawQuality = 8

    # Recognition image cache limit
    #
    # value: size_t, eg: 4096; val_size: sizeof(size_t)
    # default value is 4096
    RecoImageCacheLimit = 9


class MaaCtrlOptionEnum(IntEnum):
    Invalid = 0

    # Only one of long and short side can be set, and the other is automatically scaled according to the aspect ratio.
    # value: int, eg: 1920; val_size: sizeof(int)
    ScreenshotTargetLongSide = 1

    # Only one of long and short side can be set, and the other is automatically scaled according to the aspect ratio.
    # value: int, eg: 1080; val_size: sizeof(int)
    ScreenshotTargetShortSide = 2

    # Screenshot use raw size without scaling.
    # Please note that this option may cause incorrect coordinates on user devices with different resolutions if scaling is not performed.
    # value: bool, eg: true; val_size: sizeof(bool)
    ScreenshotUseRawSize = 3

    # Deprecated
    # Dump all screenshots and actions
    # this option will || with MaaGlobalOptionEnum.Recording
    # value: bool, eg: true; val_size: sizeof(bool)
    # Recording = 5


class MaaInferenceDeviceEnum(IntEnum):
    CPU = -2
    Auto = -1
    # and more gpu id or flag...


class MaaInferenceExecutionProviderEnum(IntEnum):
    # I don't recommend setting up MaaResOption_InferenceDevice in this case,
    # because you don't know which EP will be used on different user devices.
    Auto = 0

    # MaaResOption_InferenceDevice will not work.
    CPU = 1

    # MaaResOption_InferenceDevice will be used to set adapter id,
    # It's from Win32 API `EnumAdapters1`.
    DirectML = 2

    # MaaResOption_InferenceDevice will be used to set coreml_flag,
    # Reference to
    # https://github.com/microsoft/onnxruntime/blob/main/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h
    # But you need to pay attention to the onnxruntime version we use, the latest flag may not be supported.
    CoreML = 3

    # MaaResOption_InferenceDevice will be used to set NVIDIA GPU ID
    # TODO!
    CUDA = 4


class MaaResOptionEnum(IntEnum):
    Invalid = 0

    # Use the specified inference device.
    # Please set this option before loading the model.
    #
    # value: MaaInferenceDevice, eg: 0; val_size: sizeof(MaaInferenceDevice)
    # default value is MaaInferenceDevice_Auto
    InferenceDevice = 1

    # Use the specified inference execution provider
    # Please set this option before loading the model.
    #
    # value: MaaInferenceExecutionProvider, eg: 0; val_size: sizeof(MaaInferenceExecutionProvider)
    # default value is MaaInferenceExecutionProvider_Auto
    InferenceExecutionProvider = 2


MaaAdbScreencapMethod = ctypes.c_uint64


class MaaAdbScreencapMethodEnum(IntEnum):
    """
    Adb screencap method flags.

    Use bitwise OR to set the methods you need.
    MaaFramework will test all provided methods and use the fastest available one.

    Default: All methods except RawByNetcat, MinicapDirect, MinicapStream

    Note: MinicapDirect and MinicapStream use lossy JPEG encoding, which may
    significantly reduce template matching accuracy. Not recommended.

    | Method                | Speed      | Compatibility | Encoding | Notes                             |
    |-----------------------|------------|---------------|----------|-----------------------------------|
    | EncodeToFileAndPull   | Slow       | High          | Lossless |                                   |
    | Encode                | Slow       | High          | Lossless |                                   |
    | RawWithGzip           | Medium     | High          | Lossless |                                   |
    | RawByNetcat           | Fast       | Low           | Lossless |                                   |
    | MinicapDirect         | Fast       | Low           | Lossy    |                                   |
    | MinicapStream         | Very Fast  | Low           | Lossy    |                                   |
    | EmulatorExtras        | Very Fast  | Low           | Lossless | Emulators only: MuMu 12, LDPlayer 9 |
    """

    Null = 0

    EncodeToFileAndPull = 1
    Encode = 1 << 1
    RawWithGzip = 1 << 2
    RawByNetcat = 1 << 3
    MinicapDirect = 1 << 4
    MinicapStream = 1 << 5
    EmulatorExtras = 1 << 6

    All = ~Null
    Default = All & (~RawByNetcat) & (~MinicapDirect) & (~MinicapStream)


MaaAdbInputMethod = ctypes.c_uint64


class MaaAdbInputMethodEnum(IntEnum):
    """
    Adb input method flags.

    Use bitwise OR to set the methods you need.
    MaaFramework will select the first available method according to priority.

    Priority (high to low): EmulatorExtras > Maatouch > MinitouchAndAdbKey > AdbShell

    Default: All methods except EmulatorExtras

    | Method               | Speed | Compatibility | Notes                                 |
    |----------------------|-------|---------------|---------------------------------------|
    | AdbShell             | Slow  | High          |                                       |
    | MinitouchAndAdbKey   | Fast  | Medium        | Key press still uses AdbShell         |
    | Maatouch             | Fast  | Medium        |                                       |
    | EmulatorExtras       | Fast  | Low           | Emulators only: MuMu 12               |
    """

    Null = 0

    AdbShell = 1
    MinitouchAndAdbKey = 1 << 1
    Maatouch = 1 << 2
    EmulatorExtras = 1 << 3

    All = ~Null
    Default = All & (~EmulatorExtras)


MaaWin32ScreencapMethod = ctypes.c_uint64


class MaaWin32ScreencapMethodEnum(IntEnum):
    """
    Win32 screencap method.

    No bitwise OR, select ONE method only.

    No default value. Client should choose one as default.

    Different applications use different rendering methods, there is no universal solution.

    | Method                  | Speed     | Compatibility | Require Admin | Background Support | Notes                            |
    |-------------------------|-----------|---------------|---------------|--------------------|----------------------------------|
    | GDI                     | Fast      | Medium        | No            | No                 |                                  |
    | FramePool               | Very Fast | Medium        | No            | Yes                | Requires Windows 10 1903+        |
    | DXGI_DesktopDup         | Very Fast | Low           | No            | No                 | Desktop duplication (full screen)|
    | DXGI_DesktopDup_Window  | Very Fast | Low           | No            | No                 | Desktop duplication then crop    |
    | PrintWindow             | Medium    | Medium        | No            | Yes                |                                  |
    | ScreenDC                | Fast      | High          | No            | No                 |                                  |

    Note: When a window is minimized on Windows, all screencap methods will fail.
    Avoid minimizing the target window.
    """

    Null = 0

    GDI = 1
    FramePool = 1 << 1
    DXGI_DesktopDup = 1 << 2
    DXGI_DesktopDup_Window = 1 << 3
    PrintWindow = 1 << 4
    ScreenDC = 1 << 5


MaaWin32InputMethod = ctypes.c_uint64


class MaaWin32InputMethodEnum(IntEnum):
    """
    Win32 input method.

    No bitwise OR, select ONE method only.

    No default value. Client should choose one as default.

    Different applications process input differently, there is no universal solution.

    | Method                       | Compatibility | Require Admin | Seize Mouse | Background Support | Notes                                                       |
    |------------------------------|---------------|---------------|--------------|--------------------|-------------------------------------------------------------|
    | Seize                        | High          | No            | Yes          | No                 |                                                             |
    | SendMessage                  | Medium        | Maybe         | No           | Yes                |                                                             |
    | PostMessage                  | Medium        | Maybe         | No           | Yes                |                                                             |
    | LegacyEvent                  | Low           | No            | Yes          | No                 |                                                             |
    | PostThreadMessage            | Low           | Maybe         | No           | Yes                |                                                             |
    | SendMessageWithCursorPos     | Medium        | Maybe         | Briefly      | Yes                | Designed for apps that check real cursor position           |
    | PostMessageWithCursorPos     | Medium        | Maybe         | Briefly      | Yes                | Designed for apps that check real cursor position           |

    Note:
    - Admin rights mainly depend on the target application's privilege level.
      If the target runs as admin, MaaFramework should also run as admin for compatibility.
    - "WithCursorPos" methods briefly move the cursor to target position, send message,
      then restore cursor position. This "briefly" seizes the mouse but won't block user operations.
    """

    Null = 0

    Seize = 1
    SendMessage = 1 << 1
    PostMessage = 1 << 2
    LegacyEvent = 1 << 3
    PostThreadMessage = 1 << 4
    SendMessageWithCursorPos = 1 << 5
    PostMessageWithCursorPos = 1 << 6


# No bitwise OR, just set it
MaaDbgControllerType = ctypes.c_uint64

# No bitwise OR, just set it
MaaGamepadType = ctypes.c_uint64


class MaaGamepadTypeEnum(IntEnum):
    """
    Virtual gamepad type for GamepadController (Windows only).

    No bitwise OR, select ONE type only.

    Requires ViGEm Bus Driver to be installed.

    | Type        | Description                           |
    |-------------|---------------------------------------|
    | Xbox360     | Microsoft Xbox 360 Controller (wired) |
    | DualShock4  | Sony DualShock 4 Controller (wired)   |
    """

    Xbox360 = 0
    DualShock4 = 1


class MaaGamepadButtonEnum(IntEnum):
    """
    Gamepad button flags (XUSB protocol values).

    Use bitwise OR to combine multiple buttons.
    DS4 face buttons are aliases to Xbox face buttons.
    """

    # D-pad
    DPAD_UP = 0x0001
    DPAD_DOWN = 0x0002
    DPAD_LEFT = 0x0004
    DPAD_RIGHT = 0x0008

    # Control buttons
    START = 0x0010
    BACK = 0x0020
    LEFT_THUMB = 0x0040  # L3
    RIGHT_THUMB = 0x0080  # R3

    # Shoulder buttons
    LB = 0x0100  # Left Bumper / L1
    RB = 0x0200  # Right Bumper / R1

    # Guide button
    GUIDE = 0x0400

    # Face buttons (Xbox layout)
    A = 0x1000
    B = 0x2000
    X = 0x4000
    Y = 0x8000

    # DS4 face buttons (aliases to Xbox buttons)
    CROSS = A
    CIRCLE = B
    SQUARE = X
    TRIANGLE = Y
    L1 = LB
    R1 = RB
    L3 = LEFT_THUMB
    R3 = RIGHT_THUMB
    OPTIONS = START
    SHARE = BACK

    # DS4 special buttons (unique values)
    PS = 0x10000
    TOUCHPAD = 0x20000


class MaaGamepadContactEnum(IntEnum):
    """
    Gamepad contact (analog stick or trigger) mapping for touch_down/touch_move/touch_up.
    """

    LEFT_STICK = 0  # x: -32768~32767, y: -32768~32767
    RIGHT_STICK = 1  # x: -32768~32767, y: -32768~32767
    LEFT_TRIGGER = 2  # pressure: 0~255
    RIGHT_TRIGGER = 3  # pressure: 0~255


MaaControllerFeature = ctypes.c_uint64


# Use bitwise OR to set the features you need
class MaaControllerFeatureEnum(IntEnum):
    Null = 0

    UseMouseDownAndUpInsteadOfClick = 1
    UseKeyboardDownAndUpInsteadOfClick = 1 << 1


class MaaDbgControllerTypeEnum(IntEnum):
    Null = 0

    CarouselImage = 1
    ReplayRecording = 1 << 1


FUNCTYPE = ctypes.WINFUNCTYPE if (platform.system() == "Windows") else ctypes.CFUNCTYPE

MaaEventCallback = FUNCTYPE(
    None, ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p
)

MaaCustomRecognitionCallback = FUNCTYPE(
    MaaBool,  # return value
    MaaContextHandle,  # context
    MaaTaskId,  # task_id
    ctypes.c_char_p,  # node_name
    ctypes.c_char_p,  # custom_recognition_name
    ctypes.c_char_p,  # custom_recognition_param
    MaaImageBufferHandle,  # image
    MaaRectHandle,  # roi
    ctypes.c_void_p,  # trans_arg
    MaaRectHandle,  # [out] out_box
    MaaStringBufferHandle,  # [out] out_detail
)

MaaCustomActionCallback = FUNCTYPE(
    MaaBool,  # return value
    MaaContextHandle,  # context
    MaaTaskId,  # task_id
    ctypes.c_char_p,  # node_name
    ctypes.c_char_p,  # custom_action_name
    ctypes.c_char_p,  #
    MaaRecoId,  # reco_id
    MaaRectHandle,  # box
    ctypes.c_void_p,  # trans_arg
)


MaaToolkitAdbDeviceListHandle = ctypes.c_void_p
MaaToolkitAdbDeviceHandle = ctypes.c_void_p
MaaToolkitDesktopWindowListHandle = ctypes.c_void_p
MaaToolkitDesktopWindowHandle = ctypes.c_void_p

MaaAgentClientHandle = ctypes.c_void_p


class MaaCustomControllerCallbacks(ctypes.Structure):
    ConnectFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_void_p,
    )
    ConnectedFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_void_p,
    )
    RequestUuidFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_void_p,
        MaaStringBufferHandle,
    )
    GetFeaturesFunc = FUNCTYPE(
        MaaControllerFeature,
        ctypes.c_void_p,
    )
    StartAppFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_char_p,
        ctypes.c_void_p,
    )
    StopAppFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_char_p,
        ctypes.c_void_p,
    )
    ScreencapFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_void_p,
        MaaImageBufferHandle,
    )
    ClickFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_void_p,
    )
    SwipeFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_void_p,
    )
    TouchDownFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_void_p,
    )
    TouchMoveFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_void_p,
    )
    TouchUpFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_int32,
        ctypes.c_void_p,
    )
    ClickKeyFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_int32,
        ctypes.c_void_p,
    )
    InputTextFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_char_p,
        ctypes.c_void_p,
    )
    KeyDownFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_int32,
        ctypes.c_void_p,
    )
    KeyUpFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_int32,
        ctypes.c_void_p,
    )
    ScrollFunc = FUNCTYPE(
        MaaBool,
        ctypes.c_int32,
        ctypes.c_int32,
        ctypes.c_void_p,
    )
    _fields_ = [
        ("connect", ConnectFunc),
        ("connected", ConnectedFunc),
        ("request_uuid", RequestUuidFunc),
        ("get_features", GetFeaturesFunc),
        ("start_app", StartAppFunc),
        ("stop_app", StopAppFunc),
        ("screencap", ScreencapFunc),
        ("click", ClickFunc),
        ("swipe", SwipeFunc),
        ("touch_down", TouchDownFunc),
        ("touch_move", TouchMoveFunc),
        ("touch_up", TouchUpFunc),
        ("click_key", ClickKeyFunc),
        ("input_text", InputTextFunc),
        ("key_down", KeyDownFunc),
        ("key_up", KeyUpFunc),
        ("scroll", ScrollFunc),
    ]


class Status:
    _status: MaaStatusEnum

    def __init__(self, status: Union[MaaStatus, MaaStatusEnum, int]):
        if isinstance(status, MaaStatus):
            self._status = MaaStatusEnum(status.value)
        elif isinstance(status, MaaStatusEnum):
            self._status = status
        else:
            self._status = MaaStatusEnum(status)

    @property
    def done(self) -> bool:
        return self._status in [MaaStatusEnum.succeeded, MaaStatusEnum.failed]

    @property
    def succeeded(self) -> bool:
        return self._status == MaaStatusEnum.succeeded

    @property
    def failed(self) -> bool:
        return self._status == MaaStatusEnum.failed

    @property
    def pending(self) -> bool:
        return self._status == MaaStatusEnum.pending

    @property
    def running(self) -> bool:
        return self._status == MaaStatusEnum.running


@dataclass
class Point:
    x: int = 0
    y: int = 0

    def __add__(
        self,
        other: Union[
            "Point",
            Tuple[int, int],
            List[int],
        ],
    ):
        if (
            isinstance(other, Point)
            or isinstance(other, tuple)
            or (isinstance(other, list) and len(other) == 2)
        ):
            x1, y1 = self
            x2, y2 = other
            return Point(
                x1 + x2,
                y1 + y2,
            )

        raise TypeError(f"Cannot add {type(other).__name__} to Point")

    def __iter__(self):
        yield self.x
        yield self.y

    def __getitem__(self, key):
        return list(self)[key]


@dataclass
class Rect:
    x: int = 0
    y: int = 0
    w: int = 0
    h: int = 0

    def __add__(
        self,
        other: Union[
            "Rect",
            Tuple[int, int, int, int],
            List[int],
        ],
    ):
        if (
            isinstance(other, Rect)
            or isinstance(other, tuple)
            or (isinstance(other, list) and len(other) == 4)
        ):
            x1, y1, w1, h1 = self
            x2, y2, w2, h2 = other
            return Rect(
                x1 + x2,
                y1 + y2,
                w1 + w2,
                h1 + h2,
            )

        raise TypeError(f"Cannot add {type(other).__name__} to Rect")

    def __iter__(self):
        yield self.x
        yield self.y
        yield self.w
        yield self.h

    def __getitem__(self, key):
        return list(self)[key]


PointType = Union[
    Point,
    List[int],
    numpy.ndarray,
    Tuple[int, int],
]

RectType = Union[
    Rect,
    List[int],
    numpy.ndarray,
    Tuple[int, int, int, int],
]


class AlgorithmEnum(StrEnum):
    DirectHit = "DirectHit"
    TemplateMatch = "TemplateMatch"
    FeatureMatch = "FeatureMatch"
    ColorMatch = "ColorMatch"
    OCR = "OCR"
    NeuralNetworkClassify = "NeuralNetworkClassify"
    NeuralNetworkDetect = "NeuralNetworkDetect"
    And = "And"
    Or = "Or"
    Custom = "Custom"


class ActionEnum(StrEnum):
    DoNothing = "DoNothing"
    Click = "Click"
    LongPress = "LongPress"
    Swipe = "Swipe"
    MultiSwipe = "MultiSwipe"
    ClickKey = "ClickKey"
    LongPressKey = "LongPressKey"
    InputText = "InputText"
    StartApp = "StartApp"
    StopApp = "StopApp"
    Scroll = "Scroll"
    TouchDown = "TouchDown"
    TouchMove = "TouchMove"
    TouchUp = "TouchUp"
    KeyDown = "KeyDown"
    KeyUp = "KeyUp"
    StopTask = "StopTask"
    Command = "Command"
    Shell = "Shell"
    Custom = "Custom"


@dataclass
class BoxAndScoreResult:
    box: Rect
    score: float


TemplateMatchResult = BoxAndScoreResult


@dataclass
class BoxAndCountResult:
    box: Rect
    count: int


FeatureMatchResult = BoxAndCountResult
ColorMatchResult = BoxAndCountResult


@dataclass
class OCRResult(BoxAndScoreResult):
    text: str


@dataclass
class NeuralNetworkResult(BoxAndScoreResult):
    cls_index: int
    label: str
    box: Rect
    score: float


NeuralNetworkClassifyResult = NeuralNetworkResult
NeuralNetworkDetectResult = NeuralNetworkResult


@dataclass
class CustomRecognitionResult:
    box: Rect
    detail: Union[str, Dict]


@dataclass
class AndRecognitionResult:
    """And 算法识别结果，包含所有子识别的完整详情"""

    sub_results: List["RecognitionDetail"]


@dataclass
class OrRecognitionResult:
    """Or 算法识别结果，包含已执行子识别的完整详情"""

    sub_results: List["RecognitionDetail"]


RecognitionResult = Union[
    TemplateMatchResult,
    FeatureMatchResult,
    ColorMatchResult,
    OCRResult,
    NeuralNetworkClassifyResult,
    NeuralNetworkDetectResult,
    AndRecognitionResult,
    OrRecognitionResult,
    CustomRecognitionResult,
]

AlgorithmResultDict = {
    AlgorithmEnum.DirectHit: None,
    AlgorithmEnum.TemplateMatch: TemplateMatchResult,
    AlgorithmEnum.FeatureMatch: FeatureMatchResult,
    AlgorithmEnum.ColorMatch: ColorMatchResult,
    AlgorithmEnum.OCR: OCRResult,
    AlgorithmEnum.NeuralNetworkClassify: NeuralNetworkClassifyResult,
    AlgorithmEnum.NeuralNetworkDetect: NeuralNetworkDetectResult,
    AlgorithmEnum.And: AndRecognitionResult,
    AlgorithmEnum.Or: OrRecognitionResult,
    AlgorithmEnum.Custom: CustomRecognitionResult,
}


@dataclass
class RecognitionDetail:
    reco_id: int
    name: str
    algorithm: Union[AlgorithmEnum, str]
    hit: bool
    box: Optional[Rect]

    all_results: List[RecognitionResult]
    filtered_results: List[RecognitionResult]
    best_result: Optional[RecognitionResult]

    raw_detail: Dict
    raw_image: numpy.ndarray  # only valid in debug mode
    draw_images: List[numpy.ndarray]  # only valid in debug mode


@dataclass
class ClickActionResult:
    point: Point
    contact: int


@dataclass
class LongPressActionResult:
    point: Point
    duration: int
    contact: int


@dataclass
class SwipeActionResult:
    begin: Point
    end: List[Point]
    end_hold: List[int]
    duration: List[int]
    only_hover: bool
    starting: int
    contact: int


@dataclass
class MultiSwipeActionResult:
    swipes: List[SwipeActionResult]


@dataclass
class ClickKeyActionResult:
    keycode: List[int]


@dataclass
class LongPressKeyActionResult:
    keycode: List[int]
    duration: int


@dataclass
class InputTextActionResult:
    text: str


@dataclass
class AppActionResult:
    package: str


@dataclass
class ScrollActionResult:
    dx: int
    dy: int


@dataclass
class TouchActionResult:
    contact: int
    point: Point
    pressure: int


@dataclass
class ShellActionResult:
    cmd: str
    timeout: int
    success: bool
    output: str


ActionResult = Union[
    ClickActionResult,
    LongPressActionResult,
    SwipeActionResult,
    MultiSwipeActionResult,
    ClickKeyActionResult,
    LongPressKeyActionResult,
    InputTextActionResult,
    AppActionResult,
    ScrollActionResult,
    TouchActionResult,
    ShellActionResult,
    None,
]

ActionResultDict = {
    ActionEnum.DoNothing: None,
    ActionEnum.Click: ClickActionResult,
    ActionEnum.LongPress: LongPressActionResult,
    ActionEnum.Swipe: SwipeActionResult,
    ActionEnum.MultiSwipe: MultiSwipeActionResult,
    ActionEnum.ClickKey: ClickKeyActionResult,
    ActionEnum.LongPressKey: LongPressKeyActionResult,
    ActionEnum.InputText: InputTextActionResult,
    ActionEnum.StartApp: AppActionResult,
    ActionEnum.StopApp: AppActionResult,
    ActionEnum.Scroll: ScrollActionResult,
    ActionEnum.TouchDown: TouchActionResult,
    ActionEnum.TouchMove: TouchActionResult,
    ActionEnum.TouchUp: TouchActionResult,
    ActionEnum.KeyDown: ClickKeyActionResult,
    ActionEnum.KeyUp: ClickKeyActionResult,
    ActionEnum.StopTask: None,
    ActionEnum.Command: None,
    ActionEnum.Shell: ShellActionResult,
    ActionEnum.Custom: None,
}


@dataclass
class ActionDetail:
    action_id: int
    name: str
    action: Union[ActionEnum, str]
    box: Rect
    success: bool
    result: Optional[ActionResult]
    raw_detail: Dict


@dataclass
class NodeDetail:
    node_id: int
    name: str
    recognition: Optional[RecognitionDetail]
    action: Optional[ActionDetail]
    completed: bool


class TaskDetail:
    """任务详情 / Task detail

    nodes 属性为惰性加载，仅在首次访问时才通过 IPC 获取各节点详情并缓存结果。
    The nodes property is lazily loaded: node details are fetched via IPC
    only on the first access and cached thereafter.

    Attributes:
        task_id: 任务 ID / Task ID
        entry: 入口节点名 / Entry node name
        node_id_list: 节点 ID 列表（轻量，无 IPC 开销）/ Node ID list (lightweight, no IPC cost)
        status: 任务状态 / Task status
        nodes: 节点详情列表（惰性加载）/ Node detail list (lazily loaded)
    """

    __slots__ = (
        "task_id",
        "entry",
        "node_id_list",
        "status",
        "_node_detail_func",
        "_nodes",
    )

    def __init__(
        self,
        task_id: int,
        entry: str,
        node_id_list: List[int],
        status: "Status",
        node_detail_func: Optional[Callable[[int], Optional["NodeDetail"]]] = None,
    ):
        self.task_id = task_id
        self.entry = entry
        self.node_id_list = node_id_list
        self.status = status
        self._node_detail_func = node_detail_func
        self._nodes: Optional[List[NodeDetail]] = None

    @property
    def nodes(self) -> List[NodeDetail]:
        if self._nodes is None:
            if self._node_detail_func is not None:
                self._nodes = [self._node_detail_func(nid) for nid in self.node_id_list]
            else:
                self._nodes = []
        return self._nodes

    def __repr__(self) -> str:
        return (
            f"TaskDetail(task_id={self.task_id}, entry={self.entry!r}, "
            f"node_id_list={self.node_id_list}, status={self.status})"
        )


class LoggingLevelEnum(IntEnum):
    Off = 0
    Fatal = 1
    Error = 2
    Warn = 3
    Info = 4
    Debug = 5
    Trace = 6
    All = 7
