"""
Director Agent Tools
用于 Agent-Native 协作模式的工具集

结合 skills-agent-proto 的设计模式，使用 ToolRuntime 访问运行时状态
"""
from langchain.tools import tool, ToolRuntime
from .context import SkillAgentContext
from typing import List, Optional, Dict, Any
import json

# Director 专用的状态键
PLAN_STATE_KEY = "director_plan"
INBOX_STATE_KEY = "director_inbox"
ANNOTATION_STATE_KEY = "director_annotations"
CONTEXT_STATE_KEY = "director_context"


@tool
def update_plan(
    steps: List[str],
    current_step_index: int = 0,
    status: str = 'planning',
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Update the current execution plan displayed to the user.

    This tool updates the director's plan which will be sent to the frontend
    via WebSocket events. The plan shows the user what steps the agent is
    working on and the current progress.

    Args:
        steps: List of plan steps (e.g. ["Analyze context", "Create outline", "Write draft"])
        current_step_index: Index of the current step (0-based)
        status: Current status ('planning', 'writing', 'reviewing', 'idle')

    Returns:
        Confirmation message with plan details

    Example:
        update_plan(steps=["Research", "Outline", "Write"], current_step_index=0, status="planning")
    """
    if runtime is None:
        return "Error: runtime context not available"

    # 存储计划状态到 runtime.state
    plan_data = {
        "steps": steps,
        "current_step_index": current_step_index,
        "status": status
    }
    runtime.state[PLAN_STATE_KEY] = plan_data

    step_info = ""
    if steps and current_step_index < len(steps):
        current_step = steps[current_step_index]
        step_info = f"Current: [{current_step_index + 1}/{len(steps)}] {current_step}"

    return f"""✓ Plan updated with {len(steps)} steps
Status: {status}
{step_info}

Steps:
{chr(10).join(f'{i+1}. {step}' for i, step in enumerate(steps))}"""


@tool
def add_inbox_task(
    title: str,
    description: str,
    task_type: str = 'review',
    options: Optional[List[str]] = None,
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Add a task to the Director's Inbox for human review/decision.

    This tool creates a task that will appear in the user's inbox, allowing
    for human-in-the-loop collaboration. The user can approve, reject, or
    provide feedback on the task.

    Args:
        title: Short title of the task (e.g. "Review Episode 1 Outline")
        description: Detailed description or question for the user
        task_type: Type of task ('review', 'decision', 'notification')
        options: List of available options for decision (e.g. ["Approve", "Reject"])

    Returns:
        Confirmation message with task details

    Example:
        add_inbox_task(
            title="Review Character Arc",
            description="Does this character development make sense?",
            task_type="review",
            options=["Approve", "Request Changes"]
        )
    """
    if runtime is None:
        return "Error: runtime context not available"

    # 确保 inbox 列表存在
    if INBOX_STATE_KEY not in runtime.state:
        runtime.state[INBOX_STATE_KEY] = []

    # 创建任务
    task = {
        "id": f"task_{len(runtime.state[INBOX_STATE_KEY])}_{hash(title) % 10000}",
        "title": title,
        "description": description,
        "type": task_type,
        "options": options or ["Approve", "Reject"],
        "timestamp": None  # Will be set by WebSocket handler
    }

    # 添加到 inbox
    runtime.state[INBOX_STATE_KEY].append(task)

    return f"""✓ Task added to Director's Inbox

Title: {title}
Type: {task_type}
Description: {description[:100]}{'...' if len(description) > 100 else ''}

Waiting for director's response..."""


@tool
def add_annotation(
    content: str,
    annotation_type: str = 'review',
    suggestion: str = '',
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Add an annotation/comment to the canvas for the user to see.

    This tool creates annotations that appear on the content canvas,
    highlighting issues or providing feedback without interrupting
    the agent's workflow.

    Args:
        content: The text content being annotated or description of issue
        annotation_type: Type of annotation ('consistency', 'grammar', 'style', 'plot', 'review')
        suggestion: Suggested fix or improvement

    Returns:
        Confirmation message with annotation details

    Example:
        add_annotation(
            content="Character name changed from 'John' to 'Jon'",
            annotation_type="consistency",
            suggestion="Use consistent spelling throughout"
        )
    """
    if runtime is None:
        return "Error: runtime context not available"

    # 确保 annotations 列表存在
    if ANNOTATION_STATE_KEY not in runtime.state:
        runtime.state[ANNOTATION_STATE_KEY] = []

    # 创建批注
    annotation = {
        "content": content,
        "type": annotation_type,
        "suggestion": suggestion,
        "timestamp": None  # Will be set by WebSocket handler
    }

    # 添加到列表
    runtime.state[ANNOTATION_STATE_KEY].append(annotation)

    return f"""✓ Annotation added to canvas

Type: {annotation_type}
Content: {content[:100]}{'...' if len(content) > 100 else ''}
{suggestion and f"Suggestion: {suggestion}" or ""}"""


@tool
def update_context(
    context_type: str,
    data: Any = None,
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Update the dynamic context panel (e.g. world state, character status).

    This tool updates the context panel shown in the left sidebar, keeping
    the user informed of the current story state, character conditions,
    and other dynamic information.

    Args:
        context_type: Type of context to update ('world', 'character', 'state', 'all')
        data: Data to update (can be string, dict, or list of state objects)

    Returns:
        Confirmation message with context details

    Example:
        update_context(
            context_type="state",
            data=[{"type": "location", "value": "Throne Room"}, {"type": "time", "value": "Evening"}]
        )
    """
    if runtime is None:
        return "Error: runtime context not available"

    # 处理不同类型的数据输入
    if isinstance(data, str):
        try:
            data = json.loads(data)
        except json.JSONDecodeError:
            # 如果不是 JSON，当作简单的键值对
            data = [{"type": context_type, "value": data}]

    # 确保 context 状态存在
    if CONTEXT_STATE_KEY not in runtime.state:
        runtime.state[CONTEXT_STATE_KEY] = {}

    # 更新上下文
    if context_type == 'all' and isinstance(data, list):
        # 更新所有状态
        runtime.state[CONTEXT_STATE_KEY] = {item['type']: item['value'] for item in data}
    elif isinstance(data, list):
        # 更新特定类型的状态
        for item in data:
            if isinstance(item, dict) and 'type' in item and 'value' in item:
                runtime.state[CONTEXT_STATE_KEY][item['type']] = item['value']
    elif isinstance(data, dict):
        # 直接更新字典
        runtime.state[CONTEXT_STATE_KEY].update(data)

    # 生成状态摘要
    states = runtime.state[CONTEXT_STATE_KEY]
    state_summary = '\n'.join(f'  • {k}: {v}' for k, v in states.items())

    return f"""✓ Context updated

Type: {context_type}
Current states:
{state_summary}"""


@tool
def write_to_canvas(
    content: str,
    append: bool = True,
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Write content directly to the canvas (main content area).

    This tool updates the main content canvas with new text. It can either
    append to existing content or replace it entirely.

    Args:
        content: The text content to write to the canvas
        append: If True, append to existing content; if False, replace entirely

    Returns:
        Confirmation with content length info

    Example:
        write_to_canvas(content="# Episode 1\\n\\nOnce upon a time...", append=False)
    """
    if runtime is None:
        return "Error: runtime context not available"

    # 获取当前内容
    current_content = runtime.state.get("canvas_content", "")

    if append:
        new_content = current_content + content
    else:
        new_content = content

    # 更新状态
    runtime.state["canvas_content"] = new_content

    lines = len(content.split('\n'))
    action = "appended to" if append else "written to"
    return f"✓ Content {action} canvas ({lines} lines, {len(content)} characters)"


@tool
def save_episode(
    episode_number: int,
    title: str,
    content: str,
    outline: Optional[str] = None,
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Save episode content to the database.

    This tool saves the episode content to the project database,
    storing the outline, full content, and metadata.

    Args:
        episode_number: Episode number (e.g., 1, 2, 3...)
        title: Episode title
        content: Full episode script/content
        outline: Episode outline (optional)

    Returns:
        Confirmation message with episode details

    Example:
        save_episode(
            episode_number=1,
            title="The Beginning",
            content="Once upon a time...",
            outline="Introduction to the main characters"
        )
    """
    if runtime is None:
        return "Error: runtime context not available"

    # 获取项目 ID
    project_id = runtime.context.project_id if runtime.context else None
    if not project_id:
        return "Error: No project ID in context"

    # 存储到运行时状态（由 WebSocket 处理器保存到数据库）
    if "episodes_to_save" not in runtime.state:
        runtime.state["episodes_to_save"] = []

    episode_data = {
        "number": episode_number,
        "title": title,
        "content": content,
        "outline": outline,
        "status": "completed"
    }
    runtime.state["episodes_to_save"].append(episode_data)

    return f"""✓ Episode {episode_number} saved

Title: {title}
Content length: {len(content)} characters
{f"Outline: {outline[:50]}..." if outline else ""}

The episode has been queued for saving to the database."""


@tool
def update_memory(
    memory_type: str,
    data: Any,
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Update the story memory system.

    This tool updates various aspects of the story memory including
    timeline events, character states, pending threads, and foreshadowing.

    Args:
        memory_type: Type of memory to update ('timeline', 'character_state', 'pending_thread', 'foreshadowing')
        data: Data to update (format depends on memory_type)

    Returns:
        Confirmation message with memory details

    Example:
        update_memory(
            memory_type="character_state",
            data={"character": "Alice", "state": "injured", "description": "Leg injury from fall"}
        )
    """
    if runtime is None:
        return "Error: runtime context not available"

    # 存储到运行时状态（由 WebSocket 处理器保存到数据库）
    if "memory_updates" not in runtime.state:
        runtime.state["memory_updates"] = []

    memory_update = {
        "type": memory_type,
        "data": data
    }
    runtime.state["memory_updates"].append(memory_update)

    # 格式化输出
    if memory_type == "timeline":
        return f"✓ Timeline event added: {data.get('event', 'Unknown event')}"
    elif memory_type == "character_state":
        return f"✓ Character state updated: {data.get('character', 'Unknown')} - {data.get('state', 'Unknown state')}"
    elif memory_type == "pending_thread":
        return f"✓ Pending thread added: {data.get('description', 'Unknown thread')}"
    elif memory_type == "foreshadowing":
        return f"✓ Foreshadowing added: {data.get('description', 'Unknown foreshadowing')}"
    else:
        return f"✓ Memory updated: {memory_type}"


@tool
def request_review(
    content: str,
    review_type: str = 'quality',
    criteria: Optional[List[str]] = None,
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Request a content review from the Director.

    This tool creates a review request that will be sent to the Director
    for approval. It can be used for quality checks, consistency reviews,
    or other types of content validation.

    Args:
        content: The content to review (or description of what to review)
        review_type: Type of review ('quality', 'consistency', 'grammar', 'plot')
        criteria: Specific criteria to check (optional)

    Returns:
        Confirmation message with review request details

    Example:
        request_review(
            content="Episode 1 dialogue between Alice and Bob",
            review_type="quality",
            criteria=["Character voice consistency", "Dialogue naturalness"]
        )
    """
    if runtime is None:
        return "Error: runtime context not available"

    # 确保 inbox 列表存在
    if INBOX_STATE_KEY not in runtime.state:
        runtime.state[INBOX_STATE_KEY] = []

    # 创建审核任务
    task = {
        "id": f"review_{len(runtime.state[INBOX_STATE_KEY])}_{hash(content) % 10000}",
        "title": f"{review_type.capitalize()} Review Required",
        "description": f"Please review the following content:\n\n{content[:500]}{'...' if len(content) > 500 else ''}",
        "type": "review",
        "review_type": review_type,
        "criteria": criteria or [],
        "options": ["Approve", "Request Changes", "Skip"],
        "timestamp": None
    }

    # 添加到 inbox
    runtime.state[INBOX_STATE_KEY].append(task)

    criteria_text = "\n".join(f"  • {c}" for c in (criteria or []))
    criteria_section = ""
    if criteria_text:
        criteria_section = f"Criteria:\n{criteria_text}\n"

    return f"""✓ Review request created

Type: {review_type}
Content: {content[:100]}{'...' if len(content) > 100 else ''}
{criteria_section}
Waiting for director's review..."""


@tool
def focus_episode(
    episode_number: int,
    title: Optional[str] = None,
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Set the agent's focus to a specific episode.

    This tool tells the system (and the user) that the agent is now
    working on a specific episode. The frontend will update the active
    canvas to match this episode.

    Args:
        episode_number: The episode number to focus on (e.g., 1, 2, 3...)
        title: The title of the episode (optional)

    Returns:
        Confirmation message

    Example:
        focus_episode(episode_number=5, title="The Turning Point")
    """
    if runtime is None:
        return "Error: runtime context not available"

    # 更新运行时上下文
    runtime.context.active_episode_number = episode_number
    if title:
        runtime.context.active_episode_title = title

    # 存储到状态中，以便 WebSocket 发送通知
    runtime.state["focus_change"] = {
        "episodeNumber": episode_number,
        "episodeTitle": title
    }

    return f"✓ Focused on Episode {episode_number}{f': {title}' if title else ''}"


@tool
def list_episodes(
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    List all episodes in the current project.

    This tool returns a list of all episodes with their numbers, titles,
    and current status. Use this to see what has been written and what
    is still pending.

    Returns:
        A formatted list of episodes
    """
    if runtime is None:
        return "Error: runtime context not available"

    project_id = runtime.context.project_id if runtime.context else None
    if not project_id:
        return "Error: No project ID in context"

    # 这里我们不直接读取数据库，而是告诉 agent 我们会通过 websocket 返回或从 context 中读取
    # 实际上，context 应该已经包含了这些信息
    episodes = getattr(runtime.context, 'episodes', [])
    if not episodes:
        return "No episodes found or episode list not loaded in context."

    lines = ["Episodes in project:"]
    for ep in episodes:
        lines.append(f"- EP{ep.get('number')}: {ep.get('title')} [{ep.get('status')}]")

    return "\n".join(lines)


@tool
def get_episode_data(
    episode_number: Optional[int] = None,
    include_outline: Optional[bool] = None,
    include_content: Optional[bool] = None,
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Get episode data (outline and/or content) from the database.

    This tool retrieves episode information from the project database.
    Use this when you need to:
    - Review the current outline or content before making changes
    - Understand what the user is referring to when they say vague things like "change this"
    - Get context for a specific episode number

    IMPORTANT: If both include_outline and include_content are None (not specified),
    this tool will automatically determine what to return based on the user's current view:
    - If user is viewing the outline: returns outline only
    - If user is viewing the content: returns content only
    - If user is viewing both: returns both

    Args:
        episode_number: The episode number to retrieve (if None, uses current focused episode)
        include_outline: Whether to include the outline (default: auto-detect from view)
        include_content: Whether to include the content (default: auto-detect from view)

    Returns:
        The requested episode data in a structured format

    Example:
        get_episode_data(episode_number=1, include_outline=True, include_content=True)
    """
    if runtime is None:
        return "Error: runtime context not available"

    project_id = runtime.context.project_id if runtime.context else None
    if not project_id:
        return "Error: No project ID in context"

    # 如果没有指定剧集号，使用当前焦点剧集
    if episode_number is None:
        episode_number = getattr(runtime.context, 'active_episode_number', None)
        if episode_number is None:
            return "Error: No episode number specified and no episode is currently focused. Please use focus_episode() first."

    # 根据视图上下文自动判断返回内容
    view_context = runtime.state.get("current_view_context", {})
    view_type = view_context.get("viewType", "")

    if include_outline is None and include_content is None:
        # 自动判断：根据用户当前在看什么来决定
        if view_type == "outline":
            include_outline = True
            include_content = False
        elif view_type == "content":
            include_outline = False
            include_content = True
        elif view_type in ["both", "full"]:
            include_outline = True
            include_content = True
        else:
            # 默认返回两者
            include_outline = True
            include_content = True

    # 存储查询请求到状态中，由 WebSocket 处理器执行数据库查询
    if "episode_data_requests" not in runtime.state:
        runtime.state["episode_data_requests"] = []

    request = {
        "episode_number": episode_number,
        "include_outline": include_outline if include_outline else False,
        "include_content": include_content if include_content else False
    }
    runtime.state["episode_data_requests"].append(request)

    # 返回提示信息
    return f"""✓ Requesting episode data for EP{episode_number}
(Outline: {'Yes' if include_outline else 'No'}, Content: {'Yes' if include_content else 'No'})

The episode data will be retrieved and provided to you. Use this data to understand the user's request and make appropriate changes."""


@tool
def get_current_view(
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Get the current view context (what the user is currently looking at).

    This tool returns information about what part of the interface the user
    is currently viewing, such as:
    - viewType: 'outline', 'content', 'both', or 'none'
    - episodeNumber: the episode number currently focused
    - Any other view-specific context

    Use this tool when you need to understand what the user is referring to
    with vague expressions like "change this" or "fix this".

    Returns:
        The current view context information

    Example:
        get_current_view() -> "User is viewing: outline of EP1"
    """
    if runtime is None:
        return "Error: runtime context not available"

    view_context = runtime.state.get("current_view_context", {})
    view_type = view_context.get("viewType", "unknown")
    episode_number = view_context.get("episodeNumber")

    # 构建友好的描述
    view_descriptions = {
        "outline": "大纲编辑器",
        "content": "内容编辑器/画布",
        "both": "完整视图（大纲+内容）",
        "full": "完整视图（大纲+内容）",
        "none": "未确定"
    }

    view_desc = view_descriptions.get(view_type, "未知视图")
    episode_info = f"，聚焦剧集: EP{episode_number}" if episode_number else ""

    result = f"""【用户当前视图】
视图类型: {view_type} ({view_desc}){episode_info}
"""

    # 添加建议
    if view_type == "outline":
        result += "\n建议: 用户可能希望调整大纲。使用 get_episode_data(include_outline=True) 获取大纲内容。"
    elif view_type == "content":
        result += "\n建议: 用户可能希望调整内容。使用 get_episode_data(include_content=True) 获取内容。"
    elif view_type == "both":
        result += "\n建议: 用户可能希望调整大纲或内容。使用 get_episode_data() 获取完整数据。"

    return result


@tool
def update_episode(
    episode_number: int,
    title: Optional[str] = None,
    content: Optional[str] = None,
    status: Optional[str] = None,
    outline: Optional[str] = None,
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Update an existing episode's details.

    Use this tool to update the content, title, or status of an episode.
    The changes will be persisted to the database and updated in the UI.

    Args:
        episode_number: The episode number to update
        title: New title for the episode (optional)
        content: New content/script (optional)
        status: New status (e.g., 'completed', 'writing', 'pending')
        outline: New outline (optional)

    Returns:
        Confirmation message
    """
    if runtime is None:
        return "Error: runtime context not available"

    project_id = runtime.context.project_id if runtime.context else None
    if not project_id:
        return "Error: No project ID in context"

    # 存储到状态中，由 WebSocket 处理器保存
    if "episodes_to_update" not in runtime.state:
        runtime.state["episodes_to_update"] = []

    update_data = {
        "number": episode_number,
    }
    if title is not None: update_data["title"] = title
    if content is not None: update_data["content"] = content
    if status is not None: update_data["status"] = status
    if outline is not None: update_data["outline"] = outline

    runtime.state["episodes_to_update"].append(update_data)

    return f"✓ Episode {episode_number} queued for update."


# ============================================
# 附加工具：需要数据库访问
# ============================================

@tool
def create_episode(
    episode_number: int,
    analyze_previous_memory: bool = True,
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Create a new episode with full workflow including memory analysis.

    This tool orchestrates the complete episode creation process:
    1. Analyzes previous episode's memory (if analyze_previous_memory=True)
    2. Creates execution plan
    3. Generates outline
    4. Writes dialogue/script
    5. Performs quality review
    6. Updates memory system

    Args:
        episode_number: The episode number to create (e.g., 1, 2, 3...)
        analyze_previous_memory: Whether to analyze previous episode's memory first (default: True)

    Returns:
        Status message with creation details

    Example:
        create_episode(episode_number=2, analyze_previous_memory=True)
    """
    if runtime is None:
        return "Error: runtime context not available"

    project_id = runtime.context.project_id if runtime.context else None
    if not project_id:
        return "Error: No project ID in context"

    # 更新当前剧集焦点
    runtime.context.active_episode_number = episode_number

    # 存储剧集创作请求到状态中，由 WebSocket 处理器异步执行
    if "episodes_to_create" not in runtime.state:
        runtime.state["episodes_to_create"] = []

    creation_request = {
        "episode_number": episode_number,
        "analyze_previous_memory": analyze_previous_memory,
        "project_id": project_id,
        "status": "pending"
    }
    runtime.state["episodes_to_create"].append(creation_request)

    # 更新执行计划
    plan_steps = [
        f"分析 EP{episode_number - 1 if episode_number > 1 else 'N/A'} 的记忆系统" if analyze_previous_memory and episode_number > 1 else "跳过记忆分析（首集）",
        f"生成 EP{episode_number} 大纲",
        f"创作 EP{episode_number} 对话内容",
        f"执行质量审核",
        f"更新记忆系统"
    ]

    # 存储 plan 到状态中
    runtime.state[PLAN_STATE_KEY] = {
        "steps": plan_steps,
        "current_step_index": 0,
        "status": "planning"
    }

    return f"""✓ Episode creation started for EP{episode_number}

Steps:
1. 分析上一集记忆: {'是' if analyze_previous_memory and episode_number > 1 else '否（首集）'}
2. 生成大纲
3. 创作对话内容
4. 质量审核
5. 更新记忆系统

The creation process is running in the background.
Progress will be updated automatically."""


# ============================================
# 剧集审查规划工具
# ============================================

def _parse_episode_selector(selector: str, runtime: ToolRuntime[SkillAgentContext]) -> List[int]:
    """
    解析剧集选择器，返回剧集号列表

    支持的格式:
    - 单集: "1", "EP1", "episode 1" -> [1]
    - 多集: "1,2,3", "episodes 1, 2, 3" -> [1, 2, 3]
    - 范围: "1-5", "episodes 1-5" -> [1, 2, 3, 4, 5]
    - 全部: "all", "全部剧集" -> all episodes from context
    """
    selector_lower = selector.lower().strip()
    episodes = getattr(runtime.context, 'episodes', [])
    all_episode_numbers = [ep.get('number') for ep in episodes if ep.get('number')]

    # 处理 "all" 或 "全部剧集"
    if selector_lower in ['all', '全部', '全部剧集', 'all episodes']:
        return sorted(all_episode_numbers)

    # 处理范围 "1-5" 或 "episodes 1-5"
    if '-' in selector_lower:
        try:
            # 提取数字
            parts = selector_lower.split('-')
            start_part = parts[0].strip()
            end_part = parts[1].strip()

            # 提取起始和结束数字
            start = int(''.join(filter(str.isdigit, start_part)))
            end = int(''.join(filter(str.isdigit, end_part)))

            return list(range(start, end + 1))
        except (ValueError, IndexError):
            pass

    # 处理逗号分隔 "1,2,3"
    if ',' in selector_lower:
        try:
            numbers = selector_lower.split(',')
            result = []
            for num in numbers:
                extracted = int(''.join(filter(str.isdigit, num)))
                result.append(extracted)
            return sorted(set(result))
        except ValueError:
            pass

    # 处理单个剧集 "1" 或 "episode 1"
    try:
        number = int(''.join(filter(str.isdigit, selector_lower)))
        return [number]
    except ValueError:
        pass

    # 无法解析
    return []


@tool
def plan_episode_review(
    episode_selector: str,
    review_criteria: Optional[List[str]] = None,
    review_dimensions: Optional[List[str]] = None,
    runtime: ToolRuntime[SkillAgentContext] = None
) -> str:
    """
    Plan and execute an episode review for one or more episodes.

    This tool allows the agent to plan a comprehensive review of episodes.
    It supports:
    - Single episode review (e.g., "1", "EP1", "episode 1")
    - Multiple episodes (e.g., "1,2,3", "episodes 1-3")
    - All episodes (e.g., "all", "全部剧集")
    - Episode ranges (e.g., "1-5", "episodes 1 through 5")

    After review, results are automatically pushed to the review system.

    Args:
        episode_selector: Which episodes to review
            - Single: "1", "EP1", "episode 1"
            - Multiple: "1,2,3", "episodes 1, 2, 3"
            - Range: "1-5", "episodes 1-5"
            - All: "all", "全部剧集", "all episodes"
        review_criteria: Specific criteria to check (optional)
            Examples: ["Character consistency", "Plot logic", "Dialogue quality"]
        review_dimensions: Dimensions to evaluate (optional)
            Examples: ["quality", "consistency", "plot", "dialogue", "emotion"]

    Returns:
        Summary of the review plan and execution status

    Example:
        plan_episode_review(
            episode_selector="1-3",
            review_criteria=["Character consistency", "Plot progression"],
            review_dimensions=["quality", "consistency", "plot"]
        )
    """
    if runtime is None:
        return "Error: runtime context not available"

    project_id = runtime.context.project_id if runtime.context else None
    if not project_id:
        return "Error: No project ID in context"

    # 解析剧集选择器
    episodes_to_review = _parse_episode_selector(episode_selector, runtime)

    if not episodes_to_review:
        return f"Error: No valid episodes found for selector: '{episode_selector}'"

    # 设置默认的审查维度
    if review_dimensions is None:
        review_dimensions = ["quality", "consistency", "plot"]

    # 设置默认的审查标准
    if review_criteria is None:
        review_criteria = [
            "角色行为一致性",
            "剧情逻辑合理性",
            "对话自然度",
            "场景转换流畅性",
            "情感表达准确性"
        ]

    # 存储审查计划到状态中
    if "review_plans" not in runtime.state:
        runtime.state["review_plans"] = []

    review_plan = {
        "episodes": episodes_to_review,
        "criteria": review_criteria,
        "dimensions": review_dimensions,
        "project_id": project_id,
        "status": "pending",
        "created_at": None  # Will be set by WebSocket handler
    }
    runtime.state["review_plans"].append(review_plan)

    # 更新执行计划
    plan_steps = [
        f"解析审查请求: {len(episodes_to_review)} 集待审查",
        f"获取剧集数据: EP{', EP'.join(map(str, episodes_to_review))}",
        f"执行多维审查: {', '.join(review_dimensions)}",
        f"生成审查报告",
        f"推送到审核系统"
    ]

    if PLAN_STATE_KEY in runtime.state:
        runtime.state[PLAN_STATE_KEY] = {
            "steps": plan_steps,
            "current_step_index": 0,
            "status": "reviewing"
        }

    episode_list_str = ", ".join(f"EP{ep}" for ep in episodes_to_review)
    criteria_str = "\n".join(f"  • {c}" for c in review_criteria)
    dimensions_str = ", ".join(review_dimensions)

    return f"""✓ 剧集审查计划已创建

审查剧集: {episode_list_str}
审查维度: {dimensions_str}

审查标准:
{criteria_str}

执行步骤:
1. 解析审查请求: {len(episodes_to_review)} 集待审查
2. 获取剧集数据: EP{', EP'.join(map(str, episodes_to_review))}
3. 执行多维审查: {dimensions_str}
4. 生成审查报告
5. 推送到审核系统

审查将在后台执行，完成后结果将自动推送到审核系统。
您可以前往「审核系统」标签页查看详细结果。"""


# 导出工具列表
DIRECTOR_TOOLS = [
    update_plan,
    add_inbox_task,
    add_annotation,
    update_context,
    write_to_canvas,
    save_episode,
    update_memory,
    request_review,
    focus_episode,
    list_episodes,
    get_current_view,  # 新增获取当前视图工具
    get_episode_data,  # 新增剧集数据查询工具
    update_episode,
    create_episode,  # 新增剧集创作工具
    plan_episode_review  # 新增剧集审查规划工具
]
