from collections.abc import Iterable
from typing import Any, Generic, TypeVar

from typing_extensions import Self, override

from .core import Formula
from .operators import Bounds, M, M_neg, M_le, M_ge, M_neg_ge

class PanicException(Exception): ...

T = TypeVar("T", covariant=True)

class Top(Any):  # pyright: ignore[reportAny, reportExplicitAny]
    def __le__(self, other: object) -> bool: ...
    def __lt__(self, other: object) -> bool: ...
    @override
    def __eq__(self, other: object) -> bool: ...
    def __ge__(self, other: object) -> bool: ...
    def __gt__(self, other: object) -> bool: ...

class Bottom(Any):  # pyright: ignore[reportAny, reportExplicitAny]
    def __le__(self, other: object) -> bool: ...
    def __lt__(self, other: object) -> bool: ...
    @override
    def __eq__(self, other: object) -> bool: ...
    def __ge__(self, other: object) -> bool: ...
    def __gt__(self, other: object) -> bool: ...

class Trace(Generic[T]):
    def __new__(cls, elements: dict[float, T] | Trace[T]) -> Self: ...
    def __getitem__(self, value: float) -> T: ...
    def times(self) -> Iterable[float]: ...
    def states(self) -> Iterable[T]: ...
    def at_time(self, time: float) -> T | None: ...

class Predicate(Formula[dict[str, float], float]):
    def __new__(cls, coefficients: dict[str, float], constant: float) -> Self: ...
    @override
    def evaluate(self, trace: Trace[dict[str, float]]) -> Trace[float]: ...

S = TypeVar("S", contravariant=True)

class Not(Formula[S, M_neg]):
    def __new__(cls, subformula: Formula[S, M_neg]) -> Self: ...
    @override
    def evaluate(self, trace: Trace[S]) -> Trace[M_neg]: ...

class And(Formula[S, M_le]):
    def __new__(cls, lhs: Formula[S, M_le], rhs: Formula[S, M_le]) -> Self: ...
    @override
    def evaluate(self, trace: Trace[S]) -> Trace[M_le]: ...

class Or(Formula[S, M_ge]):
    def __new__(cls, lhs: Formula[S, M_ge], rhs: Formula[S, M_ge]) -> Self: ...
    @override
    def evaluate(self, trace: Trace[S]) -> Trace[M_ge]: ...

class Implies(Formula[S, M_neg_ge]):
    def __new__(cls, lhs: Formula[S, M_neg_ge], rhs: Formula[S, M_neg_ge]) -> Self: ...
    @override
    def evaluate(self, trace: Trace[S]) -> Trace[M_neg_ge]: ...

class Next(Formula[S, M]):
    def __new__(cls, subformula: Formula[S, M]) -> Self: ...
    @override
    def evaluate(self, trace: Trace[S]) -> Trace[M]: ...

class Always(Formula[S, M_le]):
    def __new__(cls, bounds: Bounds | None, subformula: Formula[S, M_le]) -> Self: ...
    @override
    def evaluate(self, trace: Trace[S]) -> Trace[M_le]: ...

class Eventually(Formula[S, M_ge]):
    def __new__(cls, bounds: Bounds | None, subformula: Formula[S, M_ge]) -> Self: ...
    @override
    def evaluate(self, trace: Trace[S]) -> Trace[M_ge]: ...
