import bls from "@chainsafe/bls";
import type {PublicKey} from "@chainsafe/bls/types";
import {BitArray} from "@chainsafe/ssz";
import {ApiClient} from "@lodestar/api";
import {altair, Bytes32, Root, ssz} from "@lodestar/types";
import {BeaconBlockHeader} from "@lodestar/types/phase0";
import {GenesisData} from "../index.js";
import {SyncCommitteeFast} from "../types.js";

export function sumBits(bits: BitArray): number {
  return bits.getTrueBitIndexes().length;
}

export function isZeroHash(root: Root): boolean {
  for (let i = 0; i < root.length; i++) {
    if (root[i] !== 0) {
      return false;
    }
  }
  return true;
}

export function assertZeroHashes(rootArray: Root[], expectedLength: number, errorMessage: string): void {
  if (rootArray.length !== expectedLength) {
    throw Error(`Wrong length ${errorMessage}`);
  }

  for (const root of rootArray) {
    if (!isZeroHash(root)) {
      throw Error(`Not zeroed ${errorMessage}`);
    }
  }
}

/**
 * Util to guarantee that all bits have a corresponding pubkey
 */
export function getParticipantPubkeys<T>(pubkeys: T[], bits: BitArray): T[] {
  // BitArray.intersectValues() checks the length is correct
  return bits.intersectValues(pubkeys);
}

export function toBlockHeader(block: altair.BeaconBlock): BeaconBlockHeader {
  return {
    slot: block.slot,
    proposerIndex: block.proposerIndex,
    parentRoot: block.parentRoot,
    stateRoot: block.stateRoot,
    bodyRoot: ssz.altair.BeaconBlockBody.hashTreeRoot(block.body),
  };
}

function deserializePubkeys(pubkeys: altair.LightClientUpdate["nextSyncCommittee"]["pubkeys"]): PublicKey[] {
  return pubkeys.map((pk) => bls.PublicKey.fromBytes(pk));
}

function serializePubkeys(pubkeys: PublicKey[]): altair.LightClientUpdate["nextSyncCommittee"]["pubkeys"] {
  return pubkeys.map((pk) => pk.toBytes());
}

export function deserializeSyncCommittee(syncCommittee: altair.SyncCommittee): SyncCommitteeFast {
  return {
    pubkeys: deserializePubkeys(syncCommittee.pubkeys),
    aggregatePubkey: bls.PublicKey.fromBytes(syncCommittee.aggregatePubkey),
  };
}

export function serializeSyncCommittee(syncCommittee: SyncCommitteeFast): altair.SyncCommittee {
  return {
    pubkeys: serializePubkeys(syncCommittee.pubkeys),
    aggregatePubkey: syncCommittee.aggregatePubkey.toBytes(),
  };
}

export function isEmptyHeader(header: BeaconBlockHeader): boolean {
  const emptyValue = ssz.phase0.BeaconBlockHeader.defaultValue();
  return ssz.phase0.BeaconBlockHeader.equals(emptyValue, header);
}

// Thanks https://github.com/iliakan/detect-node/blob/master/index.esm.js
export const isNode =
  Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]";

export async function getGenesisData(api: Pick<ApiClient, "beacon">): Promise<GenesisData> {
  const {genesisTime, genesisValidatorsRoot} = (await api.beacon.getGenesis()).value();

  return {
    genesisTime,
    genesisValidatorsRoot,
  };
}

export async function getFinalizedSyncCheckpoint(api: Pick<ApiClient, "beacon">): Promise<Bytes32> {
  return (await api.beacon.getStateFinalityCheckpoints({stateId: "head"})).value().finalized.root;
}
