<VirtualListContainer :realm :containerQuery >
  <div class="virtual-list {{shown ? '' : 'hidden'}}"
       style="height: {{$height}}px;"
       ref:node >
    <VirtualListHeader component="{{headerComponent}}" virtualProps="{{headerProps}}" shown="{{$showHeader}}"/>
    {{#if $visibleItems}}
      {{#each $visibleItems as visibleItem @key}}
        <VirtualListLazyItem :component
                             offset="{{visibleItem.offset}}"
                             makeProps="{{makeProps}}"
                             key="{{visibleItem.key}}"
                             index="{{visibleItem.index}}"
        />
      {{/each}}
    {{/if}}
    {{#if $showFooter}}
      <VirtualListFooter component="{{footerComponent}}" />
    {{/if}}
  </div>
</VirtualListContainer>
<style>
  .virtual-list {
    position: relative;
    transition: opacity 0.25s linear;
  }
</style>
<script>
  import VirtualListContainer from './VirtualListContainer.html'
  import VirtualListLazyItem from './VirtualListLazyItem'
  import VirtualListFooter from './VirtualListFooter.html'
  import VirtualListHeader from './VirtualListHeader.html'
  import { virtualListStore } from './virtualListStore'
  import throttle from 'lodash/throttle'
  import { mark, stop } from '../../_utils/marks'
  import isEqual from 'lodash/isEqual'

  const DISTANCE_FROM_BOTTOM_TO_FIRE = 800
  const SCROLL_EVENT_THROTTLE = 1000

  export default {
    oncreate () {
      this.fireScrollToBottom = throttle(() => {
        this.fire('scrollToBottom')
      }, SCROLL_EVENT_THROTTLE)
      this.fireScrollToTop = throttle(() => {
        this.fire('scrollToTop')
      }, SCROLL_EVENT_THROTTLE)
      this.observe('showFooter', showFooter => {
        mark('set showFooter')
        this.store.setForRealm({showFooter: showFooter})
        mark('set showFooter')
      })
      this.observe('showHeader', showHeader => {
        mark('set showHeader')
        this.store.setForRealm({showHeader: showHeader})
        stop('set showHeader')
      })
      this.observe('items', (newItems, oldItems) => {
        if (!newItems || isEqual(newItems, oldItems)) {
          return
        }
        mark('set items')
        this.store.setForRealm({items: newItems})
        stop('set items')
      })
      this.observe('allVisibleItemsHaveHeight', allVisibleItemsHaveHeight => {
        if (allVisibleItemsHaveHeight) {
          this.fire('initializedVisibleItems')
        }
      })

      let observedOnce = false

      this.observe('distanceFromBottom', (distanceFromBottom) => {
        if (!observedOnce) {
          observedOnce = true // TODO: the first time is always 0... need better way to handle this
          return
        }
        if (distanceFromBottom >= 0 &&
            distanceFromBottom <= DISTANCE_FROM_BOTTOM_TO_FIRE) {
          this.fireScrollToBottom()
        }
      })

      this.observe('scrollTop', (scrollTop) => {
        this.fire('scrollTopChanged', scrollTop)
        if (scrollTop === 0) {
          this.fireScrollToTop()
        }
        this.calculateListOffset()
      })
    },
    data: () => ({
      component: null
    }),
    store: () => virtualListStore,
    components: {
      VirtualListContainer,
      VirtualListLazyItem,
      VirtualListFooter,
      VirtualListHeader
    },
    computed: {
      distanceFromBottom: ($scrollHeight, $scrollTop, $offsetHeight) => {
        return $scrollHeight - $scrollTop - $offsetHeight
      },
      scrollTop: ($scrollTop) => $scrollTop,
      // TODO: bug in svelte store, shouldn't need to do this
      allVisibleItemsHaveHeight: ($allVisibleItemsHaveHeight) => $allVisibleItemsHaveHeight,
    },
    methods: {
      calculateListOffset() {
        // TODO: better way to get the offset top?
        let node = this.refs.node
        if (!node) {
          return
        }
        mark('calculateListOffset')
        let listOffset = node.offsetParent.offsetTop
        this.store.setForRealm({listOffset})
        stop('calculateListOffset')
      }
    }
  }
</script>