<gh_stars>0
"""A context that actually contains some data."""
import asyncio
import dataclasses
from typing import Awaitable, List, Mapping, Tuple

from . import base


@dataclasses.dataclass(frozen=True)
class DataReconstructionCtx:
    parent: "Data"
    awaitables: List[Tuple[str, Awaitable]] = dataclasses.field(default_factory=list)
    static: List[Tuple[str, object]] = dataclasses.field(default_factory=list)

    async def result(self):
        out = dict(self.static)
        if self.awaitables:
            (keys, awaitables) = tuple(zip(*self.awaitables))
            await_rv = await asyncio.gather(*awaitables)
            out |= dict(zip(keys, await_rv))
        return out


class Data(base.Base):
    _raw_data: dict
    _global_awaitable: Awaitable[dict] = None

    def __init__(self, name: str, data: dict):
        super().__init__(name)
        self._raw_data = data

    def _map_data(self, raw_data: dict, skip_tag_detect: bool = False):
        """
        Returns a tuple of <children>, <structure restoration awaitable>.

        skip_tag_detect disables top-level child command detection to prevent infinite loop
        """
        if not isinstance(raw_data, Mapping):
            # No dynamic expansion possible
            async def _return_value():
                return raw_data

            return (
                (),
                _return_value(),
            )
        detected_command_cls = None
        if len(raw_data) == 1 and not skip_tag_detect:
            # All commands have a single top-level key
            detected_command_cls = self.root.find_command(next(iter(raw_data.keys())))
        if detected_command_cls:
            the_child = detected_command_cls(
                name=f"{detected_command_cls=}", data=raw_data
            )
            out_children = (the_child,)
            out_awaitable = the_child.result()
        else:
            # No top-level command detected. Descend into the children
            out_children = []
            rv_ctx = DataReconstructionCtx(parent=self)
            for (key, value) in raw_data.items():
                if isinstance(value, dict):
                    _new_ch, _val_awaitable = self._map_data(value)
                    out_children.extend(_new_ch)
                    rv_ctx.awaitables.append((key, _val_awaitable))
                else:
                    rv_ctx.static.append((key, value))
            out_awaitable = rv_ctx.result()
        return (out_children, out_awaitable)

    async def _compile(self):
        (_children, rv_awaitable) = self._map_data(
            self._raw_data, skip_tag_detect=(self.parent is not self.root)
        )
        for child in _children:
            self.add_child(child)

        assert not self._global_awaitable
        self._global_awaitable = rv_awaitable

    async def _get_result(self):
        return await self._global_awaitable


class File(Data):
    """A top-level data file structure"""

    @property
    def file_depth(self) -> int:
        return self.parent.file_depth + 1
