"""Best-of-N-Attempts SelectionPolicy component — spend one budget unit per *attempt*, valid or not.

The attempt-counted variant of :class:`~galapagos.scaffolds.best_of_n.selection_policy.BestOfNPolicy`.
Where the faithful ``best_of_n`` advances the parent-reuse counter only on a valid child (in
``observe``), this variant advances it once per :meth:`select` — so a parse/eval failure still spends
one of the parent's N attempts and the parent rotates on a fixed cadence of N iterations.
"""
from __future__ import annotations

from ...records import RunState, Selection
from ..best_of_n.selection_policy import BestOfNPolicy


class BestOfNAttemptsPolicy(BestOfNPolicy):
    """Reuse one parent for N consecutive *attempts* (iterations), then switch to the global best.

    Only the counter-increment site differs from :class:`BestOfNPolicy`: the budget is spent at
    selection time, so failed/invalid attempts count too. Parent choice and inspiration sampling are
    inherited unchanged (``_choose_parent`` / ``_inspirations``).
    """

    def select(self, population, state: RunState | None = None) -> Selection:
        members = population.all()
        if not members:
            raise RuntimeError("cannot select from an empty population")
        parent = self._choose_parent(members)
        self.uses += 1   # attempt-counted: spend the budget now, regardless of the child's outcome
        return Selection(parent=parent, inspirations=self._inspirations(population, parent), pool=members)

    def observe(self, genome, state: RunState | None = None) -> None:
        pass  # budget already spent in select(); nothing to do on the scored child
