"""L2 Locator 抽象层基类：GeomSpec + RoiSpec + Selector + ElementHandle。

GeomSpec：几何定位规格（relative_to / ratio / offset）
RoiSpec：图像匹配感兴趣区域（相对窗口的比例矩形，缩小 OpenCV 搜索范围）
Selector：UI 元素选择器（kind / by_image / by_geom / roi / threshold / require_image）
ElementHandle：定位结果句柄（绝对坐标 + 置信度 + 策略）

L3 Actions 通过 Selector 描述要找的元素，LocatorRegistry 解析为
ElementHandle（图像命中或几何兜底），Actions 据此调 Backend 执行点击。
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Optional


@dataclass
class GeomSpec:
    """几何定位规格。

    relative_to 取值：window / window_top_left / window_bottom_right（语义占位，
    实际计算统一用 ratio + offset，offset 已含正负号区分方向）。

    所有字段带默认值，避免构造时必填字段顺序问题。
    """

    relative_to: str = "window"
    x_ratio: float = 0.0
    y_ratio: float = 0.0
    x_offset: int = 0
    y_offset: int = 0


@dataclass
class RoiSpec:
    """图像匹配感兴趣区域（ROI），相对窗口的比例矩形。

    用于将 OpenCV 模板匹配限制在窗口的某个子区域，避免误匹配到
    窗口内其他视觉相似的 UI 元素。例如 input_box 只在聊天区右下角查找，
    即使模板图像有缺陷也不会匹配到窗口中部。

    所有字段为相对窗口宽/高的比例（0.0~1.0），由 Actions 按当前窗口几何
    转换为绝对像素 ROI 传给 OpenCVBackend.find_template。

    字段：
        x_ratio: ROI 左上角相对窗口左上角的 X 比例
        y_ratio: ROI 左上角相对窗口左上角的 Y 比例
        w_ratio: ROI 宽度相对窗口宽度的比例
        h_ratio: ROI 高度相对窗口高度的比例

    边界由 Actions/opencv.py 做裁剪保护，YAML 中可略微宽松。
    """

    x_ratio: float = 0.0
    y_ratio: float = 0.0
    w_ratio: float = 1.0
    h_ratio: float = 1.0


@dataclass
class Selector:
    """UI 元素选择器。

    kind：元素类别键（如 search_box / send_button / input_box）
    by_image：图像模板名（P2 启用），非空时优先图像匹配
    by_geom：几何规格，非空时图像失败或 P1 阶段走几何兜底
    roi：图像匹配感兴趣区域（可选）。非空时限制 OpenCV 搜索范围；
         为空时使用全窗口。用于防止模板误匹配到窗口其他相似元素。
    threshold：图像匹配置信度阈值（默认 0.80）
    require_image：高风险操作为 True 时，图像失败即拒绝执行
    description：人类可读描述

    所有字段带默认值。
    """

    kind: str = ""
    by_image: Optional[str] = None
    by_geom: Optional[GeomSpec] = None
    roi: Optional[RoiSpec] = None
    threshold: float = 0.80
    require_image: bool = False
    description: str = ""


@dataclass
class ElementHandle:
    """定位结果句柄。

    selector：来源 Selector（回溯用）
    x / y：元素中心绝对坐标（点击点）
    w / h：元素宽高（图像匹配时返回，几何兜底为 0）
    confidence：匹配置信度（图像命中时为模型分数，几何兜底为 1.0）
    strategy：定位策略 image / geom

    所有字段带默认值。
    """

    selector: Optional[Selector] = None
    x: int = 0
    y: int = 0
    w: int = 0
    h: int = 0
    confidence: float = 1.0
    strategy: str = "geom"

    @property
    def center(self) -> tuple[int, int]:
        """返回元素中心点（x, y）。"""
        return (self.x, self.y)
