"""Deepagents come with planning, filesystem, and subagents."""

from collections.abc import Callable, Sequence
from typing import Any

from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware, InterruptOnConfig, TodoListMiddleware
from langchain.agents.middleware.summarization import SummarizationMiddleware
from langchain.agents.middleware.types import AgentMiddleware
from langchain.agents.structured_output import ResponseFormat
from langchain_anthropic import ChatAnthropic
from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from langgraph.cache.base import BaseCache
from langgraph.graph.state import CompiledStateGraph
from langgraph.store.base import BaseStore
from langgraph.types import Checkpointer

from deepagents.backends.protocol import BackendFactory, BackendProtocol
from deepagents.middleware.filesystem import FilesystemMiddleware
from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware
from deepagents.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
from deepagents.realtimex_middleware.agent_memory import AgentMemoryMiddleware
from deepagents.realtimex_middleware.skills.middleware import SkillsMiddleware

BASE_AGENT_PROMPT = "In order to complete the objective that the user asks of you, you have access to a number of standard tools."


def get_default_model() -> ChatAnthropic:
    """Get the default model for deep agents.

    Returns:
        ChatAnthropic instance configured with Claude Sonnet 4.
    """
    return ChatAnthropic(
        model_name="claude-sonnet-4-5-20250929",
        max_tokens=20000,
    )


def create_realtimex_deep_agent(
    model: str | BaseChatModel | None = None,
    tools: Sequence[BaseTool | Callable | dict[str, Any]] | None = None,
    *,
    system_prompt: str | None = None,
    prompt: str | None = None,
    middleware: Sequence[AgentMiddleware] = (),
    subagents: list[SubAgent | CompiledSubAgent] | None = None,
    response_format: ResponseFormat | None = None,
    context_schema: type[Any] | None = None,
    checkpointer: Checkpointer | None = None,
    store: BaseStore | None = None,
    backend: BackendProtocol | BackendFactory | None = None,
    interrupt_on: dict[str, bool | InterruptOnConfig] | None = None,
    debug: bool = False,
    name: str | None = None,
    cache: BaseCache | None = None,
    assistant_id: str = "agent",
    enable_memory: bool = False,
    global_agent_path: str | None = None,
    workspace_agent_path: str | None = None,
    enable_skills: bool = False,
    global_skills_dir: str | None = None,
    workspace_skills_dir: str | None = None,
) -> CompiledStateGraph:
    """Create a deep agent.

    This agent will by default have access to a tool to write todos (write_todos),
    seven file and execution tools: ls, read_file, write_file, edit_file, glob, grep, execute,
    and a tool to call subagents.

    The execute tool allows running shell commands if the backend implements SandboxBackendProtocol.
    For non-sandbox backends, the execute tool will return an error message.

    Args:
        model: The model to use. Defaults to Claude Sonnet 4.
        tools: The tools the agent should have access to.
        system_prompt: The additional instructions the agent should have. Will go in
            the system prompt.
        middleware: Additional middleware to apply after standard middleware.
        subagents: The subagents to use. Each subagent should be a dictionary with the
            following keys:
                - `name`
                - `description` (used by the main agent to decide whether to call the
                  sub agent)
                - `prompt` (used as the system prompt in the subagent)
                - (optional) `tools`
                - (optional) `model` (either a LanguageModelLike instance or dict
                  settings)
                - (optional) `middleware` (list of AgentMiddleware)
        response_format: A structured output response format to use for the agent.
        context_schema: The schema of the deep agent.
        checkpointer: Optional checkpointer for persisting agent state between runs.
        store: Optional store for persistent storage (required if backend uses StoreBackend).
        backend: Optional backend for file storage and execution. Pass either a Backend instance
            or a callable factory like `lambda rt: StateBackend(rt)`. For execution support,
            use a backend that implements SandboxBackendProtocol.
        interrupt_on: Optional Dict[str, bool | InterruptOnConfig] mapping tool names to
            interrupt configs.
        prompt: Alias for system_prompt for backward compatibility.
        debug: Whether to enable debug mode. Passed through to create_agent.
        name: The name of the agent. Passed through to create_agent.
        cache: The cache to use for the agent. Passed through to create_agent.
        assistant_id: Agent identifier used for display in prompts (default: "agent").
        enable_memory: Enable long-term memory injection from agent.md files.
        global_agent_path: Path to global agent.md. At least one of global_agent_path or workspace_agent_path is required if enable_memory is True.
        workspace_agent_path: Workspace/project agent.md path. At least one of global_agent_path or workspace_agent_path is required if enable_memory is True.
        enable_skills: Enable agent skills middleware (progressive disclosure of SKILL.md).
        global_skills_dir: Global skills directory. At least one of global_skills_dir or workspace_skills_dir is required if enable_skills is True.
        workspace_skills_dir: Workspace/project skills directory. At least one of global_skills_dir or workspace_skills_dir is required if enable_skills is True. Workspace skills override global skills when names conflict.

    Returns:
        A configured deep agent.
    """  # noqa: E501
    if model is None:
        model = get_default_model()

    # RealTimeX wrappers previously passed `prompt`; keep accepting it for compatibility.
    system_prompt = system_prompt if system_prompt is not None else prompt

    if (
        model.profile is not None
        and isinstance(model.profile, dict)
        and "max_input_tokens" in model.profile
        and isinstance(model.profile["max_input_tokens"], int)
    ):
        trigger = ("fraction", 0.85)
        keep = ("fraction", 0.10)
    else:
        trigger = ("tokens", 170000)
        keep = ("messages", 6)

    memory_middleware: list[AgentMiddleware] = []
    if enable_memory:
        if global_agent_path is None and workspace_agent_path is None:
            raise ValueError("At least one of global_agent_path or workspace_agent_path must be provided when enable_memory is True.")  # noqa: EM101, TRY003
        memory_middleware.append(
            AgentMemoryMiddleware(
                global_agent_path=global_agent_path,
                workspace_agent_path=workspace_agent_path,
            )
        )

    skills_middleware: list[AgentMiddleware] = []
    if enable_skills:
        if not global_skills_dir and not workspace_skills_dir:
            raise ValueError("At least one of global_skills_dir or workspace_skills_dir must be provided when enable_skills is True.")  # noqa: EM101, TRY003
        skills_backend = backend  # may be BackendProtocol or factory; middleware resolves
        skills_middleware.append(
            SkillsMiddleware(
                skills_dir=global_skills_dir or workspace_skills_dir,
                assistant_id=assistant_id,
                project_skills_dir=workspace_skills_dir,
                backend=skills_backend,
            )
        )

    deepagent_middleware = [
        TodoListMiddleware(),
        FilesystemMiddleware(backend=backend),
        SubAgentMiddleware(
            default_model=model,
            default_tools=tools,
            subagents=subagents if subagents is not None else [],
            default_middleware=[
                TodoListMiddleware(),
                FilesystemMiddleware(backend=backend),
                SummarizationMiddleware(
                    model=model,
                    trigger=trigger,
                    keep=keep,
                    trim_tokens_to_summarize=None,
                ),
                AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
                PatchToolCallsMiddleware(),
            ],
            default_interrupt_on=interrupt_on,
            general_purpose_agent=True,
        ),
        SummarizationMiddleware(
            model=model,
            trigger=trigger,
            keep=keep,
            trim_tokens_to_summarize=None,
        ),
        AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
        PatchToolCallsMiddleware(),
    ]
    if memory_middleware:
        deepagent_middleware.extend(memory_middleware)
    if skills_middleware:
        deepagent_middleware.extend(skills_middleware)
    if middleware:
        deepagent_middleware.extend(middleware)
    if interrupt_on is not None:
        deepagent_middleware.append(HumanInTheLoopMiddleware(interrupt_on=interrupt_on))

    return create_agent(
        model,
        system_prompt=system_prompt + "\n\n" + BASE_AGENT_PROMPT if system_prompt else BASE_AGENT_PROMPT,
        tools=tools,
        middleware=deepagent_middleware,
        response_format=response_format,
        context_schema=context_schema,
        checkpointer=checkpointer,
        store=store,
        debug=debug,
        name=name,
        cache=cache,
    ).with_config({"recursion_limit": 1000})
