"""Structured application errors.

Per standards: every non-2xx response carries
``{"error": {"code": "<domain>_<reason>", "message": "..."}}`` so the
generated TS client can switch on the code. Codes are constants here —
never inline strings at call sites.
"""
from __future__ import annotations

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse


class AppError(Exception):
    """Raise anywhere below a router; the handler shapes the response."""

    def __init__(self, code: str, message: str, http_status: int = 400):
        self.code = code
        self.message = message
        self.http_status = http_status
        super().__init__(f"{code}: {message}")


# --- error-code constants (grow as routers land; grouped by domain) ---
# BW Auth (Pattern B) app-session codes. (The retired shim used
# auth_grant_* codes; the app now mints its own signed session cookie.)
AUTH_SESSION_MISSING = "auth_session_missing"
AUTH_SESSION_INVALID = "auth_session_invalid"
AUTH_NOT_CONFIGURED = "auth_not_configured"
AUTH_USER_UNKNOWN = "auth_user_unknown"

NOT_FOUND = "resource_not_found"
VALIDATION_FAILED = "validation_failed"


def error_response(code: str, message: str, http_status: int) -> JSONResponse:
    return JSONResponse(
        status_code=http_status,
        content={"error": {"code": code, "message": message}},
    )


def install_handlers(app: FastAPI) -> None:
    @app.exception_handler(AppError)
    async def _app_error(_req: Request, exc: AppError) -> JSONResponse:
        return error_response(exc.code, exc.message, exc.http_status)
