from typing import List

class TimeScaler:
    """
    Convert trading intervals (e.g., '1m', '5m', '1H', '1D') into a time-step (dt)
    expressed either in trading days ('1D') or trading years ('1Y').
    Internally assumes a standard US trading session of 390 minutes and 252 trading days/year.

    Examples
    --------
    >>> ts = TimeScaler(interval='5m', unit='1D')
    >>> ts.dt
    0.01282051282051282  # 5 / 390

    >>> ts.unit = '1Y'
    >>> ts.dt
    5 / (390*252)

    Notes
    -----
    - Allowed intervals: ['1m','5m','10m','15m','30m','45m','1H','2H','4H','1D']
    - Allowed units: ['1D','1Y'] where:
        '1D' = fraction of a trading day,
        '1Y' = fraction of a trading year (252 trading days).
    """
    def __init__(self, unit: str = '1D', interval: str='1D'):
        """
        Parameters
        ----------
        interval : str
            Bar/interval string (case-insensitive). One of:
            '1m','5m','10m','15m','30m','45m','1H','2H','4H','1D'.
        unit : {'1D','1Y'}, optional
            Time unit used to express the resulting dt.
            - '1D': dt is a fraction of a trading day.
            - '1Y': dt is a fraction of a trading year.
            Default is '1D'.

        Raises
        ------
        ValueError
            If `interval` or `unit` are not in the allowed sets.
        """

        self.__allowed_time_unit: List[str] = ['1D', '1Y']
        self.__allowed_time_intervals: List[str] = ['1m', '5m', '10m', '15m', '30m', '45m', '1H', '2H', '4H', '1D']
        self.__session_minutes_per_trading_day: int = 390
        self.__trading_days_per_year: int = 252
        self.unit: str = unit
        self.interval: str = interval

    @property
    def unit(self) -> str:
        """
        str: Current base time unit, either '1D' (trading day) or '1Y' (trading year).

        Setting this property recomputes the cached `dt` according to the new unit.

        Raises
        ------
        ValueError
            If the assigned unit is not '1D' or '1Y'.
        """
        return self._unit

    @property
    def interval(self) -> str:
        """
        str: Current trading interval (e.g., '5m', '1H', '1D').

        Setting this property validates the interval and updates the cached `dt`.

        Raises
        ------
        ValueError
            If the interval is not one of the allowed values.
        """
        return self._interval

    @property
    def dt(self) -> float:
        """
        float: Cached time-step for the current `(interval, unit)`.

        - If `unit == '1D'`: dt is measured as a fraction of a trading day.
        - If `unit == '1Y'`: dt is measured as a fraction of a trading year.
        """
        return self._dt

    @unit.setter
    def unit(self, value) -> None:
        if value not in self.__allowed_time_unit:
            raise ValueError(f"Time unit must be in {self.__allowed_time_unit}")
        self._unit = value

    @interval.setter
    def interval(self, value) -> None:
        if value not in self.__allowed_time_intervals:
            raise ValueError(f"Time interval must be in {self.__allowed_time_intervals}")
        self._interval = value
        self._dt = self._compute_dt(interval=value)


    def _compute_dt(self, interval: str) -> float:
        """
        Compute dt for a given interval using class-level trading calendar constants.

        Parameters
        ----------
        interval : str
            Interval string to convert.

        Returns
        -------
        float
            Time step in the current `unit`.

        Raises
        ------
        ValueError
            If the format of `interval` is unsupported.
        """

        if interval.endswith('m'):
            bar_minutes = int(interval[:-1])
        elif interval.endswith('H'):
            bar_minutes = int(interval[:-1]) * 60
        elif interval.endswith('D'):
            bar_minutes = self.__session_minutes_per_trading_day
        else:
            raise ValueError(f"Unsupported interval '{interval}'")

        dt_day = bar_minutes / self.__session_minutes_per_trading_day
        return dt_day / self.__trading_days_per_year if self.unit == '1Y' else dt_day

    def steps_per_day(self) -> int:
        """
        Return the number of bars that fit into one trading day
        for the current `interval`.

        Returns
        -------
        int
            Number of steps per trading day. For example:
            - '5m' -> 78
            - '1H' -> 6
            - '1D' -> 1
        """
        if self.interval.endswith('m'):
            bar_minutes = int(self.interval[:-1])
        elif self.interval.endswith('H'):
            bar_minutes = int(self.interval[:-1]) * 60
        else:
            return 1
        return self.__session_minutes_per_trading_day / bar_minutes