"""
Klynx Agent - Tool dispatch mixin

Contains tool execution dispatch and formatting logic used by KlynxAgent.
"""

import uuid
import inspect
from typing import Any, Dict

from langchain_core.messages import HumanMessage

from klynx.agent.syntax import SyntaxChecker
from klynx.agent.tools import ToolRegistry

from .state import AgentState


class ToolDispatchMixin:
    """Tool execution and output formatting for KlynxAgent."""

    def _tool_executor_node(self, state: AgentState) -> Dict[str, Any]:
        """
        工具执行节点 - 执行工具调用
        
        该节点负责执行 LLM 决定的工具调用，并返回执行结果。
        所有工具的选择和使用决策都由 LLM 做出，本节点只负责执行。
        """
        tool_calls = state.get("pending_tool_calls", [])
        
        # 如果没有待执行的工具，直接返回
        if not tool_calls:
            return {
                "messages": [],
                "pending_tool_calls": [],
                "last_action": "act"
            }
        
        results = []
        executed_tools = []
        
        # 本次节点执行中可能更新的状态
        new_overall_goal = None
        new_current_task = None
        loaded_skill_names = list(state.get("loaded_skill_names", []) or [])
        skill_context = (state.get("skill_context", "") or "").strip()
        
        # 依次执行每个工具调用
        for i, tool_call in enumerate(tool_calls, 1):
            tool_name = tool_call.get('tool', 'unknown')
            params = tool_call.get('params', {})
            
            # 打印工具执行信息
            self._emit("tool_exec", f"[工具 {i}/{len(tool_calls)}] {tool_name}")
            self._print_tool_params(tool_name, params)
            
            try:
                # ====== 任务完成检测（处理 Native 模式下的 attempt_completion 工具） ======
                if tool_name == "attempt_completion":
                    output = params.get("result", "操作完成")
                    self._emit("info", f"[完成申请] 已收到 attempt_completion: {output}")
                    return {
                        "messages": [],
                        "pending_tool_calls": [],
                        "last_action": "complete_candidate",
                        "task_completed": False,
                        "completion_candidate": True,
                        "completion_candidate_result": str(output),
                        "verification_decision": "",
                        "ended_without_tools": False,
                    }
                
                # ====== 任务状态更新（处理 Native 模式下的 update_task_state 工具） ======
                elif tool_name == "update_task_state":
                    new_task_val = params.get("current_task")
                    new_goal_val = params.get("overall_goal")
                    outputs = []
                    if new_goal_val:
                        new_overall_goal = new_goal_val
                        outputs.append(f"总目标已更新为: {new_goal_val}")
                        self._emit("info", f"[任务目标更新/总目标] {new_goal_val}")
                    if new_task_val:
                        new_current_task = new_task_val
                        outputs.append(f"当前动作已更新为: {new_task_val}")
                        self._emit("info", f"[任务目标更新/下一步] {new_task_val}")
                    
                    output = "任务状态已成功同步。 " + " ; ".join(outputs)
                    # 这个工具只是一个状态同步助手，它不会完成整个系统流，继续留在工具循环里

                # ====== Skills 加载（将 SKILL.md 注入上下文） ======
                elif tool_name == "load_skill":
                    skill_name = (
                        str(params.get("name", "") or params.get("skill_name", "") or params.get("input", ""))
                        .strip()
                    )
                    skill_result = self.get_skill_markdown(skill_name)
                    if not skill_result.get("ok", False):
                        output = f"<error>{skill_result.get('error', '加载技能失败')}</error>"
                    else:
                        canonical_name = str(skill_result.get("name", skill_name)).strip()
                        if canonical_name not in loaded_skill_names:
                            loaded_skill_names.append(canonical_name)
                            block = (
                                f"[SKILL] {canonical_name}\n"
                                f"skill_dir: {skill_result.get('skill_dir', '')}\n"
                                f"skill_md: {skill_result.get('skill_md_path', '')}\n"
                                f"description: {skill_result.get('description', '')}\n"
                                f"----- SKILL.md BEGIN -----\n"
                                f"{skill_result.get('content', '')}\n"
                                f"----- SKILL.md END -----"
                            )
                            skill_context = f"{skill_context}\n\n{block}".strip() if skill_context else block
                            output = (
                                f"<success>技能已加载: {canonical_name}</success>\n"
                                f"<info>SKILL.md 已注入上下文，可按其中流程继续执行。</info>"
                            )
                        else:
                            output = (
                                f"<info>技能已在上下文中: {canonical_name}</info>\n"
                                f"<info>无需重复加载，可直接按该技能流程继续执行。</info>"
                            )
                
                # 执行工具：优先检查外部工具，然后检查新基础工具，最后使用 ToolRegistry
                elif tool_name in self.external_tool_funcs:
                    output = str(self._invoke_external_tool(self.external_tool_funcs[tool_name], params))
                # 终端工具
                elif tool_name == "create_terminal":
                    output = self.terminal_manager.create_terminal(params.get("name"), params.get("cwd"))
                elif tool_name == "run_in_terminal":
                    output = self.terminal_manager.run_command(params.get("name"), params.get("command"))
                elif tool_name == "read_terminal":
                    lines = params.get("lines", 50)
                    output = self.terminal_manager.read_terminal(params.get("name"), int(lines) if lines else 50)
                # 语法检查工具
                elif tool_name == "check_syntax":
                    output = SyntaxChecker.check_file(params.get("path"))
                # TUI 工具
                elif tool_name == "open_tui":
                    rows = int(params.get("rows", 24))
                    cols = int(params.get("cols", 80))
                    output = self.tui_manager.open_tui(params.get("name"), params.get("command"), rows, cols)
                    self.tui_manager.render_to_console(params.get("name"))
                elif tool_name == "read_tui":
                    output = self.tui_manager.read_tui(params.get("name"))
                    self.tui_manager.render_to_console(params.get("name"))
                elif tool_name == "send_keys":
                    output = self.tui_manager.send_keys(params.get("name"), params.get("keys"))
                    self.tui_manager.render_to_console(params.get("name"))
                elif tool_name == "close_tui":
                    output = self.tui_manager.close_tui(params.get("name"))
                # TUI 模式激活工具
                elif tool_name == "activate_tui_mode":
                    self._tui_guide_loaded = True
                    output = "<success>TUI 交互模式已激活。TUI 操作指南已加载到系统提示中。\n你现在可以使用 open_tui / read_tui / send_keys / close_tui 工具进行 TUI 交互。</success>"
                # TUI 一键启动工具
                elif tool_name == "launch_interactive_session":
                    self._tui_guide_loaded = True
                    # 默认使用 command 名称作为会话名（去除参数）
                    command = params.get("command", "cmd")
                    name = command.split()[0].replace(".exe", "")
                    # 避免名称重复
                    if name in self.tui_manager.sessions:
                        import uuid
                        name = f"{name}_{str(uuid.uuid4())[:4]}"
                    
                    output = self.tui_manager.open_tui(name, command, 24, 80)
                    self.tui_manager.render_to_console(name)
                    
                    # 追加指南提示
                    output += "\n<system>TUI 模式已自动激活。请遵循 TUI 操作指南（read_tui -> send_keys -> read_tui）。</system>"
                # 联网搜索工具
                elif tool_name == "web_search":
                    output = self.web_search_tool.search(
                        query=params.get("query", ""),
                        max_results=int(params.get("max_results", 5)),
                        search_depth=params.get("search_depth", "basic")
                    )
                # 知识库查询工具
                elif tool_name == "query_knowledge":
                    output = self.kb_manager.query(
                        query=params.get("query", ""),
                        top_k=int(params.get("top_k", 5)),
                        kb_name=params.get("kb_name", None)
                    )
                elif tool_name == "list_knowledge_collections":
                    output = self.kb_manager.list_collections(
                        kb_name=params.get("kb_name", None)
                    )
                # 浏览器工具
                elif tool_name == "browser_open":
                    output = self.browser_manager.goto(params.get("url"))
                elif tool_name == "browser_view":
                    output = self.browser_manager.get_content(params.get("selector"))
                elif tool_name == "browser_act":
                    output = self.browser_manager.act(
                        params.get("action"), 
                        params.get("selector"), 
                        params.get("value")
                    )
                elif tool_name == "browser_scroll":
                    output = self.browser_manager.scroll(
                        direction=params.get("direction", "down"),
                        amount=params.get("amount", None)
                    )
                elif tool_name == "browser_screenshot":
                    output = self.browser_manager.screenshot()
                elif tool_name == "browser_console_logs":
                    output = self.browser_manager.get_console_logs()
                # 原有基础工具
                else:
                    output = ToolRegistry.execute(tool_call)
                
                # 为特定工具失败注入 Actionable Hint（建设性错误反馈）
                if "<error>" in output:
                    if tool_name == "replace_in_file":
                        output += "\n<hint>1. 请先用 read_file 读取目标文件的精确行内容。2. 确保 SEARCH 块的缩进与原文件完全一致（包括空格/制表符）。3. 若多次失败，考虑用 write_to_file 重写整个文件。</hint>"
                    elif tool_name == "write_to_file":
                        output += "\n<hint>检查文件路径是否正确、目标目录是否存在。</hint>"
                
                # 打印执行结果（限制输出长度）
                self._print_tool_output(output)
                
                # 构建结果消息
                result_content = f"<tool_result tool=\"{tool_name}\">\n{output}\n</tool_result>"
                results.append(HumanMessage(content=result_content))
                
                # 记录已执行的工具
                executed_tools.append({
                    "tool": tool_name,
                    "params": params,
                    "success": True
                })
                
            except Exception as e:
                # 工具执行失败，记录错误信息
                error_msg = f"工具执行失败: {str(e)}"
                self._emit("error", f"  错误: {error_msg}")
                
                result_content = f"<tool_result tool=\"{tool_name}\" status=\"error\">\n{error_msg}\n</tool_result>"
                results.append(HumanMessage(content=result_content))
                
                executed_tools.append({
                    "tool": tool_name,
                    "params": params,
                    "success": False,
                    "error": str(e)
                })
        
        self._emit("info", f"[工具执行完成] 共 {len(tool_calls)} 个工具")
        
        # 更新进度摘要
        progress = state.get("progress_summary", "")
        for tool_info in executed_tools:
            tool_name = tool_info["tool"]
            params = tool_info["params"]
            status = "成功" if tool_info["success"] else "失败"
            
            entry = self._format_progress_entry(tool_name, params, status)
            if entry:
                progress += entry + "\n"
        
        result = {
            "messages": results,
            "pending_tool_calls": [],
            "last_action": "act",
            "empty_response_count": 0,
            "progress_summary": progress,
            "loaded_skill_names": loaded_skill_names,
            "skill_context": skill_context,
        }
        
        # 将被 update_task_state 更新的临时变量存入最终长短期图状态
        if new_overall_goal is not None:
            result["overall_goal"] = new_overall_goal
        if new_current_task is not None:
            result["current_task"] = new_current_task
        
        # 如果 TUI 模式被激活，更新状态
        if getattr(self, '_tui_guide_loaded', False):
            result["tui_guide_loaded"] = True
        
        return result


    def _invoke_external_tool(self, func, params: Dict[str, Any]) -> Any:
        """
        调用外部工具并做参数兼容。

        兼容场景：
        1) 标准 kwargs 调用；
        2) 旧通用 schema 仅传 {"input": "..."}，而函数是单参数；
        3) 无参函数。
        """
        try:
            return func(**params)
        except TypeError as first_error:
            # 兼容旧 schema：只有 input 一个参数时，回退到单位置参数调用
            if isinstance(params, dict) and set(params.keys()) == {"input"}:
                try:
                    return func(params["input"])
                except TypeError:
                    pass

            # 无参函数兼容
            try:
                sig = inspect.signature(func)
                if len(sig.parameters) == 0:
                    return func()
            except (TypeError, ValueError):
                pass

            raise first_error


    def _print_tool_params(self, tool_name: str, params: Dict[str, Any]) -> None:
        """打印工具参数信息"""
        if tool_name == 'execute_command':
            self._emit("tool_exec", f"  命令: {params.get('command', '')}")
        elif tool_name == 'read_file':
            self._emit("tool_exec", f"  文件: {params.get('path', '')}")
            if params.get('start_line'):
                self._emit("tool_exec", f"  行范围: {params.get('start_line')}-{params.get('end_line', 'end')}")
        elif tool_name == 'write_to_file':
            self._emit("tool_exec", f"  文件: {params.get('path', '')}")
            content = params.get('content', '')
            self._emit("tool_exec", f"  内容长度: {len(content)} 字符")
        elif tool_name == 'replace_in_file':
            self._emit("tool_exec", f"  文件: {params.get('path', '')}")
        elif tool_name == 'list_directory':
            self._emit("tool_exec", f"  目录: {params.get('path', '.')}")
            self._emit("tool_exec", f"  深度: {params.get('depth', 2)}")
        elif tool_name == 'create_directory':
            self._emit("tool_exec", f"  目录: {params.get('path', '')}")
        elif tool_name == 'preview_file':
            self._emit("tool_exec", f"  文件: {params.get('path', '')}")
            self._emit("tool_exec", f"  预览行数: {params.get('num_lines', 50)}")
        elif tool_name == 'search_in_files':
            self._emit("tool_exec", f"  搜索: {params.get('pattern', '')}")
            self._emit("tool_exec", f"  路径: {params.get('path', '.')}")
            if params.get('file_pattern', '*') != '*':
                self._emit("tool_exec", f"  文件类型: {params.get('file_pattern')}")
        elif tool_name == 'load_skill':
            self._emit("tool_exec", f"  技能名: {params.get('name', params.get('skill_name', params.get('input', '')))}")
        else:
            # 其他工具，打印所有参数
            for key, value in params.items():
                self._emit("tool_exec", f"  {key}: {value}")


    def _print_tool_output(self, output: str, max_lines: int = 20) -> None:
        """打印工具输出（限制行数）"""
        lines = output.split('\n')
        result_text = "\n".join(lines[:max_lines])
        if len(lines) > max_lines:
            result_text += f"\n    ... (还有 {len(lines) - max_lines} 行)"
        self._emit("tool_result", result_text)


    def _format_progress_entry(self, tool_name: str, params: Dict[str, Any], status: str) -> str:
        """格式化进度记录条目"""
        if tool_name == 'list_directory':
            path = params.get('path', '.')
            return f"- 已列出目录: {path} [{status}]"
        elif tool_name == 'read_file':
            path = params.get('path', '')
            return f"- 已读取文件: {path} [{status}]"
        elif tool_name == 'write_to_file':
            path = params.get('path', '')
            return f"- 已写入文件: {path} [{status}]"
        elif tool_name == 'preview_file':
            path = params.get('path', '')
            return f"- 已预览文件: {path} [{status}]"
        elif tool_name == 'execute_command':
            cmd = params.get('command', '')[:50]
            return f"- 已执行命令: {cmd} [{status}]"
        elif tool_name == 'replace_in_file':
            path = params.get('path', '')
            return f"- 已替换文件内容: {path} [{status}]"
        elif tool_name == 'create_directory':
            path = params.get('path', '')
            return f"- 已创建目录: {path} [{status}]"
        elif tool_name == 'search_in_files':
            pattern = params.get('pattern', '')[:50]
            path = params.get('path', '.')
            return f"- 已搜索: '{pattern}' in {path} [{status}]"
        elif tool_name == 'load_skill':
            name = params.get('name', params.get('skill_name', params.get('input', '')))
            return f"- 已加载技能: {name} [{status}]"
        else:
            return f"- 已执行 {tool_name} [{status}]"
