import asyncio
import json
from pathlib import Path
from typing import Any, Dict, List, Optional, Set

try:
    from config import (
        OUTPUT_FORMATS, DATA_DIR, PROMPTS_DIR, NEED_CONFIRMATION,
        MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE,
        MAX_READ_FILE_CHARS, READ_TOOL_DEFAULT_MAX_CHARS,
        READ_TOOL_DEFAULT_CONTEXT_BEFORE, READ_TOOL_DEFAULT_CONTEXT_AFTER,
        READ_TOOL_MAX_CONTEXT_BEFORE, READ_TOOL_MAX_CONTEXT_AFTER,
        READ_TOOL_DEFAULT_MAX_MATCHES, READ_TOOL_MAX_MATCHES,
        READ_TOOL_MAX_FILE_SIZE,
        TERMINAL_SANDBOX_MOUNT_PATH,
        TERMINAL_SANDBOX_MODE,
        TERMINAL_SANDBOX_CPUS,
        TERMINAL_SANDBOX_MEMORY,
        PROJECT_MAX_STORAGE_MB,
        CUSTOM_TOOLS_ENABLED,
    )
except ImportError:
    import sys
    project_root = Path(__file__).resolve().parents[2]
    if str(project_root) not in sys.path:
        sys.path.insert(0, str(project_root))
    from config import (
        OUTPUT_FORMATS, DATA_DIR, PROMPTS_DIR, NEED_CONFIRMATION,
        MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE,
        MAX_READ_FILE_CHARS, READ_TOOL_DEFAULT_MAX_CHARS,
        READ_TOOL_DEFAULT_CONTEXT_BEFORE, READ_TOOL_DEFAULT_CONTEXT_AFTER,
        READ_TOOL_MAX_CONTEXT_BEFORE, READ_TOOL_MAX_CONTEXT_AFTER,
        READ_TOOL_DEFAULT_MAX_MATCHES, READ_TOOL_MAX_MATCHES,
        READ_TOOL_MAX_FILE_SIZE,
        TERMINAL_SANDBOX_MOUNT_PATH,
        TERMINAL_SANDBOX_MODE,
        TERMINAL_SANDBOX_CPUS,
        TERMINAL_SANDBOX_MEMORY,
        PROJECT_MAX_STORAGE_MB,
        CUSTOM_TOOLS_ENABLED,
    )

from modules.file_manager import FileManager
from modules.search_engine import SearchEngine
from modules.terminal_ops import TerminalOperator
from modules.memory_manager import MemoryManager
from modules.terminal_manager import TerminalManager
from modules.todo_manager import TodoManager
from modules.sub_agent import SubAgentManager
from modules.webpage_extractor import extract_webpage_content, tavily_extract
from modules.ocr_client import OCRClient
from modules.easter_egg_manager import EasterEggManager
from modules.personalization_manager import (
    load_personalization_config,
    build_personalization_prompt,
    RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
    RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
    RECENT_CONVERSATIONS_PROMPT_LIMIT_DEFAULT,
)
from modules.skills_manager import (
    get_skills_catalog,
    build_skills_list,
    merge_enabled_skills,
    build_skills_prompt,
    infer_private_skills_dir,
)
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
from modules.custom_tool_executor import CustomToolExecutor

try:
    from config.limits import THINKING_FAST_INTERVAL
except ImportError:
    THINKING_FAST_INTERVAL = 10

from modules.container_monitor import collect_stats, inspect_state
from core.tool_config import TOOL_CATEGORIES
from utils.api_client import DeepSeekClient
from utils.context_manager import ContextManager, AUTO_SHALLOW_PLACEHOLDER
from utils.host_workspace_debug import write_host_workspace_debug
from utils.tool_result_formatter import format_tool_result_for_context
from utils.logger import setup_logger
from config.model_profiles import (
    get_model_profile,
    get_model_prompt_replacements,
    get_model_context_window,
    model_supports_image,
    model_supports_video,
)

logger = setup_logger(__name__)
DISABLE_LENGTH_CHECK = True


class ConversationMixin:
    """MainTerminalContextMixin conversation 能力 mixin。"""

    def _build_recent_conversations_message(self, limit: int = 10) -> Optional[str]:
            """构建最近对话提示（仅当前工作区）。"""
            try:
                manager = getattr(self.context_manager, "conversation_manager", None)
                if not manager:
                    return None
                current_id = getattr(self.context_manager, "current_conversation_id", None)
                items = manager.get_recent_conversation_summaries(
                    limit=limit,
                    exclude_conversation_id=current_id,
                    first_message_max_chars=100,
                )
                if not items:
                    return None
                lines: List[str] = []
                for index, item in enumerate(items, start=1):
                    title = str(item.get("title") or "未命名对话").strip()
                    conv_id = str(item.get("id") or "").strip()
                    first_message = str(item.get("first_user_message") or "").strip() or "（无首条用户消息）"
                    updated_at = str(item.get("updated_at") or "").strip()
                    lines.append(f"{index}. [{conv_id}] 标题：{title}")
                    lines.append(f"   首条用户消息：{first_message}")
                    if updated_at:
                        lines.append(f"   更新时间：{updated_at}")
                recent_text = "\n".join(lines)
                template = self.load_prompt("recent_conversations").strip()
                if template and "{recent_conversations}" in template:
                    return template.format(recent_conversations=recent_text)
                if template:
                    return f"{template}\n\n{recent_text}"
                return f"## 最近对话（仅当前工作区）\n\n{recent_text}"
            except Exception:
                return None

    def _tool_calls_followed_by_tools(self, conversation: List[Dict], start_idx: int, tool_calls: List[Dict]) -> bool:
            """判断指定助手消息的工具调用是否拥有后续的工具响应。"""
            if not tool_calls:
                return False
            expected_ids = [tc.get("id") for tc in tool_calls if tc.get("id")]
            if not expected_ids:
                return False
            matched: Set[str] = set()
            idx = start_idx + 1
            total = len(conversation)
            while idx < total and len(matched) < len(expected_ids):
                next_conv = conversation[idx]
                role = next_conv.get("role")
                if role == "tool":
                    call_id = next_conv.get("tool_call_id")
                    if call_id in expected_ids:
                        matched.add(call_id)
                    else:
                        break
                elif role in ("assistant", "user"):
                    break
                idx += 1
            return len(matched) == len(expected_ids)
