<script lang="ts">
import EmptyPane from '@front/features/layout/EmptyPane.vue'

import { computed, defineComponent, ref, watch } from 'vue'
import { getStorage, setStorage } from '@vue-devtools/shared-utils'
import { useRoute, useRouter } from 'vue-router'
import { onKeyDown } from '@front/util/keyboard'
import TimelineEventListItem from './TimelineEventListItem.vue'
import { selectEvent, useInspectedEvent, useLayers, useSelectedEvent } from './composable'

const itemHeight = 34

const STORAGE_TAB_ID = 'timeline.event-list.tab-id'

export default defineComponent({
  components: {
    TimelineEventListItem,
    EmptyPane,
  },

  setup() {
    const route = useRoute()
    const router = useRouter()

    const {
      selectedLayer,
    } = useLayers()

    const {
      selectedEvent,
      selectedGroupEvents,
    } = useSelectedEvent()

    const {
      inspectedEvent,
    } = useInspectedEvent()

    // Tabs

    const tabId = computed({
      get: () => route.query.tabId,
      set: (value) => {
        setStorage(STORAGE_TAB_ID, value)
        router.push({
          query: {
            ...route.query,
            tabId: value,
          },
        })
      },
    })

    if (!route.query.tabId) {
      tabId.value = getStorage(STORAGE_TAB_ID, 'all')
    }

    watch(selectedEvent, (value) => {
      if (value && !value.group && tabId.value === 'group') {
        tabId.value = 'all'
      }
    })

    const displayedEvents = computed(() => {
      switch (tabId.value) {
        case 'group':
          return selectedGroupEvents.value ?? []
        case 'all':
        default:
          return selectedLayer.value?.events ?? []
      }
    })

    // Filter

    const filter = ref('')

    const filteredEvents = computed(() => {
      const rawFilter = filter.value.trim()
      if (rawFilter) {
        const reg = new RegExp(rawFilter, 'i')
        return displayedEvents.value.filter(event =>
          (event.title && reg.test(event.title))
          || (event.subtitle && reg.test(event.subtitle)),
        )
      }
      else {
        return displayedEvents.value
      }
    })

    // Scrolling

    const scroller = ref()

    const isAtScrollBottom = ref(false)

    function onScroll() {
      const scrollerEl = scroller.value.$el
      isAtScrollBottom.value = scrollerEl.scrollTop + scrollerEl.clientHeight >= scrollerEl.scrollHeight - 400
    }

    watch(scroller, (value) => {
      if (value) {
        onScroll()
      }
    }, { immediate: true })

    watch(tabId, () => {
      checkScrollToInspectedEvent()
    }, { immediate: true })

    function scrollToInspectedEvent() {
      if (!scroller.value) {
        return
      }

      const scrollerEl = scroller.value.$el

      const index = filteredEvents.value.indexOf(inspectedEvent.value)
      if (index !== -1) {
        // We need to first disable auto bottom scroll
        isAtScrollBottom.value = false
        scrollerEl.scrollTop = itemHeight * (index + 0.5) - (scrollerEl.clientHeight) / 2
      }
    }

    watch(inspectedEvent, () => {
      checkScrollToInspectedEvent()
    }, { immediate: true })

    function checkScrollToInspectedEvent() {
      requestAnimationFrame(() => {
        if (!scroller.value) {
          return
        }

        const scrollerEl = scroller.value.$el

        const index = filteredEvents.value.indexOf(inspectedEvent.value)
        const minPosition = itemHeight * index
        const maxPosition = minPosition + itemHeight

        if (scrollerEl.scrollTop > minPosition || scrollerEl.scrollTop + scrollerEl.clientHeight < maxPosition) {
          scrollToInspectedEvent()
        }
      })
    }

    // Auto bottom scroll

    function scrollToBottom() {
      requestAnimationFrame(() => {
        if (!scroller.value) {
          return
        }
        const scrollerEl = scroller.value.$el
        scrollerEl.scrollTop = scrollerEl.scrollHeight
      })
    }

    // Important: Watch this after the scroll to inspect event watchers
    watch(() => filteredEvents.value.length, () => {
      if (isAtScrollBottom.value) {
        scrollToBottom()
      }
    }, { immediate: true })

    // List interactions

    function inspectEvent(event) {
      inspectedEvent.value = event
    }

    // Keyboard

    onKeyDown((event) => {
      const index = filteredEvents.value.indexOf(inspectedEvent.value)
      if (event.key === 'ArrowDown') {
        if (index < filteredEvents.value.length - 1) {
          inspectEvent(filteredEvents.value[index + 1])
        }
      }
      else if (event.key === 'ArrowUp') {
        if (index > 0) {
          inspectEvent(filteredEvents.value[index - 1])
        }
      }
      else if (event.key === 'Enter' || event.key === ' ') {
        if (inspectedEvent.value) {
          selectEvent(inspectedEvent.value)
        }
      }
    })

    return {
      selectedEvent,
      selectedLayer,
      tabId,
      scroller,
      filter,
      filteredEvents,
      itemHeight,
      isAtScrollBottom,
      inspectEvent,
      selectEvent,
      onScroll,
      scrollToBottom,
    }
  },
})
</script>

<template>
  <div
    v-if="selectedLayer && (filteredEvents.length || filter.length)"
    class="h-full flex flex-col relative"
  >
    <div class="flex-none flex flex-col items-stretch border-gray-200 dark:border-gray-700 border-b">
      <VueGroup
        v-if="selectedEvent && selectedEvent.group"
        v-model="tabId"
        indicator
        class="accent extend border-gray-200 dark:border-gray-700 border-b"
      >
        <VueGroupButton
          value="all"
          label="All"
          class="flat"
        />
        <VueGroupButton
          value="group"
          label="Group"
          class="flat"
        />
      </VueGroup>

      <VueInput
        v-model="filter"
        icon-left="search"
        :placeholder="`Filter ${selectedLayer.label}`"
        class="search flat h-[31px]"
      />
    </div>

    <RecycleScroller
      :key="tabId"
      ref="scroller"
      :items="filteredEvents"
      :item-size="itemHeight"
      class="flex-1"
      @scroll.passive="onScroll()"
    >
      <template #default="{ item: event }">
        <TimelineEventListItem
          :event="event"
          :selected="selectedEvent === event"
          @inspect="inspectEvent(event)"
          @select="selectEvent(event)"
        />
      </template>
    </RecycleScroller>

    <VueButton
      v-if="!isAtScrollBottom"
      v-tooltip="'Scroll to bottom'"
      icon-left="keyboard_arrow_down"
      class="icon-button absolute bottom-1 right-4 rounded-full shadow-md"
      @click="scrollToBottom()"
    />
  </div>

  <EmptyPane
    v-else
    :icon="!selectedLayer ? 'layers' : 'inbox'"
  >
    <template v-if="!selectedLayer">
      Select a layer to get started
    </template>
    <template v-else>
      No events
    </template>
  </EmptyPane>
</template>
