use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Height, Indexes, PoolSlug, StoredU64};
use derive_more::{Deref, DerefMut};
use vecdb::{BinaryTransform, Database, Exit, ReadableVec, Rw, StorageMode, Version};

use crate::{
    blocks, indexes,
    internal::{
        AmountPerBlockCumulativeRolling, CachedWindowStarts, MaskSats, PercentRollingWindows,
        RatioU64Bp16,
    },
    mining, prices,
};

use super::minor;

#[derive(Deref, DerefMut, Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
    #[deref]
    #[deref_mut]
    #[traversable(flatten)]
    pub base: minor::Vecs<M>,

    pub rewards: AmountPerBlockCumulativeRolling<M>,
    #[traversable(rename = "dominance")]
    pub dominance_rolling: PercentRollingWindows<BasisPoints16, M>,
}

impl Vecs {
    pub(crate) fn forced_import(
        db: &Database,
        slug: PoolSlug,
        version: Version,
        indexes: &indexes::Vecs,
        cached_starts: &CachedWindowStarts,
    ) -> Result<Self> {
        let suffix = |s: &str| format!("{}_{s}", slug);

        let base = minor::Vecs::forced_import(db, slug, version, indexes, cached_starts)?;

        let rewards = AmountPerBlockCumulativeRolling::forced_import(
            db,
            &suffix("rewards"),
            version,
            indexes,
            cached_starts,
        )?;

        let dominance_rolling =
            PercentRollingWindows::forced_import(db, &suffix("dominance"), version, indexes)?;

        Ok(Self {
            base,
            rewards,
            dominance_rolling,
        })
    }

    pub(crate) fn compute(
        &mut self,
        starting_indexes: &Indexes,
        pool: &impl ReadableVec<Height, PoolSlug>,
        blocks: &blocks::Vecs,
        prices: &prices::Vecs,
        mining: &mining::Vecs,
        exit: &Exit,
    ) -> Result<()> {
        self.base.compute(starting_indexes, pool, blocks, exit)?;

        for (dom, (mined, total)) in self.dominance_rolling.as_mut_array().into_iter().zip(
            self.base
                .blocks_mined
                .sum
                .as_array()
                .into_iter()
                .zip(blocks.count.total.sum.as_array()),
        ) {
            dom.compute_binary::<StoredU64, StoredU64, RatioU64Bp16>(
                starting_indexes.height,
                &mined.height,
                &total.height,
                exit,
            )?;
        }

        self.rewards
            .compute(starting_indexes.height, prices, exit, |vec| {
                Ok(vec.compute_transform2(
                    starting_indexes.height,
                    &self.base.blocks_mined.block,
                    &mining.rewards.coinbase.block.sats,
                    |(h, mask, val, ..)| (h, MaskSats::apply(mask, val)),
                    exit,
                )?)
            })?;

        Ok(())
    }
}
