"""DetailLevelMap inheritance for UKO Layer 2 paradigm vocabularies.

Implements the DetailLevelMap inheritance chain where Layer 2 (paradigm)
maps extend the Layer 1 (``uko-code:``) base map by inserting new named
levels at specific integer positions and reassigning subsequent depths.

The inheritance chain is:
  Layer 2 (e.g., ``uko-oo:``) -> Layer 1 (``uko-code:``) -> Layer 0 (``uko:``)

When a paradigm map inserts a level (e.g., ``CLASS_HIERARCHY`` at depth 3),
all subsequent depths in the parent map are shifted upward by one to
maintain consecutive integer numbering.

Thread Safety
-------------
Module-level map constants (``CODE_DETAIL_LEVEL_MAP``, etc.) are
initialised once at import time under the GIL and are thereafter
read-only.  Do **not** call ``register()`` on these constants after
module initialisation.

Based on ``docs/specification.md`` ~lines 24923-25027, 42380-42469.
"""

from __future__ import annotations

from types import MappingProxyType

import structlog

from cleveragents.domain.models.acms.crp import DetailLevelMap

__all__: list[str] = [
    "CODE_DETAIL_LEVEL_MAP",
    "FUNC_DETAIL_LEVEL_MAP",
    "OO_DETAIL_LEVEL_MAP",
    "PROC_DETAIL_LEVEL_MAP",
    "DetailLevelMapBuilder",
    "build_effective_map",
]

_log = structlog.get_logger(__name__)

#: Default max depth for code-domain maps (spec lines 24970-24985).
_CODE_MAX_DEPTH: int = 9

#: Parent domain prefix for Layer 2 maps.
_CODE_DOMAIN: str = "uko-code:"

# ---------------------------------------------------------------------------
# Layer 1 base map: uko-code: (spec lines 24970-24985)
# ---------------------------------------------------------------------------

_CODE_LEVELS: MappingProxyType[str, int] = MappingProxyType(
    {
        "MODULE_LISTING": 0,
        "MODULE_GRAPH": 1,
        "MEMBER_LISTING": 2,
        "MEMBER_SUMMARY": 3,
        "SIGNATURES": 4,
        "SIGNATURES_WITH_DOCS": 5,
        "STRUCTURAL_OUTLINE": 6,
        "KEY_LOGIC": 7,
        "NEAR_COMPLETE": 8,
        "FULL_SOURCE": 9,
    }
)

#: Layer 1 base map.  Read-only after module initialisation.
CODE_DETAIL_LEVEL_MAP = DetailLevelMap(
    domain=_CODE_DOMAIN,
    parent=None,
    levels=dict(_CODE_LEVELS),
    max_depth=_CODE_MAX_DEPTH,
)


# ---------------------------------------------------------------------------
# DetailLevelMap builder with insertion and reassignment logic
# ---------------------------------------------------------------------------


class DetailLevelMapBuilder:
    """Build a child DetailLevelMap that inherits from a parent.

    Supports inserting new named levels at specific positions, which
    shifts all subsequent integer depth assignments upward.

    Args:
        parent: The parent DetailLevelMap to inherit from.
        domain: The UKO namespace for the new map (e.g., ``"uko-oo:"``).

    Raises:
        ValueError: If *domain* is empty or whitespace-only.

    Example::

        builder = DetailLevelMapBuilder(CODE_DETAIL_LEVEL_MAP, "uko-oo:")
        builder.insert_after("MEMBER_LISTING", "CLASS_HIERARCHY")
        builder.insert_after("SIGNATURES_WITH_DOCS", "VISIBILITY_ANNOTATED")
        oo_map = builder.build()
        assert oo_map.resolve("CLASS_HIERARCHY") == 3
    """

    def __init__(self, parent: DetailLevelMap, domain: str) -> None:
        if not domain or not domain.strip():
            raise ValueError("domain must be a non-empty string")
        self._parent = parent
        self._domain = domain.strip()
        self._insertions: list[tuple[str, str]] = []
        _log.debug(
            "detail_level_map_builder.created",
            parent_domain=parent.domain,
            child_domain=self._domain,
        )

    @property
    def parent(self) -> DetailLevelMap:
        """Return the parent map."""
        return self._parent

    @property
    def domain(self) -> str:
        """Return the domain namespace."""
        return self._domain

    def insert_after(self, after_level: str, new_level: str) -> None:
        """Insert a new named level after an existing one.

        The new level will be assigned the next integer after
        *after_level*, and all subsequent levels shift upward by one.

        Args:
            after_level: Name of the existing level to insert after.
            new_level: Name of the new level to insert.

        Raises:
            ValueError: If *after_level* is not found in the parent's
                effective map, if *new_level* already exists, or if
                either argument is empty/whitespace.
        """
        if not new_level or not new_level.strip():
            raise ValueError("new_level must be a non-empty string")
        if not after_level or not after_level.strip():
            raise ValueError("after_level must be a non-empty string")
        after_stripped = after_level.strip()
        known = set(self._parent.effective_levels().keys())
        known.update(new_name for _, new_name in self._insertions)
        if after_stripped not in known:
            raise ValueError(
                f"Level '{after_stripped}' not found in parent map "
                f"'{self._parent.domain}'"
            )
        stripped = new_level.strip()
        if stripped in known:
            raise ValueError(
                f"Level '{stripped}' already exists in map '{self._parent.domain}'"
            )
        self._insertions.append((after_stripped, stripped))
        _log.debug(
            "detail_level_map_builder.insert_after",
            after_level=after_stripped,
            new_level=stripped,
            domain=self._domain,
        )

    def build(self) -> DetailLevelMap:
        """Build the child DetailLevelMap with insertions applied.

        Returns:
            A new DetailLevelMap whose levels include all parent levels
            plus inserted levels, with integer depths reassigned
            consecutively.

        Raises:
            ValueError: If a duplicate level name would be introduced
                (caught by ``build_effective_map``).
        """
        effective = build_effective_map(self._parent, self._insertions)

        # build_effective_map already validates no duplicate names
        levels = {name: depth for name, depth in effective}
        max_depth = max(levels.values()) if levels else 0

        result = DetailLevelMap(
            domain=self._domain,
            parent=self._parent,
            levels=levels,
            max_depth=max_depth,
        )

        _log.debug(
            "detail_level_map_builder.built",
            domain=self._domain,
            num_levels=len(levels),
            max_depth=max_depth,
        )
        return result


def build_effective_map(
    parent: DetailLevelMap,
    insertions: list[tuple[str, str]] | tuple[tuple[str, str], ...],
) -> tuple[tuple[str, int], ...]:
    """Build the effective (name, depth) pairs for a child map.

    Takes a parent map and a sequence of (after_level, new_level)
    insertions, applies them in order, and returns the fully
    reassigned depth mappings as consecutive integers starting from 0.

    Args:
        parent: Parent DetailLevelMap to extend.
        insertions: Sequence of (after_level, new_level) pairs.

    Returns:
        Tuple of (name, depth) pairs ordered by depth.

    Raises:
        ValueError: If an after_level is not found in the current map,
            or if a duplicate level name would be introduced.
    """
    # Start with parent's effective levels sorted by depth
    parent_levels = parent.effective_levels()
    sorted_levels = sorted(parent_levels.items(), key=lambda x: x[1])

    # Build ordered list of level names with O(1) membership set
    ordered_names: list[str] = [name for name, _depth in sorted_levels]
    known: set[str] = set(ordered_names)

    # Apply insertions in order
    for after_level, new_level in insertions:
        if after_level not in known:
            raise ValueError(
                f"Level '{after_level}' not found in effective map "
                f"during insertion of '{new_level}'"
            )
        if new_level in known:
            raise ValueError(f"Duplicate level name '{new_level}' during insertion")
        idx = ordered_names.index(after_level)
        ordered_names.insert(idx + 1, new_level)
        known.add(new_level)

    # Reassign consecutive integers
    return tuple((name, i) for i, name in enumerate(ordered_names))


# ---------------------------------------------------------------------------
# Layer 2 maps: uko-oo:, uko-func:, uko-proc:
# ---------------------------------------------------------------------------


def _build_oo_map() -> DetailLevelMap:
    """Build the uko-oo: DetailLevelMap.

    Inserts CLASS_HIERARCHY after MEMBER_LISTING (depth 3) and
    VISIBILITY_ANNOTATED after SIGNATURES_WITH_DOCS (depth 7).

    Based on specification.md ~lines 24987-25004.
    """
    builder = DetailLevelMapBuilder(CODE_DETAIL_LEVEL_MAP, "uko-oo:")
    builder.insert_after("MEMBER_LISTING", "CLASS_HIERARCHY")
    # Note: issue #575 describes this as "SIGNATURES_WITH_DOCS at depth 5"
    # but the spec (lines 24987-25004) defines VISIBILITY_ANNOTATED as
    # the OO-specific insertion.  SIGNATURES_WITH_DOCS already exists in
    # the parent uko-code: map.
    builder.insert_after("SIGNATURES_WITH_DOCS", "VISIBILITY_ANNOTATED")
    return builder.build()


def _build_passthrough_map(domain: str) -> DetailLevelMap:
    """Build a passthrough child DetailLevelMap that inherits all parent levels.

    Used for paradigm maps (e.g., ``uko-func:``, ``uko-proc:``) that inherit
    all named levels from ``uko-code:`` without additional insertions.  Levels
    are resolved via ``effective_levels()`` through the parent chain.

    Args:
        domain: UKO namespace for the child map (e.g., ``"uko-func:"``).

    Returns:
        A new ``DetailLevelMap`` with empty own levels and
        ``max_depth`` inherited from the code-domain constant.
    """
    return DetailLevelMap(
        domain=domain,
        parent=CODE_DETAIL_LEVEL_MAP,
        levels={},
        max_depth=_CODE_MAX_DEPTH,
    )


#: Layer 2 OO map.  Read-only after module initialisation.
OO_DETAIL_LEVEL_MAP: DetailLevelMap = _build_oo_map()
#: Layer 2 functional map.  Read-only after module initialisation.
FUNC_DETAIL_LEVEL_MAP: DetailLevelMap = _build_passthrough_map("uko-func:")
#: Layer 2 procedural map.  Read-only after module initialisation.
PROC_DETAIL_LEVEL_MAP: DetailLevelMap = _build_passthrough_map("uko-proc:")
