"""MarketDataWindow: batch window of OHLCV for indicators and signal generators."""

from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass
from typing import Any

import numpy as np
from numpy.typing import NDArray


@dataclass(frozen=True)
class MarketDataWindow:
    """
    Immutable window of market data: open, high, low, close, volume, optional timestamps.

    Minimum length / validity is derived from required_lookback of indicators or generators.
    """

    symbol: str
    open: NDArray[np.float64]
    high: NDArray[np.float64]
    low: NDArray[np.float64]
    close: NDArray[np.float64]
    volume: NDArray[np.float64]
    timestamps: NDArray[Any] | None = None

    def __post_init__(self) -> None:
        n = len(self.close)
        for name, arr in [
            ("open", self.open),
            ("high", self.high),
            ("low", self.low),
            ("volume", self.volume),
        ]:
            if len(arr) != n:
                raise ValueError(f"{name} length {len(arr)} != close length {n}")
        if self.timestamps is not None and len(self.timestamps) != n:
            raise ValueError(
                f"timestamps length {len(self.timestamps)} != close length {n}"
            )

    def __len__(self) -> int:
        return len(self.close)

    @property
    def last_timestamp(self) -> Any:
        """Last bar timestamp for signal attribution."""
        if self.timestamps is not None:
            return self.timestamps[-1]
        return len(self.close) - 1  # fallback index

    @classmethod
    def from_arrays(
        cls,
        symbol: str,
        open_: NDArray[np.float64] | Sequence[float],
        high: NDArray[np.float64] | Sequence[float],
        low: NDArray[np.float64] | Sequence[float],
        close: NDArray[np.float64] | Sequence[float],
        volume: NDArray[np.float64] | Sequence[float],
        timestamps: NDArray[Any] | Sequence[Any] | None = None,
    ) -> MarketDataWindow:
        """Build a MarketDataWindow from array-like inputs."""
        o = np.asarray(open_, dtype=np.float64)
        h = np.asarray(high, dtype=np.float64)
        low_arr = np.asarray(low, dtype=np.float64)
        c = np.asarray(close, dtype=np.float64)
        v = np.asarray(volume, dtype=np.float64)
        ts = np.asarray(timestamps) if timestamps is not None else None
        return cls(symbol=symbol, open=o, high=h, low=low_arr, close=c, volume=v, timestamps=ts)
