# Copyright 2025 Tenro.ai
# SPDX-License-Identifier: Apache-2.0

"""Base exceptions for Tenro SDK."""

from __future__ import annotations


class TenroError(Exception):
    """Base exception for all Tenro errors."""


class ValidationError(TenroError):
    """Raised when validation fails."""


class ConfigurationError(TenroError):
    """Raised when configuration is invalid."""


class AgentRecursionError(TenroError):
    """Raised when agent exceeds maximum nesting depth.

    This usually indicates an infinite loop between agents
    (e.g., Agent A calls Agent B, which calls Agent A again).
    """


class ConstructHarnessError(TenroError):
    """Base exception for Construct test harness errors.

    This is the common base for storage typing and except clauses.
    Subclasses define priority behavior:
    - ConstructConfigurationError: ALWAYS preempt (chain if test failed)
    - ConstructCoverageError: Suppress if test failed, raise if passed
    """


class ConstructConfigurationError(ConstructHarnessError):
    """Configuration error that invalidates the test harness.

    These errors indicate the harness itself is broken and MUST be
    surfaced even if the test body also failed. When both occur,
    the configuration error is raised with the test failure as cause.

    Priority: ALWAYS preempt (chain if test failed).
    """


class ConstructCoverageError(ConstructHarnessError):
    """Coverage error for untaken simulation paths.

    These errors indicate a registered simulation was never triggered.
    When a test fails, coverage errors are suppressed because they're
    likely collateral damage from the early failure.

    Priority: Suppress if test failed, raise if passed.
    """


class MissingLLMCallError(ConstructConfigurationError):
    """Raised when @link_llm executed but no provider call was made.

    This is a "broken link" error - the decorated function exists but
    doesn't actually call the LLM provider. This invalidates the test
    because the mock never got a chance to return its configured response.

    Examples:
        @link_llm("openai")
        def call_llm(prompt: str) -> str:
            return "stub"  # Bug! Should call openai.chat.completions.create()

    Fix: Ensure the decorated function makes a real HTTP call to the provider.
    """


class UnusedSimulationError(ConstructCoverageError):
    """Raised when simulate_llm() HTTP mock was registered but never triggered.

    This is an "untaken path" error - the simulation was set up but the
    code path that would trigger it was never executed. This often happens
    when a test fails early before reaching the mocked call.

    If the test also failed with an assertion error, this error is suppressed
    to let the developer focus on fixing the logic error first.

    Examples:
        construct.simulate_llm(provider="anthropic", response="Hello")
        # ... test code that never calls anthropic ...

    Fix: Either remove the unused simulation or ensure code triggers it.
    """
