"""
Props que toda página recibe sin pedirlas.

Van por middleware y no por cada vista porque son las mismas siempre: la sesión
en curso, el aviso de nivel cuenta, las rutas nombradas y los errores del
formulario anterior. Repetirlas en cada vista garantiza que alguna quede sin
alguna, y el aviso de credencial rota es justamente el que no puede faltar en
ninguna pantalla (FR-064).

Se comparten como funciones y no como valores ya calculados: Inertia las evalúa
sólo si la respuesta las va a usar, así que una petición parcial no paga
consultas que nadie va a leer.
"""

from django.conf import settings
from django.middleware.csrf import get_token
from inertia import share

from apps.core import routes
from apps.core.errors import pop_errors


def _auth(request) -> dict | None:
    if not request.user.is_authenticated:
        return None
    return {
        'id': str(request.user.id),
        'email': request.user.email,
        'is_staff': request.user.is_staff,
        'role': request.user.role,
    }


def _account(request) -> dict | None:
    if not request.user.is_authenticated:
        return None
    # Importación diferida: el estado de cuenta consulta credenciales y
    # dominios, y traerlo al importar el middleware ataría el arranque del
    # proyecto al orden de carga de esas aplicaciones.
    from apps.accounts.services import account_state

    return account_state(request.user).as_dict()


def _site(request) -> dict | None:
    """
    El sitio de la cuenta, o `None` mientras no haya ninguno conectado.

    Va con las props compartidas por el mismo motivo que el conteo de avisos: el
    menú lo necesita para armar sus entradas —cobertura, sitemaps y lotes cuelgan
    del identificador del sitio— y el menú está en todas las pantallas.

    Y por uno más, que es el que decide: **el criterio de cuál es el sitio tiene
    que estar escrito una sola vez.** Resuelto en cada vista quedaría en cuatro
    archivos que se desincronizan en el primer cambio; resuelto sólo en el menú,
    quien entra por una dirección directa llegaría a una pantalla que no sabe de
    qué sitio habla.

    Manda lo que hace falta para nombrarlo y para decir cómo está —el menú
    dibuja, la conexión muestra su ficha—, no el dominio entero: los ajustes de
    cupo y la cobertura son de las pantallas que los usan y pagarlos en cada
    navegación sería cobrarle a todas por lo que necesita una.

    Es una función, así que Inertia sólo la evalúa si la respuesta la va a usar.
    """
    if not request.user.is_authenticated:
        return None

    # Importación diferida, igual que el estado de cuenta: traer los servicios de
    # dominios al importar el middleware ataría el arranque del proyecto al orden
    # de carga de esa aplicación.
    from apps.domains.services import primary_shared_domain

    domain = primary_shared_domain()
    if domain is None:
        return None

    return {
        'id': str(domain.id),
        'hostname': domain.hostname,
        'property_type': domain.property_type,
        'access_state': domain.access_state,
        'access_checked_at': (
            domain.access_checked_at.isoformat() if domain.access_checked_at else None
        ),
    }


def _notifications(request) -> dict | None:
    """
    Cuántos avisos sin leer tiene la cuenta, y cuántos de ésos piden una acción.

    Va con las props compartidas y no en cada vista porque el número vive en el
    menú, que está en todas las pantallas: calcularlo por vista significaría que
    la que se olvide muestre la campana vacía teniendo algo que decir.

    Los dos conteos salen de la cuenta entera y del mismo índice. `unread_action`
    viaja acá y no como prop de la pantalla de avisos para que el contador del
    menú y la primera línea de esa pantalla digan lo mismo: derivarlo de las 50
    filas que se listan daría otra cifra en cuanto haya una segunda página.

    Es una función, así que Inertia sólo la evalúa si la respuesta la va a usar:
    una petición parcial —el sondeo de un lote, por ejemplo— no paga estas
    consultas.
    """
    if not request.user.is_authenticated:
        return None

    from apps.notifications.services import company_unread_action_count, company_unread_count

    return {
        'unread': company_unread_count(),
        'unread_action': company_unread_action_count(),
    }


def _active_work(request) -> list[dict]:
    """
    El trabajo sin terminar de la cuenta, para el indicador de la barra.

    Va con las props compartidas por el mismo motivo que el conteo de avisos: el
    indicador vive en el armazón y se dibuja en todas las pantallas, así que su
    contenido no puede depender de las props de una vista.

    Es una función, así que Inertia sólo la evalúa si la respuesta la va a usar.
    """
    if not request.user.is_authenticated:
        return []

    from apps.web.dashboard import active_work

    return active_work(request.user)


def _preferences(request) -> dict:
    """
    Idioma y tema que rigen esta pantalla.

    Viaja siempre, también sin sesión: el ingreso se dibuja igual y también
    tiene que saber en qué idioma hablar. Sin cuenta el valor sale de la cookie,
    que es lo que el servidor puede leer sin consultar la base.

    Es una función como las demás: una navegación completa la va a pedir
    siempre —el catálogo se elige con esto—, pero el sondeo parcial de un lote
    no, y no tiene por qué pagarla.
    """
    from apps.accounts.preferences import resolve

    preferences = resolve(request)
    return {'language': preferences.language, 'theme': preferences.theme}


def _app() -> dict:
    """
    Contexto de la instalación.

    La zona de presentación viaja al cliente porque toda fecha se muestra
    acompañada de ella: una fecha de obtención sin zona declarada es ambigua, y
    sobre esas fechas se apoya lo que el producto afirma (FR-065).

    Las capacidades apagadas viajan como falso, no se omiten, para que la
    interfaz tenga un lugar único donde preguntar en vez de repartir condiciones
    por todos lados (principio VI).
    """
    return {
        'display_timezone': settings.DISPLAY_TIMEZONE,
        'features': {
            'billing': settings.BILLING_ENABLED,
            'txt_verification': settings.TXT_VERIFICATION_ENABLED,
        },
    }


def shared_props_middleware(get_response):
    def middleware(request):
        # Obliga a que la cookie del testigo anti-CSRF salga en toda respuesta.
        # Django sólo la emite cuando una plantilla la pide, y las páginas de
        # Inertia no usan plantillas de formulario: sin esto, el primer envío
        # de cualquier formulario recibiría un 403 sin explicación.
        get_token(request)

        share(
            request,
            auth=lambda: _auth(request),
            account=lambda: _account(request),
            site=lambda: _site(request),
            notifications=lambda: _notifications(request),
            active_work=lambda: _active_work(request),
            app=_app,
            preferences=lambda: _preferences(request),
            routes=routes.published,
            errors=lambda: pop_errors(request),
        )
        return get_response(request)

    return middleware
