"""Shared codebase tool definitions and handler for agents.

Provides grep, read_file, read_lines tools and a factory for creating
async tool handlers bound to a CodeExplorer instance.
"""

from __future__ import annotations

import json
import logging
from typing import TYPE_CHECKING

from .types import ToolResult

if TYPE_CHECKING:
    from ..explorer import CodeExplorer

logger = logging.getLogger(__name__)


def _build_grep_tool(project_names: list[str] | None = None) -> dict:
    """Build grep tool definition.

    Args:
        project_names: When provided, makes `project` a required enum param
            with these values (primary repo name + linked repo names).
            When None, `project` param is omitted entirely.
    """
    properties: dict = {
        "pattern": {
            "type": "string",
            "description": (
                "Regex pattern (case-insensitive). Be specific: "
                "'class FixedQueuePool', 'def get_committable_offsets', 'queue_pool.shutdown'. "
                "Never use broad terms like project names or common words."
            ),
        },
        "file_glob": {
            "type": "string",
            "description": (
                "Glob to filter which files to search. ALWAYS provide this. "
                "Use '**/filename.ext' for a specific file, '**/*.ext' for all files "
                "of a language, or '**/test_*.*' for tests. Do NOT scope to a directory path."
            ),
        },
        "context_lines": {
            "type": "integer",
            "description": "Lines of context before and after each match (default: 5).",
        },
    }
    required = ["pattern"]

    if project_names:
        properties["project"] = {
            "type": "string",
            "enum": project_names,
            "description": "Which project to search. Always specify to avoid cross-project noise.",
        }
        required.append("project")

    return {
        "type": "function",
        "function": {
            "name": "grep",
            "description": (
                "Search for text/regex inside file contents using ripgrep (case-insensitive). "
                "Returns matching lines with surrounding context. "
                "IMPORTANT: Always use file_glob to scope your search to relevant files. "
                "Searching the entire codebase without file_glob returns too many irrelevant matches. "
                "Use specific patterns like class names, function names, or variable names — never broad keywords."
            ),
            "parameters": {
                "type": "object",
                "properties": properties,
                "required": required,
            },
        },
    }


READ_FILE_TOOL = {
    "type": "function",
    "function": {
        "name": "read_file",
        "description": (
            "Read an entire file by path. Prefer read_lines when grep already showed "
            "you the relevant lines — reading entire large files wastes context."
        ),
        "parameters": {
            "type": "object",
            "properties": {
                "file_path": {
                    "type": "string",
                    "description": "Absolute or relative file path (from grep results or diff)",
                },
            },
            "required": ["file_path"],
        },
    },
}

READ_LINES_TOOL = {
    "type": "function",
    "function": {
        "name": "read_lines",
        "description": (
            "Read a specific line range from a file. PREFERRED over read_file for large files. "
            "Use after grep gives you a line number to see surrounding code in detail."
        ),
        "parameters": {
            "type": "object",
            "properties": {
                "file_path": {
                    "type": "string",
                    "description": "File path from grep results or diff",
                },
                "start_line": {
                    "type": "integer",
                    "description": "Starting line number (1-indexed)",
                },
                "end_line": {
                    "type": "integer",
                    "description": "Ending line number (default: start_line + 30)",
                },
            },
            "required": ["file_path", "start_line"],
        },
    },
}

CODEBASE_TOOLS = [_build_grep_tool(), READ_FILE_TOOL, READ_LINES_TOOL]


def build_codebase_tools(project_names: list[str] | None = None) -> list[dict]:
    """Build tool definitions, optionally with project enum on grep.

    Args:
        project_names: List of project names (primary + linked repos).
            When provided, grep's `project` param becomes required with enum constraint.
            When None or empty, grep has no project param (single-repo mode).
    """
    return [_build_grep_tool(project_names or None), READ_FILE_TOOL, READ_LINES_TOOL]


def make_tool_handler(
    code_explorer: CodeExplorer,
    agent_name: str,
    max_tool_calls: int = 10,
):
    """Factory that returns an async tool handler bound to a code_explorer.

    Args:
        code_explorer: CodeExplorer or MultiCodeExplorer instance
        agent_name: Name for log messages (e.g. "FactChecker [Batch 1/2]", "Reviewer-0")
        max_tool_calls: Maximum number of tool calls allowed

    Returns:
        Async tool handler function compatible with BaseAgentImpl.analyze().
        The returned function has a `tool_calls_count` attribute tracking usage.
    """
    state = {"count": 0}

    async def handle_tool(tool_call) -> ToolResult:
        tool_name = tool_call.function.name.removeprefix("proxy_")

        if state["count"] >= max_tool_calls:
            return ToolResult(
                content=f"Tool call limit reached ({max_tool_calls}). Make your decision.",
            )

        try:
            args = json.loads(tool_call.function.arguments)
        except json.JSONDecodeError:
            args = {}

        state["count"] += 1

        if tool_name == "grep":
            pattern = args.get("pattern", "")
            file_glob = args.get("file_glob")
            context = args.get("context_lines", 5)
            project = args.get("project")
            grep_result = code_explorer.grep(
                pattern,
                file_glob=file_glob,
                context_lines=context,
                max_results=20,
                project=project,
            )
            content = json.dumps(
                [
                    {"file": m.file_path, "line": m.line, "content": m.content}
                    for m in grep_result.matches
                ]
            )
            logger.info(
                f"{agent_name} grep ({state['count']}): {pattern!r} glob={file_glob!r} ctx={context} -> {len(grep_result.matches)} matches, {len(content)} chars"
            )
            return ToolResult(content=content)

        elif tool_name == "read_file":
            file_path = args.get("file_path", "")
            content = code_explorer.read_file(file_path)
            line_count = content.count("\n")
            logger.info(
                f"{agent_name} read_file ({state['count']}): {file_path} -> {len(content)} chars, {line_count} lines"
            )
            if content.startswith("File not found"):
                logger.warning(f"{agent_name} read_file FAILED: {content}")
            return ToolResult(content=content)

        elif tool_name == "read_lines":
            file_path = args.get("file_path", "")
            start_line = args.get("start_line", 1)
            end_line = args.get("end_line")
            content = code_explorer.read_lines(file_path, start_line, end_line)
            logger.info(
                f"{agent_name} read_lines ({state['count']}): {file_path}:{start_line}-{end_line} -> {len(content)} chars"
            )
            if content.startswith("File not found"):
                logger.warning(f"{agent_name} read_lines FAILED: {content}")
            return ToolResult(content=content)

        else:
            logger.warning(f"{agent_name} unknown tool: {tool_name}")
            return ToolResult(content="Unknown tool")

    handle_tool.state = state  # type: ignore[attr-defined]

    return handle_tool
