# Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
# See LICENSE for details.
"""PolicyBoundDeck — a Deck that enforces item compatibility via a pluggable policy.

Example::

    from vcti.deck import Deck, PolicyBoundDeck, CompatibilityPolicy

    class PositiveOnly(CompatibilityPolicy[str, int]):
        def is_compatible(self, item: int, deck_type: str) -> bool:
            return item > 0

    deck = PolicyBoundDeck(deck_type="positive", policy=PositiveOnly())
    deck.add("a", 5)   # OK
    deck.add("b", -1)  # Raises ValueError
"""

from abc import ABC, abstractmethod

from .deck import Deck


class CompatibilityPolicy[DeckTypeT, ItemT](ABC):
    """Abstract base for compatibility policies.

    Implement ``is_compatible`` to define rules governing which items
    can be added to a deck of a given type.

    Type Parameters:
        DeckTypeT: The type/category of the deck.
        ItemT: The type of items to validate.
    """

    @abstractmethod
    def is_compatible(self, item: ItemT, deck_type: DeckTypeT) -> bool:
        """Check if *item* is compatible with *deck_type*.

        Returns:
            True if the item may be added, False otherwise.
        """
        ...


class PolicyBoundDeck[DeckTypeT, ItemT](Deck[ItemT]):
    """A Deck bound to a compatibility policy.

    Extends :class:`Deck` by delegating ``_is_compatible`` to an external
    :class:`CompatibilityPolicy` instance. The policy is set at construction
    time and cannot be changed.

    Type Parameters:
        DeckTypeT: The type/category of this deck.
        ItemT: The type of items stored in this deck.

    Example::

        from enum import StrEnum, auto
        from vcti.deck import PolicyBoundDeck, CompatibilityPolicy

        class LayerKind(StrEnum):
            IMAGE = auto()
            VECTOR = auto()

        class Layer:
            def __init__(self, kind: LayerKind):
                self.kind = kind

        class SameKindPolicy(CompatibilityPolicy[LayerKind, Layer]):
            def is_compatible(self, item: Layer, deck_type: LayerKind) -> bool:
                return item.kind == deck_type

        image_deck = PolicyBoundDeck(deck_type=LayerKind.IMAGE, policy=SameKindPolicy())
        image_deck.add("img1", Layer(LayerKind.IMAGE))    # OK
        image_deck.add("vec1", Layer(LayerKind.VECTOR))   # Raises ValueError
    """

    __slots__ = ("deck_type", "policy")

    def __init__(
        self,
        deck_type: DeckTypeT,
        policy: CompatibilityPolicy[DeckTypeT, ItemT],
    ) -> None:
        """Initialize with a deck type and a bound compatibility policy.

        Args:
            deck_type: The type/category of this deck, forwarded to the policy.
            policy: The compatibility policy that governs item admission.
        """
        super().__init__()
        self.deck_type = deck_type
        self.policy = policy

    def _is_compatible(self, item: ItemT) -> bool:
        """Delegate compatibility check to the bound policy."""
        return self.policy.is_compatible(item, self.deck_type)

    def __repr__(self) -> str:
        return f"PolicyBoundDeck(type={self.deck_type}, items={len(self)})"
