"""Re-export tree helpers behind a stable surface.

We deliberately route hierarchy ops through this module so a future
PostgreSQL backend (ltree / recursive CTE) can swap implementations without
touching call sites (Rule #22).

Public API
----------
Instance-based (low-level):
    ancestors_of(node, *, include_self=False) -> QuerySet
    descendants_of(node, *, include_self=False) -> QuerySet
    descendant_ids(nodes) -> set[int]
    move_node(node, new_parent) -> OrganizationNode
    create_node(...) -> OrganizationNode
    rebuild_paths(tenant_id) -> int

ID-based convenience wrappers (prefer these in views / services that only
have a primary key and don't want to pre-fetch the instance):
    get_ancestors(node_id, *, include_self=False) -> QuerySet
    get_descendants(node_id, *, include_self=False) -> QuerySet
    move_node_by_id(node_id, new_parent_id) -> OrganizationNode
"""

from __future__ import annotations

from django.db.models import QuerySet

from simorgh.apps.organizations.services import (
    HierarchyError,
    ancestors_of,
    create_node,
    descendant_ids,
    descendants_of,
    move_node,
    rebuild_paths,
)


# ---------------------------------------------------------------------------
# ID-based wrappers
# ---------------------------------------------------------------------------

def get_ancestors(node_id: int, *, include_self: bool = False) -> QuerySet:
    """Return a QuerySet of ancestor nodes ordered shallow → deep.

    Looks up the node by *node_id* then delegates to ``ancestors_of()``.
    Raises ``OrganizationNode.DoesNotExist`` if *node_id* is invalid.
    """
    from simorgh.apps.organizations.models import OrganizationNode  # local import avoids circular

    node = OrganizationNode.objects.get(pk=node_id)
    return ancestors_of(node, include_self=include_self)


def get_descendants(node_id: int, *, include_self: bool = False) -> QuerySet:
    """Return a QuerySet of all descendant nodes.

    Looks up the node by *node_id* then delegates to ``descendants_of()``.
    Raises ``OrganizationNode.DoesNotExist`` if *node_id* is invalid.
    """
    from simorgh.apps.organizations.models import OrganizationNode  # local import avoids circular

    node = OrganizationNode.objects.get(pk=node_id)
    return descendants_of(node, include_self=include_self)


def move_node_by_id(
    node_id: int,
    new_parent_id: int | None,
) -> "OrganizationNode":
    """Reparent a node identified by *node_id* under *new_parent_id*.

    Pass ``new_parent_id=None`` to promote the node to a root.
    Delegates to ``move_node()`` after resolving both instances.
    Both nodes must belong to the same tenant; ``HierarchyError`` is raised
    otherwise (or if a cycle would be created).
    """
    from simorgh.apps.organizations.models import OrganizationNode  # local import avoids circular

    node = OrganizationNode.objects.get(pk=node_id)
    new_parent = (
        OrganizationNode.objects.get(pk=new_parent_id, tenant_id=node.tenant_id)
        if new_parent_id is not None
        else None
    )
    return move_node(node, new_parent)


__all__ = [
    # instance-based
    "HierarchyError",
    "ancestors_of",
    "create_node",
    "descendant_ids",
    "descendants_of",
    "move_node",
    "rebuild_paths",
    # id-based
    "get_ancestors",
    "get_descendants",
    "move_node_by_id",
]
