# ===============================================================================
# region Internal Helpers
# ===============================================================================

import json
import re
from typing import Any, get_args, get_origin

from pydantic import BaseModel

_VALID_SUBJECT_SEGMENT = re.compile(r"^[A-Za-z0-9_-]+$")


def _extract_generic_args(
    target: Any,
    generic_origin: type,
) -> tuple[type, ...]:
    """Extract concrete type arguments bound to *generic_origin*.

    Works for:

    - **Subscripted instances** with ``__orig_class__``
      (e.g. ``MyGeneric[int, str](...)`` — Python 3.12+ sets this after
      ``__init__`` returns).
    - **Subclasses** with ``__orig_bases__``
      (e.g. ``class Sub(MyGeneric[int, str]): ...``).

    Returns an empty tuple when no matching parameterization is found.
    """
    # 1. __orig_class__ — set on instances of subscripted generics
    orig_class = getattr(target, "__orig_class__", None)
    if orig_class is not None:
        origin = get_origin(orig_class)
        if origin is generic_origin:
            return get_args(orig_class)

    # 2. __orig_bases__ — set on subclass definitions
    for base in getattr(target, "__orig_bases__", ()):
        origin = get_origin(base)
        if origin is generic_origin:
            return get_args(base)

    return ()


def _sanitize_name(raw: str) -> str:
    """Return *raw* with characters illegal in JetStream names replaced by ``_``."""
    out = raw.replace(".", "_").replace("*", "_").replace(">", "_")
    out = out.replace("/", "_").replace("\\", "_").strip()
    return re.sub(r"\s+", "_", out)


def _encode_payload(obj: Any) -> bytes:
    """Serialize *obj* to a JSON ``bytes`` payload suitable for NATS."""
    if obj is None:
        return b"null"
    if isinstance(obj, (bytes, bytearray)):
        return bytes(obj)
    if isinstance(obj, BaseModel):
        return obj.model_dump_json().encode("utf-8")
    return json.dumps(obj, ensure_ascii=False, default=str).encode("utf-8")


def _subject_with_id(base: str, identifier: str) -> str:
    """Append *identifier* as a single NATS subject token to *base*."""
    if not _VALID_SUBJECT_SEGMENT.match(identifier):
        raise ValueError(f"Identifier must be a single NATS subject segment (allowed: A-Za-z0-9_-), got: {identifier!r}")
    return f"{base}.{identifier}"
