"""Row-level locking helpers.

Thin wrapper around ``QuerySet.select_for_update`` so service code reads
intentionally:

    with transaction.atomic():
        node = with_row_lock(OrganizationNode.objects.filter(pk=node_id)).get()
        ...

On SQLite ``select_for_update`` is a no-op; on Postgres/MySQL it takes a real
row lock. The wrapper is safe to call on both.
"""

from __future__ import annotations

from typing import TypeVar

from django.db import connection, models

T = TypeVar("T", bound=models.Model)


def with_row_lock(
    queryset: models.QuerySet[T],
    *,
    nowait: bool = False,
    skip_locked: bool = False,
    of: tuple[str, ...] = (),
) -> models.QuerySet[T]:
    """Apply ``select_for_update`` if the active DB supports it; otherwise pass-through."""

    features = getattr(connection.features, "has_select_for_update", False)
    if not features:
        return queryset
    kwargs: dict[str, object] = {}
    if nowait and connection.features.has_select_for_update_nowait:
        kwargs["nowait"] = True
    if skip_locked and connection.features.has_select_for_update_skip_locked:
        kwargs["skip_locked"] = True
    if of and connection.features.has_select_for_update_of:
        kwargs["of"] = of
    return queryset.select_for_update(**kwargs)


__all__ = ["with_row_lock"]
