# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-of-a-best-selector-silently-return-a-stale-or-wrong-element-under-mutation.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-of-a-best-selector-silently-return-a-stale-or-wrong-element-under-mutation.warrant.json
# Markdown SHA-256:   dd0c5b34a6e2d996e736a72c544873a44f50961def8d5b521304d2fbd8c5fccf
# Code AST SHA-256:   439fa2832a9737d3ea4d6f42a1f0a7b87f60be2c29e2e8a4f6e57656cf20aab9
# Verified Classes:   StaleSelectionError, Selection, VersionedBestSelector
# Defensive Guards:   1 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import Callable, Generic, Iterable, Optional, TypeVar

T = TypeVar("T")


class StaleSelectionError(RuntimeError):
    """Raised when a 'best' element is consumed after the collection changed."""


@dataclass(frozen=True)
class Selection(Generic[T]):
    element: T
    version: int


class VersionedBestSelector(Generic[T]):
    """
    Selects the best element over a versioned collection and binds the
    result to the collection version observed at selection time.

    The returned Selection is a HINT. It is only safe to consume if the
    caller confirms the version is unchanged (see consume()).
    """

    def __init__(self, key: Callable[[T], float]) -> None:
        self._key = key
        self._items: list[T] = []
        self._version = 0

    def add(self, item: T) -> None:
        self._items.append(item)
        self._version += 1

    def remove(self, item: T) -> None:
        self._items.remove(item)
        self._version += 1

    @property
    def version(self) -> int:
        return self._version

    def select_best(self) -> Optional[Selection[T]]:
        # Snapshot the version BEFORE the scan so any mutation during the
        # scan invalidates the result rather than silently staling it.
        observed = self._version
        if not self._items:
            return None
        best = min(self._items, key=self._key)
        return Selection(element=best, version=observed)

    def consume(self, selection: Selection[T], use: Callable[[T], None]) -> None:
        """
        Consume a selection only if the collection has not changed since
        selection. This is the atomic-claim boundary: selection and use
        must be validated against the same version.
        """
        if selection.version != self._version:
            raise StaleSelectionError(
                f"selection made at version {selection.version}, "
                f"collection is now at version {self._version}; reselect"
            )
        use(selection.element)


# --- Example: scheduler dispatch that must not double-dispatch ---
def dispatch_next(selector: VersionedBestSelector[str],
                  dispatch: Callable[[str], bool]) -> None:
    sel = selector.select_best()
    if sel is None:
        return
    try:
        selector.consume(sel, dispatch)
    except StaleSelectionError:
        # Retry once against the fresh version; bounded by caller policy.
        sel = selector.select_best()
        if sel is None:
            return
        selector.consume(sel, dispatch)  # may raise again; caller decides
