"""Base tool interface for Jarvis tools.

This module defines the common interface that all tools must implement,
ensuring consistency with MCP tool format and enabling dictionary-based execution.
"""

from abc import ABC, abstractmethod
from typing import Dict, Any, Optional, Callable
from .types import ToolExecutionResult


class ToolContext:
    """Context object containing all the resources a tool might need."""

    def __init__(
        self,
        db,
        cfg,
        system_prompt: str,
        original_prompt: str,
        redacted_text: str,
        max_retries: int,
        user_print: Callable[[str], None],
        language: Optional[str] = None,
        broadcasting: Optional[bool] = None,
    ):
        self.db = db
        self.cfg = cfg
        self.system_prompt = system_prompt
        self.original_prompt = original_prompt
        self.redacted_text = redacted_text
        self.max_retries = max_retries
        self.user_print = user_print
        # ISO-639-1 code of the language Whisper auto-detected for the current
        # utterance (e.g. "en", "tr", "de"). None when the tool is invoked
        # outside the voice path (evals, unit tests, text entry) — tools must
        # treat absence as "no signal" and fall back to their own default
        # rather than assuming English.
        self.language = language
        # Live broadcast (screen-share / Go-Live) state for THIS turn, passed in
        # by the bot per request. Controls real-time search routing when the
        # master flag ``cfg.stream_browser`` is on: broadcasting -> on-screen
        # Chrome search (visible on the stream), not broadcasting -> Gemini.
        # ``None`` means "no signal" (evals, text entry, older bot): callers
        # fall back to the master flag so behaviour is unchanged.
        self.broadcasting = broadcasting


class Tool(ABC):
    """Base class for all Jarvis tools.

    This interface matches the MCP tool format with name, description, and inputSchema
        properties, while providing a simple execution interface focused on tool logic.

        Implementation guideline:
        - Put all operational logic directly in the `run` method.
        - Keep helper functions module-level only when they provide clear reuse (e.g. nutrition
            extraction helpers used by multiple code paths / tests). Otherwise inline.
        - `run` receives validated args (per schema) and a `ToolContext` giving access to db, cfg,
            prompts, redacted_text, retry allowance, and a user_print callable.
    """

    @property
    @abstractmethod
    def name(self) -> str:
        """The canonical tool identifier (camelCase)."""
        pass

    @property
    @abstractmethod
    def description(self) -> str:
        """Human-readable description of what the tool does."""
        pass

    @property
    @abstractmethod
    def inputSchema(self) -> Dict[str, Any]:
        """JSON Schema for tool arguments (matches MCP format)."""
        pass

    @abstractmethod
    def run(self, args: Optional[Dict[str, Any]], context: ToolContext) -> ToolExecutionResult:
        """Execute the tool with the given arguments and context.

        This is the only method tools need to implement. All common concerns
        like user printing, database access, config, etc. are provided via context.

        Args:
            args: Dictionary containing tool arguments (validated against inputSchema)
            context: ToolContext with db, cfg, user_print, etc.

        Returns:
            ToolExecutionResult with execution results
        """
        pass

    def execute(
        self,
        db,
        cfg,
        tool_args: Optional[Dict[str, Any]],
        system_prompt: str,
        original_prompt: str,
        redacted_text: str,
        max_retries: int,
        user_print: Callable[[str], None],
        language: Optional[str] = None,
    ) -> ToolExecutionResult:
        """Execute the tool (internal method used by registry).

        This method creates the context and calls the tool's run method.
        Tools should implement run(), not this method.
        """
        # Live broadcast state for this turn is carried in request-scoped
        # thread-local state (set by the bridge) rather than threaded through
        # the whole engine call chain. Absent (eval / unit test) -> None.
        try:
            from jarvis.reply.turn_state import get_broadcasting
            broadcasting = get_broadcasting()
        except Exception:
            broadcasting = None
        context = ToolContext(
            db=db,
            cfg=cfg,
            system_prompt=system_prompt,
            original_prompt=original_prompt,
            redacted_text=redacted_text,
            max_retries=max_retries,
            user_print=user_print,
            language=language,
            broadcasting=broadcasting,
        )
        return self.run(tool_args, context)
