"""Annotation markers for fixture-style target parameters (HAR-28).

Two markers are public surface:

  ``Target[T]``    — declares a dependency on a registered target by
                     parameter name. The resolved value is typed ``T``
                     (whatever the target returns — ``Step``,
                     ``RustProject``, ``GoToolchain``, etc.).

  ``BaseImage(X)`` — used in ``Annotated[Step, BaseImage("X")]``. Declares
                     a scratch-rooted Step in image "X" as the parameter
                     value. The first ``.sh()`` call on the parameter
                     inherits ``image="X"``, so the first emitted IR step
                     carries it in the v0 wire format.

Both surface as PEP 593 ``Annotated[...]`` so static type-checkers see
the concrete type (``Step``, ``RustProject``, etc.) while the runtime
decorator reads the marker from ``typing.get_type_hints(include_extras=True)``.

Examples:

    @hm.target()
    def venv(apt_base: hm.Target[hm.Step]) -> hm.Step:
        # mypy/pyright: apt_base is Step. assert_type passes.
        return apt_base.sh("python3 -m venv .venv")

    @hm.target()
    def apt_base(
        base: Annotated[hm.Step, hm.BaseImage("ubuntu-24.04")],
    ) -> hm.Step:
        # mypy/pyright: base is Step. assert_type passes.
        return base.sh("apt-get update")

The callable ``BaseImage(...)`` form is preferred over the older
``BaseImage["..."]`` subscript form because type checkers parse the
hyphenated image string as arithmetic in subscript position.
"""

from __future__ import annotations

from typing import Annotated, TypeVar

T = TypeVar("T")


class _TargetMarker:
    """Sentinel class for Annotated metadata. The module-level
    instance ``_TARGET_MARKER`` is the actual sentinel value."""

    __slots__ = ()

    def __repr__(self) -> str:
        return "<hm.Target marker>"


_TARGET_MARKER = _TargetMarker()


# Annotated with a TypeVar produces a generic alias; subscripting
# ``Target[Step]`` resolves to ``Annotated[Step, _TARGET_MARKER]``.
Target = Annotated[T, _TARGET_MARKER]


class _BaseImageMarker:
    """Metadata holder for the BaseImage("...") annotation."""

    __slots__ = ("image",)

    def __init__(self, image: str) -> None:
        self.image = image

    def __repr__(self) -> str:
        return f"<hm.BaseImage({self.image!r}) marker>"

    def __eq__(self, other: object) -> bool:
        return isinstance(other, _BaseImageMarker) and self.image == other.image

    def __hash__(self) -> int:
        return hash(("_BaseImageMarker", self.image))


def BaseImage(image: str) -> _BaseImageMarker:  # noqa: N802 — factory mimicking a type
    """Create a ``BaseImage`` annotation marker for a target parameter.

    Use as ``Annotated[Step, BaseImage("ubuntu-24.04")]`` on a parameter
    of a ``@hm.target()`` function. The runtime decorator injects a
    scratch-rooted ``Step`` with the given image set as the parameter value.
    The first ``.sh()`` call on that step inherits the image, so the first
    emitted IR step carries ``image`` in the v0 wire format.

    Args:
        image: Non-empty Docker image string (e.g. ``"ubuntu-24.04"``).

    Returns:
        A ``_BaseImageMarker`` for use in ``Annotated[Step, ...]``.

    Raises:
        TypeError: If ``image`` is not a non-empty string.

    Examples:
        >>> import harmont as hm
        >>> from typing import Annotated
        >>> @hm.target()
        ... def my_target(base: Annotated[hm.Step, hm.BaseImage("ubuntu-24.04")]) -> hm.Step:
        ...     return base.sh("apt-get update")
    """
    if not isinstance(image, str) or not image:
        msg = (
            'hm: BaseImage(...) takes a non-empty image string\n  → e.g. BaseImage("ubuntu-24.04")'
        )
        raise TypeError(msg)
    return _BaseImageMarker(image)
