import { Pagination } from './pagination.class';
import { attributeSelector } from './selector-utilities';
import { AjaxFormReload } from './ajax-form-reload';
import { RemoveFilters } from './remove-filters';
import { scrollToAnimated } from '@nimius/animation-utility';
import { ResultsListFilterGroup } from './results-list-filter-group';
import { fireEvent } from '@nimius/event-utility';

const ATTRIBUTE = Object.freeze({
    RESULT_LIST: 'data-toubiz-results-list',
    TOGGLE_BUTTON: 'data-toubiz-results-list.filter-toggle-button',
    PAGINATION: 'data-toubiz-pagination',
    LOADING: 'data-toubiz-results-loading',
    PAGE_INPUT: 'data-toubiz-results-list.page',
    TAGS: 'data-toubiz-results.filter-tags',
    EMPTY_TAGS_REDIRECT: 'data-toubiz-results.filter-tags.redirect-if-empty',
    FACET: 'data-toubiz-result-list.facet',
    SCROLL_OFFSET: 'data-toubiz-results.scroll-to-top.offset',
    SCROLL_SPEED: 'data-toubiz-results.scroll-to-top.speed',

    FILTER_GROUP: 'data-toubiz-results-list.filter-group',
    FILTER_FORM: 'data-toubiz-results-list.filter-form',
    FILTER_LIST: 'data-toubiz-results-list.filter-list',
    FILTER_LIST_TARGET: 'data-toubiz-results-list.filter-list-target',

    FORM_ACTION: 'data-toubiz-results-list.filter-form.ajax-action',
    FORM_METHOD: 'data-toubiz-results-list.filter-form.ajax-method',
    FORM_UPDATE_URL: 'data-toubiz-results-list.filter-form.ajax-update-url',
    FORM_UPDATE_URL_PARAMETERS: 'data-toubiz-results-list.filter-form.ajax-update-url-parameters',
    FORM_UPDATE_URL_PLUGIN_NAMESPACE: 'data-toubiz-results-list.filter-form.ajax-update-url-plugin-namespace',
    ACTIVE_CLASSES: 'data-toubiz-results-list.active-classes',
    INACTIVE_CLASSES: 'data-toubiz-results-list.inactive-classes',
});

/**
 * @typedef {object} ResultsListController~scrollToTopSettings
 * @property {int} offset
 * @property {int} speed
 */

/**
 * Shared behaviour for results lists.
 * This class combines a couple of components in order to form a base for list views.
 *
 * This class makes a number of assumptions:
 * - Every element described must be contained in the node passed to the controller.
 * - There must be a pagination marked with `[data-toubiz-pagination]` using the markup expected by
 *   the `Pagination` component.
 * - There may be a list of 'tags': A list of current filters that are then reset on click
 * - There must be a filter form marked with `data-toubiz-results-list.filter-form`
 *      - This form in turn can have the following attributes:
 *      - `data-toubiz-results-list.filter-form.ajax-action`
 *      - `data-toubiz-results-list.filter-form.ajax-method`
 *      - `data-toubiz-results-list.filter-form.ajax-update-url` (Set to 1 if the URL should be updated)
 *      - `data-toubiz-results-list.filter-form.ajax-update-url-parameters`
 *      - `data-toubiz-results-list.filter-form.ajax-update-url-plugin-namespace`
 * - There must be an element `[data-toubiz-results-list]` that contains the content that should be reloaded
 *   via AJAX request.
 */
export class ResultsListController {

    /**
     * @param {HTMLElement} node
     */
    constructor (node) {
        /** @private {HTMLElement} */
        this.container = node;

        /** @private {HTMLElement} */
        this.list = this.select(attributeSelector(ATTRIBUTE.RESULT_LIST));

        /** @private {HTMLElement} */
        this.toggleButton = this.select(attributeSelector(ATTRIBUTE.TOGGLE_BUTTON));

        /** @private {HTMLInputElement} */
        this.pageIndexInput = this.select(attributeSelector(ATTRIBUTE.PAGE_INPUT));

        /** @private {ResultsListController~scrollToTopSettings} */
        this.scrollToTopSettings = {
            offset: parseInt(node.getAttribute(ATTRIBUTE.SCROLL_OFFSET), 10) || 120,
            speed: parseInt(node.getAttribute(ATTRIBUTE.SCROLL_SPEED), 10) || 300,
        };

        /** @protected {AjaxFormReload} */
        this.formReload = this.initializeFormReload();

        this.filters = [];

        this.initializeFilter();
        this.initializePagination();
        this.initializeTags();
        this.initializeFilterGroups();
        this.onInitialize();
        this.addClassesToElementsIfChildrenActive();
    }

    /** @protected */
    onInitialize() {}

    /** @protected */
    onReinitialize() {}

    /** @protected */
    onReload() {}

    /**
     * @protected
     * @param {HTMLFormElement} form
     * @returns {AjaxFormReload~options}
     */
    formReloadOptions(form) {
        let lastKnownPage = parseInt(this.pageIndexInput.value, 10) || 1;
        const reInitialize = () => {
            this.container.setAttribute(ATTRIBUTE.LOADING, false);
            this.initializeTags();
            this.initializePagination();
            this.onReinitialize();
            this.addClassesToElementsIfChildrenActive();
            lastKnownPage = parseInt(this.pageIndexInput.value, 10) || 1;
        };

        return {
            target: this.list,
            action: form.getAttribute(ATTRIBUTE.FORM_ACTION) || form.action,
            method: form.getAttribute(ATTRIBUTE.FORM_METHOD) || form.method,
            updateUrl: form.getAttribute(ATTRIBUTE.FORM_UPDATE_URL) === '1',
            updateUrlParameters: form.getAttribute(ATTRIBUTE.FORM_UPDATE_URL_PARAMETERS) || '',
            updateUrlPluginNamespace: form.getAttribute(ATTRIBUTE.FORM_UPDATE_URL_PLUGIN_NAMESPACE) || '',
            beforeLoad: () => {
                this.container.setAttribute(ATTRIBUTE.LOADING, true);

                if (this.pagination) {
                    // Reset pagination when something else (non-pagination) has changed.
                    const currentPage = parseInt(this.pageIndexInput.value, 10) || 1;
                    if (currentPage !== 1 && currentPage === lastKnownPage) {
                        this.resetPagination();
                    }
                }

                this.scrollToTop();
                this.addClassesToElementsIfChildrenActive();
                this.onReload();
            },
            afterLoad: reInitialize,
            onLoadError: reInitialize,
        };
    }

    /**
     * @returns {AjaxFormReload}
     * @private
     */
    initializeFormReload() {
        const form = this.select(attributeSelector(ATTRIBUTE.FILTER_FORM));
        return new AjaxFormReload(form, this.formReloadOptions(form));
    }

    /**
     * @param {string} selector
     * @param {boolean} multiple
     * @returns {HTMLElement|NodeList}
     * @protected
     */
    select(selector, multiple = false) {
        if (multiple) {
            return document.querySelectorAll(selector);
        }

        const result = document.querySelector(selector);
        if (!result) {
            console.warn(`
                Required element not found. Please ensure that ${selector} exists on the page.
            `);
        }

        return result;
    }

    /** @private */
    initializePagination() {
        const wrapper = document.querySelector(attributeSelector(ATTRIBUTE.PAGINATION));
        if (!wrapper) {
            return;
        }

        this.pagination = new Pagination(wrapper);
        wrapper.addEventListener('toubiz-pagination.page-select', event => {
            const currentPage = parseInt(this.pageIndexInput.value, 10) || 1;
            if (event.detail.page !== currentPage) {
                this.pageIndexInput.value = event.detail.page;
                this.pagination.setPage(event.detail.page);
                this.formReload.reload();
            }
        });
    }

    /** @private */
    initializeFilterGroups() {
        for (const filterGroupNode of document.querySelectorAll(attributeSelector(ATTRIBUTE.FILTER_GROUP))) {
            const filterGroup = new ResultsListFilterGroup(filterGroupNode);

            // Toggle filter group button visibility on mobile sub filter navigation
            if (filterGroup.toggleButton) {
                filterGroup.toggleButton.addEventListener('target-enhancement.open', () => {
                    this.toggleButton.classList.add('is-collapsed');
                });
                filterGroup.toggleButton.addEventListener('target-enhancement.close', () => {
                    this.toggleButton.classList.remove('is-collapsed');
                });
            }

            filterGroupNode.addEventListener('filter-group.apply', () => {
                fireEvent(this.toggleButton, 'target-enhancement.close');
                this.resetPagination();
                this.formReload.reload();
            });

            filterGroupNode.addEventListener('filter-group.reset', () => {
                fireEvent(this.toggleButton, 'target-enhancement.close');
                this.resetPagination();
                this.formReload.reload();
            });

            this.filters.push(filterGroup);
        }
    }

    /** @private */
    initializeTags() {
        const tags = document.querySelector(attributeSelector(ATTRIBUTE.TAGS));
        if (!tags) {
            return;
        }

        new RemoveFilters(tags, this.select(attributeSelector(ATTRIBUTE.FILTER_FORM)));
        tags.addEventListener('tagRemoved', () => {
            this.filters.forEach(filterGroup => {
                filterGroup.update();
            });

            if (this.isDateSelection) {
                this.resetDateSelection();
            }

            this.formReload.reload();
        });

        if (tags.hasAttribute(ATTRIBUTE.EMPTY_TAGS_REDIRECT)) {
            tags.addEventListener('allTagsRemoved', () => {
                document.location.href = tags.getAttribute(ATTRIBUTE.EMPTY_TAGS_REDIRECT);
            });
        }
    }

    /** @private */
    resetPagination() {
        if (this.pagination) {
            this.pageIndexInput.value = 1;
            this.pagination.setPage(1);
        }
    }

    /** @private */
    initializeFilter() {
        const facets = this.select(attributeSelector(ATTRIBUTE.FACET), true);
        for (const facet of facets) {
            facet.addEventListener('click', () => this.resetPagination());
        }
    }

    /** @private */
    scrollToTop() {
        const position = this.container.getBoundingClientRect().top + window.scrollY;
        scrollToAnimated(position - this.scrollToTopSettings.offset, this.scrollToTopSettings.speed);
    }

    /** @public */
    toggleElementClasses (
        elements = [],
        state = true,
        activeClassAttribute = ATTRIBUTE.ACTIVE_CLASSES,
        inactiveClassAttribute = ATTRIBUTE.INACTIVE_CLASSES
    ) {
        for (const element of elements) {
            let activeClasses = [];
            if (element.hasAttribute(activeClassAttribute)) {
                activeClasses = element.getAttribute(activeClassAttribute).split(' ');
                element.classList.remove(...activeClasses);
            }

            let inactiveClasses = [];
            if (element.hasAttribute(inactiveClassAttribute)) {
                inactiveClasses = element.getAttribute(inactiveClassAttribute).split(' ');
                element.classList.remove(...inactiveClasses);
            }

            if (state && activeClasses.length > 0) {
                element.classList.add(...activeClasses);
            } else if (inactiveClasses.length > 0) {
                element.classList.add(...inactiveClasses);
            }
        }
    }

    /** @private */
    addClassesToElementsIfChildrenActive() {
        for (const filterGroupToggle of this.select(attributeSelector(ATTRIBUTE.FILTER_LIST_TARGET), true)) {
            const isChildActive = this.isChildActive(filterGroupToggle);
            this.toggleElementClasses(
                [ filterGroupToggle ],
                isChildActive,
                ATTRIBUTE.ACTIVE_CLASSES,
                ATTRIBUTE.INACTIVE_CLASSES
            );
        }
    }

    /**
     * @param {HTMLElement} parent
     * @returns {boolean}
     * @private
     */
    isChildActive(filterGroupToggle) {
        const target = filterGroupToggle.getAttribute(ATTRIBUTE.FILTER_LIST_TARGET);
        const filterList = target ? this.select(target) : null;

        if (filterList) {
            for (const input of filterList.querySelectorAll('input')) {
                const type = input.getAttribute('type');

                if (type === 'checkbox' && input.checked) {
                    return true;
                }

                if (
                    type === 'number'
                    && input.value !== ''
                    && parseInt(input.value, 10) !== parseInt(input.placeholder, 10)
                ) {
                    return true;
                }
            }
        }

        return false;
    }

}
