<template>
  <VMenu v-model="menuIsOpen" v-bind="menuProps">
    <template #activator="{ on }">
      <VTextField
        ref="textField"
        v-bind="[textFieldAttrs, $attrs]"
        :value="textFieldValue"
        readonly
        class="filter-input"
        @click:clear="clear(true)"
        v-on="on"
      >
        <template #append>
          <div v-if="value.length > 1" class="filter-input__more">
            +{{ value.length - 1 }} {{ textProps.more || $t('more') }}
          </div>
          <VIcon v-if="value.length > 0" class="mr-1 ml-3" @click.stop="clear(true)">fal fa-times</VIcon>
        </template>
      </VTextField>
    </template>

    <!-- Menu -->
    <VSheet v-bind="sheetProps" class="filter-submenu elevation-0" @mousedown="keepFocus" @mouseup.stop @click.stop>
      <div class="filter-submenu__heading px-4 pt-4">
        <div class="d-flex align-center">
          <h3 class="font-weight-medium">
            {{ title }}
          </h3>
          <VBadge v-if="value.length" class="ml-2" :content="value.length" inline :color="color" />
          <VSpacer />
          <VBtn class="mr-n2 mt-n1" small icon @click="closeMenu">
            <VIcon>fal fa-times</VIcon>
          </VBtn>
        </div>
        <div class="filter-submenu__actions">
          <VBtn text :color="color" :ripple="false" @click="selectAll">
            {{ textProps.selectAll || $t('Select All') }}
          </VBtn>
          <span class="mx-2 title font-weight-light">|</span>
          <VBtn text :color="color" :ripple="false" @click="clear()">{{ textProps.clear || $t('Clear') }}</VBtn>
        </div>
        <VTextField
          v-if="!hideSearch"
          v-model="searchFor"
          class="body-2 mb-3"
          height="34"
          outlined
          placeholder="Search"
          hideDetails
          dense
          clearable
        />
      </div>

      <VList ref="itemList" v-bind="listProps" class="filter-submenu__list py-0">
        <template v-for="(item, index) in listItems">
          <!-- Divider -->
          <VDivider v-if="item.type === 'divider'" :key="index" />

          <!-- Header -->
          <VListItem v-else-if="item.type === 'header'" :key="index" disabled class="grey--text text--darken-2">
            <VListItemTitle class="caption text-uppercase font-weight-medium">
              {{ item.text }}
            </VListItemTitle>
          </VListItem>

          <!-- Regular Item -->
          <VListItem
            v-else
            :key="index"
            :disabled="item.disabled"
            :inputValue="isSelected(item)"
            @click.stop="handleItemClick(item)"
          >
            <template #default="{ active }">
              <!-- Checkbox -->
              <VListItemAction class="mr-4">
                <VCheckbox :inputValue="active" :color="color" :ripple="false" />
              </VListItemAction>
              <!-- Title -->
              <VListItemContent class="body-2">
                <VListItemTitle v-text="item[itemText] || item" />
              </VListItemContent>
              <!-- Description -->
              <VListItemAction>
                <VListItemActionText v-if="item.description" v-text="item.description" />
              </VListItemAction>
            </template>
          </VListItem>
        </template>

        <!-- load more button -->
        <VListItem v-if="showLoadMore && !searchFor" class="justify-center">
          <a @click="loadMore">{{ textProps.loadMore || $t('Load more') }}</a>
        </VListItem>

        <!-- no search result -->
        <VListItem v-if="searchFor && !listItems.length" class="justify-center">
          <span class="subtitle-2 font-weight-light">{{ textProps.noData || $t('No results') }}</span>
        </VListItem>
      </VList>
    </VSheet>
  </VMenu>
</template>

<script>
import { computed, defineComponent, onMounted, onUnmounted, reactive, ref, toRefs, watch } from '@vue/composition-api'
import { search } from '@pitcher/core'
import '../../utils/i18n-checker'

/*
* props *
By default, you can use all v-text-field props on this component

* Object model for item in items array *
item: {
  text:        String => required if type is header
  value:       String => required if type is item or undefined
  type:        undefined | 'item' | 'header' | 'divider' => behaves like item if undefined
  description: String => not required, description is in the end of item row
}

* Mapping item props for text/value *
If you have any other model than this for your text or value,
you can easily use item-text="PROP_NAME" value-text="PROP_NAME" to map those as text/value

* Changing emit return type *
This behaviour is default
If you need to emit the whole object on input, use return-object=true

* Showing limited items *
items-per-page="NUMBER" prop is your friend, if you have too many items

* Editing list, sheet, menu externally *
To be able to edit v-list, v-sheet and v-menu props, you can use
list-props="V-List Props", sheet-props="V-Sheet Props", menu-props="V-Menu Props",
*/

/**
 * Pitcher Filter Dropdown component
 * @inherits v-text-field
 */

export default defineComponent({
  name: 'PFilterDropdown',
  inheritAttrs: false,
  props: {
    textProps: {
      type: Object,
      default: () => ({
        more: '',
        selectAll: '',
        clear: '',
        loadMore: '',
        noData: '',
      }),
      validator: (val) =>
        Object.keys(val).every(
          (key) =>
            ({
              more: true,
              selectAll: true,
              clear: true,
              loadMore: true,
              noData: true,
            }[key] && typeof val[key] === 'string')
        ),
    },
    value: {
      type: Array,
      default: () => [],
    },
    title: String,
    items: {
      type: Array,
      default: () => [],
    },
    itemText: {
      type: [String, Array],
      default: 'text',
    },
    itemValue: {
      type: [String, Array],
      default: 'value',
    },
    returnObject: {
      type: Boolean,
      default: true,
    },
    itemsPerPage: {
      type: Number,
      default: null,
    },
    hideSearch: Boolean,
    menuProps: {
      type: Object,
      default: () => ({
        contentClass: 'rounded-lg',
        closeOnContentClick: false,
        offsetY: true,
        nudgeTop: -10,
      }),
    },
    sheetProps: {
      type: Object,
      default: () => {},
    },
    listProps: {
      type: Object,
      default: () => ({
        maxHeight: 300,
      }),
    },
  },
  emits: ['input'],
  setup(props, ctx) {
    const localState = reactive({
      searchFor: '',
      menuIsOpen: false,
      color: computed(() => ctx.attrs.color || 'primary'),
      shouldSort: false,
      itemsVisible: props.itemsPerPage,
    })

    const textFieldAttrs = computed(() => ({
      dense: true,
      outlined: true,
      hideDetails: true,
      label: props.title,
      prependInnerIcon: ctx.attrs['prepend-inner-icon'] || 'fa fa-filter',
    }))

    const textFieldValue = computed(() => {
      if (!props.value?.length) return ''

      // return only first item
      return props.value[0][props.itemText] || props.value[0]
    })

    // transformed list items for dropdown
    const localListItems = ref(null)
    // getter
    const listItems = computed(() => {
      // if no value saved, save it first
      if (!localListItems.value) {
        localListItems.value = Array.from(props.items)
      }

      // sort
      if (localState.shouldSort) {
        // reset the list
        localListItems.value = Array.from(props.items)

        // if the list has selected values, sort it
        if (props.value.length > 0) {
          localListItems.value.sort((a, b) => (isSelected(a) > isSelected(b) ? -1 : 1))
        }
        localState.shouldSort = false
      }

      // search
      if (localState.searchFor) {
        localListItems.value = search(Array.from(props.items), localState.searchFor, [props.itemValue, props.itemText])
      }

      // show only X amount of items initially
      if (props.itemsPerPage) {
        const result = []

        for (const index in localListItems.value) {
          result.push(localListItems.value[index])

          // get the length of children that are only items
          const itemLength = result.filter((item) => !item.type || item.type === 'item').length

          // if enough items, break the loop
          if (itemLength === localState.itemsVisible) {
            break
          }
        }
        // save items
        localListItems.value = result
      }

      return localListItems.value
    })

    const isSelected = (item) => {
      // if string/number array
      if (item.constructor !== Object) {
        return props.value.includes(item)
      }

      return props.returnObject
        ? props.value.some((orgItem) => orgItem[props.itemValue] === item[props.itemValue])
        : props.value.includes(item[props.itemValue])
    }

    // handle item click
    function handleItemClick(item) {
      // if object array => filter
      // if string/number array, get the item itself
      const originalItem =
        item.constructor === Object
          ? listItems.value.find((orgItem) => orgItem[props.itemValue] === item[props.itemValue])
          : item

      // select
      if (!isSelected(originalItem)) {
        ctx.emit(
          'input',
          props.returnObject ? props.value.concat([originalItem]) : props.value.concat([originalItem[props.itemValue]])
        )

        return
      }

      // unselect
      ctx.emit(
        'input',
        // if it is a string array child, compare orgItem to originalItem
        // otherwise map the object
        props.returnObject
          ? props.value.filter(
              (orgItem) => (orgItem[props.itemValue] || orgItem) !== (originalItem[props.itemValue] || originalItem)
            )
          : props.value.filter((v) => v !== originalItem[props.itemValue])
      )
    }

    function selectAll() {
      // filter out type: header/divider & disabled items
      let filtered = listItems.value.filter((item) => (!item.type || item.type === 'item') && !item.disabled)

      // returnObject is false, map only defined values
      if (!props.returnObject) {
        filtered = filtered.map((item) => item[props.itemValue])
      }

      // map all as objects
      ctx.emit('input', filtered)
    }

    // clear all selected
    const clear = (shouldBlur = false) => {
      ctx.emit('input', [])

      if (shouldBlur) blurInput()
    }

    const closeMenu = (e) => {
      if (e) {
        e.stopPropagation()
        e.preventDefault()
      }
      localState.menuIsOpen = false
      blurInput()
    }

    // to show menu prgrammatically, careful when you use this! It is not in sync with menu show/hide events
    const showMenu = () => (localState.menuIsOpen = true)

    // to keep input focused on menu click events
    const keepFocus = (e) => {
      if (e) {
        e.stopPropagation()
        e.preventDefault()
      }
      ctx.refs.textField.focus()
    }

    // custom blur function
    const blurInput = () => ctx.refs.textField?.blur()

    // Load more button logic
    const showLoadMore = computed(() => props.itemsPerPage && props.items.length !== localState.itemsVisible)

    function loadMore() {
      const toSet = localState.itemsVisible + props.itemsPerPage

      localState.itemsVisible = toSet > props.items.length ? props.items.length : toSet
      localState.shouldSort = true
      // Scroll the list items container to the bottom when items are added
      ctx.root.$nextTick(() => {
        ctx.refs.itemList.$el.scrollTop = ctx.refs.itemList.$el.scrollHeight
      })
    }

    // this to fix a click glitch on input
    const inputSlot = computed(() => ctx.refs.textField?.$el.querySelector('.v-input__slot'))

    onMounted(() => {
      inputSlot.value.addEventListener('click', showMenu)
    })

    onUnmounted(() => {
      inputSlot.value.removeEventListener('click', showMenu)
    })

    watch(
      () => localState.menuIsOpen,
      (newVal) => {
        // menu will be shown
        if (newVal) {
          // sort the list before showing the list
          localState.shouldSort = true
          localState.itemsVisible = props.itemsPerPage
          localState.searchFor = ''

          // needs time because of the transition
          setTimeout(() => {
            ctx.refs.itemList.$el.scrollTop = 0
          }, 50)
        }
      }
    )

    // if searchFor gets empty, trigger sorting
    watch(
      () => localState.searchFor,
      (newVal, oldVal) => {
        if (oldVal?.length > 0 && !newVal) {
          localState.shouldSort = true
        }
      }
    )

    return {
      ...toRefs(localState),
      textFieldAttrs,
      textFieldValue,
      listItems,
      isSelected,
      handleItemClick,
      selectAll,
      clear,
      closeMenu,
      keepFocus,
      showLoadMore,
      loadMore,
    }
  },
})
</script>

<style lang="scss" scoped>
.v-menu-transition-enter-active,
.v-menu-transition-leave-active {
  transition-duration: 0.2s;
}
.filter-input {
  ::v-deep {
    .v-input__append-inner {
      margin: auto 0 !important;
    }

    .v-input__prepend-inner {
      .v-icon {
        font-size: 14px;
      }
    }

    .v-input__slot {
      cursor: pointer;
    }

    input {
      text-overflow: ellipsis;
      cursor: pointer;
    }
  }

  &__more {
    display: flex;
    align-items: center;
    font-size: 12px;
    min-width: 40px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: map-get($grey, 'base');
  }
}

.filter-submenu {
  &__heading {
    ::v-deep {
      .v-text-field .v-input__control .v-input__slot {
        min-height: auto !important;
        display: flex !important;
        align-items: center !important;
      }

      .v-input__slot {
        padding-right: 8px !important;
      }

      .v-input__append-inner {
        margin: auto 0 !important;
      }
    }
  }

  &__actions {
    display: flex;
    align-items: center;
    margin-bottom: 8px;

    .v-btn {
      padding: 0;
      font-weight: 400;
      min-width: unset;

      &::before {
        display: none;
      }
    }
  }

  &__list {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    border-bottom-left-radius: inherit;
    border-bottom-right-radius: inherit;
  }
}
</style>
