/**
 * Our algorithm have additional one ghost item
 * whose index as `data.length` to simplify the calculation
 */
export const GHOST_ITEM_KEY = '__vc_ghost_item__'

/**
 * Safari has the elasticity effect which provides negative `scrollTop` value.
 * We should ignore it since will make scroll animation shake.
 */
export function alignScrollTop(scrollTop, scrollRange) {
  if (scrollTop < 0) {
    return 0
  }
  if (scrollTop >= scrollRange) {
    return scrollRange
  }

  return scrollTop
}

/**
 * Get node `offsetHeight`. We prefer node is a dom element directly.
 * But if not provided, downgrade to `findDOMNode` to get the real dom element.
 */
export function getNodeHeight(node) {
  return node ? node.offsetHeight : 0
}

/**
 * Calculate the located item absolute top with whole scroll height
 */
export function getItemAbsoluteTop({ scrollTop, ...rest }) {
  return scrollTop + getItemRelativeTop(rest)
}

/**
 * Calculate the located item related top with current window height
 */
export function getItemRelativeTop({
  itemIndex,
  itemOffsetPtg,
  itemElementHeights,
  scrollPtg,
  clientHeight,
  getItemKey,
}) {
  const locatedItemHeight = itemElementHeights[getItemKey(itemIndex)] || 0
  const locatedItemTop = scrollPtg * clientHeight
  const locatedItemOffset = itemOffsetPtg * locatedItemHeight
  return Math.floor(locatedItemTop - locatedItemOffset)
}

export function getCompareItemRelativeTop({
  locatedItemRelativeTop,
  locatedItemIndex,
  compareItemIndex,
  startIndex,
  endIndex,
  getItemKey,
  itemElementHeights,
}) {
  let originCompareItemTop = locatedItemRelativeTop
  const compareItemKey = getItemKey(compareItemIndex)

  if (compareItemIndex <= locatedItemIndex) {
    for (let index = locatedItemIndex; index >= startIndex; index -= 1) {
      const key = getItemKey(index)
      if (key === compareItemKey) {
        break
      }

      const prevItemKey = getItemKey(index - 1)
      originCompareItemTop -= itemElementHeights[prevItemKey] || 0
    }
  } else {
    for (let index = locatedItemIndex; index <= endIndex; index += 1) {
      const key = getItemKey(index)
      if (key === compareItemKey) {
        break
      }

      originCompareItemTop += itemElementHeights[key] || 0
    }
  }

  return originCompareItemTop
}

export function getScrollPercentage({ scrollTop, scrollHeight, clientHeight }) {
  if (scrollHeight <= clientHeight) {
    return 0
  }

  const scrollRange = scrollHeight - clientHeight
  const alignedScrollTop = alignScrollTop(scrollTop, scrollRange)
  const scrollTopPtg = alignedScrollTop / scrollRange
  return scrollTopPtg
}

export function getElementScrollPercentage(element) {
  if (!element) {
    return 0
  }

  return getScrollPercentage(element)
}

/**
 * Get location item and its align percentage with the scroll percentage.
 * We should measure current scroll position to decide which item is the location item.
 * And then fill the top count and bottom count with the base of location item.
 *
 * `total` should be the real count instead of `total - 1` in calculation.
 */
function getLocationItem(scrollPtg, total) {
  const itemIndex = Math.floor(scrollPtg * total)
  const itemTopPtg = itemIndex / total
  const itemBottomPtg = (itemIndex + 1) / total
  const itemOffsetPtg = (scrollPtg - itemTopPtg) / (itemBottomPtg - itemTopPtg)

  return {
    index: itemIndex,
    offsetPtg: itemOffsetPtg,
  }
}

/**
 * Get display items start, end, located item index. This is pure math calculation
 */
export function getRangeIndex(scrollPtg, itemCount, visibleCount) {
  const { index, offsetPtg } = getLocationItem(scrollPtg, itemCount)

  const beforeCount = Math.ceil(scrollPtg * visibleCount)
  const afterCount = Math.ceil((1 - scrollPtg) * visibleCount)

  return {
    itemIndex: index,
    itemOffsetPtg: offsetPtg,
    startIndex: Math.max(0, index - beforeCount),
    endIndex: Math.min(itemCount - 1, index + afterCount),
  }
}

export function requireVirtual(height, itemHeight, count, virtual) {
  return virtual !== false && typeof height === 'number' && count * itemHeight > height
}
