import {sortBy} from "../../../util/sortBy.js";
import {MIN_FINALIZED_CHAIN_VALIDATED_EPOCHS, PARALLEL_HEAD_CHAINS} from "../../constants.js";
import {RangeSyncType} from "../../utils/remoteSyncType.js";
import {SyncChain} from "../chain.js";

/**
 * Priotize existing chains based on their target and peer count
 * Returns an array of chains toStart and toStop to comply with the priotization
 */
export function updateChains(chains: SyncChain[]): {toStart: SyncChain[]; toStop: SyncChain[]} {
  const finalizedChains: SyncChain[] = [];
  const headChains: SyncChain[] = [];
  for (const chain of chains) {
    if (chain.syncType === RangeSyncType.Finalized) {
      finalizedChains.push(chain);
    } else {
      headChains.push(chain);
    }
  }

  const toStart: SyncChain[] = [];
  const toStop: SyncChain[] = [];

  if (finalizedChains.length > 0) {
    // Pick first only
    const [newSyncChain] = prioritizeSyncChains(finalizedChains);

    // TODO: Should it stop all HEAD chains if going from a head sync to a finalized sync?

    const currentSyncChain = finalizedChains.find((syncChain) => syncChain.isSyncing);

    // Only switch from currentSyncChain to newSyncChain if necessary
    // Avoid unnecesary switchings and try to advance it
    if (
      !currentSyncChain ||
      (newSyncChain !== currentSyncChain &&
        newSyncChain.peers > currentSyncChain.peers &&
        currentSyncChain.validatedEpochs > MIN_FINALIZED_CHAIN_VALIDATED_EPOCHS)
    ) {
      toStart.push(newSyncChain);
      if (currentSyncChain) toStop.push(currentSyncChain);
    }
  } else {
    for (const syncChain of prioritizeSyncChains(headChains)) {
      if (toStart.length < PARALLEL_HEAD_CHAINS) {
        toStart.push(syncChain);
      } else {
        toStop.push(syncChain);
      }
    }
  }

  return {toStart, toStop};
}

/**
 * Order `syncChains` by most peers and already syncing first
 * If two chains have the same number of peers, prefer the already syncing to not drop progress
 */
function prioritizeSyncChains(syncChains: SyncChain[]): SyncChain[] {
  return sortBy(
    syncChains,
    (syncChain) => -syncChain.peers, // Sort from high peer count to low: negative to reverse
    (syncChain) => (syncChain.isSyncing ? 0 : 1) // Sort by isSyncing first = 0
  );
}
