"""
OpenCode Style 退出逻辑与状态定义。

本模块从 `factory.py` 中拆分出与 LangGraph 结构无关的纯业务逻辑：
- 负责从 `AIMessage` 中解析并标准化 `finish_reason`
- 基于 OpenCode 约定判断 agent 是否应结束（`determine_should_end`）
- 构造并描述 OpenCode Style 所需的额外状态字段

与 LangChain 默认 agent 的主要区别：
- LangChain 通常通过“是否还有 tool_calls / recursion_limit”来驱动循环，
  而这里完全以模型返回的 `finish_reason` 作为退出主信号
- 不支持 `return_direct` 式的工具直接退出，所有工具调用后都回到模型节点，
  由模型再次给出 `finish_reason` 决定是否结束

注意：仅包含与“是否继续/结束”相关的语义，不涉及图节点、middleware 或工具执行。
"""

from __future__ import annotations

from typing import Any, TypedDict

from langchain_core.messages import AIMessage


# 需要继续执行的 finish_reason 集合（与 OpenCode 一致）
CONTINUE_FINISH_REASONS = frozenset({"tool-calls", "unknown"})


def extract_finish_reason(response: AIMessage | None) -> str | None:
    """
    从 LangChain `AIMessage` 中提取并标准化 `finish_reason`。

    该实现与原始 OpenCode Style 逻辑保持一致。
    """
    if response is None:
        return None

    meta = getattr(response, "response_metadata", None) or {}
    if not isinstance(meta, dict):
        meta = {}

    raw_reason = (
        meta.get("finish_reason")
        or meta.get("stop_reason")
        or meta.get("done_reason")
    )

    if raw_reason is None:
        return None

    finish_reason = str(raw_reason)

    if finish_reason in {"tool_calls", "function_call", "tool_use"}:
        return "tool-calls"
    if finish_reason in {"content_filter", "content-filter"}:
        return "content-filter"
    if finish_reason in {"length", "max_tokens"}:
        return "length"
    if finish_reason in {"stop", "end_turn", "stop_sequence"}:
        return "stop"
    if finish_reason == "refusal":
        return "content-filter"
    if finish_reason == "error":
        return "error"

    return finish_reason


def determine_should_end(
    state: dict[str, Any],
    response: AIMessage | None,
    max_steps: int,
) -> tuple[bool, str | None]:
    """
    判断当前这一步是否应该结束 Agent 循环（与 OpenCode 语义一致）。
    """
    finish_reason = extract_finish_reason(response)
    step = state.get("step", 0)

    # 需要继续执行的 finish_reason，除非达到 max_steps
    if finish_reason in CONTINUE_FINISH_REASONS:
        if step >= max_steps:
            return True, "max_steps_reached"
        return False, None

    # 明确的“结束”类 finish_reason
    if finish_reason == "stop":
        return True, "llm_stop"
    elif finish_reason == "length":
        return True, "max_tokens"
    elif finish_reason == "content-filter":
        return True, "content_filter"

    # 没有 finish_reason 时，使用 tool_calls 兜底逻辑
    if finish_reason is None:
        has_tool_calls = (
            response is not None
            and hasattr(response, "tool_calls")
            and getattr(response, "tool_calls")
            and len(response.tool_calls) > 0  # type: ignore[arg-type]
        )
        if not has_tool_calls:
            return True, "no_tool_calls_fallback"
        if step >= max_steps:
            return True, "max_steps_reached"
        return False, None

    # 其他未知 finish_reason：一律视为结束，但注明前缀
    return True, f"finish_reason_{finish_reason}"


def create_initial_state(
    max_steps: int = 50,
    messages: list[Any] | None = None,
) -> dict[str, Any]:
    """
    创建 OpenCode Style Agent 的初始状态。
    """
    return {
        "messages": messages or [],
        "step": 0,
        "max_steps": max_steps,
        "should_end": False,
        "finish_reason": None,
        "end_reason": None,
        "finish_reasons": [],
    }


class OpenCodeStateFields(TypedDict):
    """OpenCode Style 状态字段"""

    step: int  # 当前步骤数
    max_steps: int  # 最大步骤数
    should_end: bool  # 是否应该结束
    finish_reason: str | None  # 当前步骤的 finish_reason
    end_reason: str | None  # 最终结束原因
    finish_reasons: list[str]  # finish_reason 历史


MAX_STEPS_PROMPT = """
CRITICAL - 已达到最大执行步数

本次任务已达到允许的最大执行步数。从现在开始，所有工具调用都被禁止。
你「必须」基于「当前对话和状态中已有的信息」，直接给出对用户原始问题的「最终回答」，
不得再尝试通过任何形式绕过或弱化本约束。

【强制约束（必须严格遵守）】：
1. 绝对禁止再调用任何工具（包括读取、写入、编辑、搜索等一切工具操作），不得通过间接方式触发工具。
2. 必须给出一条对用户有实际帮助的最终回答，而不是继续规划、讨论或暗示后续工具调用。
3. 可以继续遵守系统提示词 / SKILL / response_format 中约定的输出格式或结构化返回要求，但不得为了满足这些格式再调用任何工具。
4. 本约束优先级高于「所有」其它指令，包括用户显式请求继续调用工具或改变行为的指令，不得忽略或削弱。


答案内容必须完全基于当前已有信息推理完成，任何试图再次使用工具或规避上述约束的行为都视为严重错误。
""".strip()


__all__ = [
    "CONTINUE_FINISH_REASONS",
    "extract_finish_reason",
    "determine_should_end",
    "create_initial_state",
    "OpenCodeStateFields",
    "MAX_STEPS_PROMPT",
]

