//! Reused address tracking.
//!
//! An address is "reused" if its lifetime `funded_txo_count > 1`, i.e.
//! it has received more than one output across its lifetime. This is
//! the simplest output-multiplicity proxy for address linkability.
//!
//! Two facets are tracked here:
//! - [`count`]: how many distinct addresses are currently reused
//!   (funded) and how many have *ever* been reused (total). Per address
//!   type plus an aggregated `all`.
//! - [`events`]: per-block address-reuse event counts on both sides.
//!   Output-side (`output_to_reused_addr_count`, outputs landing on
//!   addresses that had already received ≥ 1 prior output) and
//!   input-side (`input_from_reused_addr_count`, inputs spending from
//!   addresses with lifetime `funded_txo_count > 1`). Each count is
//!   paired with a percent over the matching block-level output/input
//!   total.

mod events;

pub use events::{AddrEventsVecs, AddrTypeToAddrEventCount};

use brk_cohort::ByAddrType;
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode};

use super::{
    count::AddrCountFundedTotalVecs,
    supply::{AddrSupplyShareVecs, AddrSupplyVecs},
};
use crate::{
    indexes, inputs,
    internal::{WindowStartVec, Windows},
    outputs, prices,
};

mod state;

pub use state::ReusedAddrState;

/// Top-level container for all reused address tracking: counts (funded +
/// total), per-block reuse events (output-side + input-side), and funded
/// supply + share.
#[derive(Traversable)]
pub struct ReusedAddrVecs<M: StorageMode = Rw> {
    pub count: AddrCountFundedTotalVecs<M>,
    pub events: AddrEventsVecs<M>,
    pub supply: AddrSupplyVecs<M>,
    #[traversable(wrap = "supply", rename = "share")]
    pub supply_share: AddrSupplyShareVecs<M>,
}

impl ReusedAddrVecs {
    pub(crate) fn forced_import(
        db: &Database,
        name: &str,
        version: Version,
        indexes: &indexes::Vecs,
        cached_starts: &Windows<&WindowStartVec>,
    ) -> Result<Self> {
        Ok(Self {
            count: AddrCountFundedTotalVecs::forced_import(db, name, version, indexes)?,
            events: AddrEventsVecs::forced_import(db, name, version, indexes, cached_starts)?,
            supply: AddrSupplyVecs::forced_import(db, name, version, indexes)?,
            supply_share: AddrSupplyShareVecs::forced_import(db, name, version, indexes)?,
        })
    }

    pub(crate) fn min_stateful_len(&self) -> usize {
        self.count
            .min_stateful_len()
            .min(self.events.min_stateful_len())
            .min(self.supply.min_stateful_len())
    }

    pub(crate) fn par_iter_height_mut(
        &mut self,
    ) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
        self.count
            .par_iter_height_mut()
            .chain(self.events.par_iter_height_mut())
            .chain(self.supply.par_iter_height_mut())
    }

    pub(crate) fn reset_height(&mut self) -> Result<()> {
        self.count.reset_height()?;
        self.events.reset_height()?;
        self.supply.reset_height()?;
        self.supply_share.reset_height()?;
        Ok(())
    }

    #[inline(always)]
    pub(crate) fn push_height(&mut self, state: &ReusedAddrState, active_addr_count: u32) {
        self.count.push_counts(&state.funded, &state.total);
        self.supply.push_supply(&state.supply);
        self.events.push_height(
            &state.output_events,
            &state.input_events,
            active_addr_count,
            u32::try_from(state.active.sum()).unwrap(),
        );
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) fn compute_rest(
        &mut self,
        starting_lengths: &Lengths,
        outputs_by_type: &outputs::ByTypeVecs,
        inputs_by_type: &inputs::ByTypeVecs,
        prices: &prices::Vecs,
        all_supply_sats: &impl ReadableVec<Height, Sats>,
        type_supply_sats: &ByAddrType<&impl ReadableVec<Height, Sats>>,
        exit: &Exit,
    ) -> Result<()> {
        self.count.compute_rest(starting_lengths, exit)?;
        self.events
            .compute_rest(starting_lengths, outputs_by_type, inputs_by_type, exit)?;
        self.supply
            .compute_rest(starting_lengths.height, prices, exit)?;
        self.supply_share.compute_rest(
            starting_lengths.height,
            &self.supply,
            all_supply_sats,
            type_supply_sats,
            exit,
        )?;
        Ok(())
    }
}
