import {EffectiveBalanceIncrements} from "@lodestar/state-transition";
import {CachedBeaconStateAllForks} from "@lodestar/state-transition";
import {Epoch, Slot, ValidatorIndex, phase0, Root, RootHex, BeaconBlock, IndexedAttestation} from "@lodestar/types";
import {
  ProtoBlock,
  MaybeValidExecutionStatus,
  LVHExecResponse,
  ProtoNode,
  DataAvailabilityStatus,
} from "../protoArray/interface.js";
import {CheckpointWithHex} from "./store.js";
import {UpdateAndGetHeadOpt} from "./forkChoice.js";

export type CheckpointHex = {
  epoch: Epoch;
  root: RootHex;
};

export type CheckpointsWithHex = {
  justifiedCheckpoint: CheckpointWithHex;
  finalizedCheckpoint: CheckpointWithHex;
};

export type CheckpointHexWithBalance = {
  checkpoint: CheckpointWithHex;
  balances: EffectiveBalanceIncrements;
};

export type CheckpointHexWithTotalBalance = CheckpointHexWithBalance & {
  totalBalance: number;
};

export enum EpochDifference {
  current = 0,
  previous = 1,
}

export enum AncestorStatus {
  CommonAncestor,
  Descendant,
  NoCommonAncenstor,
  BlockUnknown,
}

export type AncestorResult =
  | {code: AncestorStatus.CommonAncestor; depth: number}
  | {code: AncestorStatus.Descendant}
  | {code: AncestorStatus.NoCommonAncenstor}
  | {code: AncestorStatus.BlockUnknown};

// Reason for not proposer boost reorging
export enum NotReorgedReason {
  HeadBlockIsTimely = "headBlockIsTimely",
  ParentBlockNotAvailable = "parentBlockNotAvailable",
  ProposerBoostReorgDisabled = "proposerBoostReorgDisabled",
  NotShufflingStable = "notShufflingStable",
  NotFFGCompetitive = "notFFGCompetitive",
  ChainLongUnfinality = "chainLongUnfinality",
  ParentBlockDistanceMoreThanOneSlot = "parentBlockDistanceMoreThanOneSlot",
  ReorgMoreThanOneSlot = "reorgMoreThanOneSlot",
  ProposerBoostNotWornOff = "proposerBoostNotWornOff",
  HeadBlockNotWeak = "headBlockNotWeak",
  ParentBlockNotStrong = "ParentBlockNotStrong",
  NotProposingOnTime = "notProposingOnTime",
}

export type ForkChoiceMetrics = {
  votes: number;
  queuedAttestations: number;
  validatedAttestationDatas: number;
  balancesLength: number;
  nodes: number;
  indices: number;
};

export interface IForkChoice {
  irrecoverableError?: Error;

  getMetrics(): ForkChoiceMetrics;
  /**
   * Returns the block root of an ancestor of `block_root` at the given `slot`. (Note: `slot` refers
   * to the block that is *returned*, not the one that is supplied.)
   *
   * ## Specification
   *
   * Equivalent to:
   *
   * https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#get_ancestor
   */
  getAncestor(blockRoot: RootHex, ancestorSlot: Slot): RootHex;
  /**
   * Run the fork choice rule to determine the head.
   *
   * ## Specification
   *
   * Is equivalent to:
   *
   * https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#get_head
   */
  getHeadRoot(): RootHex;
  getHead(): ProtoBlock;
  updateAndGetHead(mode: UpdateAndGetHeadOpt): {
    head: ProtoBlock;
    isHeadTimely?: boolean;
    notReorgedReason?: NotReorgedReason;
  };
  /**
   * Retrieves all possible chain heads (leaves of fork choice tree).
   */
  getHeads(): ProtoBlock[];
  /**
   * Retrieve all nodes for the debug API.
   */
  getAllNodes(): ProtoNode[];
  getFinalizedCheckpoint(): CheckpointWithHex;
  getJustifiedCheckpoint(): CheckpointWithHex;
  /**
   * Add `block` to the fork choice DAG.
   *
   * ## Specification
   *
   * Approximates:
   *
   * https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#on_block
   *
   * It only approximates the specification since it does not run the `state_transition` check.
   * That should have already been called upstream and it's too expensive to call again.
   *
   * ## Notes:
   *
   * The supplied block **must** pass the `state_transition` function as it will not be run here.
   */
  onBlock(
    block: BeaconBlock,
    state: CachedBeaconStateAllForks,
    blockDelaySec: number,
    currentSlot: Slot,
    executionStatus: MaybeValidExecutionStatus,
    dataAvailabilityStatus: DataAvailabilityStatus
  ): ProtoBlock;
  /**
   * Register `attestation` with the fork choice DAG so that it may influence future calls to `getHead`.
   *
   * ## Specification
   *
   * Approximates:
   *
   * https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#on_attestation
   *
   * It only approximates the specification since it does not perform
   * `is_valid_indexed_attestation` since that should already have been called upstream and it's
   * too expensive to call again.
   *
   * ## Notes:
   *
   * The supplied `attestation` **must** pass the `in_valid_indexed_attestation` function as it
   * will not be run here.
   */
  onAttestation(attestation: IndexedAttestation, attDataRoot: string, forceImport?: boolean): void;
  /**
   * Register attester slashing in order not to consider their votes in `getHead`
   *
   * ## Specification
   *
   * https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/phase0/fork-choice.md#on_attester_slashing
   */
  onAttesterSlashing(slashing: phase0.AttesterSlashing): void;
  getLatestMessage(validatorIndex: ValidatorIndex): LatestMessage | undefined;
  /**
   * Call `onTick` for all slots between `fcStore.getCurrentSlot()` and the provided `currentSlot`.
   */
  updateTime(currentSlot: Slot): void;

  /**
   * Returns current time slot.
   */
  getTime(): Slot;
  /**
   * Returns `true` if the block is known **and** a descendant of the finalized root.
   */
  hasBlock(blockRoot: Root): boolean;
  hasBlockHex(blockRoot: RootHex): boolean;
  /**
   * Same to hasBlock, but without checking if the block is a descendant of the finalized root.
   */
  hasBlockUnsafe(blockRoot: Root): boolean;
  hasBlockHexUnsafe(blockRoot: RootHex): boolean;
  getSlotsPresent(windowStart: number): number;
  /**
   * Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root.
   */
  getBlock(blockRoot: Root): ProtoBlock | null;
  getBlockHex(blockRoot: RootHex): ProtoBlock | null;
  getFinalizedBlock(): ProtoBlock;
  getJustifiedBlock(): ProtoBlock;
  /**
   * Returns true if the `descendantRoot` has an ancestor with `ancestorRoot`.
   *
   * Always returns `false` if either input roots are unknown.
   * Still returns `true` if `ancestorRoot===descendantRoot` (and the roots are known)
   */
  isDescendant(ancestorRoot: RootHex, descendantRoot: RootHex): boolean;
  /**
   * Prune items up to a finalized root.
   */
  prune(finalizedRoot: RootHex): ProtoBlock[];
  setPruneThreshold(threshold: number): void;
  /**
   * Iterates backwards through ancestor block summaries, starting from a block root
   */
  iterateAncestorBlocks(blockRoot: RootHex): IterableIterator<ProtoBlock>;
  getAllAncestorBlocks(blockRoot: RootHex): ProtoBlock[];
  /**
   * The same to iterateAncestorBlocks but this gets non-ancestor nodes instead of ancestor nodes.
   */
  getAllNonAncestorBlocks(blockRoot: RootHex): ProtoBlock[];
  getCanonicalBlockAtSlot(slot: Slot): ProtoBlock | null;
  getCanonicalBlockClosestLteSlot(slot: Slot): ProtoBlock | null;
  /**
   * Returns all ProtoBlock known to fork-choice. Must not mutated the returned array
   */
  forwarditerateAncestorBlocks(): ProtoBlock[];
  /**
   * Iterates forward descendants of blockRoot. Does not yield blockRoot itself
   */
  forwardIterateDescendants(blockRoot: RootHex): IterableIterator<ProtoBlock>;
  getBlockSummariesByParentRoot(parentRoot: RootHex): ProtoBlock[];
  getBlockSummariesAtSlot(slot: Slot): ProtoBlock[];
  /** Returns the distance of common ancestor of nodes to the max of the newNode and the prevNode. */
  getCommonAncestorDepth(prevBlock: ProtoBlock, newBlock: ProtoBlock): AncestorResult;
  /**
   * Optimistic sync validate till validated latest hash, invalidate any decendant branch if invalidated branch decendant provided
   */
  validateLatestHash(execResponse: LVHExecResponse): void;

  /**
   * A dependent root is the block root of the last block before the state transition that decided a specific shuffling
   */
  getDependentRoot(block: ProtoBlock, atEpochDiff: EpochDifference): RootHex;
}

/** Same to the PowBlock but we want RootHex to work with forkchoice conveniently */
export type PowBlockHex = {
  blockHash: RootHex;
  parentHash: RootHex;
  totalDifficulty: bigint;
};

export type LatestMessage = {
  epoch: Epoch;
  root: RootHex;
};

/**
 * Used for queuing attestations from the current slot. Only contains the minimum necessary
 * information about the attestation.
 */
export type QueuedAttestation = {
  slot: Slot;
  attestingIndices: ValidatorIndex[];
  blockRoot: RootHex;
  targetEpoch: Epoch;
};
