//! Stored cumulative source with pinned cached reads and lazy derived views.

use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{
    AnyStoredVec, AnyVec, CachedBoxedVec, Database, ReadOnlyClone, ReadableVec, Rw, StorageMode,
    WritableVec,
};

use super::lazy_cumulative_rolling::lazy_parts;
use crate::{
    indexes,
    internal::{
        CachedPerBlock, CachedWindowStartVec, LazyPreviousDeltaVec, LazyRollingAvgsFromHeight,
        LazyRollingSumsFromHeight, NumericValue, Windows,
    },
};

/// Like [`super::PerBlockCumulativeRolling`], with a pinned cache owned by its
/// cumulative height source.
#[derive(Traversable)]
pub struct CachedPerBlockCumulativeRolling<T, M: StorageMode = Rw>
where
    T: NumericValue + JsonSchema,
{
    pub block: LazyPreviousDeltaVec<Height, T>,
    pub cumulative: CachedPerBlock<T, M>,
    pub sum: LazyRollingSumsFromHeight<T>,
    pub average: LazyRollingAvgsFromHeight<T>,
    #[traversable(skip)]
    last_cumulative: Option<(usize, T)>,
}

impl<T> CachedPerBlockCumulativeRolling<T>
where
    T: NumericValue + JsonSchema,
{
    pub(crate) fn forced_import(
        db: &Database,
        name: &str,
        version: Version,
        indexes: &indexes::Vecs,
        cached_starts: &Windows<&CachedWindowStartVec>,
    ) -> Result<Self> {
        let cumulative =
            CachedPerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?;
        let cumulative_source = cumulative.height.read_only_clone();
        let (block, sum, average) =
            lazy_parts(name, version, &cumulative_source, cached_starts, indexes);
        let last_cumulative = cumulative
            .height
            .collect_last()
            .map(|value| (cumulative.height.len(), value));

        Ok(Self {
            block,
            cumulative,
            sum,
            average,
            last_cumulative,
        })
    }

    pub(crate) fn cached_cumulative(&self) -> CachedBoxedVec<Height, T> {
        self.cumulative.height.read_only_cached_boxed_clone()
    }

    #[inline(always)]
    pub(crate) fn push_block(&mut self, value: T)
    where
        T: Copy,
    {
        let len = self.cumulative.height.len();
        let mut cumulative = match self.last_cumulative {
            Some((cached_len, value)) if cached_len == len => value,
            _ => self.cumulative.height.collect_last().unwrap_or_default(),
        };
        cumulative += value;
        self.cumulative.height.inner.push(cumulative);
        self.last_cumulative = Some((len + 1, cumulative));
    }

    pub(crate) fn validate_and_truncate(&mut self, version: Version, height: Height) -> Result<()> {
        self.cumulative
            .height
            .inner
            .validate_and_truncate(version, height)?;
        Ok(())
    }

    pub(crate) fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> {
        self.cumulative.height.inner.truncate_if_needed_at(len)?;
        Ok(())
    }

    pub(crate) fn write(&mut self) -> Result<()> {
        self.cumulative.height.inner.write()?;
        Ok(())
    }
}
