"""
Named layout presets for defpage.

Each layout generates header rows for full-page and OOB rendering.
Layouts wrap existing helper functions from ``shared.sx.helpers`` so
defpage can reference them by name (e.g. ``:layout :root``).

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

from .helpers import (
    root_header_sx, post_header_sx, post_admin_header_sx,
    oob_header_sx,
    mobile_menu_sx, mobile_root_nav_sx,
    post_mobile_nav_sx, post_admin_mobile_nav_sx,
)


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

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

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

    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,
    ):
        self.name = name
        self._full_fn = full_fn
        self._oob_fn = oob_fn
        self._mobile_fn = mobile_fn

    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 layouts
# ---------------------------------------------------------------------------

async def _post_full(ctx: dict, **kw: Any) -> str:
    root_hdr = await root_header_sx(ctx)
    post_hdr = await post_header_sx(ctx)
    return "(<> " + root_hdr + " " + post_hdr + ")"


async def _post_oob(ctx: dict, **kw: Any) -> str:
    post_hdr = await post_header_sx(ctx, oob=True)
    # Also replace #post-header-child (empty — clears any nested admin rows)
    child_oob = await oob_header_sx("post-header-child", "", "")
    return "(<> " + post_hdr + " " + child_oob + ")"


async def _post_admin_full(ctx: dict, **kw: Any) -> str:
    slug = ctx.get("post", {}).get("slug", "")
    selected = kw.get("selected", "")
    root_hdr = await root_header_sx(ctx)
    admin_hdr = await post_admin_header_sx(ctx, slug, selected=selected)
    post_hdr = await post_header_sx(ctx, child=admin_hdr)
    return "(<> " + root_hdr + " " + post_hdr + ")"


async def _post_admin_oob(ctx: dict, **kw: Any) -> str:
    slug = ctx.get("post", {}).get("slug", "")
    selected = kw.get("selected", "")
    post_hdr = await post_header_sx(ctx, oob=True)
    admin_hdr = await post_admin_header_sx(ctx, slug, selected=selected)
    admin_oob = await oob_header_sx("post-header-child", "post-admin-header-child", admin_hdr)
    return "(<> " + post_hdr + " " + admin_oob + ")"


async def _post_mobile(ctx: dict, **kw: Any) -> str:
    return mobile_menu_sx(await post_mobile_nav_sx(ctx), await mobile_root_nav_sx(ctx))


async def _post_admin_mobile(ctx: dict, **kw: Any) -> str:
    slug = ctx.get("post", {}).get("slug", "")
    selected = kw.get("selected", "")
    return mobile_menu_sx(
        await post_admin_mobile_nav_sx(ctx, slug, selected),
        await post_mobile_nav_sx(ctx),
        await mobile_root_nav_sx(ctx),
    )


register_layout(Layout("post", _post_full, _post_oob, _post_mobile))
register_layout(Layout("post-admin", _post_admin_full, _post_admin_oob, _post_admin_mobile))


# ---------------------------------------------------------------------------
# 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.

    The defcomps read ctx values as free variables from the evaluation
    environment (populated by ``_ctx_to_env``).  Python layouts become
    one-liners::

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

    async def full_fn(ctx: dict, **kw: Any) -> str:
        env = _ctx_to_env(ctx)
        env.update({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 = _ctx_to_env(ctx, oob=True)
        env.update({k.replace("_", "-"): v for k, v in kw.items()})
        return await render_to_sx_with_env(oob_defcomp, env)

    mobile_fn = None
    if mobile_defcomp:
        async def mobile_fn(ctx: dict, **kw: Any) -> str:
            env = _ctx_to_env(ctx)
            env.update({k.replace("_", "-"): v for k, v in kw.items()})
            return await render_to_sx_with_env(mobile_defcomp, env)

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


# Register built-in "root" layout via .sx defcomps
register_sx_layout("root", "layout-root-full", "layout-root-oob", "layout-root-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))
