from typing import TYPE_CHECKING, Generic, Self, Literal, Union, TypeAlias, TypeVar, Type, final, Any
from io import TextIOWrapper

from .arithmetics import Arithmetics

__all__ = (
    # Internal
    "MathematicalValue",
    "DummyUnit",

    # Exposed
    "Unit",
    "Measurement",
    "Temperature",
    "Time",
    "Distance",
    "Weight",
    "Volume",
    "Angle",
    "Bytes",
    "Score",
    "Value",
    "RandomValue",
    "ConstantValue",
    "Text",
    "Char",
    "char_type",
    "new_char",
    "to_text",
    "to_str",
    "base_unit",
    "as_base_unit",
    "is_unit",
    "not_initialized",
    "initialized",
    "extract",
    "new",
    "copy"
)

TextInferable: TypeAlias = Union[str, object, Unit]
T = TypeVar("T", bound=Unit, default=Unit) # Default is unit itself
T2 = TypeVar("T2")
T3 = TypeVar("T3", bound=str)
T4 = TypeVar("T4", bound=str)

AnyUnit: TypeAlias = Union[T2, UninitializedUnit[T2]]
PotentialUnit: TypeAlias = Union[Type, Any]
AnyUnitOrText: TypeAlias = Union[AnyUnit, Text]

# Generators
MathematicalValue: TypeAlias = Value
DummyUnit: TypeAlias = Unit

@final
class UninitializedUnit(Generic[T]):
    def __init__(self, unit: Unit) -> None: ...
    @property
    def unit(self) -> Unit: ...
    def extract(self) -> Unit: ...

class Unit(Arithmetics): 
    def __init__(self, value: float) -> None: ...
    @classmethod
    def new(cls, unit: Type[T] = Unit, value: float = 0.0) -> T: ...
    def configure(self, name: str, measurement: Type[Measurement]) -> None: ...
    def copy(self) -> Unit: ...
    def base_unit(self) -> float: ...
    def to_text(self) -> Text: ...
    def to_str(self) -> str: ...
    def lock(self) -> None: ...
    def __set_name__(self, owner: Type, name: str) -> None: ...
    def __get__(self, instance: Any, owner: Type) -> Union[UninitializedUnit[Self], float, Self]: ...
    def __set__(self, instance: Any, value: float) -> None: ...
    def __int__(self) -> Literal[0]: ...
    def __float__(self) -> Literal[0]: ...
    def __str__(self) -> Literal[""]: ...
    def __repr__(self) -> str: ...

class Measurement:
    if TYPE_CHECKING:
        backing: str
        factor: float
    def update_backing(self) -> None: ...
    def __set_name__(self, owner: Type, name: str) -> None: ...
    def __get__(self, instance: Any, owner: Type) -> float: ...
    def __set__(self, instance: Any, value: float) -> None: ...
    def __delete__(self, instance: Any) -> None: ...

class Temperature(Unit):
    if TYPE_CHECKING:
        celsius: float
        fahrenheit: float
    def __init__(self, value: float = 0.0, *, unit: str = "F") -> None: ...
    @classmethod
    def new(cls, value: float) -> Temperature: ...
    def copy(self) -> Temperature: ...
    @property
    def c(self) -> float: ...
    @property
    def f(self) -> float: ...
    def __int__(self) -> int: ...
    def __float__(self) -> float: ...
    def __str__(self) -> str: ...

class Time(Unit):
    if TYPE_CHECKING:
        seconds: float
        nanoseconds: float
        microseconds: float
        milliseconds: float
        minutes: float
        hours: float
        days: float
    def __init__(self, seconds: float = 0.0) -> None: ...
    @classmethod
    def new(cls, seconds: float) -> Time: ...
    def copy(self) -> Time: ...
    @property
    def ns(self) -> float: ...
    @property
    def ms(self) -> float: ...
    @property
    def s(self) -> float: ...
    def __int__(self) -> int: ...
    def __float__(self) -> float: ...
    def __str__(self) -> str: ...

class Distance(Unit):
    if TYPE_CHECKING:
        meters: float
        millimeters: float
        centimeters: float
        decimeters: float
        kilometers: float
    def __init__(self, meters: float = 0.0) -> None: ...
    @classmethod
    def new(cls, meters: float) -> Distance: ...
    def copy(self) -> Distance: ...
    @property
    def mm(self) -> float: ...
    @property
    def cm(self) -> float: ...
    @property
    def dm(self) -> float: ...
    @property
    def km(self) -> float: ...
    @property
    def m(self) -> float: ...
    def __int__(self) -> int: ...
    def __float__(self) -> float: ...
    def __str__(self) -> str: ...

class Weight(Unit):
    if TYPE_CHECKING:
        grams: float
        milligrams: float
        centigrams: float
        decigrams: float
        kilograms: float
    def __init__(self, grams: float = 0.0) -> None: ...
    @classmethod
    def new(cls, grams: float) -> Weight: ...
    def copy(self) -> Weight: ...
    @property
    def mg(self) -> float: ...
    @property
    def cg(self) -> float: ...
    @property
    def dg(self) -> float: ...
    @property
    def kg(self) -> float: ...
    @property
    def g(self) -> float: ...
    def __int__(self) -> int: ...
    def __float__(self) -> float: ...
    def __str__(self) -> str: ...

class Volume(Unit):
    if TYPE_CHECKING:
        liters: float
        milliliters: float
        centiliters: float
        deciliters: float
        kiloliters: float
    def __init__(self, liters: float = 0.0) -> None: ...
    @classmethod
    def new(cls, liters: float) -> Volume: ...
    def copy(self) -> Volume: ...
    @property
    def ml(self) -> float: ...
    @property
    def cl(self) -> float: ...
    @property
    def dl(self) -> float: ...
    @property
    def kl(self) -> float: ...
    @property
    def l(self) -> float: ...
    def __int__(self) -> int: ...
    def __float__(self) -> float: ...
    def __str__(self) -> str: ...

class Angle(Unit):
    if TYPE_CHECKING:
        degrees: float
        radians: float
        gradians: float
    def __init__(self, degrees: float = 0.0) -> None: ...
    @classmethod
    def new(cls, degrees: float) -> Angle: ...
    def copy(self) -> Angle: ...
    def sin(self) -> float: ...
    def cos(self) -> float: ...
    def tan(self) -> float: ...
    def sinh(self) -> float: ...
    def cosh(self) -> float: ...
    def tanh(self) -> float: ...
    def __int__(self) -> int: ...
    def __float__(self) -> float: ...
    def __str__(self) -> str: ...

class Bytes(Unit):
    if TYPE_CHECKING:
        bytes: float
        kilobytes: float
        megabytes: float
        gigabytes: float
        terabytes: float
        petabytes: float
        exabytes: float
        zettabytes: float
        yottabytes: float
        ronnabytes: float
        quettabytes: float
    def __init__(self, bytes: float = 0.0) -> None: ...
    @classmethod
    def new(cls, bytes: float) -> Bytes: ...
    def copy(self) -> Bytes: ...
    @property
    def kb(self) -> float: ...
    @property
    def mb(self) -> float: ...
    @property
    def gb(self) -> float: ...
    @property
    def tb(self) -> float: ...
    @property
    def pb(self) -> float: ...
    @property
    def eb(self) -> float: ...
    @property
    def zb(self) -> float: ...
    @property
    def yb(self) -> float: ...
    @property
    def rb(self) -> float: ...
    @property
    def qb(self) -> float: ...
    def __int__(self) -> int: ...
    def __float__(self) -> float: ...
    def __str__(self) -> str: ...

class Score(Unit):
    if TYPE_CHECKING:
        score: float
    def __init__(self, score: float = 0.0) -> None: ...
    @classmethod
    def new(cls, score: float) -> Score: ...
    def copy(self) -> Score: ...
    @property
    def points(self) -> float: ...
    @points.setter
    def points(self, value: float) -> None: ...
    def __int__(self) -> int: ...
    def __float__(self) -> float: ...
    def __str__(self) -> str: ...

class Value(Unit):
    if TYPE_CHECKING:
        value: float
    def __init__(self, value: float) -> None: ...
    @classmethod
    def new(cls, value: float) -> Value: ...
    def copy(self) -> Value: ...
    def exp(self) -> float: ...
    def exp2(self) -> float: ...
    def sqrt(self) -> float: ...
    def __int__(self) -> int: ...
    def __float__(self) -> float: ...
    def __str__(self) -> str: ...

class RandomValue(Value):
    def __init__(self, a: float, b: float, seed: object = None) -> None: ...
    @classmethod
    def new(cls, a: float, b: float, seed: object = None) -> RandomValue: ...
    def copy(self) -> RandomValue: ...
    @property
    def value(self) -> float: ...
    def generate(self) -> float: ...
    def to_int(self) -> int: ...
    def __int__(self) -> int: ...
    def __float__(self) -> float: ...
    def __str__(self) -> str: ...

class ConstantValue(Value):
    @classmethod
    def new(self, value) -> ConstantValue: ...

class Text:
    if TYPE_CHECKING:
        text: str
    def __init__(self, text: TextInferable = "", encoding: str = "utf-8") -> None: ...
    def set_encoding(self, encoding: str = "utf-8") -> None: ...
    def open_path(self, mode: str, *args: Any, **kwargs: Any) -> TextIOWrapper: ...
    def save(self, file_path: str) -> None: ...
    def load(self, file_path: str) -> None: ...
    @property
    def length(self) -> int: ...
    @property
    def bytes(self) -> bytes: ...
    def __str__(self) -> str: ...
    def __eq__(self, other: Union[Text, str]) -> bool: ...

class Char(Text, Generic[T3]):
    if TYPE_CHECKING:
        text: T3
    def _check(self) -> None: ...

def char_type(lit: T4) -> Type[Char[T4]]: ...
def new_char(char: T4) -> Char[T4]: ...
def to_text(unit: AnyUnit) -> Text: ...
def to_str(unit: AnyUnitOrText) -> str: ...
def base_unit(unit: AnyUnit) -> float: ...
def as_base_unit(unit_or_base: Union[AnyUnit, float]) -> float: ...
def is_unit(unit: PotentialUnit) -> bool: ...
def not_initialized(unit: AnyUnit) -> bool: ...
def initialized(unit: AnyUnit) -> bool: ...
def extract(unit: Union[T2, UninitializedUnit[T2]]) -> T2: ... # Render this exectly
def new(unit: Type[T], value: float) -> T: ...
def copy(unit: T2) -> T2: ...