"""
Named layout presets for defpage.

Each layout generates header rows for full-page and OOB rendering.
Built-in layouts delegate to .sx defcomps via ``register_sx_layout``.
Services register custom layouts via ``register_custom_layout``.

Layouts are registered in ``_LAYOUT_REGISTRY`` and looked up by
``get_layout()`` at request time.
"""

from __future__ import annotations

from typing import Any, Callable, Awaitable



# ---------------------------------------------------------------------------
# Layout protocol
# ---------------------------------------------------------------------------

class Layout:
    """A named layout that generates header rows for full and OOB rendering."""

    __slots__ = ("name", "_full_fn", "_oob_fn", "_mobile_fn", "component_names")

    def __init__(
        self,
        name: str,
        full_fn: Callable[..., str | Awaitable[str]],
        oob_fn: Callable[..., str | Awaitable[str]],
        mobile_fn: Callable[..., str | Awaitable[str]] | None = None,
        component_names: list[str] | None = None,
    ):
        self.name = name
        self._full_fn = full_fn
        self._oob_fn = oob_fn
        self._mobile_fn = mobile_fn
        self.component_names = component_names or []

    async def full_headers(self, ctx: dict, **kwargs: Any) -> str:
        result = self._full_fn(ctx, **kwargs)
        if hasattr(result, "__await__"):
            result = await result
        return result

    async def oob_headers(self, ctx: dict, **kwargs: Any) -> str:
        result = self._oob_fn(ctx, **kwargs)
        if hasattr(result, "__await__"):
            result = await result
        return result

    async def mobile_menu(self, ctx: dict, **kwargs: Any) -> str:
        if self._mobile_fn is None:
            return ""
        result = self._mobile_fn(ctx, **kwargs)
        if hasattr(result, "__await__"):
            result = await result
        return result

    def __repr__(self) -> str:
        return f"<Layout:{self.name}>"


# ---------------------------------------------------------------------------
# Registry
# ---------------------------------------------------------------------------

_LAYOUT_REGISTRY: dict[str, Layout] = {}


def register_layout(layout: Layout) -> None:
    """Register a layout preset."""
    _LAYOUT_REGISTRY[layout.name] = layout


def get_layout(name: str) -> Layout | None:
    """Look up a layout by name."""
    return _LAYOUT_REGISTRY.get(name)


# Built-in post/post-admin layouts are registered below via register_sx_layout,
# after that function is defined.


# ---------------------------------------------------------------------------
# register_sx_layout — declarative layout from .sx defcomp names
# ---------------------------------------------------------------------------
# (defined below, used immediately after for built-in "root" layout)
# ---------------------------------------------------------------------------

def register_sx_layout(name: str, full_defcomp: str, oob_defcomp: str,
                       mobile_defcomp: str | None = None) -> None:
    """Register a layout that delegates entirely to .sx defcomps.

    Layout defcomps use IO primitives (via auto-fetching macros) to
    self-populate — no Python env injection needed.  Any extra kwargs
    from the caller are passed as kebab-case env entries::

        register_sx_layout("account", "account-layout-full",
                           "account-layout-oob", "account-layout-mobile")
    """
    from .helpers import _render_to_sx_with_env

    async def full_fn(ctx: dict, **kw: Any) -> str:
        env = {k.replace("_", "-"): v for k, v in kw.items()}
        return await _render_to_sx_with_env(full_defcomp, env)

    async def oob_fn(ctx: dict, **kw: Any) -> str:
        env = {k.replace("_", "-"): v for k, v in kw.items()}
        return await _render_to_sx_with_env(oob_defcomp, env)

    mobile_fn = None
    comp_names = [f"~{full_defcomp}", f"~{oob_defcomp}"]
    if mobile_defcomp:
        async def mobile_fn(ctx: dict, **kw: Any) -> str:
            env = {k.replace("_", "-"): v for k, v in kw.items()}
            return await _render_to_sx_with_env(mobile_defcomp, env)
        comp_names.append(f"~{mobile_defcomp}")

    register_layout(Layout(name, full_fn, oob_fn, mobile_fn, comp_names))


# Register built-in layouts via .sx defcomps
register_sx_layout("root", "layout-root-full", "layout-root-oob", "layout-root-mobile")
register_sx_layout("post", "layout-post-full", "layout-post-oob", "layout-post-mobile")
register_sx_layout("post-admin", "layout-post-admin-full", "layout-post-admin-oob", "layout-post-admin-mobile")


# ---------------------------------------------------------------------------
# Callable layout — services register custom Python layout functions
# ---------------------------------------------------------------------------

_CUSTOM_LAYOUTS: dict[str, tuple] = {}  # name → (full_fn, oob_fn)


def register_custom_layout(name: str,
                           full_fn: Callable[..., str | Awaitable[str]],
                           oob_fn: Callable[..., str | Awaitable[str]],
                           mobile_fn: Callable[..., str | Awaitable[str]] | None = None) -> None:
    """Register a custom layout function.

    Used by services with non-standard header patterns::

        register_custom_layout("sx-section",
            full_fn=my_full_headers,
            oob_fn=my_oob_headers,
            mobile_fn=my_mobile_menu)
    """
    register_layout(Layout(name, full_fn, oob_fn, mobile_fn))
