"""Period-aware aggregation of a daily forward-return series. NO normalization — raw per-period returns and
a trailing rolling mean. Buckets daily returns into calendar periods (daily/weekly/monthly), compounding
within each bucket, then reports each period's raw return plus the trailing rolling mean over `window`
periods. The strategy detail view shows daily/weekly/monthly side by side so behavior is legible at each
granularity without any whitening."""
from __future__ import annotations

import datetime as dt
from dataclasses import dataclass

PERIODS = ("daily", "weekly", "monthly")


@dataclass(frozen=True, slots=True)
class PeriodPoint:
    label: str           # day "2026-06-08" | week "2026-W24" | month "2026-06"
    ret: float           # raw compounded return within the period (NOT normalized)
    rolling_mean: float  # trailing mean of per-period ret over the window (raw)


def _bucket(date: str, period: str) -> str:
    y, m, d = (int(x) for x in date.split("-"))
    if period == "daily":
        return date
    if period == "weekly":
        iso = dt.date(y, m, d).isocalendar()
        return f"{iso[0]}-W{iso[1]:02d}"
    if period == "monthly":
        return f"{y:04d}-{m:02d}"
    raise ValueError(f"unknown period {period!r}")


def aggregate(dates: list[str], rets: list[float], period: str, window: int = 4) -> list[PeriodPoint]:
    if period not in PERIODS:
        raise ValueError(f"period must be one of {PERIODS}")
    order: list[str] = []
    comp: dict[str, float] = {}
    for date, r in zip(dates, rets, strict=True):
        key = _bucket(date, period)
        if key not in comp:
            comp[key] = 1.0
            order.append(key)
        comp[key] *= (1.0 + r)
    period_rets = [(k, comp[k] - 1.0) for k in order]
    out: list[PeriodPoint] = []
    for i, (label, pr) in enumerate(period_rets):
        lo = max(0, i - window + 1)
        win = [x for _, x in period_rets[lo:i + 1]]
        out.append(PeriodPoint(label=label, ret=pr, rolling_mean=sum(win) / len(win)))
    return out
