"""Base exception for all Cadence domain errors.

CadenceException is the base class for domain-specific exceptions. Error codes
use the catalog format ``PREFIX-NNNN`` (see cadence.core.exceptions.error_codes).

The base carries ``code``, ``message`` (client-facing text on the API wire),
``status_code``, and optionally ``request_id`` (set by HTTP error middleware when
the exception is caught—correlates with the JSON body; do not duplicate into log
``extra`` by hand). ``str(exception)`` includes ``code`` and ``request_id`` when
set so loggers and tracebacks correlate with the API response. Optional ``field`` /
``details`` are defined only on subclasses that need them.
"""


class CadenceException(Exception):
    """Base exception for all Cadence errors.

    Do not raise CadenceException directly in application code. Use specific
    subclasses (AuthenticationError, ValidationError, etc.) instead.

    Args:
        code: Catalog error id (PREFIX-NNNN). Client-facing contract for i18n.
        message: Human-readable text returned as ``message`` in flat JSON errors.
        status_code: HTTP status for API mapping.
        request_id: Correlation id for the HTTP request; set by error middleware
            when handling the exception. Application code normally omits this.
    """

    @property
    def error_category(self) -> str:
        """Category of the error for grouping and logging."""
        return "unknown"

    def __init__(
        self,
        code: str,
        message: str,
        status_code: int = 500,
        *,
        request_id: str | None = None,
    ) -> None:
        self.code = code
        self.message = message
        self.status_code = status_code
        self.request_id = request_id
        super().__init__(message)

    def __str__(self) -> str:
        """Include catalog code and optional request correlation id for logs."""
        if self.request_id:
            return f"[{self.request_id}] {self.code}: {self.message}"
        return f"{self.code}: {self.message}"
