//! API of a signed transaction.

use crate::{InMemorySize, MaybeCompact, MaybeSerde};
use alloc::fmt;
use alloy_consensus::transaction::{Recovered, SignerRecoverable, TxHashRef};
use alloy_eips::eip2718::{Decodable2718, Encodable2718, IsTyped2718};
use alloy_primitives::{keccak256, Address, B256};
use alloy_rlp::{Decodable, Encodable};
use core::hash::Hash;

pub use alloy_consensus::crypto::RecoveryError;

/// Helper trait that unifies all behaviour required by block to support full node operations.
pub trait FullSignedTx: SignedTransaction + MaybeCompact {}
impl<T> FullSignedTx for T where T: SignedTransaction + MaybeCompact {}

/// A signed transaction.
///
/// # Recovery Methods
///
/// This trait provides two types of recovery methods:
/// - Standard methods (e.g., `try_recover`) - enforce EIP-2 low-s signature requirement
/// - Unchecked methods (e.g., `try_recover_unchecked`) - skip EIP-2 validation for pre-EIP-2
///   transactions
///
/// Use unchecked methods only when dealing with historical pre-EIP-2 transactions.
pub trait SignedTransaction:
    Send
    + Sync
    + Unpin
    + Clone
    + fmt::Debug
    + PartialEq
    + Eq
    + Hash
    + Encodable
    + Decodable
    + Encodable2718
    + Decodable2718
    + alloy_consensus::Transaction
    + MaybeSerde
    + InMemorySize
    + SignerRecoverable
    + TxHashRef
    + IsTyped2718
{
    /// Returns whether this transaction type can be __broadcasted__ as full transaction over the
    /// network.
    ///
    /// Some transactions are not broadcastable as objects and only allowed to be broadcasted as
    /// hashes, e.g. because they missing context (e.g. blob sidecar).
    #[inline]
    fn is_broadcastable_in_full(&self) -> bool {
        // EIP-4844 transactions are not broadcastable in full, only hashes are allowed.
        !self.is_eip4844()
    }

    /// Recover signer from signature and hash.
    ///
    /// Returns an error if the transaction's signature is invalid.
    fn try_recover(&self) -> Result<Address, RecoveryError> {
        self.recover_signer()
    }

    /// Recover signer from signature and hash _without ensuring that the signature has a low `s`
    /// value_.
    ///
    /// Returns an error if the transaction's signature is invalid.
    fn try_recover_unchecked(&self) -> Result<Address, RecoveryError> {
        self.recover_signer_unchecked()
    }

    /// Calculate transaction hash, eip2728 transaction does not contain rlp header and start with
    /// tx type.
    fn recalculate_hash(&self) -> B256 {
        keccak256(self.encoded_2718())
    }

    /// Tries to recover signer and return [`Recovered`] by cloning the type.
    fn try_clone_into_recovered(&self) -> Result<Recovered<Self>, RecoveryError> {
        self.recover_signer().map(|signer| Recovered::new_unchecked(self.clone(), signer))
    }

    /// Tries to recover signer and return [`Recovered`] by cloning the type.
    fn try_clone_into_recovered_unchecked(&self) -> Result<Recovered<Self>, RecoveryError> {
        self.recover_signer_unchecked().map(|signer| Recovered::new_unchecked(self.clone(), signer))
    }

    /// Tries to recover signer and return [`Recovered`].
    ///
    /// Returns `Err(Self)` if the transaction's signature is invalid, see also
    /// [`SignerRecoverable::recover_signer`].
    fn try_into_recovered(self) -> Result<Recovered<Self>, Self> {
        match self.recover_signer() {
            Ok(signer) => Ok(Recovered::new_unchecked(self, signer)),
            Err(_) => Err(self),
        }
    }

    /// Consumes the type, recover signer and return [`Recovered`] _without
    /// ensuring that the signature has a low `s` value_ (EIP-2).
    ///
    /// Returns `RecoveryError` if the transaction's signature is invalid.
    #[deprecated(note = "Use try_into_recovered_unchecked instead")]
    fn into_recovered_unchecked(self) -> Result<Recovered<Self>, RecoveryError> {
        self.recover_signer_unchecked().map(|signer| Recovered::new_unchecked(self, signer))
    }

    /// Returns the [`Recovered`] transaction with the given sender.
    ///
    /// Note: assumes the given signer is the signer of this transaction.
    #[inline]
    fn with_signer(self, signer: Address) -> Recovered<Self> {
        Recovered::new_unchecked(self, signer)
    }

    /// Returns the [`Recovered`] transaction with the given signer, using a reference to self.
    ///
    /// Note: assumes the given signer is the signer of this transaction.
    #[inline]
    fn with_signer_ref(&self, signer: Address) -> Recovered<&Self> {
        Recovered::new_unchecked(self, signer)
    }
}

impl<T> SignedTransaction for T where
    Self: Send
        + Sync
        + Unpin
        + Clone
        + fmt::Debug
        + PartialEq
        + Eq
        + Hash
        + Encodable
        + Decodable
        + Encodable2718
        + Decodable2718
        + alloy_consensus::Transaction
        + MaybeSerde
        + InMemorySize
        + SignerRecoverable
        + TxHashRef
        + IsTyped2718
{
}
