use std::marker::PhantomData;

use bls::Signature;
use context_deserialize::{ContextDeserialize, context_deserialize};
use educe::Educe;
use merkle_proof::MerkleTree;
use metastruct::metastruct;
use serde::{Deserialize, Deserializer, Serialize};
use ssz_derive::{Decode, Encode};
use ssz_types::{FixedVector, VariableList};
use superstruct::superstruct;
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;

use crate::{
    SignedExecutionPayloadBid,
    attestation::{
        AttestationBase, AttestationElectra, AttestationRef, AttestationRefMut, PayloadAttestation,
    },
    complete_kzg_commitment_merkle_proof,
    core::{EthSpec, Graffiti, Hash256},
    deposit::Deposit,
    execution::{
        AbstractExecPayload, BlindedPayload, BlindedPayloadBellatrix, BlindedPayloadCapella,
        BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, Eth1Data, ExecutionPayload,
        ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb,
        ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequests,
        FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb,
        FullPayloadElectra, FullPayloadFulu, SignedBlsToExecutionChange,
    },
    exit::SignedVoluntaryExit,
    fork::{ForkName, map_fork_name},
    kzg_ext::KzgCommitments,
    light_client::consts::{EXECUTION_PAYLOAD_INDEX, EXECUTION_PAYLOAD_PROOF_LEN},
    slashing::{
        AttesterSlashingBase, AttesterSlashingElectra, AttesterSlashingRef, ProposerSlashing,
    },
    state::BeaconStateError,
    sync_committee::SyncAggregate,
};

/// The number of leaves (including padding) on the `BeaconBlockBody` Merkle tree.
///
/// ## Note
///
/// This constant is set with the assumption that there are `> 8` and `<= 16` fields on the
/// `BeaconBlockBody`. **Tree hashing will fail if this value is set incorrectly.**
pub const NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES: usize = 16;
/// Index of the `blob_kzg_commitments` leaf in the `BeaconBlockBody` tree post-deneb.
pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11;

/// The body of a `BeaconChain` block, containing operations.
///
/// This *superstruct* abstracts over the hard-fork.
#[superstruct(
    variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas),
    variant_attributes(
        derive(
            Debug,
            Clone,
            Serialize,
            Deserialize,
            Encode,
            Decode,
            TreeHash,
            Educe,
        ),
        educe(PartialEq, Hash(bound(E: EthSpec, Payload: AbstractExecPayload<E>))),
        serde(
            bound = "E: EthSpec, Payload: AbstractExecPayload<E>",
            deny_unknown_fields
        ),
        cfg_attr(
            feature = "arbitrary",
            derive(arbitrary::Arbitrary),
            arbitrary(bound = "E: EthSpec, Payload: AbstractExecPayload<E>"),
        ),
        context_deserialize(ForkName),
    ),
    specific_variant_attributes(
        Base(metastruct(mappings(beacon_block_body_base_fields(groups(fields))))),
        Altair(metastruct(mappings(beacon_block_body_altair_fields(groups(fields))))),
        Bellatrix(metastruct(mappings(beacon_block_body_bellatrix_fields(groups(fields))))),
        Capella(metastruct(mappings(beacon_block_body_capella_fields(groups(fields))))),
        Deneb(metastruct(mappings(beacon_block_body_deneb_fields(groups(fields))))),
        Electra(metastruct(mappings(beacon_block_body_electra_fields(groups(fields))))),
        Fulu(metastruct(mappings(beacon_block_body_fulu_fields(groups(fields))))),
        Gloas(metastruct(mappings(beacon_block_body_gloas_fields(groups(fields))))),
    ),
    cast_error(
        ty = "BeaconStateError",
        expr = "BeaconStateError::IncorrectStateVariant"
    ),
    partial_getter_error(
        ty = "BeaconStateError",
        expr = "BeaconStateError::IncorrectStateVariant"
    )
)]
#[cfg_attr(
    feature = "arbitrary",
    derive(arbitrary::Arbitrary),
    arbitrary(bound = "E: EthSpec, Payload: AbstractExecPayload<E>")
)]
#[derive(Debug, Clone, Serialize, Deserialize, Educe, TreeHash)]
#[educe(PartialEq, Hash(bound(E: EthSpec)))]
#[serde(untagged)]
#[serde(bound = "E: EthSpec, Payload: AbstractExecPayload<E>")]
#[tree_hash(enum_behaviour = "transparent")]
pub struct BeaconBlockBody<E: EthSpec, Payload: AbstractExecPayload<E> = FullPayload<E>> {
    pub randao_reveal: Signature,
    pub eth1_data: Eth1Data,
    pub graffiti: Graffiti,
    pub proposer_slashings: VariableList<ProposerSlashing, E::MaxProposerSlashings>,
    #[superstruct(
        only(Base, Altair, Bellatrix, Capella, Deneb),
        partial_getter(rename = "attester_slashings_base")
    )]
    pub attester_slashings: VariableList<AttesterSlashingBase<E>, E::MaxAttesterSlashings>,
    #[superstruct(
        only(Electra, Fulu, Gloas),
        partial_getter(rename = "attester_slashings_electra")
    )]
    pub attester_slashings:
        VariableList<AttesterSlashingElectra<E>, E::MaxAttesterSlashingsElectra>,
    #[superstruct(
        only(Base, Altair, Bellatrix, Capella, Deneb),
        partial_getter(rename = "attestations_base")
    )]
    pub attestations: VariableList<AttestationBase<E>, E::MaxAttestations>,
    #[superstruct(
        only(Electra, Fulu, Gloas),
        partial_getter(rename = "attestations_electra")
    )]
    pub attestations: VariableList<AttestationElectra<E>, E::MaxAttestationsElectra>,
    pub deposits: VariableList<Deposit, E::MaxDeposits>,
    pub voluntary_exits: VariableList<SignedVoluntaryExit, E::MaxVoluntaryExits>,
    #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))]
    pub sync_aggregate: SyncAggregate<E>,
    // We flatten the execution payload so that serde can use the name of the inner type,
    // either `execution_payload` for full payloads, or `execution_payload_header` for blinded
    // payloads.
    #[superstruct(
        only(Bellatrix),
        partial_getter(rename = "execution_payload_bellatrix")
    )]
    #[serde(flatten)]
    pub execution_payload: Payload::Bellatrix,
    #[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))]
    #[serde(flatten)]
    pub execution_payload: Payload::Capella,
    #[superstruct(only(Deneb), partial_getter(rename = "execution_payload_deneb"))]
    #[serde(flatten)]
    pub execution_payload: Payload::Deneb,
    #[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))]
    #[serde(flatten)]
    pub execution_payload: Payload::Electra,
    #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))]
    #[serde(flatten)]
    pub execution_payload: Payload::Fulu,
    #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))]
    pub bls_to_execution_changes:
        VariableList<SignedBlsToExecutionChange, E::MaxBlsToExecutionChanges>,
    #[superstruct(only(Deneb, Electra, Fulu))]
    pub blob_kzg_commitments: KzgCommitments<E>,
    #[superstruct(only(Electra, Fulu))]
    pub execution_requests: ExecutionRequests<E>,
    #[superstruct(only(Gloas))]
    pub signed_execution_payload_bid: SignedExecutionPayloadBid<E>,
    #[superstruct(only(Gloas))]
    pub payload_attestations: VariableList<PayloadAttestation<E>, E::MaxPayloadAttestations>,
    #[superstruct(only(Gloas))]
    pub parent_execution_requests: ExecutionRequests<E>,
    #[superstruct(only(Base, Altair, Gloas))]
    #[metastruct(exclude_from(fields))]
    #[ssz(skip_serializing, skip_deserializing)]
    #[tree_hash(skip_hashing)]
    #[serde(skip)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    pub _phantom: PhantomData<Payload>,
}

impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBody<E, Payload> {
    pub fn execution_payload(&self) -> Result<Payload::Ref<'_>, BeaconStateError> {
        self.to_ref().execution_payload()
    }

    /// Returns the name of the fork pertaining to `self`.
    pub fn fork_name(&self) -> ForkName {
        self.to_ref().fork_name()
    }
}

impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRef<'a, E, Payload> {
    pub fn execution_payload(&self) -> Result<Payload::Ref<'a>, BeaconStateError> {
        match self {
            Self::Base(_) | Self::Altair(_) => Err(BeaconStateError::IncorrectStateVariant),
            Self::Bellatrix(body) => Ok(Payload::Ref::from(&body.execution_payload)),
            Self::Capella(body) => Ok(Payload::Ref::from(&body.execution_payload)),
            Self::Deneb(body) => Ok(Payload::Ref::from(&body.execution_payload)),
            Self::Electra(body) => Ok(Payload::Ref::from(&body.execution_payload)),
            Self::Fulu(body) => Ok(Payload::Ref::from(&body.execution_payload)),
            Self::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant),
        }
    }

    pub fn body_merkle_leaves(&self) -> Vec<Hash256> {
        let mut leaves = vec![];
        match self {
            Self::Base(body) => {
                beacon_block_body_base_fields!(body, |_, field| leaves
                    .push(field.tree_hash_root()));
            }
            Self::Altair(body) => {
                beacon_block_body_altair_fields!(body, |_, field| leaves
                    .push(field.tree_hash_root()));
            }
            Self::Bellatrix(body) => {
                beacon_block_body_bellatrix_fields!(body, |_, field| leaves
                    .push(field.tree_hash_root()));
            }
            Self::Capella(body) => {
                beacon_block_body_capella_fields!(body, |_, field| leaves
                    .push(field.tree_hash_root()));
            }
            Self::Deneb(body) => {
                beacon_block_body_deneb_fields!(body, |_, field| leaves
                    .push(field.tree_hash_root()));
            }
            Self::Electra(body) => {
                beacon_block_body_electra_fields!(body, |_, field| leaves
                    .push(field.tree_hash_root()));
            }
            Self::Fulu(body) => {
                beacon_block_body_fulu_fields!(body, |_, field| leaves
                    .push(field.tree_hash_root()));
            }
            Self::Gloas(body) => {
                beacon_block_body_gloas_fields!(body, |_, field| leaves
                    .push(field.tree_hash_root()));
            }
        }
        leaves
    }

    /// Calculate a KZG commitment merkle proof.
    ///
    /// Prefer to use `complete_kzg_commitment_merkle_proof` with a reused proof for the
    /// `blob_kzg_commitments` field.
    pub fn kzg_commitment_merkle_proof(
        &self,
        index: usize,
    ) -> Result<FixedVector<Hash256, E::KzgCommitmentInclusionProofDepth>, BeaconStateError> {
        let kzg_commitments_proof = self.kzg_commitments_merkle_proof()?;
        let proof = self.complete_kzg_commitment_merkle_proof(index, &kzg_commitments_proof)?;
        Ok(proof)
    }

    /// Produces the proof of inclusion for a `KzgCommitment` in `self.blob_kzg_commitments`
    /// at `index` using an existing proof for the `blob_kzg_commitments` field.
    /// TODO(EIP7732) Investigate calling functions since this will no longer work for glas since no block_kzg_commitments in the body anymore
    pub fn complete_kzg_commitment_merkle_proof(
        &self,
        index: usize,
        kzg_commitments_proof: &[Hash256],
    ) -> Result<FixedVector<Hash256, E::KzgCommitmentInclusionProofDepth>, BeaconStateError> {
        match self {
            Self::Base(_)
            | Self::Altair(_)
            | Self::Bellatrix(_)
            | Self::Capella(_)
            | Self::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant),
            Self::Deneb(_) | Self::Electra(_) | Self::Fulu(_) => {
                complete_kzg_commitment_merkle_proof::<E>(
                    self.blob_kzg_commitments()?,
                    index,
                    kzg_commitments_proof,
                )
            }
        }
    }

    /// Produces the proof of inclusion for `self.blob_kzg_commitments`.
    pub fn kzg_commitments_merkle_proof(
        &self,
    ) -> Result<FixedVector<Hash256, E::KzgCommitmentsInclusionProofDepth>, BeaconStateError> {
        let body_leaves = self.body_merkle_leaves();
        let beacon_block_body_depth = body_leaves.len().next_power_of_two().ilog2() as usize;
        let tree = MerkleTree::create(&body_leaves, beacon_block_body_depth);
        let (_, proof) = tree
            .generate_proof(BLOB_KZG_COMMITMENTS_INDEX, beacon_block_body_depth)
            .map_err(BeaconStateError::MerkleTreeError)?;
        Ok(FixedVector::new(proof)?)
    }

    pub fn block_body_merkle_proof(
        &self,
        generalized_index: usize,
    ) -> Result<Vec<Hash256>, BeaconStateError> {
        let field_index = match generalized_index {
            EXECUTION_PAYLOAD_INDEX => {
                // Execution payload is a top-level field, subtract off the generalized indices
                // for the internal nodes. Result should be 9, the field offset of the execution
                // payload in the `BeaconBlockBody`:
                // https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/beacon-chain.md#beaconblockbody
                generalized_index
                    .checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES)
                    .ok_or(BeaconStateError::GeneralizedIndexNotSupported(
                        generalized_index,
                    ))?
            }
            _ => {
                return Err(BeaconStateError::GeneralizedIndexNotSupported(
                    generalized_index,
                ));
            }
        };

        let leaves = self.body_merkle_leaves();
        let depth = EXECUTION_PAYLOAD_PROOF_LEN;
        let tree = merkle_proof::MerkleTree::create(&leaves, depth);
        let (_, proof) = tree.generate_proof(field_index, depth)?;

        Ok(proof)
    }

    /// Return `true` if this block body has a non-zero number of blobs.
    pub fn has_blobs(self) -> bool {
        self.blob_kzg_commitments()
            .is_ok_and(|blobs| !blobs.is_empty())
    }

    pub fn attestations_len(&self) -> usize {
        map_beacon_block_body_ref!(&'a _, self, |inner, cons| {
            cons(inner);
            inner.attestations.len()
        })
    }

    pub fn attester_slashings_len(&self) -> usize {
        map_beacon_block_body_ref!(&'a _, self, |inner, cons| {
            cons(inner);
            inner.attester_slashings.len()
        })
    }

    pub fn attestations(&self) -> Box<dyn Iterator<Item = AttestationRef<'a, E>> + 'a> {
        match self {
            Self::Base(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)),
            Self::Altair(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)),
            Self::Bellatrix(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)),
            Self::Capella(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)),
            Self::Deneb(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)),
            Self::Electra(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)),
            Self::Fulu(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)),
            Self::Gloas(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)),
        }
    }

    pub fn attester_slashings(&self) -> Box<dyn Iterator<Item = AttesterSlashingRef<'a, E>> + 'a> {
        match self {
            Self::Base(body) => Box::new(
                body.attester_slashings
                    .iter()
                    .map(AttesterSlashingRef::Base),
            ),
            Self::Altair(body) => Box::new(
                body.attester_slashings
                    .iter()
                    .map(AttesterSlashingRef::Base),
            ),
            Self::Bellatrix(body) => Box::new(
                body.attester_slashings
                    .iter()
                    .map(AttesterSlashingRef::Base),
            ),
            Self::Capella(body) => Box::new(
                body.attester_slashings
                    .iter()
                    .map(AttesterSlashingRef::Base),
            ),
            Self::Deneb(body) => Box::new(
                body.attester_slashings
                    .iter()
                    .map(AttesterSlashingRef::Base),
            ),
            Self::Electra(body) => Box::new(
                body.attester_slashings
                    .iter()
                    .map(AttesterSlashingRef::Electra),
            ),
            Self::Fulu(body) => Box::new(
                body.attester_slashings
                    .iter()
                    .map(AttesterSlashingRef::Electra),
            ),
            Self::Gloas(body) => Box::new(
                body.attester_slashings
                    .iter()
                    .map(AttesterSlashingRef::Electra),
            ),
        }
    }
}

impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRefMut<'a, E, Payload> {
    pub fn attestations_mut(
        &'a mut self,
    ) -> Box<dyn Iterator<Item = AttestationRefMut<'a, E>> + 'a> {
        match self {
            Self::Base(body) => Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base)),
            Self::Altair(body) => {
                Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base))
            }
            Self::Bellatrix(body) => {
                Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base))
            }
            Self::Capella(body) => {
                Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base))
            }
            Self::Deneb(body) => {
                Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base))
            }
            Self::Electra(body) => {
                Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra))
            }
            Self::Fulu(body) => {
                Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra))
            }
            Self::Gloas(body) => {
                Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra))
            }
        }
    }
}

impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRef<'_, E, Payload> {
    /// Get the fork_name of this object
    pub fn fork_name(self) -> ForkName {
        match self {
            BeaconBlockBodyRef::Base { .. } => ForkName::Base,
            BeaconBlockBodyRef::Altair { .. } => ForkName::Altair,
            BeaconBlockBodyRef::Bellatrix { .. } => ForkName::Bellatrix,
            BeaconBlockBodyRef::Capella { .. } => ForkName::Capella,
            BeaconBlockBodyRef::Deneb { .. } => ForkName::Deneb,
            BeaconBlockBodyRef::Electra { .. } => ForkName::Electra,
            BeaconBlockBodyRef::Fulu { .. } => ForkName::Fulu,
            BeaconBlockBodyRef::Gloas { .. } => ForkName::Gloas,
        }
    }
}

// We can convert pre-Bellatrix block bodies without payloads into block bodies "with" payloads.
impl<E: EthSpec> From<BeaconBlockBodyBase<E, BlindedPayload<E>>>
    for BeaconBlockBodyBase<E, FullPayload<E>>
{
    fn from(body: BeaconBlockBodyBase<E, BlindedPayload<E>>) -> Self {
        let BeaconBlockBodyBase {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            _phantom,
        } = body;

        BeaconBlockBodyBase {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            _phantom: PhantomData,
        }
    }
}

impl<E: EthSpec> From<BeaconBlockBodyAltair<E, BlindedPayload<E>>>
    for BeaconBlockBodyAltair<E, FullPayload<E>>
{
    fn from(body: BeaconBlockBodyAltair<E, BlindedPayload<E>>) -> Self {
        let BeaconBlockBodyAltair {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            _phantom,
        } = body;

        BeaconBlockBodyAltair {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            _phantom: PhantomData,
        }
    }
}

// Post-Fulu block bodies without payloads can be converted into block bodies with payloads
// TODO(EIP-7732) Look into whether we can remove this in the future since no blinded blocks post-gloas
impl<E: EthSpec> From<BeaconBlockBodyGloas<E, BlindedPayload<E>>>
    for BeaconBlockBodyGloas<E, FullPayload<E>>
{
    fn from(body: BeaconBlockBodyGloas<E, BlindedPayload<E>>) -> Self {
        let BeaconBlockBodyGloas {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            bls_to_execution_changes,
            parent_execution_requests,
            signed_execution_payload_bid,
            payload_attestations,
            _phantom,
        } = body;

        BeaconBlockBodyGloas {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            bls_to_execution_changes,
            parent_execution_requests,
            signed_execution_payload_bid,
            payload_attestations,
            _phantom: PhantomData,
        }
    }
}

// Likewise bodies with payloads can be transformed into bodies without.
impl<E: EthSpec> From<BeaconBlockBodyBase<E, FullPayload<E>>>
    for (
        BeaconBlockBodyBase<E, BlindedPayload<E>>,
        Option<ExecutionPayload<E>>,
    )
{
    fn from(body: BeaconBlockBodyBase<E, FullPayload<E>>) -> Self {
        let BeaconBlockBodyBase {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            _phantom,
        } = body;

        (
            BeaconBlockBodyBase {
                randao_reveal,
                eth1_data,
                graffiti,
                proposer_slashings,
                attester_slashings,
                attestations,
                deposits,
                voluntary_exits,
                _phantom: PhantomData,
            },
            None,
        )
    }
}

impl<E: EthSpec> From<BeaconBlockBodyAltair<E, FullPayload<E>>>
    for (
        BeaconBlockBodyAltair<E, BlindedPayload<E>>,
        Option<ExecutionPayload<E>>,
    )
{
    fn from(body: BeaconBlockBodyAltair<E, FullPayload<E>>) -> Self {
        let BeaconBlockBodyAltair {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            _phantom,
        } = body;

        (
            BeaconBlockBodyAltair {
                randao_reveal,
                eth1_data,
                graffiti,
                proposer_slashings,
                attester_slashings,
                attestations,
                deposits,
                voluntary_exits,
                sync_aggregate,
                _phantom: PhantomData,
            },
            None,
        )
    }
}

impl<E: EthSpec> From<BeaconBlockBodyBellatrix<E, FullPayload<E>>>
    for (
        BeaconBlockBodyBellatrix<E, BlindedPayload<E>>,
        Option<ExecutionPayloadBellatrix<E>>,
    )
{
    fn from(body: BeaconBlockBodyBellatrix<E, FullPayload<E>>) -> Self {
        let BeaconBlockBodyBellatrix {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            execution_payload: FullPayloadBellatrix { execution_payload },
        } = body;

        (
            BeaconBlockBodyBellatrix {
                randao_reveal,
                eth1_data,
                graffiti,
                proposer_slashings,
                attester_slashings,
                attestations,
                deposits,
                voluntary_exits,
                sync_aggregate,
                execution_payload: BlindedPayloadBellatrix {
                    execution_payload_header: From::from(&execution_payload),
                },
            },
            Some(execution_payload),
        )
    }
}

impl<E: EthSpec> From<BeaconBlockBodyCapella<E, FullPayload<E>>>
    for (
        BeaconBlockBodyCapella<E, BlindedPayload<E>>,
        Option<ExecutionPayloadCapella<E>>,
    )
{
    fn from(body: BeaconBlockBodyCapella<E, FullPayload<E>>) -> Self {
        let BeaconBlockBodyCapella {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            execution_payload: FullPayloadCapella { execution_payload },
            bls_to_execution_changes,
        } = body;

        (
            BeaconBlockBodyCapella {
                randao_reveal,
                eth1_data,
                graffiti,
                proposer_slashings,
                attester_slashings,
                attestations,
                deposits,
                voluntary_exits,
                sync_aggregate,
                execution_payload: BlindedPayloadCapella {
                    execution_payload_header: From::from(&execution_payload),
                },
                bls_to_execution_changes,
            },
            Some(execution_payload),
        )
    }
}

impl<E: EthSpec> From<BeaconBlockBodyDeneb<E, FullPayload<E>>>
    for (
        BeaconBlockBodyDeneb<E, BlindedPayload<E>>,
        Option<ExecutionPayloadDeneb<E>>,
    )
{
    fn from(body: BeaconBlockBodyDeneb<E, FullPayload<E>>) -> Self {
        let BeaconBlockBodyDeneb {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            execution_payload: FullPayloadDeneb { execution_payload },
            bls_to_execution_changes,
            blob_kzg_commitments,
        } = body;

        (
            BeaconBlockBodyDeneb {
                randao_reveal,
                eth1_data,
                graffiti,
                proposer_slashings,
                attester_slashings,
                attestations,
                deposits,
                voluntary_exits,
                sync_aggregate,
                execution_payload: BlindedPayloadDeneb {
                    execution_payload_header: From::from(&execution_payload),
                },
                bls_to_execution_changes,
                blob_kzg_commitments,
            },
            Some(execution_payload),
        )
    }
}

impl<E: EthSpec> From<BeaconBlockBodyElectra<E, FullPayload<E>>>
    for (
        BeaconBlockBodyElectra<E, BlindedPayload<E>>,
        Option<ExecutionPayloadElectra<E>>,
    )
{
    fn from(body: BeaconBlockBodyElectra<E, FullPayload<E>>) -> Self {
        let BeaconBlockBodyElectra {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            execution_payload: FullPayloadElectra { execution_payload },
            bls_to_execution_changes,
            blob_kzg_commitments,
            execution_requests,
        } = body;

        (
            BeaconBlockBodyElectra {
                randao_reveal,
                eth1_data,
                graffiti,
                proposer_slashings,
                attester_slashings,
                attestations,
                deposits,
                voluntary_exits,
                sync_aggregate,
                execution_payload: BlindedPayloadElectra {
                    execution_payload_header: From::from(&execution_payload),
                },
                bls_to_execution_changes,
                blob_kzg_commitments: blob_kzg_commitments.clone(),
                execution_requests,
            },
            Some(execution_payload),
        )
    }
}

impl<E: EthSpec> From<BeaconBlockBodyFulu<E, FullPayload<E>>>
    for (
        BeaconBlockBodyFulu<E, BlindedPayload<E>>,
        Option<ExecutionPayloadFulu<E>>,
    )
{
    fn from(body: BeaconBlockBodyFulu<E, FullPayload<E>>) -> Self {
        let BeaconBlockBodyFulu {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            execution_payload: FullPayloadFulu { execution_payload },
            bls_to_execution_changes,
            blob_kzg_commitments,
            execution_requests,
        } = body;

        (
            BeaconBlockBodyFulu {
                randao_reveal,
                eth1_data,
                graffiti,
                proposer_slashings,
                attester_slashings,
                attestations,
                deposits,
                voluntary_exits,
                sync_aggregate,
                execution_payload: BlindedPayloadFulu {
                    execution_payload_header: From::from(&execution_payload),
                },
                bls_to_execution_changes,
                blob_kzg_commitments: blob_kzg_commitments.clone(),
                execution_requests,
            },
            Some(execution_payload),
        )
    }
}

impl<E: EthSpec> From<BeaconBlockBodyGloas<E, FullPayload<E>>>
    for (
        BeaconBlockBodyGloas<E, BlindedPayload<E>>,
        Option<ExecutionPayloadGloas<E>>,
    )
{
    fn from(body: BeaconBlockBodyGloas<E, FullPayload<E>>) -> Self {
        let BeaconBlockBodyGloas {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            bls_to_execution_changes,
            parent_execution_requests,
            signed_execution_payload_bid,
            payload_attestations,
            _phantom,
        } = body;

        (
            BeaconBlockBodyGloas {
                randao_reveal,
                eth1_data,
                graffiti,
                proposer_slashings,
                attester_slashings,
                attestations,
                deposits,
                voluntary_exits,
                sync_aggregate,
                bls_to_execution_changes,
                parent_execution_requests,
                signed_execution_payload_bid,
                payload_attestations,
                _phantom: PhantomData,
            },
            None,
        )
    }
}

// We can clone a full block into a blinded block, without cloning the payload.
impl<E: EthSpec> BeaconBlockBodyBase<E, FullPayload<E>> {
    pub fn clone_as_blinded(&self) -> BeaconBlockBodyBase<E, BlindedPayload<E>> {
        let (block_body, _payload) = self.clone().into();
        block_body
    }
}

impl<E: EthSpec> BeaconBlockBodyAltair<E, FullPayload<E>> {
    pub fn clone_as_blinded(&self) -> BeaconBlockBodyAltair<E, BlindedPayload<E>> {
        let (block_body, _payload) = self.clone().into();
        block_body
    }
}

impl<E: EthSpec> BeaconBlockBodyBellatrix<E, FullPayload<E>> {
    pub fn clone_as_blinded(&self) -> BeaconBlockBodyBellatrix<E, BlindedPayload<E>> {
        let BeaconBlockBodyBellatrix {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            execution_payload: FullPayloadBellatrix { execution_payload },
        } = self;

        BeaconBlockBodyBellatrix {
            randao_reveal: randao_reveal.clone(),
            eth1_data: eth1_data.clone(),
            graffiti: *graffiti,
            proposer_slashings: proposer_slashings.clone(),
            attester_slashings: attester_slashings.clone(),
            attestations: attestations.clone(),
            deposits: deposits.clone(),
            voluntary_exits: voluntary_exits.clone(),
            sync_aggregate: sync_aggregate.clone(),
            execution_payload: BlindedPayloadBellatrix {
                execution_payload_header: execution_payload.into(),
            },
        }
    }
}

impl<E: EthSpec> BeaconBlockBodyCapella<E, FullPayload<E>> {
    pub fn clone_as_blinded(&self) -> BeaconBlockBodyCapella<E, BlindedPayload<E>> {
        let BeaconBlockBodyCapella {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            execution_payload: FullPayloadCapella { execution_payload },
            bls_to_execution_changes,
        } = self;

        BeaconBlockBodyCapella {
            randao_reveal: randao_reveal.clone(),
            eth1_data: eth1_data.clone(),
            graffiti: *graffiti,
            proposer_slashings: proposer_slashings.clone(),
            attester_slashings: attester_slashings.clone(),
            attestations: attestations.clone(),
            deposits: deposits.clone(),
            voluntary_exits: voluntary_exits.clone(),
            sync_aggregate: sync_aggregate.clone(),
            execution_payload: BlindedPayloadCapella {
                execution_payload_header: execution_payload.into(),
            },
            bls_to_execution_changes: bls_to_execution_changes.clone(),
        }
    }
}

impl<E: EthSpec> BeaconBlockBodyDeneb<E, FullPayload<E>> {
    pub fn clone_as_blinded(&self) -> BeaconBlockBodyDeneb<E, BlindedPayload<E>> {
        let BeaconBlockBodyDeneb {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            execution_payload: FullPayloadDeneb { execution_payload },
            bls_to_execution_changes,
            blob_kzg_commitments,
        } = self;

        BeaconBlockBodyDeneb {
            randao_reveal: randao_reveal.clone(),
            eth1_data: eth1_data.clone(),
            graffiti: *graffiti,
            proposer_slashings: proposer_slashings.clone(),
            attester_slashings: attester_slashings.clone(),
            attestations: attestations.clone(),
            deposits: deposits.clone(),
            voluntary_exits: voluntary_exits.clone(),
            sync_aggregate: sync_aggregate.clone(),
            execution_payload: BlindedPayloadDeneb {
                execution_payload_header: execution_payload.into(),
            },
            bls_to_execution_changes: bls_to_execution_changes.clone(),
            blob_kzg_commitments: blob_kzg_commitments.clone(),
        }
    }
}

impl<E: EthSpec> BeaconBlockBodyElectra<E, FullPayload<E>> {
    pub fn clone_as_blinded(&self) -> BeaconBlockBodyElectra<E, BlindedPayload<E>> {
        let BeaconBlockBodyElectra {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            execution_payload: FullPayloadElectra { execution_payload },
            bls_to_execution_changes,
            blob_kzg_commitments,
            execution_requests,
        } = self;

        BeaconBlockBodyElectra {
            randao_reveal: randao_reveal.clone(),
            eth1_data: eth1_data.clone(),
            graffiti: *graffiti,
            proposer_slashings: proposer_slashings.clone(),
            attester_slashings: attester_slashings.clone(),
            attestations: attestations.clone(),
            deposits: deposits.clone(),
            voluntary_exits: voluntary_exits.clone(),
            sync_aggregate: sync_aggregate.clone(),
            execution_payload: BlindedPayloadElectra {
                execution_payload_header: execution_payload.into(),
            },
            bls_to_execution_changes: bls_to_execution_changes.clone(),
            blob_kzg_commitments: blob_kzg_commitments.clone(),
            execution_requests: execution_requests.clone(),
        }
    }
}

impl<E: EthSpec> BeaconBlockBodyFulu<E, FullPayload<E>> {
    pub fn clone_as_blinded(&self) -> BeaconBlockBodyFulu<E, BlindedPayload<E>> {
        let BeaconBlockBodyFulu {
            randao_reveal,
            eth1_data,
            graffiti,
            proposer_slashings,
            attester_slashings,
            attestations,
            deposits,
            voluntary_exits,
            sync_aggregate,
            execution_payload: FullPayloadFulu { execution_payload },
            bls_to_execution_changes,
            blob_kzg_commitments,
            execution_requests,
        } = self;

        BeaconBlockBodyFulu {
            randao_reveal: randao_reveal.clone(),
            eth1_data: eth1_data.clone(),
            graffiti: *graffiti,
            proposer_slashings: proposer_slashings.clone(),
            attester_slashings: attester_slashings.clone(),
            attestations: attestations.clone(),
            deposits: deposits.clone(),
            voluntary_exits: voluntary_exits.clone(),
            sync_aggregate: sync_aggregate.clone(),
            execution_payload: BlindedPayloadFulu {
                execution_payload_header: execution_payload.into(),
            },
            bls_to_execution_changes: bls_to_execution_changes.clone(),
            blob_kzg_commitments: blob_kzg_commitments.clone(),
            execution_requests: execution_requests.clone(),
        }
    }
}

impl<E: EthSpec> BeaconBlockBodyGloas<E, FullPayload<E>> {
    pub fn clone_as_blinded(&self) -> BeaconBlockBodyGloas<E, BlindedPayload<E>> {
        let (block_body, _payload) = self.clone().into();
        block_body
    }
}

impl<E: EthSpec> From<BeaconBlockBody<E, FullPayload<E>>>
    for (
        BeaconBlockBody<E, BlindedPayload<E>>,
        Option<ExecutionPayload<E>>,
    )
{
    #[allow(clippy::useless_conversion)] // Not a useless conversion
    fn from(body: BeaconBlockBody<E, FullPayload<E>>) -> Self {
        map_beacon_block_body!(body, |inner, cons| {
            let (block, payload) = inner.into();
            (cons(block), payload.map(Into::into))
        })
    }
}

impl<'de, E: EthSpec, Payload: AbstractExecPayload<E>> ContextDeserialize<'de, ForkName>
    for BeaconBlockBody<E, Payload>
{
    fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        Ok(map_fork_name!(
            context,
            Self,
            serde::Deserialize::deserialize(deserializer)?
        ))
    }
}

#[cfg(test)]
mod tests {
    mod base {
        use super::super::*;
        use crate::core::MainnetEthSpec;
        ssz_and_tree_hash_tests!(BeaconBlockBodyBase<MainnetEthSpec>);
    }
    mod altair {
        use super::super::*;
        use crate::core::MainnetEthSpec;
        ssz_and_tree_hash_tests!(BeaconBlockBodyAltair<MainnetEthSpec>);
    }
}
