<template>
    <div
        v-click-outside="closeFilter"
        @click.stop
        class="ta-filter-dropdown">
        <div v-if="type === 'records'">
            <div class="ta-filter-dropdown__body">
                <div v-for="(option, index) in options" :key="index">
                    <span
                        class="d-flex"
                        :class="{ 'mb-2' : index !== options.length - 1}">
                        <Checkbox
                            classes="align-self-baseline"
                            v-model="option.active"
                            :label="option.value">
                        </Checkbox>
                    </span>
                </div>
            </div>
        </div>

        <div v-else-if="type === 'remote'">
            <div
                v-if="column.filterOptions.loadFilterOptions && enableSearch"
                class="ta-filter-dropdown__header">
                <input
                    v-model="searchTerm"
                    @input="loadSearch"
                    class="form-control"
                    placeholder="Search filters...">
            </div>

            <div
                ref="filterDropdownBody"
                class="ta-filter-dropdown__body">
                <Transition mode="out-in" name="slide-toggle">
                    <div :key="2" v-if="remoteFilterOptions && !remoteFilterOptions.length">
                        No filter options available
                    </div>

                    <div :key="1" v-else>
                        <TransitionGroup name="slide-toggle">
                            <Checkbox
                                v-for="option in remoteFilterOptions"
                                :key="option.id"
                                v-model="option.active"
                                :label="option.value">
                            </Checkbox>
                        </TransitionGroup>
                    </div>

                </Transition>
                <div
                    v-if="initialLoading || (remoteFilterMeta && remoteFilterMeta.cursor.next)"
                    class="d-flex justify-content-center align_items-center">
                    <div class="mr-2">
                        <i
                            class="fa fa-spin fa-spinner"
                            :aria-hidden="true"/>
                    </div>
                    Loading
                </div>
            </div>
        </div>
    </div>
</template>

<script>
import _ from 'lodash'
import Checkbox from './Checkbox.vue'

export default {
    components: {
        Checkbox,
    },
    props: {
        column: {
            type: Object,
            default: () => ({}),
        },
        options: {
            type: Array,
            default: () => [],
        },
    },
    data() {
        return {
            initialLoading: false,
            remoteFilterOptions: null,
            remoteFilterMeta: null,
            searchTerm: null,
        }
    },
    computed: {
        type() {
            return this.column.filterOptions.type ? this.column.filterOptions.type : 'records'
        },
        field() {
            return this.column.filterOptions.filterField ? this.column.filterOptions.filterField : 'name'
        },
        identifier() {
            return this.column.filterOptions.filterIdentifier ? this.column.filterOptions.filterIdentifier : 'uuid'
        },
        activeOptions() {
            return this.remoteFilterOptions
                ? this.remoteFilterOptions.filter(opt => opt.active)
                : []
        },
        enableSearch() {
            if (this.column.filterOptions.enableSearch === undefined) return true
            return this.column.filterOptions.enableSearch
        },
    },
    watch: {
        activeOptions: {
            deep: true,
            handler(newOptions, oldOptions) {
                const newHash = newOptions.map(opt => JSON.stringify(opt)).join('')
                const oldHash = oldOptions.map(opt => JSON.stringify(opt)).join('')

                // We only want to emit 'change-options' in the event that selected (not available) options change
                if (newHash !== oldHash) {
                    this.$emit('change-options', { field: this.column.field, options: this.activeOptions })
                }
            },
        },
    },
    methods: {
        closeFilter() {
            this.$emit('close')
        },
        formatFilterFunction(row) {
            const filterFn = this.column.filterOptions.filterFieldFn
            if (!filterFn) {
                return row[this.field]
            }

            return filterFn(row)
        },
        watchScroll() {
            this.$refs.filterDropdownBody.onscroll = () => {
                const loadThreshold = Math.floor(this.$refs.filterDropdownBody.scrollHeight * 0.80)
                const { scrollTop } = this.$refs.filterDropdownBody
                const { offsetHeight } = this.$refs.filterDropdownBody
                const currentScroll = scrollTop + offsetHeight

                // The bottom of the scroll area has scrolled past 80% of the content
                // We can assume that they have not yet found what they are looking, lets load more
                if (currentScroll > loadThreshold) {
                    this.loadNextPage()
                }
            }
        },

        loadNextPage: _.debounce(function() { // eslint-disable-line
            this.loadRemoteFilterOptions(data => {
                const newFilterOptions = data.map(opt => ({
                    id: opt[this.identifier], value: this.formatFilterFunction(opt), active: false,
                }))

                // Merge new filter options and old filter options
                // (we wish to preserve everything we have loaded up to this point)
                this.remoteFilterOptions = _.uniqBy([
                    ...this.remoteFilterOptions,
                    ...newFilterOptions,
                ], 'id')
            }, { cursor: this.remoteFilterMeta.cursor.next }, true)
        }, 300),

        loadSearch: _.debounce(function() { // eslint-disable-line
            this.loadRemoteFilterOptions(data => {
                const activeOptions = this.remoteFilterOptions
                    .filter(opt => opt.active)

                // Create a comparison array, this will be used for filtering later
                const activeFilterComparisons = activeOptions
                    .map(({ id, value }) => ({ id, value }))
                    .map(opt => JSON.stringify(opt))

                const newFilterOptions = data
                    // Partiall construct the format we will use for comparison & eventually as filter options
                    .map(opt => ({ id: opt[this.identifier], value: this.formatFilterFunction(opt) }))
                    // We only want NEW filter options and not repeating already active ones
                    .filter(opt => !activeFilterComparisons.includes(JSON.stringify(opt)))
                    // Add the 'active' key that will be used to tell if a filter is/isnt active
                    .map(opt => ({ ...opt, active: false }))

                // Merge active options and new options (ensure no overlap)
                this.remoteFilterOptions = [
                    // We need a way to deactivate/see active ones are active -
                    // these will eventually bubble up to the top
                    ...this.activeOptions,
                    // Options that dont currently match the active filters
                    ...newFilterOptions,
                ]
            }, { search: this.searchTerm }, true)
        }, 300),

        getInitialFilterOptions() {
            if (this.column.filterOptions.loadFilterOptions) {
                this.initialLoading = true
                this.loadRemoteFilterOptions(data => {
                    const remoteOpts = data.map(opt => ({
                        id: opt[this.identifier],
                        value: this.formatFilterFunction(opt),
                        active: false,
                    }))

                    this.remoteFilterOptions = _.uniqBy([
                        ...this.options,
                        ...remoteOpts,
                    ], 'id')
                })
            } else if (this.column.filterOptions.staticFilterOptions) {
                this.loadStaticFilterOptions()
            }
        },

        loadRemoteFilterOptions(cb = () => ({}), params = {}, cycleNextPageEvent = false) {
            // Disable the scroll event so we aren't stacking load requests
            if (cycleNextPageEvent) this.$refs.filterDropdownBody.onscroll = null

            return this.column.filterOptions.loadFilterOptions(params)
                .then(({ data }) => {
                    cb(data.data)

                    // Meta needs to be set no matter what
                    this.remoteFilterMeta = data.meta
                    this.initialLoading = false
                })
                // After the request completes check for another next cursor, if there is one re-enable scroll event
                .finally(() => {
                    if (cycleNextPageEvent && this.remoteFilterMeta.cursor.next) {
                        this.watchScroll()
                    }
                })
        },

        loadStaticFilterOptions() {
            const staticOpts = this.column.filterOptions.staticFilterOptions.map(({ id, value }) => ({
                id,
                value,
                active: false,
            }))

            if (this.column.filterOptions.promoteActiveOptions) {
                this.remoteFilterOptions = _.uniqBy([
                    ...this.options,
                    ...staticOpts,
                ], 'id')
            } else {
                this.remoteFilterOptions = staticOpts.map(staticOpt => {
                    const chosenOption = this.options.find(option => option.id === staticOpt.id)
                    return {
                        ...staticOpt,
                        active: chosenOption ? chosenOption.active : false,
                    }
                })
            }
        },
    },
    mounted() {
        if (this.type === 'remote') {
            this.watchScroll()

            this.getInitialFilterOptions()
        }
    },
}
</script>
