import { attributeSelector } from './selector-utilities';
import { ChildFilter } from './child-filter';
import { stripMultilineIndention } from './string-utilities';

/**
 * @typedef {object} ItemFilter~viewOptions
 * @param {Function} limit
 */

const VIEW = Object.freeze({
    LIST: 'list',
    MORE: 'more',
});

const ATTRIBUTE = Object.freeze({
    LIST_LIMIT: 'data-item-filter.list.limit',
    MORE_LIMIT: 'data-item-filter.more.limit',
    SHOW_MORE: 'data-item-filter.show-more',
    SHOW_LESS: 'data-item-filter.show-less',
    ITEM_CONTAINER: 'data-item-filter.item-container',
    QUERY: 'data-item-filter.query',
});

/**
 * Displays a Filterable list that is truncated if it contains to many items.
 * A 'show more' button can be used to show all of the options.
 *
 * The following elements must exist inside of the given container:
 * - `[data-item-filter.item-container="list"]`: Container for all items that are searchable & expandable.
 * - `[data-item-filter.query]`: The search field(s) that are used to search the options.
 * - `[data-item-filter.show-more]`: Show more button (visible when list is truncated).
 *                                   It is recommended to hide this button by default: It will be displayed by JS.
 * - `[data-item-filter.show-less]`: Show less button (visible when list is expanded).
 *                                   It is recommended to hide this button by default: It will be displayed by JS.
 *
 * The following attributes can be added to the container itself to change the behaviour:
 * - `[data-item-filter.list.limit]`: Items displayed in truncated view (defaults to 3).
 * - `[data-item-filter.more.limit]`: Items displayed in expanded view (defaults to Infinity).
 *
 * @example
 * <div data-item-filter>
 *     <input type="text" data-item-filter.query />
 *
 *      <ul data-item-filter.item-container="list">
 *          <li><input type="checkbox">Foo</li>
 *          <li><input type="checkbox">Bar</li>
 *          <li><input type="checkbox">Baz</li>
 *      </ul>
 *
 *      <button data-item-filter.show-more type="button" style="display: none">show more</button>
 *      <button data-item-filter.show-less type="button" style="display: none">show less</button>
 * </div>
 */
export class ItemFilter {

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

        /** @private {NodeList} */
        this.showMoreButtons = container.querySelectorAll(attributeSelector(ATTRIBUTE.SHOW_MORE));

        /** @private {NodeList} */
        this.showLessButtons = container.querySelectorAll(attributeSelector(ATTRIBUTE.SHOW_LESS));

        /** @private {ChildFilter} */
        this.filter = new ChildFilter(container, {
            query: container.querySelectorAll(attributeSelector(ATTRIBUTE.QUERY)),
            selector: `${attributeSelector(ATTRIBUTE.ITEM_CONTAINER)} > *`,
            onApply: ({ matching, displayed }) => {
                this.toggleButtonVisibility(
                    this.view === VIEW.LIST,
                    matching.length > displayed.length
                );
            },
        });

        /** @private {object<string, ItemFilter~viewOptions>} */
        this.options = this.initializeOptions();

        /** @private {string} */
        this.view = VIEW.LIST;

        this.printHelpfulWarningMessages();
        this.render();
        this.initializeEventListeners();
    }

    /**
     * Main render method: Applies filters & conditional displays.
     * This method should be called every time a property gets changed
     * to ensure that the displayed elements are updated correctly.
     *
     * @public
     * @param {string|null} query
     */
    render(query = null) {
        this.filter.limit = this.options[this.view].limit;
        const { items, matching, displayed } = this.filter.apply(query);

        const container = this.container.querySelector(attributeSelector(ATTRIBUTE.ITEM_CONTAINER));
        for (const item of items) {
            container.appendChild(item);
        }

        this.toggleButtonVisibility(
            this.view === VIEW.LIST,
            matching.length > displayed.length
        );
    }

    /** @public */
    showMore() {
        this.view = VIEW.MORE;
        this.render();
    }

    /** @public */
    showLess() {
        this.view = VIEW.LIST;
        this.render();
    }


    /**
     * @private
     * @param {boolean} isList
     * @param {boolean} hasMoreItems
     */
    toggleButtonVisibility(isList, hasMoreItems) {
        for (const showMore of this.showMoreButtons) {
            showMore.style.display = isList && hasMoreItems ? '' : 'none';
        }
        for (const showLess of this.showLessButtons) {
            showLess.style.display = isList ? 'none' : '';
        }
    }

    /** @private */
    initializeEventListeners() {
        for (const showMore of this.showMoreButtons) {
            showMore.addEventListener('click', () => this.showMore());
        }

        for (const showLess of this.showLessButtons) {
            showLess.addEventListener('click', () => this.showLess());
        }
    }

    /**
     * @returns {object<string, ItemFilter~viewOptions>}
     * @private
     */
    initializeOptions() {
        return {
            [VIEW.LIST]: {
                limit: parseInt(this.container.getAttribute(ATTRIBUTE.LIST_LIMIT), 10) || 3,
            },
            [VIEW.MORE]: {
                limit: parseInt(this.container.getAttribute(ATTRIBUTE.MORE_LIMIT), 10) || Infinity,
            },
        };
    }

    /** @private */
    printHelpfulWarningMessages() {
        if (this.showMoreButtons.length === 0) {
            console.warn(stripMultilineIndention(`
                No 'show more' buttons found. These buttons trigger the 'show more'
                functionality. Without them the user can never see more items.
                Please add an element with [${ATTRIBUTE.SHOW_MORE}]
            `), { itemFilter: this });
        }
        if (this.showLessButtons.length === 0) {
            console.warn(stripMultilineIndention(`
                No 'show less' buttons found. These buttons trigger the 'show less'
                functionality. Without them the user can never switch back to the
                smaller view after showing more options.
                Please add an element with [${ATTRIBUTE.SHOW_LESS}]
            `), { itemFilter: this });
        }
        if (!this.container.querySelector(attributeSelector(ATTRIBUTE.ITEM_CONTAINER, VIEW.LIST))) {
            console.warn(stripMultilineIndention(`
                No item container for list view found. This container will contain the
                items if the filter is in 'list' or 'more' mode.
                Please make sure to add an element with [${ATTRIBUTE.ITEM_CONTAINER}="${VIEW.LIST}}"]
                to be able to see items in 'list' and 'more' modes.
            `), { itemFilter: this });
        }
        if(this.filter.options.query.length === 0) {
            console.warn(stripMultilineIndention(`
                No search query field found. The search query field is required for the user
                to type a query into.
                Please make sure to add an input with [${ATTRIBUTE.QUERY}] to the container.
            `), { itemFilter: this });
        }
    }

}
