"""Прореживание точек истории с сохранением крайностей.

Общий код для всех мест, где точки приходится выбрасывать: ответ
`GET /servers/{id}/metrics` (страница сервера) и MCP-тул `server_history`.
Точка, оставшаяся от группы, несёт дайджест `agg` всей группы — min/max по
метрикам и n/exp замеров (формат и смысл — в docstring `metrics_retention`);
без этого прореживание съедало бы ровно те всплески и простои, ради которых
дайджест и хранится.

Прореживание в БД (по возрасту точки) живёт отдельно — в
`metrics_retention.thin_once`, потому что считает бакеты по календарю, а не по
числу точек; здесь же группы нарезаются равномерно по окну ответа.
"""


def thin_points(points: list[dict], max_points: int) -> list[dict]:
    """Проредить точки (по возрастанию ts) до max_points, сливая их дайджесты.

    В каждой группе остаётся последняя точка — самая свежая, поэтому свежий
    край окна не теряется; в её `agg` собираются крайности всей группы.
    """
    step = len(points) / max_points
    out = []
    for i in range(max_points):
        lo = int(i * step)
        hi = int((i + 1) * step) if i + 1 < max_points else len(points)
        if lo >= hi:
            break  # вырожденные слоты возможны только при max_points > len(points)
        group = points[lo:hi]
        point = dict(group[-1])
        digest: dict = {}
        for member in group:
            merge_digest(digest, member.get("agg"))
        if digest:
            point["agg"] = digest
        out.append(point)
    return out


def merge_digest(acc: dict, agg: dict | None) -> dict:
    """Слить дайджесты: n/exp складываются, min/max расширяются."""
    if not agg:
        return acc
    for key, value in agg.items():
        if key in ("n", "exp"):
            acc[key] = acc.get(key, 0) + value
        elif key == "disks":
            disks = acc.setdefault("disks", {})
            for mount, pair in value.items():
                disks[mount] = merge_pair(disks.get(mount), pair)
        else:
            acc[key] = merge_pair(acc.get(key), value)
    return acc


def merge_pair(cur: list | None, pair: list) -> list:
    return list(pair) if cur is None else [min(cur[0], pair[0]), max(cur[1], pair[1])]
