"""
聊天工作流服务
负责处理完整的LLM对话生成流程
"""
import json
import re
import asyncio
import time
from typing import Dict, Any, List, Optional
from pathlib import Path
from datetime import datetime

try:
    from backend.services.character_service import CharacterService
    from backend.services.worldbook_service import WorldBookService
    from backend.services.prompt_assembler import PromptAssembler, PromptConfig
    from backend.utils.llm_client import LLMClient
    from backend.services.task_queue_manager import task_queue_manager, TaskType
    from backend.core.config import settings
    from backend.services.workflow_engine import workflow_engine
except ImportError:
    from services.character_service import CharacterService
    from services.worldbook_service import WorldBookService
    from services.prompt_assembler import PromptAssembler, PromptConfig
    from utils.llm_client import LLMClient
    from services.task_queue_manager import task_queue_manager, TaskType
    from core.config import settings
    from services.workflow_engine import workflow_engine


class ChatWorkflowService:
    """
    聊天工作流服务
    
    负责：
    1. 接收前端发送的完整数据
    2. 加载角色卡和世界书
    3. 激活世界书条目
    4. 组装提示词
    5. 调用LLM生成回复
    6. 返回生成的内容
    """
    
    def __init__(self):
        self.character_service = CharacterService()
        self.worldbook_service = WorldBookService()
        self.prompt_assembler = PromptAssembler()
        self.llm_client = LLMClient()
    
    # ==================== 动态表格条件判断 ====================
    
    def _parse_table_condition(self, condition: str, table_data: Dict[str, Any]) -> bool:
        """
        解析动态表格条件
        
        支持的语法：
        - (tb.力量 > 10)          # 数值比较
        - (tb.态度 包括 "爱情")    # 字符串包含
        - (tb.状态 = "战斗")       # 字符串相等
        
        Args:
            condition: 条件字符串，如 "(tb.力量 > 10)"
            table_data: 动态表格数据 {"力量": 15, "态度": "友好", ...}
            
        Returns:
            bool: 条件是否满足
        """
        try:
            # 去除括号
            condition = condition.strip().strip('()')
            
            # 匹配 tb.字段名 操作符 值
            # 模式1: tb.字段 > 数值
            match_num = re.match(r'tb\.(\S+)\s*(>|<|>=|<=|=|!=)\s*(\d+(?:\.\d+)?)', condition)
            if match_num:
                field_name = match_num.group(1)
                operator = match_num.group(2)
                target_value = float(match_num.group(3))
                
                actual_value = table_data.get(field_name)
                if actual_value is None:
                    return False
                
                # 转换为数值比较
                try:
                    actual_value = float(actual_value)
                except (ValueError, TypeError):
                    return False
                
                # 执行比较
                if operator == '>':
                    return actual_value > target_value
                elif operator == '<':
                    return actual_value < target_value
                elif operator == '>=':
                    return actual_value >= target_value
                elif operator == '<=':
                    return actual_value <= target_value
                elif operator == '=':
                    return actual_value == target_value
                elif operator == '!=':
                    return actual_value != target_value
            
            # 模式2: tb.字段 包括 "字符串"
            match_str_include = re.match(r'tb\.(\S+)\s*包括\s*["\'](.+?)["\']', condition)
            if match_str_include:
                field_name = match_str_include.group(1)
                target_str = match_str_include.group(2)
                
                actual_value = table_data.get(field_name, '')
                return target_str in str(actual_value)
            
            # 模式3: tb.字段 = "字符串"
            match_str_eq = re.match(r'tb\.(\S+)\s*=\s*["\'](.+?)["\']', condition)
            if match_str_eq:
                field_name = match_str_eq.group(1)
                target_str = match_str_eq.group(2)
                
                actual_value = str(table_data.get(field_name, ''))
                return actual_value == target_str
            
            return False
            
        except Exception as e:
            print(f"[TableCondition] 解析条件失败: {condition}, 错误: {e}")
            return False
    

    async def process_chat_request(
        self,
        request_data: Dict[str, Any]
    ) -> Dict[str, Any]:
        """处理聊天请求 – delegates to WorkflowEngine.run_turn."""
        result = await workflow_engine.run_turn(request_data, stream=False)
        return {
            "success": result.success,
            "content": result.content,
            "error": result.error,
            "activeEntries": result.active_entries,
            "taskIds": result.task_ids,
            "engineRunId": result.run_id,
            "workflowTemplateId": result.workflow_template_id,
        }
    
    def _normalize_worldbook_entry(self, entry_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        标准化世界书条目数据，修复类型不匹配问题
        
        Args:
            entry_data: 原始条目数据
            
        Returns:
            标准化后的条目数据
        """
        normalized = entry_data.copy()
        
        # ✅ content 必须是字符串（如果是字典或列表，转换为 JSON 字符串）
        if 'content' in normalized:
            content = normalized['content']
            if not isinstance(content, str):
                import json
                normalized['content'] = json.dumps(content, ensure_ascii=False)
        
        # uid 必须是字符串
        if 'uid' in normalized and not isinstance(normalized['uid'], str):
            normalized['uid'] = str(normalized['uid'])
        
        # position 必须是整数或 None
        if 'position' in normalized:
            pos = normalized['position']
            if pos is not None:
                try:
                    normalized['position'] = int(pos)
                except (ValueError, TypeError):
                    normalized['position'] = 0
        
        # group 必须是列表或 None
        if 'group' in normalized:
            grp = normalized['group']
            if grp is not None and not isinstance(grp, list):
                # 如果是空字符串，转为None；否则尝试解析
                if grp == '' or grp is None:
                    normalized['group'] = None
                elif isinstance(grp, str):
                    # 尝试将逗号分隔的字符串转为列表
                    normalized['group'] = [g.strip() for g in grp.split(',') if g.strip()]
                else:
                    normalized['group'] = None
        
        return normalized
    
    def _get_trigger_type(self, entry_data: Dict[str, Any]) -> str:
        """
        获取条目的触发类型
        
        Args:
            entry_data: 世界书条目数据
            
        Returns:
            str: 触发类型 ('constant', 'keyword', 'condition', 'rag', 'unknown')
        """
        trigger_config = entry_data.get("trigger_config", {})
        triggers = trigger_config.get("triggers", {})
        
        # 检查常驻触发
        constant_trigger = triggers.get("constant", [False, None])
        if constant_trigger[0]:
            return "constant"
        
        # 检查关键词触发
        keyword_trigger = triggers.get("keyword", [False, None])
        if keyword_trigger[0]:
            return "keyword"
        
        # 检查条件触发
        condition_trigger = triggers.get("condition", [False, None])
        if condition_trigger[0]:
            return "condition"
        
        # 检查RAG触发
        rag_trigger = triggers.get("rag", [False, None])
        if rag_trigger[0]:
            return "rag"
        
        return "unknown"
    
    def _get_position_label(self, position) -> str:
        """
        获取位置的中文标签
        
        Args:
            position: 位置编号 (0-7)
            
        Returns:
            str: 位置标签
        """
        position_labels = {
            0: '角色定义之后',
            1: '角色定义之前',
            2: '示例对话之前',
            3: '示例对话之后',
            4: '系统提示/作者注释',
            5: '作为系统消息',
            6: '深度插入',
            7: '宏替换'
        }
        
        try:
            pos_int = int(position) if position is not None else 0
            return position_labels.get(pos_int, f'未知位置({position})')
        except (ValueError, TypeError):
            return f'未知位置({position})'
    
    async def _collect_and_activate_worldbooks(
        self,
        request_data: Dict[str, Any],
        character
    ) -> List:
        """
        收集并激活世界书条目
        
        支持的触发类型：
        1. permanent/constant - 永久激活
        2. keyword - 关键词匹配（用户输入、历史消息）
        3. condition - 条件判断（动态表格、用户输入等）
        4. rag - RAG检索（暂不实现）
        
        Args:
            request_data: 前端发送的请求数据
            character: 角色卡对象
            
        Returns:
            List[WorldInfoEntry]: 激活的条目列表（已按位置和顺序排序）
        """
        try:
            from backend.models.internal import WorldInfoEntry
        except ImportError:
            from models.internal import WorldInfoEntry
        
        active_entries = []
        
        # 提取必要的数据
        user_message = request_data.get("mes", "")
        dynamic_table_data = request_data.get("dynamicTableData") or {}
        table_data = dynamic_table_data.get("currentValues", {}) if dynamic_table_data else {}
        chat_history = request_data.get("chatHistory", [])  # 前端传来的历史消息文本
        
        world_book_data = request_data.get("worldBookData", {})
        if not world_book_data:
            print(f"[WorldBook] ⚠️ 未收到 worldBookData")
            return active_entries
        
        # ✅ 详细日志：检查接收到的世界书数据
        global_books = world_book_data.get("globalBooks", [])
        character_book_id = world_book_data.get("characterBookId") or (character.worldInfoId if character else None)
        
        print(f"[WorldBook] 📋 接收到的世界书数据:")
        print(f"  - 全局世界书数量: {len(global_books)}")
        for wb in global_books:
            print(f"    * {wb.get('name')}")
        print(f"  - 角色绑定世界书: {character_book_id}")
        
        if not global_books and not character_book_id:
            print(f"[WorldBook] ⚠️ 既没有全局世界书，也没有角色绑定世界书")
            return active_entries
        
        # === 1. 加载全局世界书 ===
        for wb in global_books:
            try:
                wb_name = wb.get("name")
                if wb_name:
                    wb_data = self.worldbook_service._load_worldbook(wb_name)
                    if wb_data and "entries" in wb_data:
                        print(f"[WorldBook] 📖 加载全局世界书 '{wb_name}'，共 {len(wb_data['entries'])} 个条目")
                        for entry_data in wb_data["entries"]:
                            # 检查是否禁用
                            if entry_data.get("disable", False):
                                continue
                            
                            # ✅ 标准化数据类型
                            normalized_entry = self._normalize_worldbook_entry(entry_data)
                            
                            # 检查激活条件
                            if self._check_entry_activation(normalized_entry, user_message, table_data, chat_history):
                                try:
                                    entry = WorldInfoEntry(**normalized_entry)
                                    active_entries.append(entry)
                                    trigger_type = self._get_trigger_type(normalized_entry)
                                    position_label = self._get_position_label(normalized_entry.get("position", 0))
                                    print(f"[WorldBook] ✅ 全局世界书 '{wb_name}' 条目激活 | UID: {entry_data.get('uid')} | 触发: {trigger_type} | 位置: {position_label}")
                                except Exception as validation_error:
                                    print(f"[WorldBook] ⚠️ 条目验证失败: {entry_data.get('uid')}, 错误: {validation_error}")
            except Exception as e:
                print(f"[WorldBook] ❌ 加载全局世界书失败: {wb.get('name')}, 错误: {e}")
        
        # === 2. 加载角色绑定的世界书 ===
        if character_book_id:
            print(f"[WorldBook] 📖 开始加载角色绑定世界书: {character_book_id}")
            try:
                wb_data = self.worldbook_service._load_worldbook(character_book_id)
                if wb_data and "entries" in wb_data:
                    print(f"[WorldBook] ✅ 成功加载世界书 '{character_book_id}'，共 {len(wb_data['entries'])} 个条目")
                    for entry_data in wb_data["entries"]:
                        if entry_data.get("disable", False):
                            continue
                        
                        # ✅ 标准化数据类型
                        normalized_entry = self._normalize_worldbook_entry(entry_data)
                        
                        if self._check_entry_activation(normalized_entry, user_message, table_data, chat_history):
                            try:
                                entry = WorldInfoEntry(**normalized_entry)
                                active_entries.append(entry)
                                trigger_type = self._get_trigger_type(normalized_entry)
                                position_label = self._get_position_label(normalized_entry.get("position", 0))
                                print(f"[WorldBook] ✅ 角色世界书 '{character_book_id}' 条目激活 | UID: {entry_data.get('uid')} | 触发: {trigger_type} | 位置: {position_label}")
                            except Exception as validation_error:
                                print(f"[WorldBook] ⚠️ 条目验证失败: {entry_data.get('uid')}, 错误: {validation_error}")
                else:
                    print(f"[WorldBook] ⚠️ 世界书 '{character_book_id}' 不存在或无条目")
            except Exception as e:
                print(f"[WorldBook] ❌ 加载角色世界书失败: {character_book_id}, 错误: {e}")
        else:
            print(f"[WorldBook] ℹ️ 角色未绑定世界书")
        
        # === 3. 按位置和顺序排序 ===
        active_entries.sort(key=lambda x: (x.position or 0, x.order or 0))
        
        # === 4. 统计激活条目的分布情况 ===
        position_stats = {}
        trigger_stats = {}
        for entry in active_entries:
            pos = entry.position or 0
            position_stats[pos] = position_stats.get(pos, 0) + 1
            
            trigger_type = self._get_trigger_type(entry.model_dump())
            trigger_stats[trigger_type] = trigger_stats.get(trigger_type, 0) + 1
        
        print(f"\n[WorldBook] 📊 激活统计:")
        print(f"  - 总激活条目数: {len(active_entries)}")
        print(f"  - 按位置分布:")
        for pos, count in sorted(position_stats.items()):
            pos_label = self._get_position_label(pos)
            print(f"    * {pos_label} (pos={pos}): {count} 个")
        print(f"  - 按触发类型分布:")
        for trigger_type, count in sorted(trigger_stats.items()):
            print(f"    * {trigger_type}: {count} 个")
        print()
        
        return active_entries
    
    def _check_entry_activation(
        self,
        entry_data: Dict[str, Any],
        user_message: str,
        table_data: Dict[str, Any],
        chat_history: List[str]
    ) -> bool:
        """
        检查世界书条目的激活条件
        
        Args:
            entry_data: 世界书条目数据
            user_message: 用户当前输入
            table_data: 动态表格数据
            chat_history: 聊天历史文本列表
            
        Returns:
            bool: 是否应该激活
        """
        trigger_config = entry_data.get("trigger_config", {})
        triggers = trigger_config.get("triggers", {})
        
        # 1. 常驻触发 (constant) - 总是激活
        constant_trigger = triggers.get("constant", [False, None])
        if constant_trigger[0]:  # [true, null]
            print(f"[WorldBook-Check] 📌 常驻触发 | UID: {entry_data.get('uid')}")
            return True
        
        # 2. 关键词触发 (keyword) - 检查用户输入和聊天历史
        keyword_trigger = triggers.get("keyword", [False, None])
        if keyword_trigger[0] and keyword_trigger[1]:
            keyword_config = keyword_trigger[1]
            keys = keyword_config.get("key", []) or keyword_config.get("keys", [])
            
            if keys:
                # 检查用户输入
                if self._check_keyword_trigger_with_config(keys, user_message, keyword_config):
                    print(f"[WorldBook-Check] 🔑 关键词触发(用户输入) | UID: {entry_data.get('uid')} | 匹配关键词: {keys}")
                    return True
                
                # 检查聊天历史
                for i, history_text in enumerate(chat_history):
                    if self._check_keyword_trigger_with_config(keys, history_text, keyword_config):
                        print(f"[WorldBook-Check] 🔑 关键词触发(历史消息#{i}) | UID: {entry_data.get('uid')} | 匹配关键词: {keys}")
                        return True
        
        # 3. 条件触发 (condition) - 支持动态表格语法
        condition_trigger = triggers.get("condition", [False, None])
        if condition_trigger[0] and condition_trigger[1]:
            conditions = condition_trigger[1].get("conditions", [])
            if conditions:
                # 所有条件都必须满足 (AND逻辑)
                all_conditions_met = True
                for condition in conditions:
                    cond_text = condition.get("text", "")
                    if cond_text:
                        # 解析条件，如 (tb.力量 > 10)
                        if not self._parse_table_condition(cond_text, table_data):
                            all_conditions_met = False
                            break
                
                if all_conditions_met:
                    print(f"[WorldBook-Check] ⚙️ 条件触发 | UID: {entry_data.get('uid')} | 条件数: {len(conditions)}")
                    return True
        
        # 4. RAG触发 (暂不实现)
        # rag_trigger = triggers.get("rag", [False, None])
        
        return False
    
    def _check_keyword_trigger_with_config(self, keywords: List[str], text: str, config: Dict[str, Any] = None) -> bool:
        """
        检查文本中是否包含关键词（带配置）
        
        Args:
            keywords: 关键词列表
            text: 要检查的文本
            config: 关键词配置（可选）
            
        Returns:
            bool: 是否匹配
        """
        if not keywords or not text:
            return False
        
        config = config or {}
        case_sensitive = config.get("caseSensitive", False)
        match_whole_words = config.get("matchWholeWords", False)
        
        # 处理大小写
        if not case_sensitive:
            text_lower = text.lower()
            keywords_lower = [kw.lower() for kw in keywords]
        else:
            text_lower = text
            keywords_lower = keywords
        
        # 检查每个关键词
        for keyword in keywords_lower:
            if not keyword:
                continue
            
            if match_whole_words:
                # 全词匹配：使用正则表达式
                pattern = r'\b' + re.escape(keyword) + r'\b'
                if re.search(pattern, text_lower):
                    return True
            else:
                # 部分匹配：直接检查子串
                if keyword in text_lower:
                    return True
        
        return False
    
    async def _load_chat_history(
        self,
        role_name: str,
        chat_name: str
    ) -> List:
        """
        加载聊天历史
        
        TODO: 从chat_service加载完整的聊天历史
        """
        try:
            from backend.models.internal import ChatMessage
        except ImportError:
            from models.internal import ChatMessage
        
        try:
            # 读取聊天文件
            chat_dir = settings.CHATS_PATH / role_name
            chat_file = chat_dir / f"{chat_name}.jsonl"
            
            if not chat_file.exists():
                return []
            
            messages = []
            with open(chat_file, 'r', encoding='utf-8') as f:
                lines = f.readlines()
                # 跳过第一行header
                for line in lines[1:]:
                    if line.strip():
                        try:
                            msg_data = json.loads(line)
                            
                            # ✅ 确保必需字段存在
                            if 'id' not in msg_data:
                                msg_data['id'] = f"msg_{len(messages)}_{msg_data.get('name', 'unknown')}"
                            if 'sendDate' not in msg_data:
                                from datetime import datetime
                                msg_data['sendDate'] = datetime.now().isoformat()
                            if 'chatId' not in msg_data:
                                msg_data['chatId'] = f"{role_name}/{chat_name}"
                            
                            msg = ChatMessage(**msg_data)
                            messages.append(msg)
                        except Exception as msg_error:
                            print(f"[ChatWorkflow] ⚠️ 解析消息失败: {msg_error}")
                            continue
            
            return messages
        except Exception as e:
            print(f"[ChatWorkflow] 加载聊天历史失败: {e}")
            return []
    
    def _assemble_prompt(
        self,
        character,
        chat_history: List,
        user_message: str,
        active_entries: List,
        request_data: Dict[str, Any]
    ) -> List:
        """
        组装提示词
        
        根据预设组件（promptComponents）动态组装LLM消息列表
        """
        # 从预设配置中获取prompt components
        preset_config = request_data.get("presetConfig", {})
        prompt_components = preset_config.get("promptComponents", [])
        
        # ✅ 检查是否启用调试模式
        debug_prompt = request_data.get("debugPrompt", False)
        
        if debug_prompt:
            print(f"\n{'='*80}")
            print(f"[Prompt Debug] 🧩 预设组件配置")
            print(f"{'='*80}")
            print(f"[Prompt Debug] 组件数量: {len(prompt_components)}")
            for i, comp in enumerate(prompt_components, 1):
                print(f"  {i}. {comp.get('name', 'Unknown')} (enabled={comp.get('enabled', True)}, type={comp.get('type', 'N/A')})")
            print(f"{'='*80}\n")
        
        # ✅ 如果有预设组件，使用预设组件组装
        if prompt_components and len(prompt_components) > 0:
            return self._assemble_prompt_from_components(
                character,
                chat_history,
                user_message,
                active_entries,
                prompt_components,
                debug_prompt
            )
        else:
            # ✅ 否则使用默认的 SillyTavern 规范组装
            print(f"[PromptAssembler] ⚠️ 未检测到预设组件，使用默认SillyTavern规范")
            
            # 创建配置
            config = PromptConfig(
                an_position="after_history",  # 默认在历史之后
                an_depth=4,
                post_history_instructions=None
            )
            
            # 组装提示词
            messages = self.prompt_assembler.assemble(
                character=character,
                chat_history=chat_history,
                user_input=user_message,
                active_entries=active_entries,
                config=config
            )
            
            return messages
    
    def _assemble_prompt_from_components(
        self,
        character,
        chat_history: List,
        user_message: str,
        active_entries: List,
        prompt_components: List[Dict],
        debug_prompt: bool = False
    ) -> List:
        """
        根据预设组件组装提示词
        
        Args:
            character: 角色卡数据
            chat_history: 聊天历史
            user_message: 用户输入
            active_entries: 激活的世界书条目
            prompt_components: 预设组件列表
            debug_prompt: 是否输出调试信息
            
        Returns:
            List[BaseMessage]: 组装好的消息列表
        """
        from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
        
        messages = []
        
        # ✅ 按顺序处理每个启用的组件
        enabled_components = [comp for comp in prompt_components if comp.get('enabled', True)]
        
        if debug_prompt:
            print(f"\n[Prompt Debug] 🔄 开始组装 {len(enabled_components)} 个启用的组件")
        
        for i, component in enumerate(enabled_components, 1):
            comp_name = component.get('name', 'Unknown')
            comp_type = component.get('type', 'text')  # text, system, user, assistant
            comp_content = component.get('content', '')
            
            if debug_prompt:
                print(f"\n[Prompt Debug] --- 组件 {i}: {comp_name} ---")
                print(f"  类型: {comp_type}")
                print(f"  内容长度: {len(comp_content)} 字符")
            
            # ✅ 替换模板变量
            processed_content = self._process_component_content(
                comp_content,
                character,
                chat_history,
                user_message,
                active_entries,
                comp_type  # ✅ 传递组件类型
            )
            
            if debug_prompt:
                print(f"  处理后长度: {len(processed_content)} 字符")
                if len(processed_content) < 500:
                    print(f"  完整内容:\n{processed_content}")
                else:
                    print(f"  预览:\n{processed_content[:200]}...")
            
            # ✅ 跳过空内容
            if not processed_content or processed_content.strip() == "":
                if debug_prompt:
                    print(f"  ⚠️ 内容为空，跳过")
                continue
            
            # ✅ 根据组件类型创建对应的消息
            if comp_type == 'system':
                messages.append(SystemMessage(content=processed_content))
            elif comp_type == 'user':
                messages.append(HumanMessage(content=processed_content))
            elif comp_type == 'assistant':
                messages.append(AIMessage(content=processed_content))
            else:
                # 默认为 system
                messages.append(SystemMessage(content=processed_content))
        
        # ✅ 最后添加用户输入（如果还没有添加）
        # 检查最后一个消息是否是用户输入
        if messages and isinstance(messages[-1], HumanMessage):
            # 已经包含用户输入，不需要再添加
            pass
        else:
            # 添加用户输入
            messages.append(HumanMessage(content=user_message))
        
        if debug_prompt:
            print(f"\n[Prompt Debug] ✅ 组装完成，总消息数: {len(messages)}")
            print(f"{'='*80}\n")
        
        return messages
    
    def _process_component_content(
        self,
        content: str,
        character,
        chat_history: List,
        user_message: str,
        active_entries: List,
        component_type: str = 'system'  # ✅ 新增：组件类型（system/user/assistant）
    ) -> str:
        """
        处理组件内容，替换模板变量
        
        支持的变量：
        - {{char}}: 角色名称
        - {{user}}: 用户名称（暂时用 User）
        - {{description}}: 角色描述
        - {{personality}}: 角色性格
        - {{scenario}}: 场景
        - {{mes_example}}: 对话示例
        - {{first_mes}}: 第一条消息
        - {{history}}: 聊天历史
        - {{world_info}}: 世界书信息
        
        Args:
            content: 原始内容
            character: 角色卡数据
            chat_history: 聊天历史
            user_message: 用户输入
            active_entries: 激活的世界书条目
            component_type: 组件类型（system/user/assistant）
            
        Returns:
            str: 处理后的内容
        """
        if not content:
            return ""
        
        # 替换角色相关变量
        content = content.replace('{{char}}', getattr(character, 'name', 'Character'))
        content = content.replace('{{user}}', 'User')  # TODO: 从配置获取用户名
        content = content.replace('{{description}}', getattr(character, 'description', ''))
        content = content.replace('{{personality}}', getattr(character, 'personality', ''))
        content = content.replace('{{scenario}}', getattr(character, 'scenario', ''))
        content = content.replace('{{mes_example}}', getattr(character, 'mes_example', ''))
        content = content.replace('{{first_mes}}', getattr(character, 'first_mes', ''))
        
        # 替换聊天历史
        if '{{history}}' in content:
            history_text = self._format_chat_history(chat_history)
            content = content.replace('{{history}}', history_text)
        
        # 替换世界书信息
        if '{{world_info}}' in content and active_entries:
            world_info_text = self._format_world_info(active_entries)
            content = content.replace('{{world_info}}', world_info_text)
        
        # ✅ 应用 System Prompt 正则规则（placement=0）
        if component_type == 'system':
            from services.regex_service import regex_service
            from models.regex_rules import RegexPlacement
            
            # 获取预设名称
            # TODO: 这里应该从请求数据中获取 preset_name，暂时传 None
            content = regex_service.apply_rules_by_placement(
                text=content,
                placement=RegexPlacement.SYSTEM_PROMPT.value,
                character_name=getattr(character, 'name', None),
                preset_name=None,  # TODO: 从请求中获取
                message_depth=0,
                is_for_llm=True,  # ✅ 系统提示词会发送给 LLM
                is_markdown_rendered=False
            )
        
        return content
    
    def _format_chat_history(self, chat_history: List) -> str:
        """
        格式化聊天历史为文本
        
        Args:
            chat_history: 聊天历史列表
            
        Returns:
            str: 格式化后的历史文本
        """
        lines = []
        for msg in chat_history:
            # ✅ 过滤已被总结的空消息
            if hasattr(msg, 'is_summarized') and msg.is_summarized and (msg.mes == "" or msg.mes.strip() == ""):
                continue
            
            name = getattr(msg, 'name', 'User' if msg.is_user else 'Assistant')
            mes = getattr(msg, 'mes', '')
            lines.append(f"{name}: {mes}")
        
        return "\n".join(lines)
    
    def _format_world_info(self, active_entries: List) -> str:
        """
        格式化世界书信息为文本
        
        Args:
            active_entries: 激活的世界书条目列表
            
        Returns:
            str: 格式化后的世界书文本
        """
        lines = []
        for entry in active_entries:
            name = getattr(entry, 'name', 'Unknown')
            content = getattr(entry, 'content', '')
            lines.append(f"[{name}]\n{content}")
        
        return "\n\n".join(lines)
    
    async def _generate_response(
        self,
        prompt_messages: List,
        api_config: Dict[str, str],
        preset_config: Dict[str, Any],
        stream: bool = False
    ) -> Dict[str, Any]:
        """
        调用LLM生成回复
        
        Args:
            prompt_messages: 组装好的提示词消息列表
            api_config: API配置 {api_url, api_key, model}
            preset_config: 预设配置 {parameters, ...}
            stream: 是否启用流式输出
            
        Returns:
            {
                "content": str,  # 生成的文本内容
                "usage": dict,   # Token 使用信息
                "duration": float  # 请求耗时
            }
        """
        try:
            # 提取API配置
            api_url = api_config.get("api_url", "")
            api_key = api_config.get("api_key", "")
            model = api_config.get("model", "gpt-3.5-turbo")
            
            # 提取生成参数
            parameters = preset_config.get("parameters", {})
            temperature = parameters.get("temperature", 1.0)
            max_tokens = parameters.get("max_tokens", 500)
            
            # 调用LLM客户端
            response = await self.llm_client.chat_completion(
                messages=prompt_messages,
                api_url=api_url,
                api_key=api_key,
                model=model,
                temperature=temperature,
                max_tokens=max_tokens,
                request_timeout=parameters.get("request_timeout", 60),  # ✅ 传递超时时间
                stream=stream
            )
            
            # 提取回复内容
            if isinstance(response, dict):
                content = response.get("choices", [{}])[0].get("message", {}).get("content", "")
                usage = response.get("usage", {})
                duration = response.get("duration", 0)
            else:
                content = str(response)
                usage = {}
                duration = 0
            
            return {
                "content": content,
                "usage": usage,
                "duration": duration
            }
            
        except Exception as e:
            print(f"[ChatWorkflow] LLM调用失败: {e}")
            raise
    
    # ==================== 异步并行任务 ====================
    
    async def _start_parallel_tasks(
        self,
        request_data: Dict[str, Any],
        ai_response: str,
        image_task_id: str = None,
        table_task_id: str = None
    ):
        """
        启动异步并行任务
        
        包括：
        1. LLM维护动态表格（如果启用）
        2. 生图工作流（如果启用）
        
        这些任务与主工作流互不干扰，在后台异步执行
        
        Args:
            request_data: 前端发送的请求数据
            ai_response: AI生成的回复内容
            image_task_id: 生图任务ID
            table_task_id: 动态表格任务ID
        """
        options = request_data.get("options", {})
        
        # 创建任务列表
        tasks = []
        
        # 1. 动态表格维护任务
        if options.get("dynamicTable", False) and table_task_id:
            task = asyncio.create_task(
                self._update_dynamic_table(request_data, ai_response, table_task_id)
            )
            tasks.append(task)
            print(f"[ParallelTask] 启动动态表格维护任务: {table_task_id}")
        
        # 2. 生图工作流任务
        if options.get("imageWorkflow", False) and image_task_id:
            task = asyncio.create_task(
                self._generate_image(request_data, ai_response, image_task_id)
            )
            tasks.append(task)
            print(f"[ParallelTask] 启动生图工作流任务: {image_task_id}")
        
        # 等待所有任务完成（但不阻塞主流程）
        if tasks:
            try:
                await asyncio.gather(*tasks, return_exceptions=True)
                print("[ParallelTask] 所有并行任务完成")
            except Exception as e:
                print(f"[ParallelTask] 并行任务执行失败: {e}")
    
    async def _update_dynamic_table(
        self,
        request_data: Dict[str, Any],
        ai_response: str,
        task_id: str
    ):
        """
        使用LLM维护动态表格
        
        TODO: 实现具体逻辑
        - 分析AI回复，提取表格字段的变化
        - 根据时间戳解决冲突（前端优先）
        - 更新聊天文件中的表格数据
        
        Args:
            request_data: 请求数据
            ai_response: AI回复
            task_id: 任务ID
        """
        try:
            await task_queue_manager.start_task(task_id)
            print(f"[DynamicTable] 开始维护动态表格: {task_id}")
            
            # 获取当前表格数据和时间戳
            table_data = request_data.get("dynamicTableData", {})
            client_timestamp = request_data.get("timestamp", datetime.now().timestamp())
            
            # TODO: 调用LLM分析AI回复，提取表格变化
            # TODO: 检查时间戳，如果前端有更新则跳过
            # TODO: 更新表格数据到聊天文件
            
            await task_queue_manager.complete_task(task_id, {
                "modifiedFields": [],  # TODO: 实际修改的字段
                "prompt": "TODO: 使用的提示词"
            })
            print(f"[DynamicTable] 动态表格维护完成: {task_id}")
            
        except asyncio.CancelledError:
            await task_queue_manager.cancel_task(task_id)
            print(f"[DynamicTable] 任务被取消: {task_id}")
        except Exception as e:
            await task_queue_manager.fail_task(task_id, str(e))
            print(f"[DynamicTable] 维护失败: {e}")
    
    async def _generate_image(
        self,
        request_data: Dict[str, Any],
        ai_response: str,
        task_id: str
    ):
        """
        生图工作流
        
        TODO: 实现具体逻辑
        - 从AI回复中提取场景描述
        - 调用ComfyUI API生成图片
        - 保存图片并返回路径
        
        Args:
            request_data: 请求数据
            ai_response: AI回复
            task_id: 任务ID
        """
        try:
            await task_queue_manager.start_task(task_id)
            print(f"[ImageWorkflow] 开始生图: {task_id}")
            
            # TODO: 提取场景描述
            # TODO: 调用ComfyUI
            # TODO: 保存图片
            
            await task_queue_manager.complete_task(task_id, {
                "imagePath": "TODO: 生成的图片路径",
                "prompt": "TODO: 使用的提示词"
            })
            print(f"[ImageWorkflow] 生图完成: {task_id}")
            
        except asyncio.CancelledError:
            await task_queue_manager.cancel_task(task_id)
            print(f"[ImageWorkflow] 任务被取消: {task_id}")
        except Exception as e:
            await task_queue_manager.fail_task(task_id, str(e))
            print(f"[ImageWorkflow] 生图失败: {e}")
    
    async def process_chat_request_stream(
        self,
        request_data: Dict[str, Any],
        on_chunk,
        on_worldbook_active=None,
        on_tasks_created=None,
    ) -> Dict[str, Any]:
        """处理流式聊天请求 – delegates to WorkflowEngine.run_turn with callbacks."""
        result = await workflow_engine.run_turn(
            request_data,
            stream=True,
            on_chunk=on_chunk,
            on_worldbook_active=on_worldbook_active,
            on_tasks_created=on_tasks_created,
        )
        return {
            "success": result.success,
            "content": result.content,
            "error": result.error,
            "activeEntries": result.active_entries,
            "taskIds": result.task_ids,
            "engineRunId": result.run_id,
            "workflowTemplateId": result.workflow_template_id,
        }
