"""Custom exception hierarchy for SD-API."""


class SDAPIError(Exception):
    """Base exception for all SD-API errors."""

    pass


# ── API errors ──────────────────────────────────────────────────────────────


class APIRequestError(SDAPIError):
    """Raised when an HTTP request fails or times out."""

    def __init__(self, message: str, *, url: str | None = None, status_code: int | None = None):
        self.url = url
        self.status_code = status_code
        super().__init__(message)

    def __str__(self) -> str:
        parts = [super().__str__()]
        if self.url:
            parts.append(f"url={self.url}")
        if self.status_code:
            parts.append(f"status={self.status_code}")
        return " | ".join(parts)


class APIResponseError(SDAPIError):
    """Raised when the API returns an error payload (non-2xx or error JSON)."""

    def __init__(self, message: str, *, url: str | None = None, response_body: str | None = None):
        self.url = url
        self.response_body = response_body
        super().__init__(message)

    def __str__(self) -> str:
        parts = [super().__str__()]
        if self.url:
            parts.append(f"url={self.url}")
        return " | ".join(parts)


# ── Stream errors ───────────────────────────────────────────────────────────


class StreamError(SDAPIError):
    """Raised when an SSE or polling stream encounters an unrecoverable error."""

    pass


# ── Storage errors ──────────────────────────────────────────────────────────


class StorageError(SDAPIError):
    """Raised when reading or writing to storage fails."""

    pass


# ── Plotting errors ─────────────────────────────────────────────────────────


class PlotError(SDAPIError):
    """Raised when plotting fails."""

    pass


# ── Validation errors ───────────────────────────────────────────────────────


class ValidationError(SDAPIError):
    """Raised when a data object fails validation."""

    pass
