from __future__ import annotations

from typing import TYPE_CHECKING, Annotated, Final, Never, override

__all__ = ["DEFAULT", "DefaultSentinel", "Hyper", "HyperMarker"]


class DefaultSentinel:
  """Sentinel for default nested config values."""

  @override
  def __repr__(self) -> str:
    return "DEFAULT"


class HyperMarker:
  """Internal marker for hyperparameters."""


if TYPE_CHECKING:
  # In type checking mode, Hyper is a transparent alias to T (annotated with marker)
  # This allows Hyper[int] to be treated exactly as int by type checkers.
  # We use PEP 695 syntax (Python 3.12+) which is supported by modern type checkers.
  type Hyper[T, *Args] = Annotated[T, HyperMarker, *Args]

  # DEFAULT should be assignable to any type (int, float, MakeableModel, etc.)
  DEFAULT: Never
else:

  class Hyper:
    """Type supporting Hyper[T] and Hyper[T, Ge[2], Le[100]] syntax."""

    def __class_getitem__[T](
      cls, args: type[T] | tuple[type[T], *tuple[object, ...]]
    ) -> object:
      """Support Hyper[T] or Hyper[T, Ge[2], Le[100]] syntax.

      Examples:
        period: Hyper[int] = 14  # Simple type
        period: Hyper[int, Ge[2], Le[100]] = 14  # With constraints

      Returns:
        Annotated type with HyperMarker for runtime detection.
      """
      # Handle simple case: Hyper[int]
      if not isinstance(args, tuple):
        return Annotated[args, HyperMarker]

      # Handle constraint case: Hyper[int, Ge[2], Le[100]]
      # We just pass everything to Annotated. The extraction logic
      # in _extract_hyper_field_info will handle converting constraints to Field.
      inner_type = args[0]
      constraints = args[1:]
      return Annotated[inner_type, HyperMarker, *constraints]

  DEFAULT: Final[DefaultSentinel] = DefaultSentinel()
