from typing import Any, Generic, Optional, TypeVar, Union

import numpy as np
from numpy.typing import NDArray

_DigitalDtype = TypeVar("_DigitalDtype", bound=Union[np.int16, np.int32])


class LazyLoader(Generic[_DigitalDtype]):
    """
    Class to load data for a single signal from a buffer of EDF data records (array or memmap).

    Parameters
    ----------
    buffer : numpy.ndarray or numpy.memmap
        Buffer of EDF data records.
    start_sample : int
        Offset of the signal in the data records (in samples).
    end_sample : int
        End of the signal in the data records (in samples).
    """

    def __init__(
        self,
        buffer: Union[NDArray[_DigitalDtype], np.memmap[Any, np.dtype[_DigitalDtype]]],
        start_sample: int,
        end_sample: int,
    ) -> None:
        self.buffer = buffer
        self.start_sample = start_sample
        self.end_sample = end_sample

    def load(
        self, start_record: Optional[int] = None, end_record: Optional[int] = None
    ) -> NDArray[_DigitalDtype]:
        """
        Load signal data from the buffer.

        Parameters
        ----------
        start_record : int, optional
            The first EDF data record to load samples from. If None, load from the beginning of the buffer.
        end_record : int, optional
            The last EDF data record to load samples from. If None, load until the end of the buffer.

        Returns
        -------
        numpy.ndarray
            Signal data (digital).
        """
        if start_record is None:
            start_record = 0
        if end_record is None:
            end_record = self.buffer.shape[0]
        if (
            end_record < start_record
            or start_record < 0
            or end_record > self.buffer.shape[0]
        ):
            raise ValueError("Invalid slice: Slice exceeds EDF duration")
        return self.buffer[
            start_record:end_record, self.start_sample : self.end_sample
        ].flatten()
