use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use vecdb::{
    AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, VecValue, WritableVec,
};

use crate::{
    indexes,
    internal::{
        CachedWindowStartVec, FiatPerBlock, FiatType, LazyFiatBlock, LazyRollingSumsFiatFromHeight,
        Windows,
    },
};

#[derive(Traversable)]
pub struct FiatPerBlockCumulativeWithSums<C: FiatType, M: StorageMode = Rw> {
    pub block: LazyFiatBlock<C>,
    pub cumulative: FiatPerBlock<C, M>,
    pub sum: LazyRollingSumsFiatFromHeight<C>,
    #[traversable(skip)]
    last_cumulative: Option<(usize, C)>,
}

const VERSION: Version = Version::ONE;

impl<C: FiatType> FiatPerBlockCumulativeWithSums<C> {
    pub(crate) fn forced_import(
        db: &Database,
        name: &str,
        version: Version,
        indexes: &indexes::Vecs,
        cached_starts: &Windows<&CachedWindowStartVec>,
    ) -> Result<Self> {
        let v = version + VERSION;
        let cumulative =
            FiatPerBlock::forced_import(db, &format!("{name}_cumulative"), v, indexes)?;
        let last_cumulative = cumulative
            .cents
            .height
            .collect_last()
            .map(|value| (cumulative.cents.height.len(), value));
        let block = LazyFiatBlock::from_cumulative(name, v, &cumulative);
        let sum = LazyRollingSumsFiatFromHeight::new(
            &format!("{name}_sum"),
            v,
            &cumulative.cents.height,
            cached_starts,
            indexes,
        );
        Ok(Self {
            block,
            cumulative,
            sum,
            last_cumulative,
        })
    }

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

    pub(crate) fn compute_from_cumulative_pair<S1, S2>(
        &mut self,
        max_from: Height,
        source1: &impl ReadableVec<Height, S1>,
        source2: &impl ReadableVec<Height, S2>,
        mut transform: impl FnMut(Height, S1, S2) -> C,
        exit: &Exit,
    ) -> Result<()>
    where
        S1: VecValue,
        S2: VecValue,
    {
        self.cumulative.cents.height.compute_transform2(
            max_from,
            source1,
            source2,
            |(height, value1, value2, ..)| (height, transform(height, value1, value2)),
            exit,
        )?;
        self.last_cumulative = None;
        Ok(())
    }

    pub(crate) fn compute_sum_of_others(
        &mut self,
        max_from: Height,
        others: &[&Self],
        exit: &Exit,
    ) -> Result<()> {
        self.cumulative.cents.height.compute_sum_of_others(
            max_from,
            &others
                .iter()
                .map(|v| &v.cumulative.cents.height)
                .collect::<Vec<_>>(),
            exit,
        )?;
        self.last_cumulative = None;
        Ok(())
    }

    pub(crate) fn stored_mut(&mut self) -> &mut dyn AnyStoredVec {
        self.last_cumulative = None;
        &mut self.cumulative.cents.height
    }
}
