(module series
  (kernel)
  (data Series (vars a)
    (doc "Bounded ring buffer with financial-style indexing (index 0 = newest). Pure AILang library extension over RawBuf: the buffer holds the elements, the four Int fields are lookback (capacity), head (next write slot), count (live elements, saturates at lookback), and total (monotone count of all pushes ever). Element type restricted to {Int, Float} via param-in; Bool deferred (#61).")
    (param-in (a Int Float))
    (ctor S (con RawBuf a)
            (con Int)
            (con Int)
            (con Int)
            (con Int)))
  (fn new
    (doc "Allocate an empty Series of capacity `lookback`. The inner RawBuf is allocated via `(new RawBuf lookback)` with NO element type-arg — the element type is solved from the expected ctor-field type `(con RawBuf a)`, which the base-tier mono fix (b11a6d9) carries through to the caller's concrete type.")
    (type (forall (vars a) (fn-type (params (own (con Int))) (ret (own (con Series a))))))
    (params lookback)
    (body
      (term-ctor Series S
        (new RawBuf lookback) lookback 0 0 0)))
  (fn push
    (doc "Append `v`, evicting the oldest element when full. own-in/own-out linear threading: mutates the buffer at head, advances head mod lookback, saturates count at lookback, increments total.")
    (type (forall (vars a) (fn-type (params (own (con Series a)) (own a)) (ret (own (con Series a))))))
    (params s v)
    (body
      (match s
        (case (pat-ctor S buf lookback head count total)
          (term-ctor Series S
            (app RawBuf.set buf head v)
            lookback
            (app % (app + head 1) lookback)
            (if (app lt count lookback) (app + count 1) count)
            (app + total 1))))))
  (fn at
    (doc "Read the element `i` positions back from newest (i=0 is the most recent push). Financial indexing: slot = (head - 1 - i + 2*lookback) mod lookback. UB if i >= len; caller checks via len/total_count.")
    (type (forall (vars a) (fn-type (params (borrow (con Series a)) (own (con Int))) (ret (own a)))))
    (params s i)
    (body
      (match s
        (case (pat-ctor S buf lookback head count total)
          (app RawBuf.get buf
            (app % (app + (app - (app - head 1) i) (app * lookback 2)) lookback))))))
  (fn len
    (doc "Number of live elements (saturates at lookback).")
    (type (forall (vars a) (fn-type (params (borrow (con Series a))) (ret (own (con Int))))))
    (params s)
    (body (match s (case (pat-ctor S buf lookback head count total) count))))
  (fn total_count
    (doc "Monotone count of all pushes ever (does not saturate). Drives the SMA emit gate.")
    (type (forall (vars a) (fn-type (params (borrow (con Series a))) (ret (own (con Int))))))
    (params s)
    (body (match s (case (pat-ctor S buf lookback head count total) total)))))
