"""基础关键字类 - 元素定位和通用操作"""

from playwright.sync_api import Page, Locator, FrameLocator
from typing import Optional, Union, Literal, List

from ..common import LOCATOR_TYPES
from ..reference import INFO

LocatorType = Literal[
    'id', 'name', 'class_name', 'class', 'css', 'xpath',
    'text', 'role', 'data_testid', 'placeholder', 'link_text', 'partial_link_text'
]


class BaseKeyword:
    """基础关键字类

    提供元素定位和通用操作方法，是所有关键字类的基类。
    """

    def __init__(self, page: Page):
        """初始化基础关键字

        Args:
            page: Playwright Page 实例
        """
        self.page = page
        self._current_frame: Optional[FrameLocator] = None
        self._frame_stack: List[FrameLocator] = []

    def locator(
        self,
        targeting: LocatorType,
        element: str,
        index: Optional[Union[int, str]] = None,
        parent: Optional[Locator] = None
    ) -> Locator:
        """统一元素定位方法

        Args:
            targeting: 定位类型 (id, name, class_name, css, xpath, text, role, data_testid, placeholder)
            element: 定位表达式
            index: 索引 (数字、'first'、'last')，None 表示不使用索引
            parent: 父元素定位器，用于链式定位

        Returns:
            Locator: Playwright Locator 实例
        """
        # 确定基础定位上下文
        base = parent or self._current_frame or self.page

        # 标准化定位类型
        targeting = LOCATOR_TYPES.get(targeting, targeting)

        # 根据定位类型创建 Locator
        locator_map = {
            'id': lambda: base.locator(f'#{element}'),
            'name': lambda: base.locator(f'[name="{element}"]'),
            'class_name': lambda: base.locator(f'.{element}'),
            'css': lambda: base.locator(element),
            'xpath': lambda: base.locator(f'xpath={element}'),
            'text': lambda: base.get_by_text(element, exact=False),
            'role': lambda: base.get_by_role(element),
            'data_testid': lambda: base.get_by_test_id(element),
            'placeholder': lambda: base.get_by_placeholder(element),
        }

        loc = locator_map.get(targeting, lambda: base.locator(element))()

        # 处理索引
        if index is not None:
            if index == 'first' or index == 0:
                loc = loc.first
            elif index == 'last' or index == -1:
                loc = loc.last
            elif isinstance(index, int):
                loc = loc.nth(index)
            elif isinstance(index, str) and index.isdigit():
                loc = loc.nth(int(index))

        return loc

    def locator_by_expression(self, expression: str, index: Optional[Union[int, str]] = None) -> Locator:
        """通过表达式定位元素

        支持格式：
        - "xpath=//div" 或 "xpath://div"
        - "css=.class" 或 "css:.class"
        - "text=文本"
        - "id=elementId"
        - 纯 CSS 选择器

        Args:
            expression: 定位表达式
            index: 索引

        Returns:
            Locator: Playwright Locator 实例
        """
        base = self._current_frame or self.page

        # 解析表达式
        if '=' in expression:
            parts = expression.split('=', 1)
            prefix = parts[0].lower().rstrip(':')
            value = parts[1]

            if prefix == 'xpath':
                loc = base.locator(f'xpath={value}')
            elif prefix == 'css':
                loc = base.locator(value)
            elif prefix == 'text':
                loc = base.get_by_text(value)
            elif prefix == 'id':
                loc = base.locator(f'#{value}')
            elif prefix == 'name':
                loc = base.locator(f'[name="{value}"]')
            elif prefix == 'class':
                loc = base.locator(f'.{value}')
            else:
                loc = base.locator(expression)
        else:
            # 纯 CSS 选择器
            loc = base.locator(expression)

        # 处理索引
        if index is not None:
            if index == 'first' or index == 0:
                loc = loc.first
            elif index == 'last' or index == -1:
                loc = loc.last
            elif isinstance(index, int):
                loc = loc.nth(index)

        return loc

    def switch_frame(self, targeting: str, element: str) -> None:
        """切换到 iframe

        Args:
            targeting: 定位类型
            element: 定位表达式
        """
        if targeting == 'xpath':
            frame_locator = self.page.frame_locator(f'xpath={element}')
        elif targeting in ('css', 'selector'):
            frame_locator = self.page.frame_locator(element)
        elif targeting == 'name':
            frame_locator = self.page.frame_locator(f'[name="{element}"]')
        elif targeting == 'id':
            frame_locator = self.page.frame_locator(f'#{element}')
        else:
            frame_locator = self.page.frame_locator(element)

        self._frame_stack.append(self._current_frame)
        self._current_frame = frame_locator
        INFO(f"切换到 iframe: {element}")

    def switch_frame_parent(self) -> None:
        """切换到父级 frame"""
        if self._frame_stack:
            self._current_frame = self._frame_stack.pop()
        else:
            self._current_frame = None
        INFO("切换到父级 frame")

    def frame_default(self) -> None:
        """返回主文档"""
        self._current_frame = None
        self._frame_stack.clear()
        INFO("切换到主文档")

    def wait_for_loading(self, timeout: int = 30000) -> None:
        """等待页面加载完成

        Args:
            timeout: 超时时间 (毫秒)
        """
        self.page.wait_for_load_state('networkidle', timeout=timeout)

    def wait_for_dom_ready(self, timeout: int = 30000) -> None:
        """等待 DOM 加载完成

        Args:
            timeout: 超时时间 (毫秒)
        """
        self.page.wait_for_load_state('domcontentloaded', timeout=timeout)

    def wait_for_element_plus_loading(self, timeout: int = 30000) -> None:
        """等待 Element Plus 的 v-loading 消失

        Args:
            timeout: 超时时间 (毫秒)
        """
        loading = self.page.locator('.el-loading-mask')
        try:
            if loading.count() > 0:
                loading.first.wait_for(state='hidden', timeout=timeout)
        except Exception:
            pass  # loading 可能已经消失

    def wait_for_element(
        self,
        targeting: str,
        element: str,
        state: str = 'visible',
        timeout: int = 30000
    ) -> None:
        """等待元素状态

        Args:
            targeting: 定位类型
            element: 定位表达式
            state: 等待状态 (visible, hidden, attached, detached)
            timeout: 超时时间 (毫秒)
        """
        loc = self.locator(targeting, element)
        loc.wait_for(state=state, timeout=timeout)

    def is_element_visible(self, targeting: str, element: str, index: Optional[int] = None) -> bool:
        """检查元素是否可见

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引

        Returns:
            bool: 元素是否可见
        """
        loc = self.locator(targeting, element, index)
        return loc.is_visible()

    def is_element_enabled(self, targeting: str, element: str, index: Optional[int] = None) -> bool:
        """检查元素是否可用

        Args:
            targeting: 定位类型
            element: 定位表达式
            index: 索引

        Returns:
            bool: 元素是否可用
        """
        loc = self.locator(targeting, element, index)
        return loc.is_enabled()

    def get_element_count(self, targeting: str, element: str) -> int:
        """获取元素数量

        Args:
            targeting: 定位类型
            element: 定位表达式

        Returns:
            int: 元素数量
        """
        loc = self.locator(targeting, element)
        return loc.count()

    def execute_script(self, script: str, *args) -> any:
        """执行 JavaScript

        Args:
            script: JavaScript 代码
            *args: 脚本参数

        Returns:
            脚本执行结果
        """
        return self.page.evaluate(script, args if args else None)

    def execute_script_on_element(
        self,
        targeting: str,
        element: str,
        script: str,
        index: Optional[int] = None
    ) -> any:
        """在元素上执行 JavaScript

        Args:
            targeting: 定位类型
            element: 定位表达式
            script: JavaScript 代码 (使用 el 引用元素)
            index: 索引

        Returns:
            脚本执行结果
        """
        loc = self.locator(targeting, element, index)
        return loc.evaluate(script)
