"""Navigation: Struktur, Sichtbarkeit, Reihenfolge.

Uebernommen vom Vorgaenger (das Muster war gut), aber mit zwei Aenderungen:

* Sichtbarkeit haengt an einer Liste von Rechten **plus** optional einer
  Bedingung. Im Vorgaenger war das eine ``if/elif``-Kette in
  ``_nav_key_visible()`` -- jede neue Seite bedeutete einen Eingriff in diese
  Funktion. Hier deklariert jeder Eintrag selbst, was er braucht.
* "Sichtbar, wenn *irgendeines* der Rechte greift" ist der Default. Im
  Vorgaenger war eine Seite mit reinem Export-Recht unerreichbar, weil die
  Navigation nur auf ``.view`` prueft.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Callable, Iterable, Sequence

VisibilityFn = Callable[[Any], bool]


@dataclass(frozen=True, slots=True)
class NavItem:
    key: str
    label: str
    icon: str
    endpoint: str = ""
    #: Sichtbar, wenn eines dieser Rechte greift (leer = immer sichtbar).
    permissions: tuple[str, ...] = ()
    #: Zusaetzliche Bedingung, z. B. Modul-Lizenz oder vorhandene Freigaben.
    condition: VisibilityFn | None = field(default=None, compare=False)
    children: tuple["NavItem", ...] = ()
    badge: Callable[[], str] | None = field(default=None, compare=False)
    hint: str = ""

    @property
    def is_group(self) -> bool:
        return bool(self.children)


class NavTree:
    """Navigationsbaum mit laufzeitkonfigurierbarer Reihenfolge."""

    def __init__(self, items: Sequence[NavItem]) -> None:
        self.items: tuple[NavItem, ...] = tuple(items)
        self._by_key: dict[str, NavItem] = {}
        for item in self.items:
            self._by_key[item.key] = item
            for child in item.children:
                self._by_key[child.key] = child

    def item(self, key: str) -> NavItem | None:
        return self._by_key.get(key)

    def default_order(self) -> list[str]:
        return [item.key for item in self.items]

    def default_child_order(self, group_key: str) -> list[str]:
        item = self._by_key.get(group_key)
        return [child.key for child in item.children] if item else []

    # ------------------------------------------------------------------ #

    def visible(self, item: NavItem, user: Any) -> bool:
        if item.permissions and not user.has_any(item.permissions):
            return False
        if item.condition is not None and not item.condition(user):
            return False
        return True

    def resolve(
        self,
        user: Any,
        *,
        order: Sequence[str] | None = None,
        child_order: dict[str, Sequence[str]] | None = None,
    ) -> list[dict[str, Any]]:
        """Liefert die gefilterte, sortierte Navigation fuer das Template."""
        child_order = child_order or {}
        result: list[dict[str, Any]] = []

        for item in self._ordered(self.items, order):
            if item.is_group:
                children = [
                    child
                    for child in self._ordered(item.children, child_order.get(item.key))
                    if self.visible(child, user)
                ]
                if not children:
                    continue
                result.append(
                    {
                        "key": item.key,
                        "label": item.label,
                        "icon": item.icon,
                        "endpoint": "",
                        "hint": item.hint,
                        "children": [self._as_dict(child) for child in children],
                    }
                )
                continue
            if not self.visible(item, user):
                continue
            result.append(self._as_dict(item))
        return result

    def _as_dict(self, item: NavItem) -> dict[str, Any]:
        return {
            "key": item.key,
            "label": item.label,
            "icon": item.icon,
            "endpoint": item.endpoint,
            "hint": item.hint,
            "badge": item.badge() if item.badge else "",
            "children": [],
        }

    @staticmethod
    def _ordered(
        items: Sequence[NavItem], order: Sequence[str] | None
    ) -> list[NavItem]:
        if not order:
            return list(items)
        index = {key: position for position, key in enumerate(order)}
        # Unbekannte Schluessel (neu dazugekommene Seiten) landen hinten, statt
        # zu verschwinden -- der Vorgaenger verlor sie stillschweigend.
        return sorted(items, key=lambda item: (index.get(item.key, len(index)), item.label))

    # ------------------------------------------------------------------ #

    def sanitize_order(self, keys: Iterable[str]) -> list[str]:
        known = self.default_order()
        chosen = [key for key in keys if key in known]
        return chosen + [key for key in known if key not in chosen]

    def sanitize_child_order(self, group_key: str, keys: Iterable[str]) -> list[str]:
        known = self.default_child_order(group_key)
        chosen = [key for key in keys if key in known]
        return chosen + [key for key in known if key not in chosen]

    def searchable(self, user: Any) -> list[dict[str, str]]:
        """Flache Liste aller sichtbaren Seiten -- Datenbasis der Befehlspalette."""
        entries: list[dict[str, str]] = []
        for item in self.items:
            if item.is_group:
                for child in item.children:
                    if self.visible(child, user):
                        entries.append(
                            {
                                "label": child.label,
                                "group": item.label,
                                "endpoint": child.endpoint,
                                "icon": child.icon,
                            }
                        )
            elif self.visible(item, user):
                entries.append(
                    {"label": item.label, "group": "", "endpoint": item.endpoint, "icon": item.icon}
                )
        return entries
