from abc import ABC, abstractmethod
from typing import Generic, Iterable, Self, Iterator, TypeVar

from .functional_types import Predicate, Function

I = TypeVar('I')  # Input value
O = TypeVar('O')  # Output value
U = TypeVar('U')  # Unused value


class Monad(Generic[I], ABC, Iterable[I]):
    """
    Base interface for all monadic structures.

    A Monad wraps a value and provides a functional API based on:
    - mapping a function over the value
    - chaining computations
    - safe extraction
    - optional filtering
    - iteration over the contained value (0 or 1 element)

    This interface is inspired by the Scala `Monad` type class and is meant
    to be implemented by concrete monads such as `Option`, `Try`, etc.
    """

    @abstractmethod
    def for_each(self, fn: Function[I, U]) -> None:
        """
        Applies a function for its side effects to the wrapped value.

        If the monad contains a value, `fn(value)` is executed.
        If it contains no value (e.g., Nothing, Failure), nothing happens.

        Equivalent to Scala's `foreach`.

        Example:
            Option.of(10).for_each(print)  # prints 10
        """
        pass

    @abstractmethod
    def map(self, mapping_fn: Function[I, O]) -> "Monad[O]":
        """
        Transforms the wrapped value using the provided function.

        - If the monad contains a value -> returns a monad containing mapping_fn(value)
        - If the monad is empty or failed -> returns the same empty/failure monad

        This operation does NOT flatten nested monads.

        Example:
            Option.of(10).map(lambda x: x * 2)  # Some(20)
        """
        pass

    @abstractmethod
    def flat_map(self, mapping_fn: Function[I, "Monad[O]"]) -> "Monad[O]":
        """
        Applies a function returning a monad and flattens the result.

        - Used for chaining monadic operations
        - Behaves like `map` but avoids nested monads (Monad[Monad[T]])

        Equivalent to Scala's `flatMap`.

        Example:
            def safe_div(x):
                return Try.of(lambda: 10 / x)

            Try.of(lambda: 2).flat_map(safe_div)  # Success(5)
        """
        pass

    @abstractmethod
    def get_or_else(self, default: I) -> I:
        """
        Extracts the contained value or returns the provided default.

        - For monads with a value -> returns the value
        - For empty/failure monads -> returns `default`

        Example:
            Option.of(None).get_or_else(99)  # 99
        """
        pass

    @abstractmethod
    def or_else(self, default: "Monad[I]") -> "Monad[I]":
        """
        Returns this monad if it contains a value, otherwise returns the fallback monad.

        - Useful for specifying fallback computations
        - Does not unwrap values

        Example:
            Option.of(None).or_else(Option.of(5))  # Some(5)
        """
        pass

    @abstractmethod
    def filter(self, predicate: Predicate[I]) -> "Monad[I]":
        """
        Retains the value only if it satisfies the predicate.

        - If the monad contains a value and predicate(value) is True → return the monad
        - Otherwise → return an empty/failure monad

        Example:
            Option.of(10).filter(lambda x: x > 5)  # Some(10)
            Option.of(3).filter(lambda x: x > 5)   # Nothing
        """
        pass

    @abstractmethod
    def flatten(self) -> "Monad[I]":
        """
        Removes one level of monadic nesting.

        - If the monad wraps another monad (e.g., Some(Some(x))) → returns inner monad
        - Otherwise → returns itself

        Example:
            Option.of(Option.of(10)).flatten()  # Some(10)
        """
        pass

    @abstractmethod
    def __iter__(self) -> Iterator[I]:
        """
        Allows the monad to be iterated over like a container with 0 or 1 element.

        - Monads with a value → yield that value
        - Empty/failure monads → yield nothing

        Enables:
            for x in Option.of(10):
                print(x)  # prints 10
        """
        pass
