#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：smart_locate_service.py
模块描述：智能定位服务，自动访问被测系统、执行操作、提取选择器

作者：czj
创建日期：2026-08-05
最后修改：2026-08-05
"""

import logging
import os
import base64
from typing import List, Dict, Any, Optional
from datetime import datetime

from app.executors.playwright_executor import PlaywrightExecutor
from app.services.keyword_matcher import (
    extract_keywords,
    extract_value_from_description,
    detect_action_type,
    is_verification_step,
    match_element_by_keywords,
    find_element_by_semantic,
    get_candidate_details  # 新增：获取候选元素详情
)
from app.services.selector_extractor import (
    extract_selectors,
    extract_element_info,
    build_step_with_selectors
)
from app.services.login_template_service import LoginTemplateService
from app.services.claude_service import ClaudeService  # 新增：Claude 服务
from app.config import settings

logger = logging.getLogger(__name__)


class SmartLocateService:
    """
    智能定位服务

    核心流程：
    1. 启动 Playwright 浏览器
    2. 执行登录模板（自动登录）
    3. 导航到目标菜单
    4. 逐步骤定位 + 执行
    5. 返回所有步骤的选择器

    特点：
    - 不依赖 Claude CLI，通过实际执行验证选择器
    - 支持登录模板自动执行
    - 支持功能中心导航
    - 提取多候选选择器，按优先级回退
    """

    def __init__(self):
        """初始化智能定位服务"""
        self.executor: Optional[PlaywrightExecutor] = None
        self.screenshot_dir = settings.SCREENSHOT_DIR
        self.use_claude: bool = True  # 是否启用 Claude 语义增强

    def locate_steps(
        self,
        steps: List[Dict[str, Any]],
        auto_login: bool = True,
        navigate_menu: str = "",
        page_url: str = "https://192.168.5.44",
        use_claude: bool = True
    ) -> List[Dict[str, Any]]:
        """
        智能定位主流程

        Args:
            steps (List[Dict]): 步骤列表，格式：
                [
                    {"order": 1, "name": "输入用户名", "action": "fill", "params": {"value": "admin@xty"}},
                    {"order": 2, "name": "点击登录按钮", "action": "click", "params": {}}
                ]
            auto_login (bool): 是否自动登录
            navigate_menu (str): 目标菜单名称（如"信息发布"）
            page_url (str): 被测系统基础 URL
            use_claude (bool): 是否启用 Claude 语义增强，默认 True

        Returns:
            List[Dict]: 定位结果列表，格式：
                [
                    {
                        "order": 1,
                        "name": "输入用户名",
                        "success": True,
                        "action": "fill",
                        "params": {"selector": "...", "value": "admin@xty"},
                        "selectors": {...},
                        "screenshot": "base64..."
                    }
                ]
        """
        # 设置 Claude 增强开关
        self.use_claude = use_claude and settings.CLAUDE_ENABLED
        logger.info(f"开始智能定位: {len(steps)} 个步骤, auto_login={auto_login}, navigate_menu={navigate_menu}")

        results = []

        try:
            # 1. 启动 Playwright
            logger.info("启动 Playwright 浏览器...")
            self.executor = PlaywrightExecutor({
                'headless': settings.PLAYWRIGHT_HEADLESS,
                'timeout': 30000,
                'screenshot': True,
                'screenshot_dir': self.screenshot_dir,
                'ignore_https_errors': True,
            })
            self.executor.start()

            # 2. 自动登录
            if auto_login:
                logger.info("执行自动登录...")
                login_success = self._do_login_template()
                if not login_success:
                    logger.warning("自动登录失败，可能影响后续步骤定位")
                else:
                    logger.info("自动登录成功")

            # 3. 导航到目标菜单
            if navigate_menu:
                logger.info(f"导航到目标菜单: {navigate_menu}")
                nav_success = self._navigate_to_menu(navigate_menu)
                if not nav_success:
                    logger.warning(f"导航到菜单 '{navigate_menu}' 失败")

            # 4. 逐步骤定位
            skip_remaining = False
            for step in steps:
                # 如果前置步骤失败，跳过后续步骤
                if skip_remaining:
                    result = {
                        'order': step.get('order', 0),
                        'name': step.get('name', ''),
                        'success': False,
                        'action': step.get('action', ''),
                        'params': step.get('params', {}),
                        'selectors': {'primary': None, 'candidates': []},
                        'message': '前置步骤失败，跳过执行'
                    }
                    results.append(result)
                    continue

                result = self._locate_single_step(step)
                results.append(result)

                # 如果定位成功且操作类型需要执行，则实际执行（确保页面状态正确）
                if result.get('success') and result.get('action') != 'navigate':
                    exec_success = self._execute_step(result)
                    if not exec_success:
                        # 执行失败，回退标记定位结果为失败
                        logger.warning(f"步骤 {step.get('order')} 执行验证失败，回退标记定位为失败")
                        result['success'] = False
                        result['exec_success'] = False
                        result['message'] = f'定位成功但执行验证失败: {result.get("message", "")}'
                        # 后续步骤全部跳过
                        skip_remaining = True

            logger.info(f"智能定位完成: {len(results)} 个步骤")

        except Exception as e:
            logger.error(f"智能定位异常: {e}")
            # 将异常信息添加到结果
            if not results:
                results.append({
                    'order': 0,
                    'name': '智能定位',
                    'success': False,
                    'message': f'定位异常: {str(e)}'
                })

        finally:
            # 5. 关闭浏览器
            if self.executor:
                try:
                    self.executor.stop()
                    logger.info("Playwright 浏览器已关闭")
                except Exception as e:
                    logger.warning(f"关闭浏览器失败: {e}")

        return results

    def _do_login_template(self) -> bool:
        """
        执行登录模板（使用 PlaywrightExecutor 内置的 do_login 方法）

        Returns:
            bool: 是否登录成功
        """
        try:
            return self.executor.do_login()
        except Exception as e:
            logger.error(f"执行登录模板失败: {e}")
            return False

    def _navigate_to_menu(self, menu_name: str) -> bool:
        """
        导航到目标菜单（功能中心 → 抽屉 → 目标菜单）
        支持二级菜单：先点击分类展开，再点击子菜单

        Args:
            menu_name (str): 目标菜单名称

        Returns:
            bool: 是否导航成功
        """
        # 二级菜单映射：子菜单 → 分类（使用 menu_mapping 统一配置）
        try:
            from app.utils.menu_mapping import get_menu_parent, is_direct_menu
            category = get_menu_parent(menu_name)
        except ImportError:
            # 回退：本地映射
            category_map = {
                '资产信息': '资产管理', '资产故障': '资产管理', '资产设备': '资产管理',
                '壁纸推送': '集控控制', '脚本命令': '集控控制', '应用管理': '集控控制',
                '屏幕管理': '集控控制', '软件卸载': '集控控制', '设备网络': '集控控制',
                '门口屏发布': '集控控制', '播放器发布': '集控控制',
                '运维设备': '会议运维', '运维巡检': '会议运维',
                '工单列表': '维护工单', '我的工单': '维护工单', '告警工单': '维护工单',
                '信息窗管理': '信息管理', '消息通知': '信息管理',
                '会务统筹': '会务管理', '会务工单': '会务管理',
            }
            category = category_map.get(menu_name)

        try:
            page = self.executor._page

            # 步骤 1: 点击功能中心图标
            logger.debug("点击功能中心图标...")

            # 功能中心选择器候选列表（按优先级排序）
            func_center_selectors = [
                "//*[@id='Home']/div[1]/div[1]/i",   # XPath（已验证）
                "//*[@id='Home']/div[1]/div[1]",      # XPath div
                ".home_nav_left",                      # class 选择器
            ]

            func_center_clicked = False
            for sel in func_center_selectors:
                try:
                    page.click(sel, timeout=10000)
                    func_center_clicked = True
                    logger.info(f"成功点击功能中心图标: {sel}")
                    break
                except Exception as e:
                    logger.debug(f"功能中心选择器 {sel} 点击失败: {e}")
                    continue

            # 回退：JS click（处理元素在负坐标或被遮挡的情况）
            if not func_center_clicked:
                try:
                    page.evaluate("""
                        () => {
                            const el = document.querySelector('.home_nav_left')
                                || document.querySelector('[id="Home"] > div:first-child > div:first-child');
                            if (el) { el.click(); return true; }
                            return false;
                        }
                    """)
                    func_center_clicked = True
                    logger.info("成功点击功能中心图标（JS click 回退）")
                except Exception as e2:
                    logger.error(f"无法点击功能中心图标（所有方式均失败）: {e2}")
                    return False

            # 步骤 2: 等待功能抽屉打开并完成动画
            logger.debug("等待功能抽屉打开...")
            try:
                # 2.1 等待抽屉元素出现
                page.wait_for_selector('.el-drawer', timeout=5000)
                logger.debug("抽屉元素已出现")

                # 2.2 等待抽屉动画完成（检测抽屉是否展开）
                # Element Plus 抽屉展开动画约 300ms
                import time
                time.sleep(0.3)

                # 2.3 确认抽屉已完全展开
                for _ in range(10):  # 最多等待 1 秒
                    try:
                        drawer = page.query_selector('.el-drawer')
                        if drawer:
                            # 检查抽屉是否展开（宽度大于 0 且可见）
                            is_open = drawer.evaluate("""
                                el => {
                                    const style = window.getComputedStyle(el);
                                    const width = parseFloat(style.width);
                                    return width > 100;  // 抽屉展开后宽度应该大于 100px
                                }
                            """)
                            if is_open:
                                logger.debug("抽屉已完全展开")
                                break
                    except Exception:
                        pass
                    time.sleep(0.1)

                # 2.4 额外稳定等待
                time.sleep(0.2)

            except Exception as e:
                logger.warning(f"等待抽屉超时: {e}，继续尝试后续步骤")

            # 步骤 3: 如果是二级菜单，先点击分类展开
            if category:
                logger.debug(f"二级菜单导航: {category} -> {menu_name}")
                try:
                    category_selector = f'.el-drawer >> text="{category}"'
                    # 直接尝试点击（page.click 内部会等待元素出现）
                    page.click(category_selector, timeout=8000)
                    page.wait_for_timeout(800)
                    logger.info(f"成功点击分类: {category}")
                except Exception as e:
                    logger.warning(f"点击分类失败: {e}，尝试直接点击菜单")

            # 步骤 4: 点击目标菜单（带滚动和重试）
            logger.debug(f"点击目标菜单: {menu_name}")

            # 菜单选择器候选列表（按优先级排序）
            menu_selectors = [
                f'.el-drawer >> text="{menu_name}"',           # 优先：直接文本匹配
                f'.el-drawer a:has-text("{menu_name}")',       # 备选：链接元素
                f'.el-drawer button:has-text("{menu_name}")',  # 备选：按钮元素
                f'.el-drawer [role="menuitem"]:has-text("{menu_name}")',  # 备选：菜单项
            ]

            click_success = False
            for i, menu_selector in enumerate(menu_selectors):
                try:
                    logger.debug(f"尝试选择器 {i+1}/{len(menu_selectors)}: {menu_selector}")

                    # 直接尝试点击（page.click 内部会等待元素出现）
                    page.click(menu_selector, timeout=8000)
                    click_success = True
                    logger.info(f"成功点击菜单（选择器 {i+1}）: {menu_name}")
                    break

                except Exception as e:
                    logger.warning(f"选择器 {i+1} 点击失败: {e}")
                    continue

            if click_success:
                page.wait_for_timeout(1500)  # 等待页面加载
                logger.info(f"成功导航到菜单: {menu_name}")
                return True
            else:
                logger.error(f"所有选择器都无法点击菜单: {menu_name}")
                return False

        except Exception as e:
            logger.error(f"导航到菜单异常: {e}")
            return False

    def _locate_single_step(self, step: Dict[str, Any]) -> Dict[str, Any]:
        """
        为单个步骤定位元素

        Args:
            step (Dict): 步骤定义

        Returns:
            Dict: 定位结果
        """
        order = step.get('order', 0)
        name = step.get('name', '')
        action = step.get('action', '')
        params = step.get('params', {})

        logger.info(f"定位步骤 {order}: {name} (action={action})")

        result = {
            'order': order,
            'name': name,
            'success': False,
            'action': action,
            'params': params,
            'selectors': {'primary': None, 'candidates': []},
            'element_info': {},
            'screenshot': None,
            'message': ''
        }

        try:
            # ⚠️ 关键修复：先获取 page 对象，验证步骤需要使用
            page = self.executor._page

            # ⚠️ 新增：如果步骤已有选择器，直接使用，不重新定位
            existing_selector = params.get('selector', '')
            if existing_selector:
                logger.info(f"步骤 {order} 已有选择器: {existing_selector}，直接使用")
                result['success'] = True
                result['selectors'] = {
                    'primary': existing_selector,
                    'candidates': [{'type': 'css', 'value': existing_selector, 'confidence': 0.95, 'priority': 1}]
                }
                result['message'] = f'使用已有选择器: {existing_selector}'
                # 验证选择器是否有效（增加等待时间）
                try:
                    page.wait_for_selector(existing_selector, timeout=8000)
                    logger.info(f"步骤 {order} 已有选择器验证通过")
                    return result
                except Exception as e:
                    logger.warning(f"步骤 {order} 已有选择器验证失败: {e}，仍使用原选择器")
                    # 选择器验证失败，但仍然使用原选择器（可能是页面状态问题）
                    # 不再尝试智能定位，避免覆盖用户手动配置的选择器
                    result['message'] = f'使用已有选择器（验证超时）: {existing_selector}'
                    return result

            # navigate 类型：需要设置 url 参数
            if action == 'navigate':
                # 检查是否已有 url 参数
                url = params.get('url', '')
                if not url:
                    # 从步骤名称中提取 URL 或使用默认值
                    import re
                    url_match = re.search(r'https?://[^\s]+', name)
                    if url_match:
                        url = url_match.group(0)
                    else:
                        # 使用默认 URL
                        url = 'https://192.168.5.44'

                result['success'] = True
                result['params']['url'] = url
                result['selectors'] = {'primary': url, 'candidates': []}
                result['message'] = f'导航步骤: {url}'
                logger.info(f"步骤 {order} 导航到: {url}")
                return result

            # ⚠️ 新增：登录相关步骤优先使用已验证的选择器
            login_selectors = self._get_login_selector(name)
            if login_selectors:
                result['success'] = True
                result['selectors'] = {
                    'primary': login_selectors,
                    'candidates': [{'type': 'css', 'value': login_selectors, 'confidence': 0.95, 'priority': 1}]
                }
                result['params']['selector'] = login_selectors
                result['message'] = f'登录模板选择器: {login_selectors}'
                logger.info(f"步骤 {order} 使用登录模板选择器: {login_selectors}")

                # 自动修正 fill 步骤的 value
                if action == 'fill':
                    extracted_value = extract_value_from_description(name)
                    if extracted_value:
                        result['params']['value'] = extracted_value
                        logger.info(f"步骤 {order} 自动修正 value: '{params.get('value', '')}' -> '{extracted_value}'")

                return result

            # ⚠️ 新增：验证类步骤特殊处理
            # 验证类步骤（如"查看是否新增成功"）支持两种验证方式：
            # 1. 提示语验证：等待成功提示出现
            # 2. 列表搜索验证：在列表中搜索判断是否新增
            if is_verification_step(name):
                logger.info(f"步骤 {order} 为验证类步骤，尝试查找操作结果")

                # ===== 方式1：提示语验证 =====
                message_selectors = [
                    '.el-message--success .el-message__content',  # 成功提示
                    '.el-message__content',  # 通用提示内容
                    '//p[@class="el-message__content"]',  # XPath 方式
                    '.el-message',  # 整个提示框
                ]

                for sel in message_selectors:
                    try:
                        # 等待提示出现（操作后通常会有短暂延迟）
                        page.wait_for_selector(sel, timeout=5000)
                        element = page.locator(sel).first
                        if element:
                            # 提取提示文本用于验证
                            message_text = element.inner_text().strip()
                            logger.info(f"找到操作结果提示: '{message_text}'")

                            result['success'] = True
                            result['action'] = 'wait'  # 改为 wait 类型
                            result['selectors'] = {
                                'primary': sel,
                                'candidates': [
                                    {'type': 'css', 'value': sel, 'confidence': 0.95, 'priority': 1}
                                ]
                            }
                            result['params']['selector'] = sel
                            result['params']['timeout'] = 10000
                            result['params']['expected_text'] = message_text  # 记录提示文本
                            result['params']['verify_method'] = 'message'  # 验证方式
                            result['message'] = f'验证类步骤（提示语），找到提示: {message_text}'
                            logger.info(f"步骤 {order} 定位成功（提示验证）: {sel}")

                            # 截图
                            try:
                                screenshot_path = os.path.join(
                                    self.screenshot_dir,
                                    f"smart_locate_{order}_{datetime.now().strftime('%H%M%S')}.png"
                                )
                                page.screenshot(path=screenshot_path)
                                with open(screenshot_path, 'rb') as f:
                                    result['screenshot'] = base64.b64encode(f.read()).decode('utf-8')
                            except Exception as e:
                                logger.debug(f"截图失败（非致命）: {e}")

                            return result
                    except Exception as e:
                        logger.debug(f"提示语选择器 {sel} 未找到: {e}")
                        continue

                logger.info(f"未找到提示语，尝试列表搜索验证...")

                # ===== 方式2：列表搜索验证 =====
                # 查找页面上的搜索输入框
                search_input_selectors = [
                    'input[placeholder*="搜索"]',
                    'input[placeholder*="查询"]',
                    'input[placeholder*="查找"]',
                    'input[placeholder*="关键字"]',
                    '.search-input',
                    '.el-input__inner',
                ]

                for sel in search_input_selectors:
                    try:
                        element = page.locator(sel).first
                        if element and element.is_visible():
                            logger.info(f"找到搜索输入框: {sel}")

                            result['success'] = True
                            result['action'] = 'fill'  # 改为 fill 类型（输入搜索词）
                            result['selectors'] = {
                                'primary': sel,
                                'candidates': [
                                    {'type': 'css', 'value': sel, 'confidence': 0.85, 'priority': 1}
                                ]
                            }
                            result['params']['selector'] = sel
                            # 从步骤名称中提取搜索关键词（如"查看列表是否正确新增数据"）
                            # 用户需要手动指定搜索词，这里只提供选择器
                            result['params']['verify_method'] = 'search'  # 验证方式
                            result['message'] = f'验证类步骤（列表搜索），找到搜索框: {sel}'
                            logger.info(f"步骤 {order} 定位成功（列表搜索）: {sel}")

                            # 截图
                            try:
                                screenshot_path = os.path.join(
                                    self.screenshot_dir,
                                    f"smart_locate_{order}_{datetime.now().strftime('%H%M%S')}.png"
                                )
                                page.screenshot(path=screenshot_path)
                                with open(screenshot_path, 'rb') as f:
                                    result['screenshot'] = base64.b64encode(f.read()).decode('utf-8')
                            except Exception as e:
                                logger.debug(f"截图失败（非致命）: {e}")

                            return result
                    except Exception as e:
                        logger.debug(f"搜索框选择器 {sel} 未找到: {e}")
                        continue

                # 如果两种方式都没有找到
                logger.warning(f"步骤 {order} 为验证类但未找到提示元素或搜索框")
                result['success'] = False
                result['message'] = f'验证类步骤，但未找到操作结果提示元素或列表搜索框'
                return result

            # 提取关键词
            keywords = extract_keywords(name)
            logger.debug(f"提取关键词: {keywords}")

            # ⚠️ 新增：步骤模式预处理
            # 识别"xxx选择：yyy"（列表复选框）和"xxx切换为：yyy"（Tab切换）模式
            pattern_result = self._preprocess_step_pattern(name, action)
            if pattern_result and pattern_result['selectors']:
                result['success'] = True
                result['selectors'] = {
                    'primary': pattern_result['selectors'][0],
                    'candidates': [
                        {'type': 'css', 'value': sel, 'confidence': pattern_result['confidence'], 'priority': i + 1}
                        for i, sel in enumerate(pattern_result['selectors'])
                    ]
                }
                result['params']['selector'] = pattern_result['selectors'][0]
                result['message'] = f"模式匹配成功: {pattern_result['type']}"
                logger.info(f"步骤 {order} 匹配特殊模式: {pattern_result['type']}")
                return result

            # 第一级：关键词直接匹配（返回候选列表）
            # Claude 语义增强已能理解元素上下文，不再需要手动区分抽屉内/外
            elements_list, selectors = match_element_by_keywords(page, keywords, action)

            if elements_list and selectors:
                # === Phase 5: Claude 语义增强（单候选也验证） ===
                if self.use_claude and len(elements_list) >= 1:
                    try:
                        logger.info(f"步骤 {order} 启用 Claude 语义增强，候选数: {len(elements_list)}")

                        # 获取候选元素详情
                        candidates = get_candidate_details(page, elements_list)

                        # Phase 5: 传递页面上下文和步骤上下文
                        page_url = page.url if page else ""
                        previous_steps_info = []
                        # 获取前面步骤的名称作为上下文
                        for prev_step in self.steps[:step_idx]:
                            previous_steps_info.append({
                                'name': prev_step.get('name', ''),
                                'action': prev_step.get('action', '')
                            })

                        # 调用 Claude 服务进行语义排序
                        claude_service = ClaudeService()
                        selected_idx, confidence, reason, confirmed = claude_service.rank_candidates(
                            step_name=name,
                            action=action,
                            params=params,
                            candidates=candidates,
                            page_url=page_url,
                            previous_steps=previous_steps_info
                        )

                        # Phase 5: 处理 Claude 返回结果
                        if confirmed and 0 <= selected_idx < len(selectors):
                            primary_selector = selectors[selected_idx]['value']
                            result['success'] = True
                            result['selectors'] = {
                                'primary': primary_selector,
                                'candidates': selectors
                            }
                            result['claude_enhanced'] = True
                            result['claude_confidence'] = confidence
                            result['claude_reason'] = reason
                            result['message'] = f'Claude语义增强定位成功: {primary_selector}'
                            logger.info(f"步骤 {order} Claude 确认: index={selected_idx}, confidence={confidence:.2f}, reason={reason}")
                        elif not confirmed:
                            # Claude 认为当前候选都不匹配
                            # 仍然使用第一个候选，但标记为未确认
                            primary_selector = selectors[0]['value']
                            result['success'] = True
                            result['selectors'] = {'primary': primary_selector, 'candidates': selectors}
                            result['claude_enhanced'] = True
                            result['claude_confidence'] = confidence
                            result['claude_reason'] = reason
                            result['claude_confirmed'] = False
                            result['message'] = f'Claude未确认最佳候选，回退到第一个: {primary_selector}'
                            logger.warning(f"步骤 {order} Claude 未确认: reason={reason}")
                        else:
                            # Claude 返回索引无效，回退到第一个
                            primary_selector = selectors[0]['value']
                            result['success'] = True
                            result['selectors'] = {'primary': primary_selector, 'candidates': selectors}
                            result['claude_enhanced'] = False
                            result['message'] = f'关键词匹配成功（Claude返回无效索引）: {primary_selector}'
                            logger.warning(f"步骤 {order} Claude 返回索引无效: {selected_idx}")

                    except Exception as e:
                        # Claude 失败，回退到第一个候选
                        logger.warning(f"步骤 {order} Claude 语义增强失败，回退: {e}")
                        primary_selector = selectors[0]['value']
                        result['success'] = True
                        result['selectors'] = {'primary': primary_selector, 'candidates': selectors}
                        result['claude_enhanced'] = False
                        result['message'] = f'关键词匹配成功（Claude回退）: {primary_selector}'

                # 未启用 Claude，直接使用第一个
                elif not self.use_claude:
                    primary_selector = selectors[0]['value']
                    result['success'] = True
                    result['selectors'] = {'primary': primary_selector, 'candidates': selectors}
                    result['claude_enhanced'] = False
                    result['message'] = f'关键词匹配成功: {primary_selector}'
                    logger.info(f"步骤 {order} 定位成功（关键词匹配）: {primary_selector}")

                # 兜底
                else:
                    primary_selector = selectors[0]['value']
                    result['success'] = True
                    result['selectors'] = {'primary': primary_selector, 'candidates': selectors}
                    result['claude_enhanced'] = False
                    result['message'] = f'关键词匹配成功: {primary_selector}'
                    logger.info(f"步骤 {order} 定位成功（默认）: {primary_selector}")

                # 提取元素信息（使用第一个元素）
                if elements_list:
                    result['element_info'] = extract_element_info(elements_list[0])

            else:
                # 第二级：语义推断（回退策略，只返回单一元素）
                element, selectors = find_element_by_semantic(page, keywords, action)

                if element and selectors:
                    result['success'] = True
                    primary_selector = selectors[0]['value'] if selectors else None
                    result['selectors'] = {
                        'primary': primary_selector,
                        'candidates': selectors
                    }
                    result['element_info'] = extract_element_info(element)
                    result['claude_enhanced'] = False
                    result['message'] = f'语义推断成功: {primary_selector}'
                    logger.info(f"步骤 {order} 定位成功（语义推断）: {primary_selector}")
                else:
                    result['success'] = False
                    result['message'] = f'未找到匹配元素: {name}'
                    logger.warning(f"步骤 {order} 定位失败: {result['message']}")

            # 更新 params 中的 selector
            if result['success'] and result['selectors'].get('primary'):
                result['params']['selector'] = result['selectors']['primary']

            # ⚠️ 自动修正 fill 步骤的 value
            if action == 'fill' and result['success']:
                current_value = result['params'].get('value', '')
                # 从步骤名称中提取正确的 value
                extracted_value = extract_value_from_description(name)
                if extracted_value and extracted_value != current_value:
                    logger.info(f"步骤 {order} 自动修正 value: '{current_value}' -> '{extracted_value}'")
                    result['params']['value'] = extracted_value

            # 截图
            try:
                screenshot_path = os.path.join(
                    self.screenshot_dir,
                    f"smart_locate_{order}_{datetime.now().strftime('%H%M%S')}.png"
                )
                page.screenshot(path=screenshot_path)
                with open(screenshot_path, 'rb') as f:
                    result['screenshot'] = base64.b64encode(f.read()).decode('utf-8')
            except Exception as e:
                logger.debug(f"截图失败（非致命）: {e}")

        except Exception as e:
            result['success'] = False
            result['message'] = f'定位异常: {str(e)}'
            logger.error(f"步骤 {order} 定位异常: {e}")

        return result

    def _execute_step(self, result: Dict[str, Any]) -> bool:
        """
        实际执行步骤（验证选择器是否有效）
        如果点击后预期弹窗未出现，自动尝试替代选择器

        Args:
            result (Dict): 定位结果（包含 selector 和 params）

        Returns:
            bool: 是否执行成功
        """
        action = result.get('action', '')
        params = result.get('params', {})
        selector = params.get('selector', '')
        step_name = result.get('name', '')
        order = result.get('order', 0)

        if not selector:
            return False

        logger.debug(f"执行步骤验证: action={action}, selector={selector}")

        try:
            page = self.executor._page

            if action == 'fill':
                value = params.get('value', '')
                page.wait_for_selector(selector, timeout=5000)
                page.fill(selector, str(value))
                logger.debug(f"填充成功: {selector} = {value}")
                return True

            elif action == 'click':
                # 判断点击后是否预期打开弹窗/抽屉
                dialog_keywords = ['新建', '编辑', '添加', '新增', '创建', '修改', '详情',
                                   '确定创建', '取消会议', '完成']
                expects_dialog = any(kw in step_name for kw in dialog_keywords)

                # 构建替代选择器列表（优先级从高到低）
                click_selector_chain = [selector]

                # 如果当前选择器是 div:has-text 格式，尝试补充 button:has-text
                if 'div:visible:has-text' in selector or 'div:has-text' in selector:
                    import re
                    text_match = re.search(r'has-text\("([^"]+)"\)', selector)
                    if text_match:
                        btn_text = text_match.group(1)
                        click_selector_chain.append(f'button:has-text("{btn_text}")')
                        click_selector_chain.append(f'[role="button"]:has-text("{btn_text}")')
                        click_selector_chain.append(f'.el-button:has-text("{btn_text}")')

                # 如果当前选择器是 input:visible（错误匹配），尝试从步骤名提取关键词
                if 'input:visible' in selector:
                    click_selector_chain.append(f'button:has-text("{step_name}")')

                # 逐级尝试点击
                click_success = False
                actual_selector_used = None
                for sel in click_selector_chain:
                    try:
                        page.wait_for_selector(sel, timeout=5000)
                        page.click(sel)
                        page.wait_for_timeout(800)
                        logger.debug(f"点击成功: {sel}")
                        click_success = True
                        actual_selector_used = sel
                        break
                    except Exception as e:
                        logger.debug(f"选择器 {sel} 点击失败: {e}")
                        continue

                if not click_success:
                    logger.warning(f"步骤 {order} 所有选择器点击均失败")
                    return False

                # 更新 params 中的 selector 为实际使用的选择器
                if actual_selector_used != selector:
                    result['params']['selector'] = actual_selector_used
                    logger.info(f"步骤 {order} 选择器已更新: {selector} -> {actual_selector_used}")

                # === 页面状态验证 ===
                # 1. 检测是否是功能中心点击
                if '功能中心' in step_name:
                    logger.debug("检测到功能中心点击，等待抽屉打开...")
                    try:
                        page.wait_for_selector('.el-drawer', timeout=8000)
                        page.wait_for_timeout(500)
                        logger.debug("功能中心抽屉已打开")
                    except Exception as e:
                        logger.warning(f"功能中心抽屉未打开，标记为失败: {e}")
                        return False

                # 2. 检测弹窗是否出现（如果步骤名称包含弹窗关键词）
                if expects_dialog:
                    dialog_opened = False
                    for dialog_sel in ['.el-dialog', '.el-drawer', '.el-message-box']:
                        try:
                            page.wait_for_selector(dialog_sel, timeout=3000)
                            dialog_opened = True
                            logger.debug(f"弹窗已打开: {dialog_sel}")
                            break
                        except Exception:
                            continue

                    if not dialog_opened:
                        logger.warning(f"步骤 '{step_name}' 点击后未检测到弹窗，所有选择器均失败")
                        # 重要：弹窗未打开，返回 False 触发 skip_remaining
                        return False

                # 3. 检测页面是否跳转（如果步骤名称包含导航关键词）
                nav_keywords = ['进入', '打开', '跳转', '导航']
                if any(kw in step_name for kw in nav_keywords):
                    page.wait_for_timeout(1000)

                return True

            elif action == 'wait':
                timeout = params.get('timeout', 10000)
                page.wait_for_selector(selector, timeout=timeout)
                logger.debug(f"等待成功: {selector}")
                return True

            elif action == 'select':
                value = params.get('value', '')
                page.wait_for_selector(selector, timeout=5000)
                page.select_option(selector, str(value))
                logger.debug(f"选择成功: {selector} = {value}")
                return True

            return True

        except Exception as e:
            logger.warning(f"执行步骤验证失败: {e}")
            return False

    def _preprocess_step_pattern(self, step_name: str, action: str) -> Optional[Dict[str, Any]]:
        """
        预处理特殊步骤模式，生成选择器

        处理模式：
        1. "xxx选择：yyy" → 列表内复选框（如"会议室选择：北京展厅会议室"）
        2. "xxx切换为：yyy" / "切换到：yyy" → Tab切换项（如"时间切换为：预定会议"）

        Args:
            step_name (str): 步骤名称
            action (str): 动作类型

        Returns:
            Optional[Dict]: 模式匹配结果，包含：
                - type: 模式类型
                - selectors: 候选选择器列表
                - confidence: 置信度

        Example:
            >>> _preprocess_step_pattern("会议室选择：北京展厅会议室", "click")
            {
                'type': 'list_checkbox',
                'selectors': [
                    '.el-checkbox:has-text("北京展厅会议室")',
                    '.el-table__row:has-text("北京展厅会议室") .el-checkbox'
                ],
                'confidence': 0.85
            }
        """
        import re

        if action != 'click':
            return None

        # 模式1：列表选择模式（"xxx选择：yyy"）
        # 适用于：会议室选择、设备选择、人员选择等
        match = re.search(r'选择[:：]\s*(.+)$', step_name)
        if match:
            item_name = match.group(1).strip()
            logger.info(f"识别列表选择模式，目标项: {item_name}")

            return {
                'type': 'list_checkbox',
                'selectors': [
                    f'.el-checkbox:has-text("{item_name}")',
                    f'.el-table__row:has-text("{item_name}") .el-checkbox',
                    f'.el-tree-node:has-text("{item_name}") .el-checkbox',
                    f'li:has-text("{item_name}") .el-checkbox',
                    f'[class*="list"]:has-text("{item_name}") .el-checkbox',
                ],
                'confidence': 0.85
            }

        # 模式2：Tab切换模式（"xxx切换为：yyy" / "切换到：yyy"）
        match = re.search(r'切换[为到]?[:：]\s*(.+)$', step_name)
        if match:
            tab_name = match.group(1).strip()
            logger.info(f"识别Tab切换模式，目标Tab: {tab_name}")

            return {
                'type': 'tab_item',
                'selectors': [
                    f'.el-tabs__item:has-text("{tab_name}")',
                    f'[role="tab"]:has-text("{tab_name}")',
                    f'.tab-item:has-text("{tab_name}")',
                    f'li[role="tab"]:has-text("{tab_name}")',
                ],
                'confidence': 0.85
            }

        return None

    def _get_login_selector(self, step_name: str) -> Optional[str]:
        """
        获取登录相关步骤和导航步骤的选择器（使用已验证的模板）

        Args:
            step_name (str): 步骤名称

        Returns:
            Optional[str]: 选择器，如果不是已知步骤返回 None
        """
        # 登录相关关键词映射到选择器
        login_selector_map = {
            # 输入账号
            '账号': 'input[placeholder*="手机号"]',
            '用户名': 'input[placeholder*="手机号"]',
            '手机号': 'input[placeholder*="手机号"]',
            # 输入密码
            '密码': 'input[type="password"]',
            # 输入验证码
            '验证码': 'input[placeholder*="图"]',
            # 勾选协议
            '协议': '.el-checkbox',
            '勾选': '.el-checkbox',
            # 点击登录
            '登录': 'button:has-text("登录")',
        }

        # 检查步骤名称是否包含登录关键词
        for keyword, selector in login_selector_map.items():
            if keyword in step_name:
                return selector

        # ⚠️ 新增：功能中心导航相关步骤
        nav_selector_map = {
            '功能中心': '//*[@id="Home"]/div[1]/div[1]',
            '首页': '//*[@id="Home"]/div[1]/div[1]',
        }

        for keyword, selector in nav_selector_map.items():
            if keyword in step_name:
                return selector

        return None


# ==================== 工厂函数 ====================

def get_smart_locate_service() -> SmartLocateService:
    """
    获取智能定位服务实例

    Returns:
        SmartLocateService: 服务实例
    """
    return SmartLocateService()