import {
  BitListType,
  BitVectorType,
  ContainerType,
  ListBasicType,
  ListCompositeType,
  VectorCompositeType,
} from "@chainsafe/ssz";
import {
  CURRENT_SYNC_COMMITTEE_DEPTH_ELECTRA,
  EPOCHS_PER_SYNC_COMMITTEE_PERIOD,
  FINALIZED_ROOT_DEPTH_ELECTRA,
  HISTORICAL_ROOTS_LIMIT,
  MAX_ATTESTATIONS_ELECTRA,
  MAX_ATTESTER_SLASHINGS_ELECTRA,
  MAX_COMMITTEES_PER_SLOT,
  MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
  MAX_DEPOSIT_REQUESTS_PER_PAYLOAD,
  MAX_VALIDATORS_PER_COMMITTEE,
  MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
  NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA,
  PENDING_CONSOLIDATIONS_LIMIT,
  PENDING_DEPOSITS_LIMIT,
  PENDING_PARTIAL_WITHDRAWALS_LIMIT,
  SLOTS_PER_EPOCH,
} from "@lodestar/params";
import {ssz as altairSsz} from "../altair/index.js";
import {ssz as bellatrixSsz} from "../bellatrix/index.js";
import {ssz as capellaSsz} from "../capella/index.js";
import {ssz as denebSsz} from "../deneb/index.js";
import {ssz as phase0Ssz} from "../phase0/index.js";
import {ssz as primitiveSsz} from "../primitive/index.js";

const {
  Epoch,
  Gwei,
  UintNum64,
  Slot,
  Root,
  BLSSignature,
  UintBn256,
  Bytes32,
  BLSPubkey,
  DepositIndex,
  UintBn64,
  ExecutionAddress,
  ValidatorIndex,
  CommitteeIndex,
} = primitiveSsz;

export const CurrentSyncCommitteeBranch = new VectorCompositeType(Bytes32, CURRENT_SYNC_COMMITTEE_DEPTH_ELECTRA);

export const FinalityBranch = new VectorCompositeType(Bytes32, FINALIZED_ROOT_DEPTH_ELECTRA);

export const NextSyncCommitteeBranch = new VectorCompositeType(Bytes32, NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA);

export const AggregationBits = new BitListType(MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT);

// This CommitteeBits serves a different purpose than CommitteeBits in phase0
// TODO Electra: Rename phase0.CommitteeBits to ParticipationBits to avoid confusion
export const CommitteeBits = new BitVectorType(MAX_COMMITTEES_PER_SLOT);

export const AttestingIndices = new ListBasicType(
  ValidatorIndex,
  MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT
);

export const Attestation = new ContainerType(
  {
    aggregationBits: AggregationBits, // Modified in ELECTRA
    data: phase0Ssz.AttestationData,
    signature: BLSSignature,
    committeeBits: CommitteeBits, // New in ELECTRA
  },
  {typeName: "Attestation", jsonCase: "eth2"}
);

// New type in ELECTRA
export const SingleAttestation = new ContainerType(
  {
    committeeIndex: CommitteeIndex,
    attesterIndex: ValidatorIndex,
    data: phase0Ssz.AttestationData,
    signature: BLSSignature,
  },
  {typeName: "SingleAttestation", jsonCase: "eth2"}
);

export const IndexedAttestation = new ContainerType(
  {
    attestingIndices: AttestingIndices, // Modified in ELECTRA
    data: phase0Ssz.AttestationData,
    signature: BLSSignature,
  },
  {typeName: "IndexedAttestation", jsonCase: "eth2"}
);

/** Same as `IndexedAttestation` but epoch, slot and index are not bounded and must be a bigint */
export const IndexedAttestationBigint = new ContainerType(
  {
    attestingIndices: AttestingIndices, // Modified in ELECTRA
    data: phase0Ssz.AttestationDataBigint,
    signature: BLSSignature,
  },
  {typeName: "IndexedAttestation", jsonCase: "eth2"}
);

export const AttesterSlashing = new ContainerType(
  {
    attestation1: IndexedAttestationBigint, // Modified in ELECTRA
    attestation2: IndexedAttestationBigint, // Modified in ELECTRA
  },
  {typeName: "AttesterSlashing", jsonCase: "eth2"}
);

export const AggregateAndProof = new ContainerType(
  {
    aggregatorIndex: ValidatorIndex,
    aggregate: Attestation, // Modified in ELECTRA
    selectionProof: BLSSignature,
  },
  {typeName: "AggregateAndProof", jsonCase: "eth2", cachePermanentRootStruct: true}
);

export const SignedAggregateAndProof = new ContainerType(
  {
    message: AggregateAndProof, // Modified in ELECTRA
    signature: BLSSignature,
  },
  {typeName: "SignedAggregateAndProof", jsonCase: "eth2"}
);

export const DepositRequest = new ContainerType(
  {
    pubkey: BLSPubkey,
    withdrawalCredentials: Bytes32,
    // this is actually gwei uintbn64 type, but super unlikely to get a high amount here
    // to warrant a bn type
    amount: UintNum64,
    signature: BLSSignature,
    index: DepositIndex,
  },
  {typeName: "DepositRequest", jsonCase: "eth2"}
);

export const DepositRequests = new ListCompositeType(DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD);

export const WithdrawalRequest = new ContainerType(
  {
    sourceAddress: ExecutionAddress,
    validatorPubkey: BLSPubkey,
    amount: Gwei,
  },
  {typeName: "WithdrawalRequest", jsonCase: "eth2"}
);
export const WithdrawalRequests = new ListCompositeType(WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD);
export const ConsolidationRequest = new ContainerType(
  {
    sourceAddress: ExecutionAddress,
    sourcePubkey: BLSPubkey,
    targetPubkey: BLSPubkey,
  },
  {typeName: "ConsolidationRequest", jsonCase: "eth2"}
);
export const ConsolidationRequests = new ListCompositeType(
  ConsolidationRequest,
  MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD
);

export const ExecutionRequests = new ContainerType(
  {
    deposits: DepositRequests,
    withdrawals: WithdrawalRequests,
    consolidations: ConsolidationRequests,
  },
  {typeName: "ExecutionRequests", jsonCase: "eth2"}
);

// Explicitly defining electra containers for consistency's sake
export const ExecutionPayloadHeader = denebSsz.ExecutionPayloadHeader;
export const ExecutionPayload = denebSsz.ExecutionPayload;

// We have to preserve Fields ordering while changing the type of ExecutionPayload
export const BeaconBlockBody = new ContainerType(
  {
    randaoReveal: phase0Ssz.BeaconBlockBody.fields.randaoReveal,
    eth1Data: phase0Ssz.BeaconBlockBody.fields.eth1Data,
    graffiti: phase0Ssz.BeaconBlockBody.fields.graffiti,
    proposerSlashings: phase0Ssz.BeaconBlockBody.fields.proposerSlashings,
    attesterSlashings: new ListCompositeType(AttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA), // Modified in ELECTRA
    attestations: new ListCompositeType(Attestation, MAX_ATTESTATIONS_ELECTRA), // Modified in ELECTRA
    deposits: phase0Ssz.BeaconBlockBody.fields.deposits,
    voluntaryExits: phase0Ssz.BeaconBlockBody.fields.voluntaryExits,
    syncAggregate: altairSsz.BeaconBlockBody.fields.syncAggregate,
    executionPayload: ExecutionPayload,
    blsToExecutionChanges: capellaSsz.BeaconBlockBody.fields.blsToExecutionChanges,
    blobKzgCommitments: denebSsz.BeaconBlockBody.fields.blobKzgCommitments,
    executionRequests: ExecutionRequests, // New in ELECTRA:EIP7251
  },
  {typeName: "BeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true}
);

export const BeaconBlock = new ContainerType(
  {
    ...denebSsz.BeaconBlock.fields,
    body: BeaconBlockBody, // Modified in ELECTRA
  },
  {typeName: "BeaconBlock", jsonCase: "eth2", cachePermanentRootStruct: true}
);

export const SignedBeaconBlock = new ContainerType(
  {
    message: BeaconBlock, // Modified in ELECTRA
    signature: BLSSignature,
  },
  {typeName: "SignedBeaconBlock", jsonCase: "eth2"}
);

export const BlindedBeaconBlockBody = new ContainerType(
  {
    randaoReveal: phase0Ssz.BeaconBlockBody.fields.randaoReveal,
    eth1Data: phase0Ssz.BeaconBlockBody.fields.eth1Data,
    graffiti: phase0Ssz.BeaconBlockBody.fields.graffiti,
    proposerSlashings: phase0Ssz.BeaconBlockBody.fields.proposerSlashings,
    attesterSlashings: new ListCompositeType(AttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA), // Modified in ELECTRA
    attestations: new ListCompositeType(Attestation, MAX_ATTESTATIONS_ELECTRA), // Modified in ELECTRA
    deposits: phase0Ssz.BeaconBlockBody.fields.deposits,
    voluntaryExits: phase0Ssz.BeaconBlockBody.fields.voluntaryExits,
    syncAggregate: altairSsz.SyncAggregate,
    executionPayloadHeader: ExecutionPayloadHeader,
    blsToExecutionChanges: capellaSsz.BeaconBlockBody.fields.blsToExecutionChanges,
    blobKzgCommitments: denebSsz.BeaconBlockBody.fields.blobKzgCommitments,
    executionRequests: ExecutionRequests, // New in ELECTRA
  },
  {typeName: "BlindedBeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true}
);

export const BlindedBeaconBlock = new ContainerType(
  {
    ...denebSsz.BlindedBeaconBlock.fields,
    body: BlindedBeaconBlockBody, // Modified in ELECTRA
  },
  {typeName: "BlindedBeaconBlock", jsonCase: "eth2", cachePermanentRootStruct: true}
);

export const SignedBlindedBeaconBlock = new ContainerType(
  {
    message: BlindedBeaconBlock, // Modified in ELECTRA
    signature: BLSSignature,
  },
  {typeName: "SignedBlindedBeaconBlock", jsonCase: "eth2"}
);

export const BuilderBid = new ContainerType(
  {
    header: ExecutionPayloadHeader, // Modified in ELECTRA
    blobKzgCommitments: denebSsz.BlobKzgCommitments,
    executionRequests: ExecutionRequests, // New in ELECTRA
    value: UintBn256,
    pubkey: BLSPubkey,
  },
  {typeName: "BuilderBid", jsonCase: "eth2"}
);

export const SignedBuilderBid = new ContainerType(
  {
    message: BuilderBid,
    signature: BLSSignature,
  },
  {typeName: "SignedBuilderBid", jsonCase: "eth2"}
);

export const PendingDeposit = new ContainerType(
  {
    pubkey: BLSPubkey,
    withdrawalCredentials: Bytes32,
    // this is actually gwei uintbn64 type, but super unlikely to get a high amount here
    // to warrant a bn type
    amount: UintNum64,
    signature: BLSSignature,
    slot: Slot,
  },
  {typeName: "PendingDeposit", jsonCase: "eth2"}
);

export const PendingDeposits = new ListCompositeType(PendingDeposit, PENDING_DEPOSITS_LIMIT);

export const PendingPartialWithdrawal = new ContainerType(
  {
    validatorIndex: ValidatorIndex,
    amount: Gwei,
    withdrawableEpoch: Epoch,
  },
  {typeName: "PendingPartialWithdrawal", jsonCase: "eth2"}
);

export const PendingPartialWithdrawals = new ListCompositeType(
  PendingPartialWithdrawal,
  PENDING_PARTIAL_WITHDRAWALS_LIMIT
);

export const PendingConsolidation = new ContainerType(
  {
    sourceIndex: ValidatorIndex,
    targetIndex: ValidatorIndex,
  },
  {typeName: "PendingConsolidation", jsonCase: "eth2"}
);

export const PendingConsolidations = new ListCompositeType(PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT);

// In EIP-7251, we spread deneb fields as new fields are appended at the end
export const BeaconState = new ContainerType(
  {
    genesisTime: UintNum64,
    genesisValidatorsRoot: Root,
    slot: primitiveSsz.Slot,
    fork: phase0Ssz.Fork,
    // History
    latestBlockHeader: phase0Ssz.BeaconBlockHeader,
    blockRoots: phase0Ssz.HistoricalBlockRoots,
    stateRoots: phase0Ssz.HistoricalStateRoots,
    // historical_roots Frozen in Capella, replaced by historical_summaries
    historicalRoots: new ListCompositeType(Root, HISTORICAL_ROOTS_LIMIT),
    // Eth1
    eth1Data: phase0Ssz.Eth1Data,
    eth1DataVotes: phase0Ssz.Eth1DataVotes,
    eth1DepositIndex: UintNum64,
    // Registry
    validators: phase0Ssz.Validators,
    balances: phase0Ssz.Balances,
    randaoMixes: phase0Ssz.RandaoMixes,
    // Slashings
    slashings: phase0Ssz.Slashings,
    // Participation
    previousEpochParticipation: altairSsz.EpochParticipation,
    currentEpochParticipation: altairSsz.EpochParticipation,
    // Finality
    justificationBits: phase0Ssz.JustificationBits,
    previousJustifiedCheckpoint: phase0Ssz.Checkpoint,
    currentJustifiedCheckpoint: phase0Ssz.Checkpoint,
    finalizedCheckpoint: phase0Ssz.Checkpoint,
    // Inactivity
    inactivityScores: altairSsz.InactivityScores,
    // Sync
    currentSyncCommittee: altairSsz.SyncCommittee,
    nextSyncCommittee: altairSsz.SyncCommittee,
    // Execution
    latestExecutionPayloadHeader: ExecutionPayloadHeader,
    // Withdrawals
    nextWithdrawalIndex: capellaSsz.BeaconState.fields.nextWithdrawalIndex,
    nextWithdrawalValidatorIndex: capellaSsz.BeaconState.fields.nextWithdrawalValidatorIndex,
    // Deep history valid from Capella onwards
    historicalSummaries: capellaSsz.BeaconState.fields.historicalSummaries,
    depositRequestsStartIndex: UintBn64, // New in ELECTRA:EIP6110
    depositBalanceToConsume: Gwei, // New in ELECTRA:EIP7251
    exitBalanceToConsume: Gwei, // New in ELECTRA:EIP7251
    earliestExitEpoch: Epoch, // New in ELECTRA:EIP7251
    consolidationBalanceToConsume: Gwei, // New in ELECTRA:EIP7251
    earliestConsolidationEpoch: Epoch, // New in ELECTRA:EIP7251
    pendingDeposits: PendingDeposits, // New in ELECTRA:EIP7251
    pendingPartialWithdrawals: PendingPartialWithdrawals, // New in ELECTRA:EIP7251
    pendingConsolidations: PendingConsolidations, // New in ELECTRA:EIP7251
  },
  {typeName: "BeaconState", jsonCase: "eth2"}
);

export const LightClientBootstrap = new ContainerType(
  {
    header: denebSsz.LightClientHeader,
    currentSyncCommittee: altairSsz.SyncCommittee,
    currentSyncCommitteeBranch: CurrentSyncCommitteeBranch,
  },
  {typeName: "LightClientBootstrap", jsonCase: "eth2"}
);

export const LightClientUpdate = new ContainerType(
  {
    attestedHeader: denebSsz.LightClientHeader,
    nextSyncCommittee: altairSsz.SyncCommittee,
    nextSyncCommitteeBranch: NextSyncCommitteeBranch, // Modified in ELECTRA
    finalizedHeader: denebSsz.LightClientHeader,
    finalityBranch: FinalityBranch, // Modified in ELECTRA
    syncAggregate: altairSsz.SyncAggregate,
    signatureSlot: Slot,
  },
  {typeName: "LightClientUpdate", jsonCase: "eth2"}
);

export const LightClientFinalityUpdate = new ContainerType(
  {
    attestedHeader: denebSsz.LightClientHeader,
    finalizedHeader: denebSsz.LightClientHeader,
    finalityBranch: FinalityBranch, // Modified in ELECTRA
    syncAggregate: altairSsz.SyncAggregate,
    signatureSlot: Slot,
  },
  {typeName: "LightClientFinalityUpdate", jsonCase: "eth2"}
);

export const LightClientOptimisticUpdate = new ContainerType(
  {
    attestedHeader: denebSsz.LightClientHeader,
    syncAggregate: altairSsz.SyncAggregate,
    signatureSlot: Slot,
  },
  {typeName: "LightClientOptimisticUpdate", jsonCase: "eth2"}
);

export const LightClientStore = new ContainerType(
  {
    snapshot: LightClientBootstrap,
    validUpdates: new ListCompositeType(LightClientUpdate, EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH),
  },
  {typeName: "LightClientStore", jsonCase: "eth2"}
);

// PayloadAttributes primarily for SSE event
export const PayloadAttributes = new ContainerType(
  {
    ...capellaSsz.PayloadAttributes.fields,
    parentBeaconBlockRoot: Root,
  },
  {typeName: "PayloadAttributes", jsonCase: "eth2"}
);

export const SSEPayloadAttributes = new ContainerType(
  {
    ...bellatrixSsz.SSEPayloadAttributesCommon.fields,
    payloadAttributes: PayloadAttributes,
  },
  {typeName: "SSEPayloadAttributes", jsonCase: "eth2"}
);

export const BlockContents = new ContainerType(
  {
    block: BeaconBlock,
    kzgProofs: denebSsz.KZGProofs,
    blobs: denebSsz.Blobs,
  },
  {typeName: "BlockContents", jsonCase: "eth2"}
);

export const SignedBlockContents = new ContainerType(
  {
    signedBlock: SignedBeaconBlock,
    kzgProofs: denebSsz.KZGProofs,
    blobs: denebSsz.Blobs,
  },
  {typeName: "BlockContents", jsonCase: "eth2"}
);
