"""Type-safe tool decorator — extracts schema from type hints automatically."""

from __future__ import annotations

import inspect
import re
from typing import Any, Callable, Dict, List, Optional, get_type_hints

from .types import ToolDef, ToolParam

# Python type → JSON Schema type
_TYPE_MAP: Dict[type, str] = {
    str: "string",
    int: "integer",
    float: "number",
    bool: "boolean",
    list: "array",
    dict: "object",
}


def _python_type_to_json(tp: Any) -> str:
    """Convert a Python type annotation to a JSON Schema type string."""
    origin = getattr(tp, "__origin__", None)
    if origin is list or origin is List:
        return "array"
    if origin is dict or origin is Dict:
        return "object"
    return _TYPE_MAP.get(tp, "string")


def _parse_docstring_params(docstring: str) -> Dict[str, str]:
    """Parse Google-style docstring Args section into {param_name: description}."""
    result: Dict[str, str] = {}
    if not docstring:
        return result

    # Find the Args: section
    args_match = re.search(r"^\s*Args?\s*:\s*$", docstring, re.MULTILINE)
    if not args_match:
        return result

    lines = docstring[args_match.end() :].split("\n")
    current_param: Optional[str] = None
    current_desc: List[str] = []

    for line in lines:
        # Stop at next section (e.g., Returns:, Raises:)
        if re.match(r"^\s*\S+.*:\s*$", line) and not re.match(r"^\s+\w+\s*[\(:]", line):
            # Could be a new section header — check indent level
            stripped = line.lstrip()
            indent = len(line) - len(stripped)
            if indent == 0 or (stripped and not stripped[0].isspace()):
                break

        # Match "    param_name: description" or "    param_name (type): description"
        param_match = re.match(r"^\s{2,}(\w+)\s*(?:\([^)]*\))?\s*:\s*(.+)$", line)
        if param_match:
            if current_param:
                result[current_param] = " ".join(current_desc).strip()
            current_param = param_match.group(1)
            current_desc = [param_match.group(2).strip()]
        elif current_param and line.strip():
            # Continuation line for current param
            current_desc.append(line.strip())
        elif current_param and not line.strip():
            # Blank line ends current param
            result[current_param] = " ".join(current_desc).strip()
            current_param = None
            current_desc = []

    if current_param:
        result[current_param] = " ".join(current_desc).strip()

    return result


def tool(
    fn: Optional[Callable] = None,
    *,
    name: Optional[str] = None,
    description: Optional[str] = None,
) -> Any:
    """
    Decorator that turns a function into an agent tool.

    Usage:
        @tool
        def search_web(query: str) -> str:
            '''Search the web for information.'''
            ...

        @tool(name="calc", description="Do math")
        def calculator(expression: str) -> str:
            ...
    """

    def _wrap(f: Callable) -> Callable:
        hints = get_type_hints(f)
        sig = inspect.signature(f)
        doc = inspect.getdoc(f) or ""
        docstring_params = _parse_docstring_params(doc)

        params: List[ToolParam] = []
        for pname, param in sig.parameters.items():
            if pname == "self" or pname == "cls":
                continue
            tp = hints.get(pname, str)
            json_type = _python_type_to_json(tp)
            has_default = param.default is not inspect.Parameter.empty

            # Use docstring description if available, else fall back to type name
            param_desc = docstring_params.get(pname, json_type)

            params.append(
                ToolParam(
                    name=pname,
                    type=json_type,
                    description=param_desc,
                    required=not has_default,
                    default=param.default if has_default else None,
                )
            )

        tool_def = ToolDef(
            name=name or f.__name__,
            description=description or doc or f.__name__,
            parameters=params,
            fn=f,
        )
        f._tool_def = tool_def  # type: ignore[attr-defined]
        return f

    if fn is not None:
        return _wrap(fn)
    return _wrap
