"""
Immutable, hashable route type with compact wire encoding.

`Route` models a navigable path made of hashable *segments* (e.g., keys or
indices) to address locations inside nested structures (dictionary-like
objects, in-memory stores, etc.). It supports:

- Construction from arbitrary hashable segments
- Path extension via the `/` operator
- Compact Base64URL serialize/deserialize (tuples preserved via a tagged wire format)
- Friendly string/repr formats, iteration, slicing, and hashing

Round-trip property (for supported segments):
    Route.deserialize(Route(...).serialize()) == Route(...)
"""

from __future__ import annotations

import json
from dataclasses import dataclass
from typing import Union, Hashable, Tuple, overload
from collections.abc import Iterator
from pyroute.utils import b64u_encode, b64u_decode
from pyroute.serializer import from_wire, to_wire

Segment = Hashable


@dataclass(frozen=True, slots=True)
class Route:
    """
    A compact, immutable path built from hashable *segments*.

    Each segment is a key/index used to navigate into nested, dictionary-like
    structures (lists, dicts, custom mappings). Segment types include:
        - `str`, `int`, `float`, `bool`, `None`
        - `tuple` of segments (recursively nested)

    Features:
        - Wire-safe Base64URL serialization with tuple preservation.
        - `/` operator for ergonomic path extension.
        - Human-friendly compact form: `<root><a><1><b>`.
    """

    segments: Tuple[Segment, ...]

    def __init__(self, *segments: Segment) -> None:
        """
        Build a new `Route` from hashable segments.

        Args:
            *segments: Path components (must be hashable).

        Raises:
            TypeError: If any segment is not hashable.
        """
        if not all(isinstance(s, Hashable) for s in segments):
            raise TypeError("Route segments must be Hashable")
        object.__setattr__(self, "segments", tuple(segments))

    def serialize(self) -> str:
        """
        Encode this route into a compact Base64URL string.

        Implementation notes:
            - Each segment is converted to a wire-safe node via `to_wire`.
            - The node list is serialized to bytes and then Base64URL-encoded.

        Returns:
            A Base64URL string representing this route.

        Example:
            >>> Route("a", ("b", 1)).serialize()
            'eyJfX3QiOiJ0dXBsZSIsInYiOlsiYSIseyJfX3QiOiJ0dXBsZSIsInYiOlsiYiIsMV19XX0'
        """
        payload = [to_wire(s) for s in self.segments]
        data = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode(
            "utf-8"
        )
        return b64u_encode(data)

    @classmethod
    def deserialize(cls, serial: str) -> Route:
        """
        Decode a Base64URL string back into a `Route`.

        Args:
            serial: A string previously produced by `serialize()`.

        Returns:
            A reconstructed `Route` instance.

        Raises:
            ValueError: If the payload is malformed or has an unexpected shape.
        """
        try:
            raw = b64u_decode(serial)
            payload = json.loads(raw.decode("utf-8"))
        except Exception as e:
            raise ValueError("Invalid route payload (decode failure)") from e

        if not isinstance(payload, list):
            raise ValueError("Invalid route payload (expected a list of segments)")

        segs: Tuple[Segment, ...] = tuple(from_wire(x) for x in payload)
        return cls(*segs)

    def __repr__(self) -> str:
        """
        Compact human-readable form.

        Example:
            >>> repr(Route("a", "b", ("c", 4)))
            '<root><a><b><(c, 4)>'
        """
        return "<root>" + "".join(f"<{s}>" for s in self.segments)

    __str__ = __repr__

    def __eq__(self, other: object) -> bool:
        """Equality by segment tuple."""
        if not isinstance(other, Route):
            return NotImplemented
        return self.segments == other.segments

    def __hash__(self) -> int:
        """Hash by the underlying segment tuple (dict/set friendly)."""
        return hash(self.segments)

    def __len__(self) -> int:
        """Number of segments."""
        return len(self.segments)

    def __iter__(self) -> Iterator[Segment]:
        """Iterate segments from left to right."""
        return iter(self.segments)

    def __reversed__(self) -> Iterator[Segment]:
        """Iterate segments from right to left."""
        return reversed(self.segments)

    def __contains__(self, item: Segment) -> bool:
        """Return True if a segment exists in this route."""
        return item in self.segments

    @overload
    def __getitem__(self, idx: int) -> Segment: ...
    @overload
    def __getitem__(self, idx: slice) -> Route: ...

    def __getitem__(self, idx: Union[int, slice]) -> Union[Segment, Route]:
        """
        Index or slice the route.

        - Integer index returns a single segment.
        - Slice returns a new `Route` containing the subsequence.
        """
        if isinstance(idx, slice):
            return Route(*self.segments[idx])
        return self.segments[idx]

    def __truediv__(self, other: Union[Segment, Route]) -> Route:
        """
        Return a new route extended by one segment using `/`.

        Examples:
            >>> Route("a") / "b"
            <root><a><b>
            >>> Route("a") / Route("b", 1)
            <root><a><b><1>
        """
        if isinstance(other, Route):
            return Route(*self.segments, *other.segments)
        if isinstance(other, Hashable):
            return Route(*self.segments, other)
        raise TypeError("Operand must be a Route or a hashable segment")

    def __add__(self, other: Union[Segment, Route]) -> Route:
        """
        Concatenate another Route or a single segment.

        Accepted forms:
            - Route + Route   -> concatenation of segments
            - Route + segment -> append one segment

        Examples:
            >>> Route("a") + Route("b", 1)
            <root><a><b><1>
            >>> Route("a") + "b"
            <root><a><b>
        """
        if isinstance(other, Route):
            return Route(*self.segments, *other.segments)
        if isinstance(other, Hashable):
            return Route(*self.segments, other)
        raise TypeError("Operand must be a Route or a hashable segment")
