from typing import List, Optional, Any, Callable
from django.db import models


class Component:
    """
    Unified DOM-like base class for all UI elements.

    The component class represents a node in the UI tree. It can be instantiated
    and manipulated dynamically before being rendered into HTML.

    Attributes:
        uid (str): Uniquely identifies the element. Essential for HTMX targeting.
        classes (str): Tailwind utility classes applied to the component.
        roles (List[str]): Access control roles. Components automatically prune if
                          the current user lacks the required roles.
        children (List[Component]): Nested child UI components.
        key (str): Binds the component to a context dictionary key or model field.
        static_value (Any): Bypasses context value lookup to force a static render value.
        url (Callable | str): An explicit URL, or a callable resolving to a URL.
    """

    def __init__(
        self,
        uid: str = "",
        classes: str = "",
        roles: List[str] = [],
        children: List["Component"] = [],
        key: str | None = None,
        static_value: Any = None,
        url: Callable | str | None = None,
    ):
        self.uid = uid
        self.classes = classes
        self.roles = roles or []
        # Making children a standard attribute makes DOM operations universally safe
        self.children = children or []
        self.key = key
        self.static_value = static_value
        self.url = url

    # --- Security & Authorization ---
    def check(self, request=None, **kwargs) -> bool:
        """
        Evaluate if the current request satisfies the component's `roles` requirements.
        If this returns False, the component and all descendants are excluded from rendering.
        """
        if not request or not self.roles:
            return True
        user = request.user
        if not user.is_authenticated:
            return False
        if user.is_superuser:
            return True
        return user.role.name in self.roles

    # --- HTML Rendering ---
    def render_html(self, **kwargs) -> str:
        tree = self.build()
        if tree is not self:
            return tree.render(**kwargs)
        return ""

    def render(self, **kwargs) -> str:
        """
        The primary entry point for converting the component tree into an HTML string.
        Safely enforces role checks before delegating to `render_html`.
        """
        request = kwargs.get("request")
        if not self.check(request=request):
            return ""
        return self.render_html(**kwargs)

    def get_children(self) -> List["Component"]:
        return self.children

    def render_partial(self, target_uid: str, **kwargs) -> Optional[str]:
        """
        Recursively scans the tree for a node matching the `target_uid`.
        If found, performs an isolated render of only that node and its children.
        """
        if self.uid == target_uid:
            return self.render(**kwargs)
        for child in self.get_children():
            result = child.render_partial(target_uid, **kwargs)
            if result is not None:
                return result
        return None

    # --- DOM-style Traversal & Mutation ---
    def find(self, target_uid: str) -> Optional["Component"]:
        """Search this node and all descendants for a UID."""
        if not target_uid:
            return None
        if self.uid == target_uid:
            return self
        for child in self.get_children():
            found = child.find(target_uid)
            if found:
                return found
        return None

    def find_parent(self, target_uid: str) -> Optional["Component"]:
        """Find the immediate parent of a specific UID."""
        for child in self.get_children():
            if child.uid == target_uid:
                return self
            found = child.find_parent(target_uid)
            if found:
                return found
        return None

    def insert_before(self, target_uid: str, new_component: "Component") -> bool:
        target_parent = self.find_parent(target_uid)
        if not target_parent:
            raise ValueError("Parent not found")
        idx = None
        for i, child in enumerate(target_parent.get_children()):
            if child.uid == target_uid:
                idx = i
                break
        if idx is None:
            raise ValueError("Why do dis")
        target_parent.children.insert(idx, new_component)
        return True

    def insert_after(self, target_uid: str, new_component: "Component") -> bool:
        target_parent = self.find_parent(target_uid)
        if not target_parent:
            raise ValueError("Target Parent not found")
        idx = None
        for i, child in enumerate(target_parent.get_children()):
            if child.uid == target_uid:
                idx = i
                break
        if idx is None:
            raise ValueError("Sibling not found")
        target_parent.children.insert(idx + 1, new_component)
        return True

    def append(self, target_uid: str, new_component: "Component") -> bool:
        """Find a target node in the tree and append a child to it."""
        target = self.find(target_uid)
        if target is not None and hasattr(target, "children"):
            target.children.append(new_component)
            return True
        return False

    def remove(self, target_uid: str) -> bool:
        """Find the parent of a target node and remove the target from its children."""
        parent = self.find_parent(target_uid)
        if parent is not None and hasattr(parent, "children"):
            parent.children = [c for c in parent.children if c.uid != target_uid]
            return True
        return False

    def replace(self, target_uid: str, new_component: "Component") -> bool:
        """Find a target node and replace it completely in its parent's list."""
        parent = self.find_parent(target_uid)
        if parent is not None and hasattr(parent, "children"):
            for index, child in enumerate(parent.children):
                if child.uid == target_uid:
                    parent.children[index] = new_component
                    return True
        return False

    def build(self) -> "Component":
        """
        Hook for dynamic structures (like Layouts or Forms) to construct their
        internal node hierarchy immediately before rendering.
        Returns the root component of the built tree (usually `self`).
        """
        return self

    def get_root_value(self, key: str, **kwargs):
        path: list[str] = key.split(".")
        if not (len(path) > 0):
            return None

        path.reverse()
        return kwargs.get(path.pop(), None)

    def get_value(self, **kwargs):
        if self.static_value is not None:
            return self.static_value
        key = self.key
        if self.key is None:
            key = "object"

        value = self.get_root_value(key, **kwargs)
        if value is None:
            key = f"object.{key}"
            value = self.get_root_value(key, **kwargs)

        path = key.split(".")
        for key in path[1:]:
            if isinstance(value, dict):
                value = value.get(key, None)
            else:
                value = getattr(value, key, None)
            if isinstance(value, models.Manager):
                value = list(value.all())
            elif callable(value):
                value = value()

        return value

    def get_url(self, **kwargs):
        if self.url is None:
            return None
        if hasattr(self.url, "__call__"):
            obj = self.get_value(**kwargs)
            if obj is not None:
                return self.url(obj)
            else:
                return self.url()
        return self.url
