//! `SeriesFold` — the online accumulator behind `summarize`'s three pip
//! statistics (`last`, `max_drawdown`, `sign_flips`). One arithmetic source of
//! truth, foldable a value at a time, so a slice driver (`summarize`) and a
//! per-`eval` streaming sink (`SeriesReducer`) compute byte-identical results.

/// Three-way sign: `-1.0` / `0.0` / `+1.0`. A zero maps to `0.0` so flat is
/// distinct from long/short in the flip count (unlike `f64::signum`).
fn sign0(v: f64) -> f64 {
    if v > 0.0 {
        1.0
    } else if v < 0.0 {
        -1.0
    } else {
        0.0
    }
}

/// Online fold of a single `f64` series into the pip summary statistics. The
/// operations and their order mirror `summarize`'s loops exactly, so a
/// slice-driven and a per-value-driven fold agree bit-for-bit (IEEE-754
/// non-associativity respected).
#[derive(Clone, Debug)]
pub struct SeriesFold {
    /// the last value folded (`total_pips` for an equity series); `0.0` if none.
    pub last: f64,
    peak: f64,
    /// running `max(peak - value)`, always `>= 0.0`.
    pub max_drawdown: f64,
    prev_sign: Option<f64>,
    /// count of adjacent normalized-sign changes.
    pub sign_flips: u64,
}

impl Default for SeriesFold {
    fn default() -> Self {
        Self { last: 0.0, peak: f64::NEG_INFINITY, max_drawdown: 0.0, prev_sign: None, sign_flips: 0 }
    }
}

impl SeriesFold {
    pub fn new() -> Self {
        Self::default()
    }

    /// Fold one value into the running statistics.
    pub fn fold(&mut self, v: f64) {
        self.last = v;
        if v > self.peak {
            self.peak = v;
        }
        let dd = self.peak - v;
        if dd > self.max_drawdown {
            self.max_drawdown = dd;
        }
        let s = sign0(v);
        if let Some(p) = self.prev_sign
            && s != p
        {
            self.sign_flips += 1;
        }
        self.prev_sign = Some(s);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn series_fold_empty_is_zero() {
        let f = SeriesFold::new();
        assert_eq!(f.last, 0.0);
        assert_eq!(f.max_drawdown, 0.0);
        assert_eq!(f.sign_flips, 0);
    }

    #[test]
    fn series_fold_tracks_last_and_drawdown() {
        let mut f = SeriesFold::new();
        for v in [10.0, 12.0, 8.0, 9.0] {
            f.fold(v);
        }
        assert_eq!(f.last, 9.0);
        assert_eq!(f.max_drawdown, 4.0); // peak 12 - trough 8
        assert_eq!(f.sign_flips, 0); // all positive
    }

    #[test]
    fn series_fold_counts_sign_flips_zero_distinct() {
        let mut f = SeriesFold::new();
        for v in [1.0, -1.0, 0.0, 2.0] {
            f.fold(v);
        }
        // signs + - 0 + -> flips at -, 0, + = 3
        assert_eq!(f.sign_flips, 3);
    }
}
