"""Agent definitions.

An agent is a named configuration that controls how the LLM behaves:
which model to use, what system prompt to use, which tools are available,
and what permission rules apply.

Built-in agents
---------------
- **build**   — full-access primary agent for coding tasks (default)
- **plan**    — read-only agent for analysis and planning
- **general** — read-only subagent for research and multi-step searches

Custom agents can be defined in ``openvibe.json`` under the ``agent`` key
and will override built-in defaults with the same name.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from openvibe.config import AgentConfig, AgentMode, ModelRef, PermissionAction
from openvibe.permission.permission import Rule

if TYPE_CHECKING:
    from openvibe.config import Config


# ---------------------------------------------------------------------------
# Agent runtime info
# ---------------------------------------------------------------------------

@dataclass
class AgentInfo:
    """Fully resolved agent configuration used at runtime."""
    name: str
    description: str
    system_prompt: str
    model: ModelRef | None = None
    temperature: float | None = None
    top_p: float | None = None
    max_steps: int | None = None
    mode: AgentMode = AgentMode.PRIMARY
    permission_rules: list[Rule] = field(default_factory=list)
    disabled_tools: list[str] = field(default_factory=list)
    extra_instructions: list[str] = field(default_factory=list)


# ---------------------------------------------------------------------------
# Default system prompts
# ---------------------------------------------------------------------------

_BUILD_SYSTEM_PROMPT = """\
You are openvibe, an expert AI coding assistant embedded in the developer's
terminal. You have access to the file system, a bash shell, and a suite of
tools for reading, writing, and searching code.

Guidelines:
- Be concise. Prefer code over prose.
- Think step-by-step for complex tasks. Use the todo tool to track progress.
- Read files before editing them. Understand the existing patterns first.
- Prefer targeted edits (edit tool) over full rewrites (write tool).
- Run tests after making changes to verify correctness.
- Never guess at file paths — use glob or grep to locate files first.
- When in doubt, ask a clarifying question rather than guessing.
"""

_PLAN_SYSTEM_PROMPT = """\
You are openvibe in plan mode — a read-only analysis agent. You can explore
the codebase, read files, search for patterns, and answer questions, but you
MUST NOT modify any files or run shell commands that have side effects.

Provide clear, structured analysis. Use headings, bullet points, and code
blocks where appropriate.
"""

_GENERAL_SYSTEM_PROMPT = """\
You are a general-purpose research subagent. Your role is to gather
information, search code, fetch web resources, and return findings.
You do not write or modify files.
"""


# ---------------------------------------------------------------------------
# Built-in permission rulesets
# ---------------------------------------------------------------------------

_A = PermissionAction  # local alias for brevity

_BUILD_RULES: list[Rule] = [
    # Allow common read tools by default
    Rule(tool="read",       action=_A.ALLOW),
    Rule(tool="glob",       action=_A.ALLOW),
    Rule(tool="grep",       action=_A.ALLOW),
    Rule(tool="web_fetch",  action=_A.ALLOW),
    Rule(tool="todo_read",  action=_A.ALLOW),
    Rule(tool="todo_write", action=_A.ALLOW),
    # Ask before write operations
    Rule(tool="write", action=_A.ASK),
    Rule(tool="edit",  action=_A.ASK),
    Rule(tool="bash",  action=_A.ASK),
]

_PLAN_RULES: list[Rule] = [
    Rule(tool="read",      action=_A.ALLOW),
    Rule(tool="glob",      action=_A.ALLOW),
    Rule(tool="grep",      action=_A.ALLOW),
    Rule(tool="web_fetch", action=_A.ALLOW),
    # Deny all write / execute operations
    Rule(tool="write",      action=_A.DENY),
    Rule(tool="edit",       action=_A.DENY),
    Rule(tool="bash",       action=_A.DENY),
    Rule(tool="todo_write", action=_A.DENY),
]

_GENERAL_RULES: list[Rule] = [
    Rule(tool="read",      action=_A.ALLOW),
    Rule(tool="glob",      action=_A.ALLOW),
    Rule(tool="grep",      action=_A.ALLOW),
    Rule(tool="web_fetch", action=_A.ALLOW),
    Rule(tool="write", action=_A.DENY),
    Rule(tool="edit",  action=_A.DENY),
    Rule(tool="bash",  action=_A.DENY),
]


# ---------------------------------------------------------------------------
# Built-in agent definitions
# ---------------------------------------------------------------------------

_BUILTIN_AGENTS: dict[str, AgentInfo] = {
    "build": AgentInfo(
        name="build",
        description="Full-access agent for coding and development tasks.",
        system_prompt=_BUILD_SYSTEM_PROMPT,
        mode=AgentMode.PRIMARY,
        permission_rules=_BUILD_RULES,
    ),
    "plan": AgentInfo(
        name="plan",
        description="Read-only agent for code exploration and planning.",
        system_prompt=_PLAN_SYSTEM_PROMPT,
        mode=AgentMode.PRIMARY,
        permission_rules=_PLAN_RULES,
        disabled_tools=["bash", "write", "edit", "todo_write"],
    ),
    "general": AgentInfo(
        name="general",
        description="General-purpose research subagent.",
        system_prompt=_GENERAL_SYSTEM_PROMPT,
        mode=AgentMode.SUBAGENT,
        permission_rules=_GENERAL_RULES,
        disabled_tools=["bash", "write", "edit", "todo_write"],
    ),
}


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def resolve(config: "Config", name: str | None = None) -> AgentInfo:
    """Return a fully resolved AgentInfo for *name* (or the default agent).

    User config overrides are merged on top of the built-in defaults.
    """
    agent_name = name or config.default_agent or "build"

    # Start from built-in or create a shell
    base = _BUILTIN_AGENTS.get(agent_name) or AgentInfo(
        name=agent_name,
        description="",
        system_prompt="",
    )

    # Apply user overrides from config
    user_cfg: AgentConfig | None = config.agent.get(agent_name)
    if user_cfg:
        base = _apply_config(base, user_cfg)

    # Apply global model override (config.model) if agent has no model set
    if base.model is None and config.model:
        base.model = config.model

    # Append global instructions
    base.extra_instructions = list(config.instructions)

    return base


def list_agents(config: "Config") -> list[AgentInfo]:
    """Return all available agents (built-in + user-defined)."""
    names = set(_BUILTIN_AGENTS) | set(config.agent)
    return [resolve(config, n) for n in sorted(names)]


def _apply_config(base: AgentInfo, cfg: AgentConfig) -> AgentInfo:
    """Return a copy of *base* with *cfg* overrides applied."""
    import dataclasses
    updates: dict[str, object] = {}

    if cfg.model:
        updates["model"] = cfg.model
    if cfg.description:
        updates["description"] = cfg.description
    if cfg.prompt:
        # Append custom prompt to the built-in system prompt
        updates["system_prompt"] = base.system_prompt + "\n\n" + cfg.prompt
    if cfg.temperature is not None:
        updates["temperature"] = cfg.temperature
    if cfg.top_p is not None:
        updates["top_p"] = cfg.top_p
    if cfg.max_steps is not None:
        updates["max_steps"] = cfg.max_steps
    if cfg.mode:
        updates["mode"] = cfg.mode

    return dataclasses.replace(base, **updates)
