# Released under the MIT License. See LICENSE for details.
#
"""Codegen for type-safe asset-reference wrapper modules.

Given a package's ``apverid`` and its asset listing (logical path + single-
char kind code per asset), emit a Python module exposing per-kind roots
(``textures``, ``meshes``, ...) whose members are type-safe, ergonomic
references. A leaf reads as a property yielding the kind's reference type
(``zoe_icon: TextureRef``); a subdir nests another group class. Precise
types live in an ``if TYPE_CHECKING`` shadow; runtime resolution is the
dynamic :class:`AssetRefDir` (no per-asset runtime class).

This mirrors the structure of both the client-side asset-package wrappers
(``bauiv1.builtinassets``: ``TexturesGroup`` with asset properties) and the
language-string wrappers (``bacommon.langstr`` codegen) -- same ``FooGroup``
hierarchy, assets exposed as properties. Logical paths split on ``/`` into
subdir nesting (``textures/ui/star`` -> ``textures.ui.star``); each segment
must be an identifier.
"""

#: Reference type name emitted for each single-char asset kind code.
_KIND_TYPES = {'t': 'TextureRef', 'm': 'MeshRef', 's': 'SoundRef'}

#: A nested name tree: a leaf maps a name to its kind code, a subdir maps a
#: name to a nested tree (paths split on ``/``).
type _Tree = dict[str, 'str | _Tree']


def _build_tree(assets: list[tuple[str, str]]) -> _Tree:
    """Split each ``foo/bar`` logical path into a nested name tree."""
    root: _Tree = {}
    for path, kind in assets:
        parts = path.split('/')
        node = root
        for part in parts[:-1]:
            child = node.get(part)
            if child is None:
                child = {}
                node[part] = child
            if not isinstance(child, dict):
                raise ValueError(
                    f'path {path!r} collides with an asset at {part!r}'
                )
            node = child
        leaf = parts[-1]
        if leaf in node:
            raise ValueError(f'duplicate asset path {path!r}')
        node[leaf] = kind
    return root


def _pascal_case(segment: str) -> str:
    """``some_thing`` -> ``SomeThing``."""
    return ''.join(part.capitalize() for part in segment.split('_'))


def _dir_class_name(segments: list[str]) -> str:
    """Public group class name (``['textures'] -> TexturesGroup``)."""
    return ''.join(_pascal_case(s) for s in segments) + 'Group'


def _emit_dir_classes(out: list[str], tree: _Tree, segments: list[str]) -> None:
    """Emit the typed group class for this dir (children first).

    A subdir is a typed attribute pointing at its group class; a leaf asset
    is a property typed by its kind's reference type.
    """
    for name in sorted(tree):
        value = tree[name]
        if isinstance(value, dict):
            _emit_dir_classes(out, value, [*segments, name])
    label = '/'.join(segments)
    out += [
        f'    class {_dir_class_name(segments)}:',
        f'        """Type-safe asset references ({label})."""',
        '',
    ]
    for name in sorted(tree):
        value = tree[name]
        if isinstance(value, dict):
            sub = _dir_class_name([*segments, name])
            out.append(f'        {name}: {sub}')
        else:
            out.append(f'        {name}: {_KIND_TYPES[value]}')
    out.append('')


def generate_asset_ref_wrapper_module(
    apverid: str, assets: list[tuple[str, str]]
) -> str:
    """Return the ``.py`` source for a package's type-safe ref wrapper.

    ``assets`` is the package's asset listing as ``(logical_path, kind_code)``
    pairs (e.g. ``('textures/zoe_icon', 't')``). The output is valid but not
    auto-formatted (the ``_TREE`` literal in particular); a caller writing it
    to the tree should run the formatter before committing.
    """
    for path, kind in assets:
        if kind not in _KIND_TYPES:
            raise ValueError(f'unknown asset kind {kind!r} for {path!r}')
        for part in path.split('/'):
            if not part.isidentifier():
                raise ValueError(f'bad path segment {part!r} in {path!r}')
    tree = _build_tree(sorted(assets))

    # The kind types referenced anywhere in the tree (for the import line).
    used_types = sorted({_KIND_TYPES[k] for _p, k in assets})
    join_used_types = ', '.join(used_types)

    out: list[str] = [
        '# Released under the MIT License. See LICENSE for details.',
        '#',
        '# AUTO-GENERATED by bacommon.assetref codegen; do not edit by hand.',
        f'"""Type-safe asset references for {apverid}."""',
        '',
        'from typing import TYPE_CHECKING',
        '',
        'from bacommon.assetref import AssetRefDir',
        '',
        f"__asset_package__ = '{apverid}'",
        '',
        'if TYPE_CHECKING:',
        f'    from bacommon.assetref import {join_used_types}',
        '',
    ]
    # One group-class hierarchy + module-level accessor per top-level kind
    # dir (``textures``, ``meshes``, ...), mirroring the client wrappers.
    for top in sorted(tree):
        subtree = tree[top]
        assert isinstance(subtree, dict)
        _emit_dir_classes(out, subtree, [top])
        out.append(f'    {top}: {_dir_class_name([top])}')
    out += [
        '',
        f'_TREE = {tree!r}',
        '',
        'if not TYPE_CHECKING:',
    ]
    for top in sorted(tree):
        args = f'__asset_package__, _TREE[{top!r}], {top!r}'
        out.append(f'    {top} = AssetRefDir({args})')
    out.append('')
    return '\n'.join(out)
