# beacon_chain
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed and distributed under either of
#   * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
#   * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

{.push raises: [].}

import
  ./datatypes/[phase0, altair, bellatrix, capella, deneb, electra],
  ./eth2_merkleization

type
  LightClientDataFork* {.pure.} = enum  # Append only, used in DB data!
    None = 0,  # only use non-0 in DB to detect accidentally uninitialized data
    Altair = 1,
    Capella = 2,
    Deneb = 3,
    Electra = 4

  ForkyCurrentSyncCommitteeBranch* =
    altair.CurrentSyncCommitteeBranch |
    electra.CurrentSyncCommitteeBranch

  ForkyLightClientHeader* =
    altair.LightClientHeader |
    capella.LightClientHeader |
    deneb.LightClientHeader |
    electra.LightClientHeader

  ForkyLightClientBootstrap* =
    altair.LightClientBootstrap |
    capella.LightClientBootstrap |
    deneb.LightClientBootstrap |
    electra.LightClientBootstrap

  ForkyLightClientUpdate* =
    altair.LightClientUpdate |
    capella.LightClientUpdate |
    deneb.LightClientUpdate |
    electra.LightClientUpdate

  ForkyLightClientFinalityUpdate* =
    altair.LightClientFinalityUpdate |
    capella.LightClientFinalityUpdate |
    deneb.LightClientFinalityUpdate |
    electra.LightClientFinalityUpdate

  ForkyLightClientOptimisticUpdate* =
    altair.LightClientOptimisticUpdate |
    capella.LightClientOptimisticUpdate |
    deneb.LightClientOptimisticUpdate |
    electra.LightClientOptimisticUpdate

  SomeForkyLightClientUpdateWithSyncCommittee* =
    ForkyLightClientUpdate

  SomeForkyLightClientUpdateWithFinality* =
    ForkyLightClientUpdate |
    ForkyLightClientFinalityUpdate

  SomeForkyLightClientUpdate* =
    ForkyLightClientUpdate |
    ForkyLightClientFinalityUpdate |
    ForkyLightClientOptimisticUpdate

  SomeForkyLightClientObject* =
    ForkyLightClientBootstrap |
    SomeForkyLightClientUpdate

  ForkyLightClientStore* =
    altair.LightClientStore |
    capella.LightClientStore |
    deneb.LightClientStore |
    electra.LightClientStore

  ForkedLightClientHeader* = object
    case kind*: LightClientDataFork
    of LightClientDataFork.None:
      discard
    of LightClientDataFork.Altair:
      altairData*: altair.LightClientHeader
    of LightClientDataFork.Capella:
      capellaData*: capella.LightClientHeader
    of LightClientDataFork.Deneb:
      denebData*: deneb.LightClientHeader
    of LightClientDataFork.Electra:
      electraData*: electra.LightClientHeader

  ForkedLightClientBootstrap* = object
    case kind*: LightClientDataFork
    of LightClientDataFork.None:
      discard
    of LightClientDataFork.Altair:
      altairData*: altair.LightClientBootstrap
    of LightClientDataFork.Capella:
      capellaData*: capella.LightClientBootstrap
    of LightClientDataFork.Deneb:
      denebData*: deneb.LightClientBootstrap
    of LightClientDataFork.Electra:
      electraData*: electra.LightClientBootstrap

  ForkedLightClientUpdate* = object
    case kind*: LightClientDataFork
    of LightClientDataFork.None:
      discard
    of LightClientDataFork.Altair:
      altairData*: altair.LightClientUpdate
    of LightClientDataFork.Capella:
      capellaData*: capella.LightClientUpdate
    of LightClientDataFork.Deneb:
      denebData*: deneb.LightClientUpdate
    of LightClientDataFork.Electra:
      electraData*: electra.LightClientUpdate

  ForkedLightClientFinalityUpdate* = object
    case kind*: LightClientDataFork
    of LightClientDataFork.None:
      discard
    of LightClientDataFork.Altair:
      altairData*: altair.LightClientFinalityUpdate
    of LightClientDataFork.Capella:
      capellaData*: capella.LightClientFinalityUpdate
    of LightClientDataFork.Deneb:
      denebData*: deneb.LightClientFinalityUpdate
    of LightClientDataFork.Electra:
      electraData*: electra.LightClientFinalityUpdate

  ForkedLightClientOptimisticUpdate* = object
    case kind*: LightClientDataFork
    of LightClientDataFork.None:
      discard
    of LightClientDataFork.Altair:
      altairData*: altair.LightClientOptimisticUpdate
    of LightClientDataFork.Capella:
      capellaData*: capella.LightClientOptimisticUpdate
    of LightClientDataFork.Deneb:
      denebData*: deneb.LightClientOptimisticUpdate
    of LightClientDataFork.Electra:
      electraData*: electra.LightClientOptimisticUpdate

  SomeForkedLightClientUpdateWithSyncCommittee* =
    ForkedLightClientUpdate

  SomeForkedLightClientUpdateWithFinality* =
    ForkedLightClientUpdate |
    ForkedLightClientFinalityUpdate

  SomeForkedLightClientUpdate* =
    ForkedLightClientUpdate |
    ForkedLightClientFinalityUpdate |
    ForkedLightClientOptimisticUpdate

  SomeForkedLightClientObject* =
    ForkedLightClientBootstrap |
    SomeForkedLightClientUpdate

  ForkedLightClientStore* = object
    case kind*: LightClientDataFork
    of LightClientDataFork.None:
      discard
    of LightClientDataFork.Altair:
      altairData*: altair.LightClientStore
    of LightClientDataFork.Capella:
      capellaData*: capella.LightClientStore
    of LightClientDataFork.Deneb:
      denebData*: deneb.LightClientStore
    of LightClientDataFork.Electra:
      electraData*: electra.LightClientStore

func lcDataForkAtEpoch*(
    cfg: RuntimeConfig, epoch: Epoch): LightClientDataFork =
  static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
  if epoch >= cfg.ELECTRA_FORK_EPOCH:
    LightClientDataFork.Electra
  elif epoch >= cfg.DENEB_FORK_EPOCH:
    LightClientDataFork.Deneb
  elif epoch >= cfg.CAPELLA_FORK_EPOCH:
    LightClientDataFork.Capella
  elif epoch >= cfg.ALTAIR_FORK_EPOCH:
    LightClientDataFork.Altair
  else:
    LightClientDataFork.None

template kind*(
    # `SomeLightClientObject`: https://github.com/nim-lang/Nim/issues/18095
    x: typedesc[
      altair.LightClientHeader |
      altair.LightClientBootstrap |
      altair.LightClientUpdate |
      altair.LightClientFinalityUpdate |
      altair.LightClientOptimisticUpdate |
      altair.LightClientStore]): LightClientDataFork =
  LightClientDataFork.Altair

template kind*(
    # `SomeLightClientObject`: https://github.com/nim-lang/Nim/issues/18095
    x: typedesc[
      capella.LightClientHeader |
      capella.LightClientBootstrap |
      capella.LightClientUpdate |
      capella.LightClientFinalityUpdate |
      capella.LightClientOptimisticUpdate |
      capella.LightClientStore]): LightClientDataFork =
  LightClientDataFork.Capella

template kind*(
    # `SomeLightClientObject`: https://github.com/nim-lang/Nim/issues/18095
    x: typedesc[
      deneb.LightClientHeader |
      deneb.LightClientBootstrap |
      deneb.LightClientUpdate |
      deneb.LightClientFinalityUpdate |
      deneb.LightClientOptimisticUpdate |
      deneb.LightClientStore]): LightClientDataFork =
  LightClientDataFork.Deneb

template kind*(
    # `SomeLightClientObject`: https://github.com/nim-lang/Nim/issues/18095
    x: typedesc[
      electra.LightClientHeader |
      electra.LightClientBootstrap |
      electra.LightClientUpdate |
      electra.LightClientFinalityUpdate |
      electra.LightClientOptimisticUpdate |
      electra.LightClientStore]): LightClientDataFork =
  LightClientDataFork.Electra

template finalized_root_gindex*(
    kind: static LightClientDataFork): GeneralizedIndex =
  when kind >= LightClientDataFork.Electra:
    FINALIZED_ROOT_GINDEX_ELECTRA
  elif kind >= LightClientDataFork.Altair:
    FINALIZED_ROOT_GINDEX
  else:
    static: raiseAssert "Unreachable"

template FinalityBranch*(kind: static LightClientDataFork): auto =
  when kind >= LightClientDataFork.Electra:
    typedesc[electra.FinalityBranch]
  elif kind >= LightClientDataFork.Altair:
    typedesc[altair.FinalityBranch]
  else:
    static: raiseAssert "Unreachable"

template current_sync_committee_gindex*(
    kind: static LightClientDataFork): GeneralizedIndex =
  when kind >= LightClientDataFork.Electra:
    CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA
  elif kind >= LightClientDataFork.Altair:
    CURRENT_SYNC_COMMITTEE_GINDEX
  else:
    static: raiseAssert "Unreachable"

template CurrentSyncCommitteeBranch*(kind: static LightClientDataFork): auto =
  when kind >= LightClientDataFork.Electra:
    typedesc[electra.CurrentSyncCommitteeBranch]
  elif kind >= LightClientDataFork.Altair:
    typedesc[altair.CurrentSyncCommitteeBranch]
  else:
    static: raiseAssert "Unreachable"

template next_sync_committee_gindex*(
    kind: static LightClientDataFork): GeneralizedIndex =
  when kind >= LightClientDataFork.Electra:
    NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA
  elif kind >= LightClientDataFork.Altair:
    NEXT_SYNC_COMMITTEE_GINDEX
  else:
    static: raiseAssert "Unreachable"

template NextSyncCommitteeBranch*(kind: static LightClientDataFork): auto =
  when kind >= LightClientDataFork.Electra:
    typedesc[electra.NextSyncCommitteeBranch]
  elif kind >= LightClientDataFork.Altair:
    typedesc[altair.NextSyncCommitteeBranch]
  else:
    static: raiseAssert "Unreachable"

template LightClientHeader*(kind: static LightClientDataFork): auto =
  when kind == LightClientDataFork.Electra:
    typedesc[electra.LightClientHeader]
  elif kind == LightClientDataFork.Deneb:
    typedesc[deneb.LightClientHeader]
  elif kind == LightClientDataFork.Capella:
    typedesc[capella.LightClientHeader]
  elif kind == LightClientDataFork.Altair:
    typedesc[altair.LightClientHeader]
  else:
    static: raiseAssert "Unreachable"

template LightClientBootstrap*(kind: static LightClientDataFork): auto =
  when kind == LightClientDataFork.Electra:
    typedesc[electra.LightClientBootstrap]
  elif kind == LightClientDataFork.Deneb:
    typedesc[deneb.LightClientBootstrap]
  elif kind == LightClientDataFork.Capella:
    typedesc[capella.LightClientBootstrap]
  elif kind == LightClientDataFork.Altair:
    typedesc[altair.LightClientBootstrap]
  else:
    static: raiseAssert "Unreachable"

template LightClientUpdate*(kind: static LightClientDataFork): auto =
  when kind == LightClientDataFork.Electra:
    typedesc[electra.LightClientUpdate]
  elif kind == LightClientDataFork.Deneb:
    typedesc[deneb.LightClientUpdate]
  elif kind == LightClientDataFork.Capella:
    typedesc[capella.LightClientUpdate]
  elif kind == LightClientDataFork.Altair:
    typedesc[altair.LightClientUpdate]
  else:
    static: raiseAssert "Unreachable"

template LightClientFinalityUpdate*(kind: static LightClientDataFork): auto =
  when kind == LightClientDataFork.Electra:
    typedesc[electra.LightClientFinalityUpdate]
  elif kind == LightClientDataFork.Deneb:
    typedesc[deneb.LightClientFinalityUpdate]
  elif kind == LightClientDataFork.Capella:
    typedesc[capella.LightClientFinalityUpdate]
  elif kind == LightClientDataFork.Altair:
    typedesc[altair.LightClientFinalityUpdate]
  else:
    static: raiseAssert "Unreachable"

template LightClientOptimisticUpdate*(kind: static LightClientDataFork): auto =
  when kind == LightClientDataFork.Electra:
    typedesc[electra.LightClientOptimisticUpdate]
  elif kind == LightClientDataFork.Deneb:
    typedesc[deneb.LightClientOptimisticUpdate]
  elif kind == LightClientDataFork.Capella:
    typedesc[capella.LightClientOptimisticUpdate]
  elif kind == LightClientDataFork.Altair:
    typedesc[altair.LightClientOptimisticUpdate]
  else:
    static: raiseAssert "Unreachable"

template LightClientStore*(kind: static LightClientDataFork): auto =
  when kind == LightClientDataFork.Electra:
    typedesc[electra.LightClientStore]
  elif kind == LightClientDataFork.Deneb:
    typedesc[deneb.LightClientStore]
  elif kind == LightClientDataFork.Capella:
    typedesc[capella.LightClientStore]
  elif kind == LightClientDataFork.Altair:
    typedesc[altair.LightClientStore]
  else:
    static: raiseAssert "Unreachable"

template Forky*(
    x: typedesc[ForkedLightClientHeader],
    kind: static LightClientDataFork): auto =
  kind.LightClientHeader

template Forky*(
    x: typedesc[ForkedLightClientBootstrap],
    kind: static LightClientDataFork): auto =
  kind.LightClientBootstrap

template Forky*(
    x: typedesc[ForkedLightClientUpdate],
    kind: static LightClientDataFork): auto =
  kind.LightClientUpdate

template Forky*(
    x: typedesc[ForkedLightClientFinalityUpdate],
    kind: static LightClientDataFork): auto =
  kind.LightClientFinalityUpdate

template Forky*(
    x: typedesc[ForkedLightClientOptimisticUpdate],
    kind: static LightClientDataFork): auto =
  kind.LightClientOptimisticUpdate

template Forky*(
    x: typedesc[ForkedLightClientStore],
    kind: static LightClientDataFork): auto =
  kind.LightClientStore

template Forked*(x: typedesc[ForkyLightClientHeader]): auto =
  typedesc[ForkedLightClientHeader]

template Forked*(x: typedesc[ForkyLightClientBootstrap]): auto =
  typedesc[ForkedLightClientBootstrap]

template Forked*(x: typedesc[ForkyLightClientUpdate]): auto =
  typedesc[ForkedLightClientUpdate]

template Forked*(x: typedesc[ForkyLightClientFinalityUpdate]): auto =
  typedesc[ForkedLightClientFinalityUpdate]

template Forked*(x: typedesc[ForkyLightClientOptimisticUpdate]): auto =
  typedesc[ForkedLightClientOptimisticUpdate]

template Forked*(x: typedesc[ForkyLightClientStore]): auto =
  typedesc[ForkedLightClientStore]

template withAll*(
    x: typedesc[LightClientDataFork], body: untyped): untyped =
  static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
  block:
    const lcDataFork {.inject, used.} = LightClientDataFork.Electra
    body
  block:
    const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
    body
  block:
    const lcDataFork {.inject, used.} = LightClientDataFork.Capella
    body
  block:
    const lcDataFork {.inject, used.} = LightClientDataFork.Altair
    body
  block:
    const lcDataFork {.inject, used.} = LightClientDataFork.None
    body

template withLcDataFork*(
    x: LightClientDataFork, body: untyped): untyped =
  case x
  of LightClientDataFork.Electra:
    const lcDataFork {.inject, used.} = LightClientDataFork.Electra
    body
  of LightClientDataFork.Deneb:
    const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
    body
  of LightClientDataFork.Capella:
    const lcDataFork {.inject, used.} = LightClientDataFork.Capella
    body
  of LightClientDataFork.Altair:
    const lcDataFork {.inject, used.} = LightClientDataFork.Altair
    body
  of LightClientDataFork.None:
    const lcDataFork {.inject, used.} = LightClientDataFork.None
    body

template withForkyHeader*(
    x: ForkedLightClientHeader, body: untyped): untyped =
  case x.kind
  of LightClientDataFork.Electra:
    const lcDataFork {.inject, used.} = LightClientDataFork.Electra
    template forkyHeader: untyped {.inject, used.} = x.electraData
    body
  of LightClientDataFork.Deneb:
    const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
    template forkyHeader: untyped {.inject, used.} = x.denebData
    body
  of LightClientDataFork.Capella:
    const lcDataFork {.inject, used.} = LightClientDataFork.Capella
    template forkyHeader: untyped {.inject, used.} = x.capellaData
    body
  of LightClientDataFork.Altair:
    const lcDataFork {.inject, used.} = LightClientDataFork.Altair
    template forkyHeader: untyped {.inject, used.} = x.altairData
    body
  of LightClientDataFork.None:
    const lcDataFork {.inject, used.} = LightClientDataFork.None
    body

template withForkyBootstrap*(
    x: ForkedLightClientBootstrap, body: untyped): untyped =
  case x.kind
  of LightClientDataFork.Electra:
    const lcDataFork {.inject, used.} = LightClientDataFork.Electra
    template forkyBootstrap: untyped {.inject, used.} = x.electraData
    body
  of LightClientDataFork.Deneb:
    const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
    template forkyBootstrap: untyped {.inject, used.} = x.denebData
    body
  of LightClientDataFork.Capella:
    const lcDataFork {.inject, used.} = LightClientDataFork.Capella
    template forkyBootstrap: untyped {.inject, used.} = x.capellaData
    body
  of LightClientDataFork.Altair:
    const lcDataFork {.inject, used.} = LightClientDataFork.Altair
    template forkyBootstrap: untyped {.inject, used.} = x.altairData
    body
  of LightClientDataFork.None:
    const lcDataFork {.inject, used.} = LightClientDataFork.None
    body

template withForkyUpdate*(
    x: ForkedLightClientUpdate, body: untyped): untyped =
  case x.kind
  of LightClientDataFork.Electra:
    const lcDataFork {.inject, used.} = LightClientDataFork.Electra
    template forkyUpdate: untyped {.inject, used.} = x.electraData
    body
  of LightClientDataFork.Deneb:
    const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
    template forkyUpdate: untyped {.inject, used.} = x.denebData
    body
  of LightClientDataFork.Capella:
    const lcDataFork {.inject, used.} = LightClientDataFork.Capella
    template forkyUpdate: untyped {.inject, used.} = x.capellaData
    body
  of LightClientDataFork.Altair:
    const lcDataFork {.inject, used.} = LightClientDataFork.Altair
    template forkyUpdate: untyped {.inject, used.} = x.altairData
    body
  of LightClientDataFork.None:
    const lcDataFork {.inject, used.} = LightClientDataFork.None
    body

template withForkyFinalityUpdate*(
    x: ForkedLightClientFinalityUpdate, body: untyped): untyped =
  case x.kind
  of LightClientDataFork.Electra:
    const lcDataFork {.inject, used.} = LightClientDataFork.Electra
    template forkyFinalityUpdate: untyped {.inject, used.} = x.electraData
    body
  of LightClientDataFork.Deneb:
    const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
    template forkyFinalityUpdate: untyped {.inject, used.} = x.denebData
    body
  of LightClientDataFork.Capella:
    const lcDataFork {.inject, used.} = LightClientDataFork.Capella
    template forkyFinalityUpdate: untyped {.inject, used.} = x.capellaData
    body
  of LightClientDataFork.Altair:
    const lcDataFork {.inject, used.} = LightClientDataFork.Altair
    template forkyFinalityUpdate: untyped {.inject, used.} = x.altairData
    body
  of LightClientDataFork.None:
    const lcDataFork {.inject, used.} = LightClientDataFork.None
    body

template withForkyOptimisticUpdate*(
    x: ForkedLightClientOptimisticUpdate, body: untyped): untyped =
  case x.kind
  of LightClientDataFork.Electra:
    const lcDataFork {.inject, used.} = LightClientDataFork.Electra
    template forkyOptimisticUpdate: untyped {.inject, used.} = x.electraData
    body
  of LightClientDataFork.Deneb:
    const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
    template forkyOptimisticUpdate: untyped {.inject, used.} = x.denebData
    body
  of LightClientDataFork.Capella:
    const lcDataFork {.inject, used.} = LightClientDataFork.Capella
    template forkyOptimisticUpdate: untyped {.inject, used.} = x.capellaData
    body
  of LightClientDataFork.Altair:
    const lcDataFork {.inject, used.} = LightClientDataFork.Altair
    template forkyOptimisticUpdate: untyped {.inject, used.} = x.altairData
    body
  of LightClientDataFork.None:
    const lcDataFork {.inject, used.} = LightClientDataFork.None
    body

template withForkyObject*(
    x: SomeForkedLightClientObject, body: untyped): untyped =
  case x.kind
  of LightClientDataFork.Electra:
    const lcDataFork {.inject, used.} = LightClientDataFork.Electra
    template forkyObject: untyped {.inject, used.} = x.electraData
    body
  of LightClientDataFork.Deneb:
    const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
    template forkyObject: untyped {.inject, used.} = x.denebData
    body
  of LightClientDataFork.Capella:
    const lcDataFork {.inject, used.} = LightClientDataFork.Capella
    template forkyObject: untyped {.inject, used.} = x.capellaData
    body
  of LightClientDataFork.Altair:
    const lcDataFork {.inject, used.} = LightClientDataFork.Altair
    template forkyObject: untyped {.inject, used.} = x.altairData
    body
  of LightClientDataFork.None:
    const lcDataFork {.inject, used.} = LightClientDataFork.None
    body

template withForkyStore*(
    x: ForkedLightClientStore, body: untyped): untyped =
  case x.kind
  of LightClientDataFork.Electra:
    const lcDataFork {.inject, used.} = LightClientDataFork.Electra
    template forkyStore: untyped {.inject, used.} = x.electraData
    body
  of LightClientDataFork.Deneb:
    const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
    template forkyStore: untyped {.inject, used.} = x.denebData
    body
  of LightClientDataFork.Capella:
    const lcDataFork {.inject, used.} = LightClientDataFork.Capella
    template forkyStore: untyped {.inject, used.} = x.capellaData
    body
  of LightClientDataFork.Altair:
    const lcDataFork {.inject, used.} = LightClientDataFork.Altair
    template forkyStore: untyped {.inject, used.} = x.altairData
    body
  of LightClientDataFork.None:
    const lcDataFork {.inject, used.} = LightClientDataFork.None
    body

func init*(
    x: typedesc[
      ForkedLightClientHeader |
      SomeForkedLightClientObject |
      ForkedLightClientStore],
    forkyData:
      ForkyLightClientHeader |
      SomeForkyLightClientObject |
      ForkyLightClientStore): auto =
  type ResultType = typeof(forkyData).Forked
  static: doAssert ResultType is x
  const kind = typeof(forkyData).kind
  when kind == LightClientDataFork.Electra:
    ResultType(kind: kind, electraData: forkyData)
  elif kind == LightClientDataFork.Deneb:
    ResultType(kind: kind, denebData: forkyData)
  elif kind == LightClientDataFork.Capella:
    ResultType(kind: kind, capellaData: forkyData)
  elif kind == LightClientDataFork.Altair:
    ResultType(kind: kind, altairData: forkyData)
  else:
    static: raiseAssert "Unreachable"

template forky*(
    x:
      ForkedLightClientHeader |
      SomeForkedLightClientObject |
      ForkedLightClientStore,
    kind: static LightClientDataFork): untyped =
  when kind == LightClientDataFork.Electra:
    x.electraData
  elif kind == LightClientDataFork.Deneb:
    x.denebData
  elif kind == LightClientDataFork.Capella:
    x.capellaData
  elif kind == LightClientDataFork.Altair:
    x.altairData
  else:
    static: raiseAssert "Unreachable"

func toFull*(
    update: SomeForkyLightClientUpdate): auto =
  type ResultType = typeof(update).kind.LightClientUpdate
  when update is ForkyLightClientUpdate:
    static: doAssert update is ResultType
    update
  elif update is SomeForkyLightClientUpdateWithFinality:
    ResultType(
      attested_header: update.attested_header,
      finalized_header: update.finalized_header,
      finality_branch: update.finality_branch,
      sync_aggregate: update.sync_aggregate,
      signature_slot: update.signature_slot)
  else:
    ResultType(
      attested_header: update.attested_header,
      sync_aggregate: update.sync_aggregate,
      signature_slot: update.signature_slot)

func toFull*(
    update: SomeForkedLightClientUpdate): ForkedLightClientUpdate =
  when update is ForkyLightClientUpdate:
    update
  else:
    withForkyObject(update):
      when lcDataFork > LightClientDataFork.None:
        ForkedLightClientUpdate.init(forkyObject.toFull())
      else:
        default(ForkedLightClientUpdate)

func toFinality*(
    update: SomeForkyLightClientUpdate): auto =
  type ResultType = typeof(update).kind.LightClientFinalityUpdate
  when update is ForkyLightClientFinalityUpdate:
    update
  elif update is SomeForkyLightClientUpdateWithFinality:
    ResultType(
      attested_header: update.attested_header,
      finalized_header: update.finalized_header,
      finality_branch: update.finality_branch,
      sync_aggregate: update.sync_aggregate,
      signature_slot: update.signature_slot)
  else:
    ResultType(
      attested_header: update.attested_header,
      sync_aggregate: update.sync_aggregate,
      signature_slot: update.signature_slot)

func toFinality*(
    update: SomeForkedLightClientUpdate): ForkedLightClientFinalityUpdate =
  when update is ForkyLightClientFinalityUpdate:
    update
  else:
    withForkyObject(update):
      when lcDataFork > LightClientDataFork.None:
        ForkedLightClientFinalityUpdate.init(forkyObject.toFinality())
      else:
        default(ForkedLightClientFinalityUpdate)

func toOptimistic*(
    update: SomeForkyLightClientUpdate): auto =
  type ResultType = typeof(update).kind.LightClientOptimisticUpdate
  when update is ForkyLightClientOptimisticUpdate:
    update
  else:
    ResultType(
      attested_header: update.attested_header,
      sync_aggregate: update.sync_aggregate,
      signature_slot: update.signature_slot)

func toOptimistic*(
    update: SomeForkedLightClientUpdate): ForkedLightClientOptimisticUpdate =
  when update is ForkyLightClientOptimisticUpdate:
    update
  else:
    withForkyObject(update):
      when lcDataFork > LightClientDataFork.None:
        ForkedLightClientOptimisticUpdate.init(forkyObject.toOptimistic())
      else:
        default(ForkedLightClientOptimisticUpdate)

func matches*[A, B: SomeForkyLightClientUpdate](a: A, b: B): bool =
  static: doAssert typeof(A).kind == typeof(B).kind
  if a.attested_header != b.attested_header:
    return false
  when a is SomeForkyLightClientUpdateWithSyncCommittee and
      b is SomeForkyLightClientUpdateWithSyncCommittee:
    if a.next_sync_committee != b.next_sync_committee:
      return false
    if a.next_sync_committee_branch != b.next_sync_committee_branch:
      return false
  when a is SomeForkyLightClientUpdateWithFinality and
      b is SomeForkyLightClientUpdateWithFinality:
    if a.finalized_header != b.finalized_header:
      return false
    if a.finality_branch != b.finality_branch:
      return false
  if a.sync_aggregate != b.sync_aggregate:
    return false
  if a.signature_slot != b.signature_slot:
    return false
  true

func matches*[A, B: SomeForkedLightClientUpdate](a: A, b: B): bool =
  if a.kind != b.kind:
    return false
  withForkyObject(a):
    when lcDataFork > LightClientDataFork.None:
      forkyObject.matches(b.forky(lcDataFork))
    else:
      true

func migrateToDataFork*(
    x: var ForkedLightClientHeader,
    newKind: static LightClientDataFork) =
  if newKind == x.kind:
    # Already at correct kind
    discard
  elif newKind < x.kind:
    # Downgrade not supported, re-initialize
    x = ForkedLightClientHeader(kind: newKind)
  else:
    # Upgrade to Altair
    when newKind >= LightClientDataFork.Altair:
      if x.kind == LightClientDataFork.None:
        x = ForkedLightClientHeader(
          kind: LightClientDataFork.Altair)

    # Upgrade to Capella
    when newKind >= LightClientDataFork.Capella:
      if x.kind == LightClientDataFork.Altair:
        x = ForkedLightClientHeader(
          kind: LightClientDataFork.Capella,
          capellaData: upgrade_lc_header_to_capella(
            x.forky(LightClientDataFork.Altair)))

    # Upgrade to Deneb
    when newKind >= LightClientDataFork.Deneb:
      if x.kind == LightClientDataFork.Capella:
        x = ForkedLightClientHeader(
          kind: LightClientDataFork.Deneb,
          denebData: upgrade_lc_header_to_deneb(
            x.forky(LightClientDataFork.Capella)))

    # Upgrade to Electra
    when newKind >= LightClientDataFork.Electra:
      if x.kind == LightClientDataFork.Deneb:
        x = ForkedLightClientHeader(
          kind: LightClientDataFork.Electra,
          electraData: upgrade_lc_header_to_electra(
            x.forky(LightClientDataFork.Deneb)))

    static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
    doAssert x.kind == newKind

func migrateToDataFork*(
    x: var ForkedLightClientBootstrap,
    newKind: static LightClientDataFork) =
  if newKind == x.kind:
    # Already at correct kind
    discard
  elif newKind < x.kind:
    # Downgrade not supported, re-initialize
    x = ForkedLightClientBootstrap(kind: newKind)
  else:
    # Upgrade to Altair
    when newKind >= LightClientDataFork.Altair:
      if x.kind == LightClientDataFork.None:
        x = ForkedLightClientBootstrap(
          kind: LightClientDataFork.Altair)

    # Upgrade to Capella
    when newKind >= LightClientDataFork.Capella:
      if x.kind == LightClientDataFork.Altair:
        x = ForkedLightClientBootstrap(
          kind: LightClientDataFork.Capella,
          capellaData: upgrade_lc_bootstrap_to_capella(
            x.forky(LightClientDataFork.Altair)))

    # Upgrade to Deneb
    when newKind >= LightClientDataFork.Deneb:
      if x.kind == LightClientDataFork.Capella:
        x = ForkedLightClientBootstrap(
          kind: LightClientDataFork.Deneb,
          denebData: upgrade_lc_bootstrap_to_deneb(
            x.forky(LightClientDataFork.Capella)))

    # Upgrade to Electra
    when newKind >= LightClientDataFork.Electra:
      if x.kind == LightClientDataFork.Deneb:
        x = ForkedLightClientBootstrap(
          kind: LightClientDataFork.Electra,
          electraData: upgrade_lc_bootstrap_to_electra(
            x.forky(LightClientDataFork.Deneb)))

    static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
    doAssert x.kind == newKind

func migrateToDataFork*(
    x: var ForkedLightClientUpdate,
    newKind: static LightClientDataFork) =
  if newKind == x.kind:
    # Already at correct kind
    discard
  elif newKind < x.kind:
    # Downgrade not supported, re-initialize
    x = ForkedLightClientUpdate(kind: newKind)
  else:
    # Upgrade to Altair
    when newKind >= LightClientDataFork.Altair:
      if x.kind == LightClientDataFork.None:
        x = ForkedLightClientUpdate(
          kind: LightClientDataFork.Altair)

    # Upgrade to Capella
    when newKind >= LightClientDataFork.Capella:
      if x.kind == LightClientDataFork.Altair:
        x = ForkedLightClientUpdate(
          kind: LightClientDataFork.Capella,
          capellaData: upgrade_lc_update_to_capella(
            x.forky(LightClientDataFork.Altair)))

    # Upgrade to Deneb
    when newKind >= LightClientDataFork.Deneb:
      if x.kind == LightClientDataFork.Capella:
        x = ForkedLightClientUpdate(
          kind: LightClientDataFork.Deneb,
          denebData: upgrade_lc_update_to_deneb(
            x.forky(LightClientDataFork.Capella)))

    # Upgrade to Electra
    when newKind >= LightClientDataFork.Electra:
      if x.kind == LightClientDataFork.Deneb:
        x = ForkedLightClientUpdate(
          kind: LightClientDataFork.Electra,
          electraData: upgrade_lc_update_to_electra(
            x.forky(LightClientDataFork.Deneb)))

    static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
    doAssert x.kind == newKind

func migrateToDataFork*(
    x: var ForkedLightClientFinalityUpdate,
    newKind: static LightClientDataFork) =
  if newKind == x.kind:
    # Already at correct kind
    discard
  elif newKind < x.kind:
    # Downgrade not supported, re-initialize
    x = ForkedLightClientFinalityUpdate(kind: newKind)
  else:
    # Upgrade to Altair
    when newKind >= LightClientDataFork.Altair:
      if x.kind == LightClientDataFork.None:
        x = ForkedLightClientFinalityUpdate(
          kind: LightClientDataFork.Altair)

    # Upgrade to Capella
    when newKind >= LightClientDataFork.Capella:
      if x.kind == LightClientDataFork.Altair:
        x = ForkedLightClientFinalityUpdate(
          kind: LightClientDataFork.Capella,
          capellaData: upgrade_lc_finality_update_to_capella(
            x.forky(LightClientDataFork.Altair)))

    # Upgrade to Deneb
    when newKind >= LightClientDataFork.Deneb:
      if x.kind == LightClientDataFork.Capella:
        x = ForkedLightClientFinalityUpdate(
          kind: LightClientDataFork.Deneb,
          denebData: upgrade_lc_finality_update_to_deneb(
            x.forky(LightClientDataFork.Capella)))

    # Upgrade to Electra
    when newKind >= LightClientDataFork.Electra:
      if x.kind == LightClientDataFork.Deneb:
        x = ForkedLightClientFinalityUpdate(
          kind: LightClientDataFork.Electra,
          electraData: upgrade_lc_finality_update_to_electra(
            x.forky(LightClientDataFork.Deneb)))

    static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
    doAssert x.kind == newKind

func migrateToDataFork*(
    x: var ForkedLightClientOptimisticUpdate,
    newKind: static LightClientDataFork) =
  if newKind == x.kind:
    # Already at correct kind
    discard
  elif newKind < x.kind:
    # Downgrade not supported, re-initialize
    x = ForkedLightClientOptimisticUpdate(kind: newKind)
  else:
    # Upgrade to Altair
    when newKind >= LightClientDataFork.Altair:
      if x.kind == LightClientDataFork.None:
        x = ForkedLightClientOptimisticUpdate(
          kind: LightClientDataFork.Altair)

    # Upgrade to Capella
    when newKind >= LightClientDataFork.Capella:
      if x.kind == LightClientDataFork.Altair:
        x = ForkedLightClientOptimisticUpdate(
          kind: LightClientDataFork.Capella,
          capellaData: upgrade_lc_optimistic_update_to_capella(
            x.forky(LightClientDataFork.Altair)))

    # Upgrade to Deneb
    when newKind >= LightClientDataFork.Deneb:
      if x.kind == LightClientDataFork.Capella:
        x = ForkedLightClientOptimisticUpdate(
          kind: LightClientDataFork.Deneb,
          denebData: upgrade_lc_optimistic_update_to_deneb(
            x.forky(LightClientDataFork.Capella)))

    # Upgrade to Electra
    when newKind >= LightClientDataFork.Electra:
      if x.kind == LightClientDataFork.Deneb:
        x = ForkedLightClientOptimisticUpdate(
          kind: LightClientDataFork.Electra,
          electraData: upgrade_lc_optimistic_update_to_electra(
            x.forky(LightClientDataFork.Deneb)))

    static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
    doAssert x.kind == newKind

func migrateToDataFork*(
    x: var ForkedLightClientStore,
    newKind: static LightClientDataFork) =
  if newKind == x.kind:
    # Already at correct kind
    discard
  elif newKind < x.kind:
    # Downgrade not supported, re-initialize
    x = ForkedLightClientStore(kind: newKind)
  else:
    # Upgrade to Altair
    when newKind >= LightClientDataFork.Altair:
      if x.kind == LightClientDataFork.None:
        x = ForkedLightClientStore(
          kind: LightClientDataFork.Altair)

    # Upgrade to Capella
    when newKind >= LightClientDataFork.Capella:
      if x.kind == LightClientDataFork.Altair:
        x = ForkedLightClientStore(
          kind: LightClientDataFork.Capella,
          capellaData: upgrade_lc_store_to_capella(
            x.forky(LightClientDataFork.Altair)))

    # Upgrade to Deneb
    when newKind >= LightClientDataFork.Deneb:
      if x.kind == LightClientDataFork.Capella:
        x = ForkedLightClientStore(
          kind: LightClientDataFork.Deneb,
          denebData: upgrade_lc_store_to_deneb(
            x.forky(LightClientDataFork.Capella)))

    # Upgrade to Electra
    when newKind >= LightClientDataFork.Electra:
      if x.kind == LightClientDataFork.Deneb:
        x = ForkedLightClientStore(
          kind: LightClientDataFork.Electra,
          electraData: upgrade_lc_store_to_electra(
            x.forky(LightClientDataFork.Deneb)))

    static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
    doAssert x.kind == newKind

func migratingToDataFork*[
    T:
      ForkedLightClientHeader |
      SomeForkedLightClientObject |
      ForkedLightClientStore](
    x: T, newKind: static LightClientDataFork): T =
  var upgradedObject = x
  upgradedObject.migrateToDataFork(newKind)
  upgradedObject

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/altair/light-client/full-node.md#block_to_light_client_header
func toAltairLightClientHeader(
    # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095
    blck:
      phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock |
      altair.SignedBeaconBlock | altair.TrustedSignedBeaconBlock |
      bellatrix.SignedBeaconBlock | bellatrix.TrustedSignedBeaconBlock
): altair.LightClientHeader =
  altair.LightClientHeader(
    beacon: blck.message.toBeaconBlockHeader())

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/capella/light-client/full-node.md#modified-block_to_light_client_header
func toCapellaLightClientHeader(
    # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095
    blck:
      phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock |
      altair.SignedBeaconBlock | altair.TrustedSignedBeaconBlock |
      bellatrix.SignedBeaconBlock | bellatrix.TrustedSignedBeaconBlock
): capella.LightClientHeader =
  # Note that during fork transitions, `finalized_header` may still
  # point to earlier forks. While Bellatrix blocks also contain an
  # `ExecutionPayload` (minus `withdrawals_root`), it was not included
  # in the corresponding light client data. To ensure compatibility
  # with legacy data going through `upgrade_lc_header_to_capella`,
  # leave out execution data.
  capella.LightClientHeader(
    beacon: blck.message.toBeaconBlockHeader())

func toCapellaLightClientHeader(
    # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095
    blck:
      capella.SignedBeaconBlock | capella.TrustedSignedBeaconBlock
): capella.LightClientHeader =
  template payload: untyped = blck.message.body.execution_payload
  capella.LightClientHeader(
    beacon: blck.message.toBeaconBlockHeader(),
    execution: capella.ExecutionPayloadHeader(
      parent_hash: payload.parent_hash,
      fee_recipient: payload.fee_recipient,
      state_root: payload.state_root,
      receipts_root: payload.receipts_root,
      logs_bloom: payload.logs_bloom,
      prev_randao: payload.prev_randao,
      block_number: payload.block_number,
      gas_limit: payload.gas_limit,
      gas_used: payload.gas_used,
      timestamp: payload.timestamp,
      extra_data: payload.extra_data,
      base_fee_per_gas: payload.base_fee_per_gas,
      block_hash: payload.block_hash,
      transactions_root: hash_tree_root(payload.transactions),
      withdrawals_root: hash_tree_root(payload.withdrawals)),
    execution_branch: blck.message.body.build_proof(
      capella.EXECUTION_PAYLOAD_GINDEX).get)

# https://github.com/ethereum/consensus-specs/blob/v1.4.0-alpha.0/specs/deneb/light-client/full-node.md#modified-block_to_light_client_header
func toDenebLightClientHeader(
    # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095
    blck:
      phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock |
      altair.SignedBeaconBlock | altair.TrustedSignedBeaconBlock |
      bellatrix.SignedBeaconBlock | bellatrix.TrustedSignedBeaconBlock
): deneb.LightClientHeader =
  # Note that during fork transitions, `finalized_header` may still
  # point to earlier forks. While Bellatrix blocks also contain an
  # `ExecutionPayload` (minus `withdrawals_root`), it was not included
  # in the corresponding light client data. To ensure compatibility
  # with legacy data going through `upgrade_lc_header_to_capella`,
  # leave out execution data.
  deneb.LightClientHeader(
    beacon: blck.message.toBeaconBlockHeader())

func toDenebLightClientHeader(
    # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095
    blck:
      capella.SignedBeaconBlock | capella.TrustedSignedBeaconBlock
): deneb.LightClientHeader =
  template payload: untyped = blck.message.body.execution_payload
  deneb.LightClientHeader(
    beacon: blck.message.toBeaconBlockHeader(),
    execution: deneb.ExecutionPayloadHeader(
      parent_hash: payload.parent_hash,
      fee_recipient: payload.fee_recipient,
      state_root: payload.state_root,
      receipts_root: payload.receipts_root,
      logs_bloom: payload.logs_bloom,
      prev_randao: payload.prev_randao,
      block_number: payload.block_number,
      gas_limit: payload.gas_limit,
      gas_used: payload.gas_used,
      timestamp: payload.timestamp,
      extra_data: payload.extra_data,
      base_fee_per_gas: payload.base_fee_per_gas,
      block_hash: payload.block_hash,
      transactions_root: hash_tree_root(payload.transactions),
      withdrawals_root: hash_tree_root(payload.withdrawals)),
    execution_branch: blck.message.body.build_proof(
      capella.EXECUTION_PAYLOAD_GINDEX).get)

func toDenebLightClientHeader(
    # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095
    blck:
      deneb.SignedBeaconBlock | deneb.TrustedSignedBeaconBlock
): deneb.LightClientHeader =
  template payload: untyped = blck.message.body.execution_payload
  deneb.LightClientHeader(
    beacon: blck.message.toBeaconBlockHeader(),
    execution: deneb.ExecutionPayloadHeader(
      parent_hash: payload.parent_hash,
      fee_recipient: payload.fee_recipient,
      state_root: payload.state_root,
      receipts_root: payload.receipts_root,
      logs_bloom: payload.logs_bloom,
      prev_randao: payload.prev_randao,
      block_number: payload.block_number,
      gas_limit: payload.gas_limit,
      gas_used: payload.gas_used,
      timestamp: payload.timestamp,
      extra_data: payload.extra_data,
      base_fee_per_gas: payload.base_fee_per_gas,
      block_hash: payload.block_hash,
      transactions_root: hash_tree_root(payload.transactions),
      withdrawals_root: hash_tree_root(payload.withdrawals),
      blob_gas_used: payload.blob_gas_used,
      excess_blob_gas: payload.excess_blob_gas),
    execution_branch: blck.message.body.build_proof(
      capella.EXECUTION_PAYLOAD_GINDEX).get)

# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/full-node.md#modified-block_to_light_client_header
func toElectraLightClientHeader(
    # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095
    blck:
      phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock |
      altair.SignedBeaconBlock | altair.TrustedSignedBeaconBlock |
      bellatrix.SignedBeaconBlock | bellatrix.TrustedSignedBeaconBlock
): electra.LightClientHeader =
  # Note that during fork transitions, `finalized_header` may still
  # point to earlier forks. While Bellatrix blocks also contain an
  # `ExecutionPayload` (minus `withdrawals_root`), it was not included
  # in the corresponding light client data. To ensure compatibility
  # with legacy data going through `upgrade_lc_header_to_capella`,
  # leave out execution data.
  electra.LightClientHeader(
    beacon: blck.message.toBeaconBlockHeader())

func toElectraLightClientHeader(
    # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095
    blck:
      capella.SignedBeaconBlock | capella.TrustedSignedBeaconBlock
): electra.LightClientHeader =
  template payload: untyped = blck.message.body.execution_payload
  electra.LightClientHeader(
    beacon: blck.message.toBeaconBlockHeader(),
    execution: electra.ExecutionPayloadHeader(
      parent_hash: payload.parent_hash,
      fee_recipient: payload.fee_recipient,
      state_root: payload.state_root,
      receipts_root: payload.receipts_root,
      logs_bloom: payload.logs_bloom,
      prev_randao: payload.prev_randao,
      block_number: payload.block_number,
      gas_limit: payload.gas_limit,
      gas_used: payload.gas_used,
      timestamp: payload.timestamp,
      extra_data: payload.extra_data,
      base_fee_per_gas: payload.base_fee_per_gas,
      block_hash: payload.block_hash,
      transactions_root: hash_tree_root(payload.transactions),
      withdrawals_root: hash_tree_root(payload.withdrawals)),
    execution_branch: blck.message.body.build_proof(
      capella.EXECUTION_PAYLOAD_GINDEX).get)

func toElectraLightClientHeader(
    # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095
    blck:
      deneb.SignedBeaconBlock | deneb.TrustedSignedBeaconBlock
): electra.LightClientHeader =
  template payload: untyped = blck.message.body.execution_payload
  electra.LightClientHeader(
    beacon: blck.message.toBeaconBlockHeader(),
    execution: electra.ExecutionPayloadHeader(
      parent_hash: payload.parent_hash,
      fee_recipient: payload.fee_recipient,
      state_root: payload.state_root,
      receipts_root: payload.receipts_root,
      logs_bloom: payload.logs_bloom,
      prev_randao: payload.prev_randao,
      block_number: payload.block_number,
      gas_limit: payload.gas_limit,
      gas_used: payload.gas_used,
      timestamp: payload.timestamp,
      extra_data: payload.extra_data,
      base_fee_per_gas: payload.base_fee_per_gas,
      block_hash: payload.block_hash,
      transactions_root: hash_tree_root(payload.transactions),
      withdrawals_root: hash_tree_root(payload.withdrawals),
      blob_gas_used: payload.blob_gas_used,
      excess_blob_gas: payload.excess_blob_gas),
    execution_branch: blck.message.body.build_proof(
      capella.EXECUTION_PAYLOAD_GINDEX).get)

func toElectraLightClientHeader(
    # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095
    blck:
      electra.SignedBeaconBlock | electra.TrustedSignedBeaconBlock
): electra.LightClientHeader =
  template payload: untyped = blck.message.body.execution_payload
  electra.LightClientHeader(
    beacon: blck.message.toBeaconBlockHeader(),
    execution: electra.ExecutionPayloadHeader(
      parent_hash: payload.parent_hash,
      fee_recipient: payload.fee_recipient,
      state_root: payload.state_root,
      receipts_root: payload.receipts_root,
      logs_bloom: payload.logs_bloom,
      prev_randao: payload.prev_randao,
      block_number: payload.block_number,
      gas_limit: payload.gas_limit,
      gas_used: payload.gas_used,
      timestamp: payload.timestamp,
      extra_data: payload.extra_data,
      base_fee_per_gas: payload.base_fee_per_gas,
      block_hash: payload.block_hash,
      transactions_root: hash_tree_root(payload.transactions),
      withdrawals_root: hash_tree_root(payload.withdrawals),
      blob_gas_used: payload.blob_gas_used,
      excess_blob_gas: payload.excess_blob_gas,
      deposit_requests_root: hash_tree_root(payload.deposit_requests),
      withdrawal_requests_root: hash_tree_root(payload.withdrawal_requests),
      consolidation_requests_root:
        hash_tree_root(payload.consolidation_requests)),
    execution_branch: blck.message.body.build_proof(
      capella.EXECUTION_PAYLOAD_GINDEX).get)

func toLightClientHeader*(
    # `SomeSignedBeaconBlock`: https://github.com/nim-lang/Nim/issues/18095
    blck:
      phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock |
      altair.SignedBeaconBlock | altair.TrustedSignedBeaconBlock |
      bellatrix.SignedBeaconBlock | bellatrix.TrustedSignedBeaconBlock |
      capella.SignedBeaconBlock | capella.TrustedSignedBeaconBlock |
      deneb.SignedBeaconBlock | deneb.TrustedSignedBeaconBlock |
      electra.SignedBeaconBlock | electra.TrustedSignedBeaconBlock,
    kind: static LightClientDataFork): auto =
  when kind == LightClientDataFork.Electra:
    blck.toElectraLightClientHeader()
  elif kind == LightClientDataFork.Deneb:
    blck.toDenebLightClientHeader()
  elif kind == LightClientDataFork.Capella:
    blck.toCapellaLightClientHeader()
  elif kind == LightClientDataFork.Altair:
    blck.toAltairLightClientHeader()
  else:
    static: raiseAssert "Unreachable"

import chronicles

func shortLog*[
    T:
      ForkedLightClientHeader |
      SomeForkedLightClientObject |
      ForkedLightClientStore](
    x: T): auto =
  type ResultType = object
    case kind: LightClientDataFork
    of LightClientDataFork.None:
      discard
    of LightClientDataFork.Altair:
      altairData: typeof(x.altairData.shortLog())
    of LightClientDataFork.Capella:
      capellaData: typeof(x.capellaData.shortLog())
    of LightClientDataFork.Deneb:
      denebData: typeof(x.denebData.shortLog())
    of LightClientDataFork.Electra:
      electraData: typeof(x.electraData.shortLog())

  let xKind = x.kind  # https://github.com/nim-lang/Nim/issues/23762
  case xKind
  of LightClientDataFork.Electra:
    ResultType(kind: xKind, electraData: x.electraData.shortLog())
  of LightClientDataFork.Deneb:
    ResultType(kind: xKind, denebData: x.denebData.shortLog())
  of LightClientDataFork.Capella:
    ResultType(kind: xKind, capellaData: x.capellaData.shortLog())
  of LightClientDataFork.Altair:
    ResultType(kind: xKind, altairData: x.altairData.shortLog())
  of LightClientDataFork.None:
    ResultType(kind: xKind)

chronicles.formatIt ForkedLightClientHeader: it.shortLog
chronicles.formatIt SomeForkedLightClientObject: it.shortLog
chronicles.formatIt ForkedLightClientStore: it.shortLog