import template from './sw-entity-multi-select.html.twig';
import './sw-entity-multi-select.scss';

const { Component, Mixin } = Shopware;
const { debounce, get } = Shopware.Utils;
const { Criteria, EntityCollection } = Shopware.Data;

/**
 * @private
 */
Component.register('sw-entity-multi-select', {
    template,

    inheritAttrs: false,

    compatConfig: Shopware.compatConfig,

    inject: [
        'repositoryFactory',
        'feature',
    ],

    emits: [
        'search',
        'update:entityCollection',
        'item-add',
        'item-remove',
        'display-values-expand',
        'search-term-change',
    ],

    mixins: [
        Mixin.getByName('remove-api-error'),
    ],

    props: {
        labelProperty: {
            type: [
                String,
                Array,
            ],
            required: false,
            default: 'name',
        },

        resultLimit: {
            type: Number,
            required: false,
            default: 25,
        },

        valueLimit: {
            type: Number,
            required: false,
            default: 5,
        },

        placeholder: {
            type: String,
            required: false,
            default: '',
        },

        alwaysShowPlaceholder: {
            type: Boolean,
            required: false,
            default: false,
        },

        criteria: {
            type: Object,
            required: false,
            default(props) {
                return new Criteria(1, props.resultLimit);
            },
        },

        disabled: {
            type: Boolean,
            required: false,
            // eslint-disable-next-line vue/no-boolean-default
            default: undefined,
        },

        highlightSearchTerm: {
            type: Boolean,
            required: false,
            // eslint-disable-next-line vue/no-boolean-default
            default: true,
        },

        entityCollection: {
            type: Array,
            required: true,
        },

        entityName: {
            type: String,
            required: false,
            default: null,
        },

        context: {
            type: Object,
            required: false,
            default() {
                return Shopware.Context.api;
            },
        },

        hideLabels: {
            type: Boolean,
            required: false,
            default: false,
        },

        selectionDisablingMethod: {
            type: Function,
            required: false,
            default: () => false,
        },

        descriptionPosition: {
            type: String,
            required: false,
            default: 'right',
            validValues: [
                'bottom',
                'right',
            ],
            validator(value) {
                return [
                    'bottom',
                    'right',
                ].includes(value);
            },
        },

        advancedSelectionComponent: {
            type: String,
            required: false,
            default() {
                return '';
            },
        },

        advancedSelectionParameters: {
            type: Object,
            required: false,
            default() {
                return {};
            },
        },

        displayVariants: {
            type: Boolean,
            required: false,
            default: false,
        },
        label: {
            type: String,
            required: false,
            default: undefined,
        },
    },

    data() {
        return {
            searchTerm: '',
            limit: this.valueLimit,
            searchCriteria: null,
            isLoading: false,
            currentCollection: null,
            resultCollection: null,
            isAdvancedSelectionModalVisible: false,
        };
    },

    computed: {
        listeners() {
            // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
            if (this.isCompatEnabled('INSTANCE_LISTENERS')) {
                return this.$listeners;
            }

            return {};
        },

        repository() {
            return this.repositoryFactory.create(this.entityName || this.entityCollection.entity);
        },

        visibleValues() {
            if (!this.currentCollection || this.currentCollection.length <= 0) {
                return [];
            }

            return this.currentCollection.slice(0, this.limit);
        },

        totalValuesCount() {
            if (this.currentCollection.length) {
                return this.currentCollection.length;
            }

            return 0;
        },

        invisibleValueCount() {
            if (!this.currentCollection) {
                return 0;
            }

            return Math.max(0, this.totalValuesCount - this.limit);
        },

        isAdvancedSelectionActive() {
            return this.advancedSelectionComponent && Component.getComponentRegistry().has(this.advancedSelectionComponent);
        },
    },

    watch: {
        entityCollection() {
            this.refreshCurrentCollection();
        },
    },

    created() {
        this.createdComponent();
    },

    methods: {
        createdComponent() {
            this.refreshCurrentCollection();
        },

        refreshCurrentCollection() {
            if (this.entityCollection) {
                this.currentCollection = EntityCollection.fromCollection(this.entityCollection);
            }
        },

        createEmptyCollection() {
            return new EntityCollection(
                this.entityCollection.source,
                this.entityCollection.entity,
                this.entityCollection.context,
                this.entityCollection.criteria,
            );
        },

        isSelected(item) {
            return this.currentCollection.has(item.id);
        },

        loadData() {
            this.isLoading = true;

            return this.repository.search(this.criteria, { ...this.context, inheritance: true }).then((result) => {
                this.displaySearch(result);

                this.isLoading = false;

                return result;
            });
        },

        search() {
            if (this.criteria.term === this.searchTerm) {
                return Promise.resolve();
            }

            this.resetCriteria();
            this.resultCollection = null;

            const searchPromise = this.loadData().then((result) => {
                this.resetActiveItem();
                return result;
            });
            this.$emit('search', searchPromise);

            return searchPromise;
        },

        displaySearch(result) {
            if (!this.resultCollection) {
                this.resultCollection = result;
            } else {
                result.forEach((item) => {
                    // Prevent duplicate entries
                    if (!this.resultCollection.has(item.id)) {
                        this.resultCollection.push(item);
                    }
                });
            }
        },

        displayLabelProperty(item) {
            const labelProperties = [];

            if (Array.isArray(this.labelProperty)) {
                labelProperties.push(...this.labelProperty);
            } else {
                labelProperties.push(this.labelProperty);
            }

            return labelProperties
                .map((labelProperty) => {
                    return this.getKey(item, labelProperty) || this.getKey(item, `translated.${labelProperty}`);
                })
                .join(' ');
        },

        resetActiveItem() {
            if (this.$refs.swSelectResultList) {
                this.$refs.swSelectResultList.setActiveItemIndex(0);
            }
        },

        resetCriteria() {
            this.criteria.setPage(1);
            this.criteria.setLimit(this.resultLimit);
            this.criteria.setTerm(this.searchTerm);
        },

        paginate() {
            if (!this.resultCollection || this.resultCollection.total < this.criteria.page * this.criteria.limit) {
                return;
            }

            this.criteria.setPage(this.criteria.page + 1);

            this.loadData();
        },

        emitChanges(newCollection) {
            this.$emit('update:entityCollection', newCollection);
        },

        addItem(item) {
            if (this.isSelected(item)) {
                this.remove(item);
                return;
            }

            this.$emit('item-add', item);

            const newCollection = EntityCollection.fromCollection(this.currentCollection);
            newCollection.add(item);

            this.emitChanges(newCollection);

            this.$refs.selectionList.focus();
            this.$refs.selectionList.select();
        },

        remove(item) {
            this.$emit('item-remove', item);

            const newCollection = EntityCollection.fromCollection(this.currentCollection);
            newCollection.remove(item.id);

            this.emitChanges(newCollection);
        },

        removeLastItem() {
            if (!this.currentCollection.length) {
                return;
            }

            if (this.invisibleValueCount > 0) {
                this.expandValueLimit();
                return;
            }

            const lastSelection = this.currentCollection[this.currentCollection.length - 1];
            this.remove(lastSelection);
        },

        onSelectExpanded() {
            this.resetCriteria();
            this.resultCollection = null;

            this.loadData();

            this.$refs.selectionList.focus();
        },

        onSelectCollapsed() {
            this.searchTerm = '';
            this.$refs.selectionList.blur();
        },

        expandValueLimit() {
            this.$emit('display-values-expand');

            this.limit += this.limit;
        },

        onSearchTermChange(term) {
            this.searchTerm = term;
            this.$emit('search-term-change', term);
            this.debouncedSearch();
        },

        debouncedSearch: debounce(function updateSearchTerm() {
            this.search();
        }, 400),

        // Used to reset the results. Normally all search results are appended to the results list, because of pagination.
        // Useful when the criteria changes from outside and we have to search again because of changed filters for example.
        resetResultCollection() {
            this.resultCollection = null;

            // Direct new search if the select field is expanded
            if (this.$refs.selectBase.expanded) {
                this.loadData();
            }
        },

        getKey(object, keyPath, defaultValue) {
            return get(object, keyPath, defaultValue);
        },

        isSelectionDisabled(selection) {
            if (this.disabled) {
                return true;
            }

            return this.selectionDisablingMethod(selection);
        },

        openAdvancedSelectionModal() {
            this.isAdvancedSelectionModalVisible = true;
        },

        closeAdvancedSelectionModal() {
            this.isAdvancedSelectionModalVisible = false;
        },

        onAdvancedSelectionSubmit(selectedItems) {
            const newCollection = this.createEmptyCollection();

            selectedItems.forEach((item) => {
                newCollection.add(item);
            });

            this.emitChanges(newCollection);

            this.$refs.selectionList.focus();
            this.$refs.selectionList.select();
        },

        clearSelection() {
            this.emitChanges(this.createEmptyCollection());
            this.searchTerm = '';
            this.$refs.selectionList.blur();
        },
    },
});
