import abc
import datetime
from collections.abc import Generator
from enum import Enum
from typing import TypedDict, overload, type_check_only
from typing_extensions import Self

@type_check_only
class _DateDict(TypedDict):
    year: int
    month: int
    day: int

class Rounding(Enum):
    PREVIOUS_DAY = 1
    NEXT_DAY = 2
    EXCEPTION = 3

class BaseDate(abc.ABC, metaclass=abc.ABCMeta):
    @property
    @abc.abstractmethod
    def jd(self) -> float: ...
    @abc.abstractmethod
    def to_heb(self) -> HebrewDate: ...
    def __hash__(self) -> int: ...
    def __add__(self, other: float) -> BaseDate: ...
    @overload
    def __sub__(self, other: float) -> BaseDate: ...
    @overload
    def __sub__(self, other: BaseDate) -> int: ...
    def __eq__(self, other: object) -> bool: ...
    def __ne__(self, other: object) -> bool: ...
    def __lt__(self, other: object) -> bool: ...
    def __gt__(self, other: object) -> bool: ...
    def __le__(self, other: object) -> bool: ...
    def __ge__(self, other: object) -> bool: ...
    def weekday(self) -> int: ...
    def isoweekday(self) -> int: ...
    def shabbos(self) -> Self: ...
    def fast_day(self, hebrew: bool = False) -> str | None: ...
    def festival(
        self, israel: bool = False, hebrew: bool = False, include_working_days: bool = True, prefix_day: bool = False
    ) -> str | None: ...
    def holiday(self, israel: bool = False, hebrew: bool = False, prefix_day: bool = False) -> str | None: ...

class CalendarDateMixin:
    year: int
    month: int
    day: int
    def __init__(self, year: int, month: int, day: int, jd: float | None = None) -> None: ...
    def __iter__(self) -> Generator[int]: ...
    def tuple(self) -> tuple[int, int, int]: ...
    def dict(self) -> _DateDict: ...
    def replace(self, year: int | None = None, month: int | None = None, day: int | None = None) -> Self: ...

class JulianDay(BaseDate):
    day: float
    def __init__(self, day: float) -> None: ...
    @property
    def jd(self) -> float: ...
    @staticmethod
    def from_pydate(pydate: datetime.date) -> JulianDay: ...
    @staticmethod
    def today() -> JulianDay: ...
    def to_greg(self) -> GregorianDate: ...
    def to_heb(self) -> HebrewDate: ...
    def to_pydate(self) -> datetime.date: ...

class GregorianDate(BaseDate, CalendarDateMixin):
    def __init__(self, year: int, month: int, day: int, jd: float | None = None) -> None: ...
    def __format__(self, fmt: str) -> str: ...
    def strftime(self, fmt: str) -> str: ...
    @property
    def jd(self) -> float: ...
    @classmethod
    def from_pydate(cls, pydate: datetime.date) -> Self: ...
    @staticmethod
    def today() -> GregorianDate: ...
    def is_leap(self) -> bool: ...
    def to_jd(self) -> JulianDay: ...
    def to_heb(self) -> HebrewDate: ...
    def to_pydate(self) -> datetime.date: ...

class HebrewDate(BaseDate, CalendarDateMixin):
    def __init__(self, year: int, month: int, day: int, jd: float | None = None) -> None: ...
    def __format__(self, fmt: str) -> str: ...
    @property
    def jd(self) -> float: ...
    @staticmethod
    def from_pydate(pydate: datetime.date) -> HebrewDate: ...
    @staticmethod
    def today() -> HebrewDate: ...
    def to_jd(self) -> JulianDay: ...
    def to_greg(self) -> GregorianDate: ...
    def to_pydate(self) -> datetime.date: ...
    def to_heb(self) -> HebrewDate: ...
    def month_name(self, hebrew: bool = False) -> str: ...
    def hebrew_day(self, withgershayim: bool = True) -> str: ...
    def hebrew_year(self, thousands: bool = False, withgershayim: bool = True) -> str: ...
    def hebrew_date_string(self, thousands: bool = False) -> str: ...
    def add(
        self, years: int = 0, months: int = 0, days: int = 0, adar1: bool | None = False, rounding: Rounding = Rounding.NEXT_DAY
    ) -> HebrewDate: ...
    def subtract(
        self, years: int = 0, months: int = 0, days: int = 0, adar1: bool | None = False, rounding: Rounding = Rounding.NEXT_DAY
    ) -> HebrewDate: ...
