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 { fireEvent } from '@nimius/event-utility';

const ATTRIBUTE = Object.freeze({
    RESULT_LIST: 'data-toubiz-results-list',
    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_FORM: 'data-toubiz-results-list.filter-form',
    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',
    ADD_CLASS_IF_ACTIVE: 'data-toubiz-results-list.add-class-if-active',
});

/**
 * @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 {HTMLInputElement} */
        this.pageIndexInput = this.select(attributeSelector(ATTRIBUTE.PAGE_INPUT));

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

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

        this.initializeFilter();
        this.initializePagination();
        this.initializeTags();
        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: async () => {
                this.container.setAttribute(ATTRIBUTE.LOADING, true);
                this.scrollToTop();
                this.closeAllTargetEnhancedPopups();
                this.addClassesToElementsIfChildrenActive();
                await this.onReload();

                // Reset pagination when something else (non-pagination) has changed.
                const currentPage = parseInt(this.pageIndexInput.value, 10) || 1;
                if (currentPage === lastKnownPage) {
                    this.pageIndexInput.value = '1';
                }
            },
            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;
        }

        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.formReload.reload();
            }
        });
    }

    /** @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.formReload.reload());

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

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

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

    /** @private */
    closeAllTargetEnhancedPopups() {
        for (const link of this.formReload.form.querySelectorAll('[data-target-enhancement]')) {
            fireEvent(link, 'target-enhancement.close');
        }
    }

    /** @private */
    addClassesToElementsIfChildrenActive() {
        for (const element of this.select(attributeSelector(ATTRIBUTE.ADD_CLASS_IF_ACTIVE), true)) {
            const href = element.getAttribute('href');
            const container = href ? this.select(href) : element;
            const classes = element.getAttribute(ATTRIBUTE.ADD_CLASS_IF_ACTIVE).split(' ');

            element.classList.remove(...classes);
            if (this.isChildActive(container)) {
                element.classList.add(...classes);
            }
        }
    }

    /**
     * @param {HTMLElement} parent
     * @returns {boolean}
     * @private
     */
    isChildActive(parent) {
        for (const input of parent.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;
    }

}
