"""
Error hierarchy for the Morphe Adapter SDK.

All public errors extend MorpheError so callers can catch the entire
SDK surface with a single ``except MorpheError`` clause.
"""
from __future__ import annotations

from typing import List, Optional

from .types import JsonApiError, ValidationFailure


class MorpheError(Exception):
    """Base class for all Morphe SDK errors."""


class ApiError(MorpheError):
    """
    Raised when the Control Plane returns a non-2xx response.

    Exposes the first JSON:API error's ``code``, ``title``, and ``detail``
    fields as top-level attributes for easy exception handling, plus the
    full ``errors`` list for callers that want all details.
    """

    def __init__(self, status: int, errors: List[JsonApiError]) -> None:
        first: JsonApiError = errors[0] if errors else {
            "code": "unknown_error",
            "title": "Unknown error",
        }
        super().__init__(first.get("title", "Unknown error"))
        self.status = status
        self.code = first.get("code", "unknown_error")
        self.title = first.get("title", "Unknown error")
        self.detail: Optional[str] = first.get("detail")
        self.errors = errors


class SchemaValidationError(MorpheError):
    """
    Raised when pre-emit schema validation fails.

    No HTTP request is made when this error is raised — the payload was
    rejected locally against the cached JSON Schema document.
    """

    def __init__(self, failures: List[ValidationFailure]) -> None:
        parts = [f"{f['field']} {f['message']}" for f in failures]
        super().__init__(f"Schema validation failed: {'; '.join(parts)}")
        self.failures = failures


class NetworkError(MorpheError):
    """
    Raised when a network-level failure prevents reaching the Control Plane.

    Stores the original exception as ``original_error`` rather than ``cause``
    to avoid shadowing Python's built-in ``Exception.__cause__`` attribute.
    """

    def __init__(self, message: str, original_error: BaseException) -> None:
        super().__init__(message)
        self.original_error = original_error
