"""Canonical weeks-of-cover engine — ONE walk-forward, used everywhere.

Per the agreed definition (FORECASTING_PLAN.md / POLLEO_AI_KNOWLEDGE.md):
weeks of cover = a week-by-week walk of warehouse stock, consuming the real
weekly forecast and receiving incoming POs, floored at 0:

    closing_w = max(0, opening_w + incoming_w - demand_w);  opening_{w+1} = closing_w

Two entry points, same walk:
  * walk_forward(...)   → the full {opening, demand, incoming, closing} trajectory
                          (supply dashboard / stock projection / coverage grid).
  * weeks_of_cover(...) → the scalar "weeks until stock runs out" (partial last
                          week = remaining / that week's demand; zero-demand weeks
                          count as full; if stock outlasts the horizon, extrapolate
                          by trailing average, or +999 when there is no demand).

Demand per week should be the live forecast; the FALLBACK for SKUs with no
forecast is a run-rate (avg of the last 8 non-promo weeks) — that fallback is
built by the caller and passed in here as the demand series.
"""
from __future__ import annotations

from typing import Optional

NO_DEMAND_SENTINEL = 999.0


def walk_forward(
    stock_now: float,
    demand_by_week: list[float],
    incoming_by_week: Optional[list[float]] = None,
) -> list[dict]:
    """Roll closing stock forward across the horizon. Returns one dict per week:
    {opening, demand, incoming, closing}. Incoming arrives within its week."""
    n = len(demand_by_week)
    inc = incoming_by_week if incoming_by_week is not None else [0.0] * n
    out: list[dict] = []
    opening = float(stock_now)
    for i in range(n):
        d = float(demand_by_week[i])
        iv = float(inc[i]) if i < len(inc) else 0.0
        closing = max(0.0, opening - d + iv)
        out.append({"opening": opening, "demand": d, "incoming": iv, "closing": closing})
        opening = closing
    return out


def weeks_of_cover(
    stock_now: float,
    demand_by_week: list[float],
    incoming_by_week: Optional[list[float]] = None,
    *,
    extrapolate_avg: bool = True,
) -> float:
    """Scalar weeks of cover from the same walk. Incoming (if given) is added at
    the start of its week before consuming that week's demand."""
    n = len(demand_by_week)
    inc = incoming_by_week if incoming_by_week is not None else [0.0] * n
    remaining = float(stock_now)
    weeks = 0.0
    for i in range(n):
        remaining += float(inc[i]) if i < len(inc) else 0.0
        d = float(demand_by_week[i])
        if d <= 0:
            weeks += 1
            continue
        if remaining >= d:
            remaining -= d
            weeks += 1
        else:
            return weeks + remaining / d
    if extrapolate_avg:
        avg = sum(float(x) for x in demand_by_week) / max(n, 1)
        if avg > 0:
            return weeks + remaining / avg
    return weeks + NO_DEMAND_SENTINEL
