"""Domain exception hierarchy — framework-agnostic.

Services raise these; the API layer maps them to HTTP responses
(see ``app.api.errors``). The domain never references HTTP status codes.
"""


class DomainError(Exception):
    """Base for all expected, business-meaningful failures.

    ``code`` is a stable, machine-readable identifier returned to clients.
    """

    code: str = "domain_error"

    def __init__(self, message: str | None = None) -> None:
        super().__init__(message or self.__class__.__doc__ or self.code)
        self.message = str(self)


class NotFoundError(DomainError):
    """Requested resource does not exist."""

    code = "not_found"


class AlreadyExistsError(DomainError):
    """Resource conflicts with an existing one (e.g. duplicate)."""

    code = "already_exists"


class ConflictError(DomainError):
    """Operation conflicts with current state (e.g. stale version on write)."""

    code = "conflict"


class ValidationError(DomainError):
    """Input is well-formed but violates a business rule."""

    code = "validation_error"


class AuthenticationError(DomainError):
    """Caller could not be authenticated."""

    code = "authentication_error"


class PermissionDeniedError(DomainError):
    """Caller authenticated but not authorized for this action."""

    code = "permission_denied"


class DependencyUnavailableError(DomainError):
    """An external dependency (source, ML, MusicBrainz) is unavailable.

    Callers should degrade gracefully rather than propagate as a hard 500.
    """

    code = "dependency_unavailable"


class StorageError(DomainError):
    """File storage operation failed."""

    code = "storage_error"


class RangeNotSatisfiableError(DomainError):
    """Requested byte range cannot be satisfied."""

    code = "range_not_satisfiable"

    def __init__(self, total_size: int) -> None:
        super().__init__("Requested range is not satisfiable.")
        self.total_size = total_size
