// We need this module to effectively track safe checkpoint id
// this is very cheap to do in memory, while requires a lot of work on a db
// especially when save_full_history is enabled.
// The safe checkpoint id can be used to optimize checkpoints traverse logic and
// make pruning operation super cheap.
type t = {
  checkpointIds: array<bigint>,
  checkpointBlockNumbers: array<int>,
  maxReorgDepth: int,
}

let make = (
  ~maxReorgDepth,
  ~shouldRollbackOnReorg,
  ~chainReorgCheckpoints: array<Internal.reorgCheckpoint>,
) => {
  if maxReorgDepth > 0 && shouldRollbackOnReorg {
    let checkpointIds = Utils.Array.jsArrayCreate(chainReorgCheckpoints->Array.length)
    let checkpointBlockNumbers = Utils.Array.jsArrayCreate(
      chainReorgCheckpoints->Array.length,
    )
    chainReorgCheckpoints->Array.forEachWithIndex((checkpoint, idx) => {
      checkpointIds->Array.setUnsafe(idx, checkpoint.checkpointId)
      checkpointBlockNumbers->Array.setUnsafe(idx, checkpoint.blockNumber)
    })
    Some({
      checkpointIds,
      checkpointBlockNumbers,
      maxReorgDepth,
    })
  } else {
    None
  }
}

let getSafeCheckpointId = (safeCheckpointTracking: t, ~sourceBlockNumber: int) => {
  let safeBlockNumber = sourceBlockNumber - safeCheckpointTracking.maxReorgDepth

  switch safeCheckpointTracking.checkpointIds {
  | [] => 0n
  | _
    if safeCheckpointTracking.checkpointBlockNumbers->Array.getUnsafe(0) >
      safeBlockNumber => 0n
  | [checkpointId] => checkpointId
  | _ => {
      let trackingCheckpointsCount = safeCheckpointTracking.checkpointIds->Array.length
      let result = ref(None)
      let idx = ref(1)

      while idx.contents < trackingCheckpointsCount && result.contents === None {
        if (
          safeCheckpointTracking.checkpointBlockNumbers->Array.getUnsafe(idx.contents) >
            safeBlockNumber
        ) {
          result :=
            Some(safeCheckpointTracking.checkpointIds->Array.getUnsafe(idx.contents - 1))
        }
        idx := idx.contents + 1
      }

      switch result.contents {
      | Some(checkpointId) => checkpointId
      | None =>
        safeCheckpointTracking.checkpointIds->Array.getUnsafe(trackingCheckpointsCount - 1)
      }
    }
  }
}

let updateOnNewBatch = (
  safeCheckpointTracking: t,
  ~sourceBlockNumber: int,
  ~chainId: int,
  ~batchCheckpointIds: array<bigint>,
  ~batchCheckpointBlockNumbers: array<int>,
  ~batchCheckpointChainIds: array<int>,
) => {
  let safeCheckpointId = getSafeCheckpointId(safeCheckpointTracking, ~sourceBlockNumber)

  let mutCheckpointIds = []
  let mutCheckpointBlockNumbers = []

  // Copy + Clean up old checkpoints
  for idx in 0 to safeCheckpointTracking.checkpointIds->Array.length - 1 {
    let checkpointId = safeCheckpointTracking.checkpointIds->Array.getUnsafe(idx)
    if checkpointId >= safeCheckpointId {
      mutCheckpointIds->Array.push(checkpointId)->ignore
      mutCheckpointBlockNumbers
      ->Array.push(safeCheckpointTracking.checkpointBlockNumbers->Array.getUnsafe(idx))
      ->ignore
    }
  }

  // Append new checkpoints
  for idx in 0 to batchCheckpointIds->Array.length - 1 {
    if batchCheckpointChainIds->Array.getUnsafe(idx) === chainId {
      mutCheckpointIds->Array.push(batchCheckpointIds->Array.getUnsafe(idx))->ignore
      mutCheckpointBlockNumbers
      ->Array.push(batchCheckpointBlockNumbers->Array.getUnsafe(idx))
      ->ignore
    }
  }

  {
    checkpointIds: mutCheckpointIds,
    checkpointBlockNumbers: mutCheckpointBlockNumbers,
    maxReorgDepth: safeCheckpointTracking.maxReorgDepth,
  }
}

let rollback = (safeCheckpointTracking: t, ~targetBlockNumber: int) => {
  let mutCheckpointIds = []
  let mutCheckpointBlockNumbers = []

  for idx in 0 to safeCheckpointTracking.checkpointIds->Array.length - 1 {
    let blockNumber = safeCheckpointTracking.checkpointBlockNumbers->Array.getUnsafe(idx)
    if blockNumber <= targetBlockNumber {
      mutCheckpointIds
      ->Array.push(safeCheckpointTracking.checkpointIds->Array.getUnsafe(idx))
      ->ignore
      mutCheckpointBlockNumbers
      ->Array.push(safeCheckpointTracking.checkpointBlockNumbers->Array.getUnsafe(idx))
      ->ignore
    }
  }

  {
    checkpointIds: mutCheckpointIds,
    checkpointBlockNumbers: mutCheckpointBlockNumbers,
    maxReorgDepth: safeCheckpointTracking.maxReorgDepth,
  }
}
