from __future__ import annotations

from typing import Any, Dict, List, Optional, Tuple

from django import template
from django.urls import URLPattern, URLResolver, get_resolver, reverse, NoReverseMatch

register = template.Library()


def public_route(label: Optional[str] = None, order: int = 100, icon: Optional[str] = None):
    """Decorator to mark a view as public-menu eligible.

    label: Display label for menus (defaults to view name)
    order: Sorting order (lower comes first)
    icon:  Optional icon name (for your UI layer)
    """

    def _decorator(view):
        meta = {
            "label": label or getattr(view, "__name__", "").replace("_", " ").title(),
            "order": order,
            "icon": icon,
        }
        setattr(view, "_public_route", meta)
        return view

    return _decorator


def _iter_patterns(resolver: URLResolver, ns_path: str = ""):
    for entry in resolver.url_patterns:
        if isinstance(entry, URLResolver):
            new_ns = ns_path
            if entry.namespace:
                new_ns = f"{ns_path}:{entry.namespace}" if ns_path else entry.namespace
            yield from _iter_patterns(entry, new_ns)
        elif isinstance(entry, URLPattern):
            full_name = entry.name
            if full_name and ns_path:
                full_name = f"{ns_path}:{full_name}"
            yield entry, full_name


def collect_public_urls(app_namespace: Optional[str] = None, public_only: bool = False) -> List[Dict[str, Any]]:
    """Return a list of resolvable URLs (optionally limited to an app namespace).

    Only includes patterns that can be reversed without args. If a view is decorated
    with @public_route, its metadata is included; otherwise a sensible default is used.
    """

    resolver = get_resolver()
    items: List[Dict[str, Any]] = []

    for pattern, full_name in _iter_patterns(resolver):
        if not full_name:
            continue
        if app_namespace and not full_name.startswith(app_namespace + ":"):
            continue

        # Try to reverse without args; skip otherwise
        try:
            url = reverse(full_name)
        except NoReverseMatch:
            continue

        # Resolve view + metadata
        view = pattern.callback
        view_obj = getattr(view, "view_class", view)
        meta = getattr(view_obj, "_public_route", None)
        if public_only and not meta:
            continue

        label = meta.get("label") if meta else full_name.split(":")[-1].replace("_", " ").title()
        order = meta.get("order") if meta else 1000
        icon = meta.get("icon") if meta else None

        items.append({
            "name": full_name,
            "url": url,
            "label": label,
            "order": order,
            "icon": icon,
        })

    return sorted(items, key=lambda x: (x["order"], x["label"]))


@register.simple_tag(takes_context=False)
def app_menu(app_namespace: str, public_only: bool = True) -> List[Dict[str, Any]]:
    """Template tag to fetch menu items for an app namespace.

    Usage: {% load public_urls %} {% app_menu 'admin_frontend' as items %}
    Then: {% for it in items %}<a href="{{ it.url }}">{{ it.label }}</a>{% endfor %}
    """
    return collect_public_urls(app_namespace=app_namespace, public_only=public_only)

