"""
Command tree support for MoaT commands
"""

from __future__ import annotations

from moat.util import attrdict
from moat.lib.micro import L, TaskGroup
from moat.lib.rpc import LoadCmd

from .dir import BaseSuperCmd

# Typing
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from moat.lib.path import PathElem
    from moat.lib.rpc import BaseCmd, Msg


class BaseLayerCmd(BaseSuperCmd):
    """
    A handler for a single nested app.

    This handler doesn't affect the command hierarchy.
    Its own commands, if any, are reachable by prepending "!" to the command.

    You need to override "gen_cmd" to create the app object.
    """

    app: BaseCmd | None = None
    name: str = "_"

    async def run_app(self):
        """
        The command handler's executor. By default, calls ``self.app.run``
        within the command's context.

        You might override this e.g. for restarting or
        shielding the rest of MoaT from errors.
        """
        if self.app is None:
            raise RuntimeError("No app")
        await self.app.run()

    async def task(self):
        """
        Run the app as a subtask.

        You typically don't override this.
        """
        async with TaskGroup() as tg:
            app = self.app
            if app is not None:
                tg.start_soon(self.run_app)
            if L:
                if app is None:
                    raise RuntimeError("No app")
                await app.wait_ready()
                self.set_ready()

            # await self.app.stopped()
            # the return from the taskgroup already does that

    async def setup(self):
        "attach the nested app"
        await super().setup()
        self.app = await self.gen_cmd()
        if self.app is not None:
            self.app.attached(self, self.name)
            if L:
                self.set_ready()

    async def reload(self):
        "reload the nested app"
        await super().reload()
        if self.app is not None:
            await self.app.reload()

    if L:

        async def wait_ready(self, wait=True):
            "wait for nested app"
            if await super().wait_ready(wait=wait):
                return True
            if self.app is None:
                return None
            return await self.app.wait_ready(wait=wait)

    async def gen_cmd(self) -> BaseCmd | None:
        """
        Create the actual app to use.

        The default uses `None` and leaves the setup to `task`.
        """
        return None

    async def handle(self, msg: Msg, rcmd: list[PathElem]):
        """
        Forward to the sub-app.

        The subcommand "!" redirects to the local handler.
        """
        if rcmd and isinstance(rcmd[-1], str) and rcmd[-1] == "!":
            rcmd.pop()
            return await super().handle(msg, rcmd)

        if L:
            await self.wait_ready()
        if self.app is None:
            raise RuntimeError("No app")
        return await self.app.handle(msg, rcmd)

    if L:

        def set_ready(self):
            "guards that the nested app exists"
            if self.app is None:
                raise RuntimeError("early")
            super().set_ready()

    def __getattr__(self, k):
        if k.startswith("_"):
            raise AttributeError(k)
        if k.endswith("_f"):
            return getattr(self, k[:-2])
        return getattr(self.app, k)


class BaseFwdCmd(BaseLayerCmd):
    """
    A handler for a single nested app that's configured locally.
    """

    async def gen_cmd(self):
        """
        Create the underlying app object
        """
        gcfg = self.cfg
        cfg = gcfg.get("cfg", attrdict())

        # log("Setup %s: %s", self.path, cfg["app"])
        return LoadCmd(cfg)
