"""
OpenCode Style agent 的 model 节点实现。

本模块从 `factory.py` 中拆分出“model/amodel 节点”的执行逻辑：
- 调用 `get_bound_model` / `handle_model_output` 执行一次模型调用（sync / async）
- 基于 OpenCode 退出语义更新状态字段（step / should_end / finish_reason 等）
- 将本次调用结果包装为一组 `Command(update=...)` 供 LangGraph 使用

注意：这里只关心单个 model 节点的执行，不负责图路由（edges）或工具节点。
"""

from __future__ import annotations

from typing import Any, Callable, Dict, List, Tuple
import logging

from langchain.agents.middleware.types import AgentMiddleware, AgentState, ContextT
from langchain.agents.structured_output import ResponseFormat
from langchain_core.messages import AIMessage
from langgraph.types import Command
from langgraph.runtime import Runtime

from .exit_logic import determine_should_end, extract_finish_reason
from .tool_and_model_binding import get_bound_model, handle_model_output

logger = logging.getLogger(__name__)


def make_model_nodes(
    *,
    model: Any,
    default_tools: List[Any],
    system_message: Any,
    initial_response_format: ResponseFormat[Any] | None,
    max_steps: int,
    name: str | None,
    structured_output_tools: Dict[str, Any],
    tool_strategy_for_setup: Any,
    wrap_model_call_handler: Any,
    awrap_model_call_handler: Any,
    tool_node: Any,
    wrap_tool_call_wrapper: Any,
    awrap_tool_call_wrapper: Any,
    dynamic_tool_error_template: str,
) -> Tuple[
    Callable[[AgentState[Any], Runtime[ContextT]], List[Command[Any]]],
    Callable[[AgentState[Any], Runtime[ContextT]], Any],
]:
    """根据传入配置构造 sync/async model 节点函数."""

    def _execute_model_sync(request) -> Any:
        model_, effective_response_format = get_bound_model(
            request=request,
            structured_output_tools=structured_output_tools,
            initial_response_format=initial_response_format,
            tool_strategy_for_setup=tool_strategy_for_setup,
            tool_node=tool_node,
            wrap_tool_call_wrapper=wrap_tool_call_wrapper,
            awrap_tool_call_wrapper=awrap_tool_call_wrapper,
            dynamic_tool_error_template=dynamic_tool_error_template,
        )
        messages = request.messages
        if request.system_message:
            messages = [request.system_message, *messages]

        output = model_.invoke(messages)
        if name:
            output.name = name

        handled_output = handle_model_output(
            output=output,
            effective_response_format=effective_response_format,
            structured_output_tools=structured_output_tools,
        )
        messages_list = handled_output["messages"]
        structured_response = handled_output.get("structured_response")

        from langchain.agents.middleware.types import ModelResponse as _ModelResponse

        return _ModelResponse(
            result=messages_list,
            structured_response=structured_response,
        )

    def model_node(state: AgentState[Any], runtime: Runtime[ContextT]) -> List[Command[Any]]:
        from langchain.agents.middleware.types import ModelRequest as _ModelRequest

        request = _ModelRequest(
            model=model,
            tools=default_tools,
            system_message=system_message,
            response_format=initial_response_format,
            messages=state["messages"],
            tool_choice=None,
            state=state,
            runtime=runtime,
        )

        if wrap_model_call_handler is None:
            model_response = _execute_model_sync(request)
            middleware_commands: List[Command[Any]] = []
        else:
            result = wrap_model_call_handler(request, _execute_model_sync)
            model_response = result.model_response
            middleware_commands = getattr(result, "commands", [])

        last_ai_message = None
        for msg in reversed(model_response.result):
            if isinstance(msg, AIMessage):
                last_ai_message = msg
                break

        should_end, end_reason = determine_should_end(state, last_ai_message, max_steps)
        finish_reason = extract_finish_reason(last_ai_message)

        if should_end:
            logger.info(
                "OpenCode-style agent stopping (sync) | end_reason=%s, step=%s, finish_reason=%s",
                end_reason,
                state.get("step", 0) + 1,
                finish_reason,
            )

        finish_reasons = list(state.get("finish_reasons", []))
        if finish_reason:
            finish_reasons.append(finish_reason)

        state_update: Dict[str, Any] = {
            "step": state.get("step", 0) + 1,
            "max_steps": max_steps,
            "should_end": should_end,
            "finish_reason": finish_reason,
            "end_reason": end_reason,
            "finish_reasons": finish_reasons,
        }

        base_state: Dict[str, Any] = {"messages": model_response.result}
        if model_response.structured_response is not None:
            base_state["structured_response"] = model_response.structured_response
            state_update["structured_response"] = model_response.structured_response

        commands: List[Command[Any]] = [Command(update=base_state), Command(update=state_update)]
        commands.extend(middleware_commands)

        return commands

    async def _execute_model_async(request) -> Any:
        model_, effective_response_format = get_bound_model(
            request=request,
            structured_output_tools=structured_output_tools,
            initial_response_format=initial_response_format,
            tool_strategy_for_setup=tool_strategy_for_setup,
            tool_node=tool_node,
            wrap_tool_call_wrapper=wrap_tool_call_wrapper,
            awrap_tool_call_wrapper=awrap_tool_call_wrapper,
            dynamic_tool_error_template=dynamic_tool_error_template,
        )
        messages = request.messages
        if request.system_message:
            messages = [request.system_message, *messages]

        output = await model_.ainvoke(messages)
        if name:
            output.name = name

        handled_output = handle_model_output(
            output=output,
            effective_response_format=effective_response_format,
            structured_output_tools=structured_output_tools,
        )
        messages_list = handled_output["messages"]
        structured_response = handled_output.get("structured_response")

        from langchain.agents.middleware.types import ModelResponse as _ModelResponse

        return _ModelResponse(
            result=messages_list,
            structured_response=structured_response,
        )

    async def amodel_node(state: AgentState[Any], runtime: Runtime[ContextT]) -> List[Command[Any]]:
        from langchain.agents.middleware.types import ModelRequest as _ModelRequest

        request = _ModelRequest(
            model=model,
            tools=default_tools,
            system_message=system_message,
            response_format=initial_response_format,
            messages=state["messages"],
            tool_choice=None,
            state=state,
            runtime=runtime,
        )

        if awrap_model_call_handler is None:
            model_response = await _execute_model_async(request)
            middleware_commands: List[Command[Any]] = []
        else:
            result = await awrap_model_call_handler(request, _execute_model_async)
            model_response = result.model_response
            middleware_commands = getattr(result, "commands", [])

        last_ai_message = None
        for msg in reversed(model_response.result):
            if isinstance(msg, AIMessage):
                last_ai_message = msg
                break

        should_end, end_reason = determine_should_end(state, last_ai_message, max_steps)
        finish_reason = extract_finish_reason(last_ai_message)

        if should_end:
            logger.info(
                "OpenCode-style agent stopping (async) | end_reason=%s, step=%s, finish_reason=%s",
                end_reason,
                state.get("step", 0) + 1,
                finish_reason,
            )

        finish_reasons = list(state.get("finish_reasons", []))
        if finish_reason:
            finish_reasons.append(finish_reason)

        state_update: Dict[str, Any] = {
            "step": state.get("step", 0) + 1,
            "max_steps": max_steps,
            "should_end": should_end,
            "finish_reason": finish_reason,
            "end_reason": end_reason,
            "finish_reasons": finish_reasons,
        }

        base_state: Dict[str, Any] = {"messages": model_response.result}
        if model_response.structured_response is not None:
            base_state["structured_response"] = model_response.structured_response
            state_update["structured_response"] = model_response.structured_response

        commands: List[Command[Any]] = [Command(update=base_state), Command(update=state_update)]
        commands.extend(middleware_commands)

        return commands

    return model_node, amodel_node


__all__ = ["make_model_nodes"]

