bitflags::bitflags! {
    /// Marker to represents the current state of a transaction in the pool and from which the corresponding sub-pool is derived, depending on what bits are set.
    ///
    /// This mirrors [erigon's ephemeral state field](https://github.com/ledgerwatch/erigon/wiki/Transaction-Pool-Design#ordering-function).
    ///
    /// The [SubPool] the transaction belongs to is derived from its state and determined by the following sequential checks:
    ///
    /// - If it satisfies the [TxState::PENDING_POOL_BITS] it belongs in the pending sub-pool: [SubPool::Pending].
    /// - If it is an EIP-4844 blob transaction it belongs in the blob sub-pool: [SubPool::Blob].
    /// - If it satisfies the [TxState::BASE_FEE_POOL_BITS] it belongs in the base fee sub-pool: [SubPool::BaseFee].
    ///
    /// Otherwise, it belongs in the queued sub-pool: [SubPool::Queued].
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
    pub(crate) struct TxState: u8 {
        /// Set to `1` if all ancestor transactions are pending.
        const NO_PARKED_ANCESTORS = 0b10000000;
        /// Set to `1` of the transaction is either the next transaction of the sender (on chain nonce == tx.nonce) or all prior transactions are also present in the pool.
        const NO_NONCE_GAPS = 0b01000000;
        /// Bit derived from the sender's balance.
        ///
        /// Set to `1` if the sender's balance can cover the maximum cost for this transaction (`feeCap * gasLimit + value`).
        /// This includes cumulative costs of prior transactions, which ensures that the sender has enough funds for all max cost of prior transactions.
        const ENOUGH_BALANCE = 0b00100000;
        /// Bit set to true if the transaction has a lower gas limit than the block's gas limit.
        const NOT_TOO_MUCH_GAS = 0b00010000;
        /// Covers the Dynamic fee requirement.
        ///
        /// Set to 1 if `maxFeePerGas` of the transaction meets the requirement of the pending block.
        const ENOUGH_FEE_CAP_BLOCK = 0b00001000;
        /// Covers the dynamic blob fee requirement, only relevant for EIP-4844 blob transactions.
        ///
        /// Set to 1 if `maxBlobFeePerGas` of the transaction meets the requirement of the pending block.
        const ENOUGH_BLOB_FEE_CAP_BLOCK = 0b00000100;
        /// Marks whether the transaction is a blob transaction.
        ///
        /// We track this as part of the state for simplicity, since blob transactions are handled differently and are mutually exclusive with normal transactions.
        const BLOB_TRANSACTION = 0b00000010;

        const PENDING_POOL_BITS = Self::NO_PARKED_ANCESTORS.bits() | Self::NO_NONCE_GAPS.bits() | Self::ENOUGH_BALANCE.bits() | Self::NOT_TOO_MUCH_GAS.bits() |  Self::ENOUGH_FEE_CAP_BLOCK.bits() | Self::ENOUGH_BLOB_FEE_CAP_BLOCK.bits();

        const BASE_FEE_POOL_BITS = Self::NO_PARKED_ANCESTORS.bits() | Self::NO_NONCE_GAPS.bits() | Self::ENOUGH_BALANCE.bits() | Self::NOT_TOO_MUCH_GAS.bits();

        const QUEUED_POOL_BITS  = Self::NO_PARKED_ANCESTORS.bits();

        const BLOB_POOL_BITS  = Self::BLOB_TRANSACTION.bits();
    }
}

// === impl TxState ===

impl TxState {
    /// The state of a transaction is considered `pending`, if the transaction has:
    ///   - _No_ parked ancestors
    ///   - enough balance
    ///   - enough fee cap
    ///   - enough blob fee cap
    #[inline]
    pub(crate) const fn is_pending(&self) -> bool {
        self.bits() >= Self::PENDING_POOL_BITS.bits()
    }

    /// Whether this transaction is a blob transaction.
    #[inline]
    pub(crate) const fn is_blob(&self) -> bool {
        self.contains(Self::BLOB_TRANSACTION)
    }

    /// Returns `true` if the transaction has a nonce gap.
    #[inline]
    pub(crate) const fn has_nonce_gap(&self) -> bool {
        !self.intersects(Self::NO_NONCE_GAPS)
    }
}

/// Identifier for the transaction Sub-pool
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(u8)]
pub enum SubPool {
    /// The queued sub-pool contains transactions that are not ready to be included in the next
    /// block because they have missing or queued ancestors or the sender the lacks funds to
    /// execute this transaction.
    Queued = 0,
    /// The base-fee sub-pool contains transactions that are not ready to be included in the next
    /// block because they don't meet the base fee requirement.
    BaseFee,
    /// The blob sub-pool contains all blob transactions that are __not__ pending.
    Blob,
    /// The pending sub-pool contains transactions that are ready to be included in the next block.
    Pending,
}

// === impl SubPool ===

impl SubPool {
    /// Whether this transaction is to be moved to the pending sub-pool.
    #[inline]
    pub const fn is_pending(&self) -> bool {
        matches!(self, Self::Pending)
    }

    /// Whether this transaction is in the queued pool.
    #[inline]
    pub const fn is_queued(&self) -> bool {
        matches!(self, Self::Queued)
    }

    /// Whether this transaction is in the base fee pool.
    #[inline]
    pub const fn is_base_fee(&self) -> bool {
        matches!(self, Self::BaseFee)
    }

    /// Whether this transaction is in the blob pool.
    #[inline]
    pub const fn is_blob(&self) -> bool {
        matches!(self, Self::Blob)
    }

    /// Returns whether this is a promotion depending on the current sub-pool location.
    #[inline]
    pub fn is_promoted(&self, other: Self) -> bool {
        self > &other
    }
}

impl From<TxState> for SubPool {
    fn from(value: TxState) -> Self {
        if value.is_pending() {
            return Self::Pending
        }
        if value.is_blob() {
            // all _non-pending_ blob transactions are in the blob sub-pool
            return Self::Blob
        }
        if value.bits() < TxState::BASE_FEE_POOL_BITS.bits() {
            return Self::Queued
        }
        Self::BaseFee
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_promoted() {
        assert!(SubPool::BaseFee.is_promoted(SubPool::Queued));
        assert!(SubPool::Pending.is_promoted(SubPool::BaseFee));
        assert!(SubPool::Pending.is_promoted(SubPool::Queued));
        assert!(SubPool::Pending.is_promoted(SubPool::Blob));
        assert!(!SubPool::BaseFee.is_promoted(SubPool::Pending));
        assert!(!SubPool::Queued.is_promoted(SubPool::BaseFee));
    }

    #[test]
    fn test_tx_state() {
        let mut state = TxState::default();
        state |= TxState::NO_NONCE_GAPS;
        assert!(state.intersects(TxState::NO_NONCE_GAPS))
    }

    #[test]
    fn test_tx_queued() {
        let state = TxState::default();
        assert_eq!(SubPool::Queued, state.into());

        let state = TxState::NO_PARKED_ANCESTORS |
            TxState::NO_NONCE_GAPS |
            TxState::NOT_TOO_MUCH_GAS |
            TxState::ENOUGH_FEE_CAP_BLOCK;
        assert_eq!(SubPool::Queued, state.into());
    }

    #[test]
    fn test_tx_pending() {
        let state = TxState::PENDING_POOL_BITS;
        assert_eq!(SubPool::Pending, state.into());
        assert!(state.is_pending());

        let bits = 0b11111100;
        let state = TxState::from_bits(bits).unwrap();
        assert_eq!(SubPool::Pending, state.into());
        assert!(state.is_pending());

        let bits = 0b11111110;
        let state = TxState::from_bits(bits).unwrap();
        assert_eq!(SubPool::Pending, state.into());
        assert!(state.is_pending());
    }

    #[test]
    fn test_blob() {
        let mut state = TxState::PENDING_POOL_BITS;
        state.insert(TxState::BLOB_TRANSACTION);
        assert!(state.is_pending());

        state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
        assert!(state.is_blob());
        assert!(!state.is_pending());

        state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
        state.remove(TxState::ENOUGH_FEE_CAP_BLOCK);
        assert!(state.is_blob());
        assert!(!state.is_pending());
    }
}
