mod resolver;
mod source;

pub(crate) use resolver::InputResolver;
pub(super) use source::InputSource;

use bitcoin::Transaction;
use brk_cohort::ByAddrType;
use brk_error::Result;
use brk_store::Store;
use brk_types::{
    AddrIndexOutPoint, AddrIndexTxIndex, OutPoint, OutputType, TxInIndex, TxIndex, TxOutIndex,
    TypeIndex, Unit, Vin,
};
use vecdb::{PcoVec, WritableVec, unlikely};

use super::{BlockProcessor, transaction::ComputedTx, txout::ProcessedOutput};
use crate::InputsVecs;

impl<'a> BlockProcessor<'a> {
    pub(crate) fn process_inputs<'b>(
        &self,
        txs: &[ComputedTx],
        resolver: &'b mut InputResolver,
    ) -> Result<&'b [InputSource]> {
        resolver.resolve(self, txs)
    }
}

#[allow(clippy::too_many_arguments)]
pub(super) fn finalize_inputs(
    transactions: &[Transaction],
    base_tx_index: TxIndex,
    base_txin_index: TxInIndex,
    first_txin_index: &mut PcoVec<TxIndex, TxInIndex>,
    inputs: &mut InputsVecs,
    addr_tx_index_stores: &mut ByAddrType<Store<AddrIndexTxIndex, Unit>>,
    addr_outpoint_stores: &mut ByAddrType<Store<AddrIndexOutPoint, Unit>>,
    txins: &[InputSource],
    txouts: &[ProcessedOutput],
) {
    let mut input_offset = 0;
    for (block_tx_index, tx) in transactions.iter().enumerate() {
        let tx_index = base_tx_index + TxIndex::from(block_tx_index);
        let next_input_offset = input_offset + tx.input.len();

        for (vin, input_source) in txins[input_offset..next_input_offset].iter().enumerate() {
            let block_txin_index = input_offset + vin;
            let txin_index = base_txin_index + TxInIndex::from(block_txin_index);
            let vin = Vin::from(vin);
            let (outpoint, txout_index, output_type, type_index) = match input_source {
                InputSource::PreviousBlock {
                    outpoint,
                    txout_index,
                    output_type,
                    legacy_sigops: _,
                    type_index,
                } => (*outpoint, *txout_index, *output_type, *type_index),
                InputSource::Coinbase => (
                    OutPoint::COINBASE,
                    TxOutIndex::COINBASE,
                    OutputType::Unknown,
                    TypeIndex::COINBASE,
                ),
                InputSource::SameBlock {
                    outpoint,
                    txout_offset,
                    txout_index,
                } => {
                    let output = &txouts[*txout_offset];
                    (
                        *outpoint,
                        *txout_index,
                        output.output_type,
                        output.resolved_type_index(),
                    )
                }
            };

            if vin.is_zero() {
                first_txin_index.debug_checked_push(tx_index, txin_index);
            }

            inputs.tx_index.debug_checked_push(txin_index, tx_index);
            inputs.outpoint.debug_checked_push(txin_index, outpoint);
            inputs
                .txout_index
                .debug_checked_push(txin_index, txout_index);
            inputs
                .output_type
                .debug_checked_push(txin_index, output_type);
            inputs.type_index.debug_checked_push(txin_index, type_index);

            if unlikely(!output_type.is_addr()) {
                continue;
            }
            let addr_type = output_type;
            let addr_index = type_index;

            addr_tx_index_stores
                .get_mut_unwrap(addr_type)
                .insert(AddrIndexTxIndex::from((addr_index, tx_index)), Unit);

            addr_outpoint_stores
                .get_mut_unwrap(addr_type)
                .remove(AddrIndexOutPoint::from((addr_index, outpoint)));
        }

        input_offset = next_input_offset;
    }
}
