class Multiselect extends HTMLDivElement {
    constructor() {
        super();
        this.initialized = false;
        this.toSearch = '';
        this.highlighted = 0;
    }

    connectedCallback() {
        if (!this.isConnected) {
            return;
        }
        if (!this.initialized) {
            for (const child of this.children) {
                switch (child.nodeName) {
                    case 'SELECT':
                        this.select = child;
                        break;
                    case 'LABEL':
                        this.label = child;
                        break;
                }
            }
            if (this.label && this.select) {
                this.init();
            }
        }
        if (this.initialized) {
            this.onClickHandler = () => this.toggle();
            this.tags.addEventListener('click', this.onClickHandler);
            this.onSearchInputHandler = (e) => {
                this.open();
                this.filter(e.target.value);
            }
            this.onSearchKeydownHandler = (e) => {
                switch (e.code) {
                    case "Enter":
                        e.preventDefault();
                        this.selectHighlighted();
                        break;
                    case "ArrowDown":
                        if (e.altKey) {
                            this.open();
                        } else {
                            e.preventDefault();
                            this.highlightNext();
                        }
                        break;
                    case "ArrowUp":
                        if (e.altKey) {
                            this.close();
                        } else {
                            e.preventDefault();
                            this.highlightPrev();
                        }
                        break;
                    case "Backspace":
                        if (e.target.value === '') {
                            this.deselectLast();
                        }
                        break;
                    case "Escape":
                        this.close();
                        break;
                }
            };
            this.search.addEventListener('input', this.onSearchInputHandler);
            this.search.addEventListener('keydown', this.onSearchKeydownHandler);
            this.onFocusOutHandler = (e) => {
                if (this.contains(e.target)) return;
                if (!e.target.isConnected) return;
                this.close();
            }
            window.addEventListener('focusin', this.onFocusOutHandler);
            document.addEventListener('click', this.onFocusOutHandler);

            this.rebuild()
        }
    }

    disconnectedCallback() {
        if (this.initialized) {
            this.removeTags();
            this.options.innerHTML = '';
            document.removeEventListener('click', this.onFocusOutHandler);
            window.removeEventListener('focusin', this.onFocusOutHandler);
            this.onFocusOutHandler = null;
            this.search.removeEventListener('keydown', this.onSearchKeydownHandler);
            this.search.removeEventListener('input', this.onSearchInputHandler);
            this.onSearchInputHandler = null;
            this.onSearchKeydownHandler = null;
            this.tags.removeEventListener('click', this.onClickHandler);
            this.onClickHandler = null;
        }
    }

    init() {
        this.initialized = true;

        this.select.style.display = 'none';
        for (const option of this.select.options) {
            option.dataset.numerusSearch = Multiselect.normalize(option.textContent);
        }

        this.tags = document.createElement('div');
        this.append(this.tags);
        this.tags.classList.add('tags');

        this.search = document.createElement('input');
        this.tags.append(this.search);
        this.search.id = this.select.id;
        this.select.removeAttribute('id');
        this.select.setAttribute('aria-hidden', 'true');
        this.search.setAttribute('spellcheck', 'false');
        this.search.setAttribute('autocomplete', 'false');
        this.search.setAttribute('role', 'combobox');

        // Must come after the search input
        this.tags.append(this.label);

        this.options = document.createElement('ul');
        this.append(this.options);
        this.options.id = this.search.id + '-options-' + Array.from(crypto.getRandomValues(new Uint8Array(4))).map(n => n.toString(16)).join('');
        this.search.setAttribute('aria-controls', this.options.id);
        this.options.classList.add('options');
        this.options.setAttribute('role', 'listbox');
        this.close();
    }

    rebuildOptions() {
        this.options.innerHTML = '';
        let highlight = true;
        for (const option of this.select.options) {
            if (!this.isOptionSelectable(option)) {
                continue;
            }
            const li = document.createElement('li');
            this.options.append(li);
            li.id = this.options.id + '-' + option.index;
            li.dataset.index = option.index;
            li.textContent = option.textContent;
            li.addEventListener('click', () => this.selectOption(option, true));
            if (highlight && (option.index >= this.highlighted)) {
                highlight = false;
                this.highlight(null, li);
            }
        }
        if (highlight) {
            this.highlight(null, this.options.querySelector('li:last-child'));
        }
    }

    highlightNext() {
        const current = this.currentHighlighted();
        if (!current) {
            return;
        }
        this.highlight(current, current.nextElementSibling);
    }

    highlightPrev() {
        const current = this.currentHighlighted();
        if (!current) {
            return;
        }
        this.highlight(current, current.previousElementSibling);
    }

    highlight(current, upcoming) {
        if (!upcoming) {
            return;
        }
        if (current) {
            current.classList.remove('highlight');
        }
        upcoming.classList.remove('highlight');
        this.search.setAttribute('aria-activedescendant', upcoming.id);
        this.highlighted = upcoming.dataset.index;
        upcoming.classList.add('highlight');
    }

    currentHighlighted() {
        return document.getElementById(this.search.getAttribute('aria-activedescendant'));
    }

    isOptionSelectable(option) {
        if (!option) {
            return false;
        }
        if (option.selected) {
            return false;
        }
        return this.toSearch === '' || option.dataset.numerusSearch.includes(this.toSearch);
    }

    selectOption(option, selected) {
        if (!option) {
            return;
        }
        if (option.selected !== selected) {
            option.selected = selected;
            this.select.dispatchEvent(new Event('change', {bubbles: true}));
            this.toSearch = this.search.value = '';
            this.rebuild();
        }
    }

    selectHighlighted() {
        if (!this.isOpen()) {
            return;
        }
        const option = this.select.options[this.highlighted];
        if (this.isOptionSelectable(option)) {
            this.selectOption(option, true);
        }
    }

    deselectLast() {
        const options = this.select.options;
        for (let i = options.length - 1; i >= 0; i--) {
            const option = options[i];
            if (option.selected) {
                this.selectOption(option, false);
                break;
            }
        }
    }

    rebuild() {
        this.rebuildTags();
        this.rebuildOptions();
    }

    removeTags() {
        this.tags.querySelectorAll('.tag').forEach((tag) => tag.remove());
    }

    rebuildTags() {
        this.removeTags();
        let empty = true;
        for (const option of this.select.options) {
            if (!option.selected) {
                continue;
            }
            empty = false;
            const tag = document.createElement('div');
            this.tags.insertBefore(tag, this.search);
            tag.classList.add('tag');
            tag.dataset.numerusSearch = option.dataset.numerusSearch;

            const span = document.createElement('span');
            tag.append(span);
            span.textContent = option.textContent;

            const button = document.createElement('button');
            tag.append(button);
            button.type = 'button';
            button.textContent = '×';
            button.addEventListener('click', (e) => {
                e.stopPropagation();
                this.selectOption(option, false)
            });
        }
        if (empty) {
            this.search.setAttribute('placeholder', this.label.textContent);
        } else {
            this.search.removeAttribute('placeholder');
        }
    }

    close() {
        this.options.style.display = 'none';
        this.search.setAttribute('aria-expanded', 'false');
    }

    open() {
        this.options.removeAttribute('style');
        this.search.setAttribute('aria-expanded', 'true');
    }

    toggle() {
        if (this.isOpen()) {
            this.close();
        } else {
            this.open();
        }
    }

    isOpen() {
        return !this.options.hasAttribute('style');
    }

    filter(search) {
        this.toSearch = Multiselect.normalize(search);
        this.rebuildOptions()
    }

    static normalize(s) {
        return s.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase().trim();
    }
}

class Tags extends HTMLDivElement {
    constructor() {
        super();
        this.initialized = false;
        this.tags = [];
    }

    connectedCallback() {
        if (!this.isConnected) {
            return;
        }
        if (!this.initialized) {
            for (const child of this.children) {
                switch (child.nodeName) {
                    case 'INPUT':
                        this.input = child;
                        break;
                    case 'LABEL':
                        this.label = child;
                        break;
                }
            }
            if (this.label && this.input) {
                this.init();
            }
        }
        if (this.initialized) {
            this.onSearchKeydownHandler = (e) => {
                switch (e.code) {
                    case "Space":
                        e.preventDefault();
                    // fallthrough
                    case "Enter":
                        if (e.target.value.trim() !== '') {
                            e.preventDefault();
                            this.createTag();
                        }
                        break;
                    case "Backspace":
                        if (e.target.value === '') {
                            this.removeLastTag();
                        }
                        break;
                }
            }
            this.search.addEventListener('keydown', this.onSearchKeydownHandler);
            this.onFocusOutHandler = (e) => {
                if (this.contains(e.target)) return;
                if (!e.target.isConnected) return;
                if (this.search.value && this.search.value.trim() !== '') {
                    this.createTag();
                }
                this.dispatchEvent(new CustomEvent("numerus-tags-out", {bubbles: true}))
            };
            window.addEventListener('focusin', this.onFocusOutHandler);
            document.addEventListener('click', this.onFocusOutHandler);

            this.rebuild();
        }
    }

    disconnectedCallback() {
        if (this.initialized) {
            this.removeTags();
            document.removeEventListener('click', this.onFocusOutHandler);
            window.removeEventListener('focusin', this.onFocusOutHandler);
            this.onFocusOutHandler = null;
            this.search.removeEventListener('keydown', this.onSearchKeydownHandler);
            this.onSearchKeydownHandler = null;
        }
    }

    init() {
        this.initialized = true;

        this.tagList = document.createElement('div');
        this.append(this.tagList);
        this.tagList.classList.add('tags');

        this.input.type = 'hidden';
        const tagsText = this.input.value.split(',');
        for (const tagText of tagsText) {
            const tagNormalized = Tags.normalize(tagText);
            if (tagNormalized !== '' && this.tags.indexOf(tagNormalized) === -1) {
                this.tags.push(tagNormalized);
            }
        }

        this.search = document.createElement('input');
        this.tagList.append(this.search);
        this.search.id = this.input.id;
        this.input.removeAttribute('id');
        this.search.setAttribute('type', 'search');
        this.input.setAttribute('aria-hidden', 'true');
        this.search.setAttribute('spellcheck', 'false');
        this.search.setAttribute('autocomplete', 'false');

        // Must come after the search input
        this.tagList.append(this.label);

        const conditionsId = this.input.dataset.conditions;
        if (conditionsId !== "") {
            const conditions = document.getElementById(conditionsId);
            if (conditions) {
                const details = document.createElement('details');
                details.classList.add('menu');
                this.tagList.append(details);

                const summary = document.createElement('summary');
                details.append(summary);

                const ul = document.createElement('ul');
                ul.setAttribute('role', 'menu');
                details.append(ul);

                const options = conditions.querySelectorAll('label');
                if (options && options.length > 0) {
                    summary.textContent = options[0].textContent.trim();
                }
                for (const option of options) {
                    const li = document.createElement('li');
                    li.setAttribute('role', 'presentation');
                    li.append(option);
                    ul.append(li);

                    const checkbox = option.querySelector('input');
                    if (checkbox && checkbox.checked) {
                        summary.textContent = option.textContent.trim();
                    }
                }
                conditions.remove();

                details.addEventListener('toggle', function (e) {
                    const menu = e.target.querySelector('[role="menu"]');
                    if (e.target.open) {
                        const rect = menu.getBoundingClientRect();
                        if (rect.right > document.body.clientWidth) {
                            menu.style.left = Math.ceil(document.body.clientWidth - rect.right) + 'px';
                        }
                        console.log(rect, document.body.clientWidth);
                    } else {
                        menu.style.left = 0;
                    }
                });
            }
        }
    }

    removeTags() {
        this.tagList.querySelectorAll('.tag').forEach((tag) => tag.remove());
    }

    rebuild() {
        const newValue = this.tags.join(',');
        if (newValue !== this.input.value) {
            this.input.value = newValue;
            this.input.dispatchEvent(new Event('change', {bubbles: true}));
        }
        this.removeTags();
        if (this.tags.length === 0) {
            this.search.setAttribute('placeholder', this.label.textContent);
        } else {
            this.search.removeAttribute('placeholder');
            for (const tagText of this.tags) {
                const tag = document.createElement('div');
                this.tagList.insertBefore(tag, this.search);
                tag.classList.add('tag');

                const span = document.createElement('span');
                tag.append(span);
                span.textContent = tagText;

                const button = document.createElement('button');
                tag.append(button);
                button.type = 'button';
                button.textContent = '×';
                button.addEventListener('click', (e) => {
                    e.stopPropagation();
                    this.removeTag(tagText);
                    this.search.focus();
                });
            }
        }
    }

    createTag() {
        const tagText = Tags.normalize(this.search.value);
        this.search.value = '';
        if (this.tags.indexOf(tagText) === -1) {
            this.tags.push(tagText);
            this.rebuild();
        }
    }

    static normalize(s) {
        return s.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase().trim();
    }

    removeLastTag() {
        this.tags.pop();
        this.rebuild();
    }

    removeTag(tagText) {
        this.tags.splice(this.tags.indexOf(tagText), 1);
        this.rebuild();
    }
}

class ProductSearch extends HTMLDivElement {
    constructor() {
        super();
        this.initialized = false;
        this.tags = [];
    }

    connectedCallback() {
        if (!this.isConnected) {
            return;
        }
        if (!this.initialized) {
            for (const child of this.children) {
                switch (child.nodeName) {
                    case 'INPUT':
                        this.input = child;
                        break;
                    case 'LABEL':
                        this.label = child;
                        break;
                }
            }
            if (this.label && this.input) {
                this.init();
            }
        }
        if (this.initialized) {
            this.onSearchKeydownHandler = (e) => {
                switch (e.code) {
                    case "Enter":
                        e.preventDefault();
                        this.selectHighlighted();
                        break;
                    case "ArrowDown":
                        if (e.altKey) {
                            this.open();
                        } else {
                            e.preventDefault();
                            this.highlightNext();
                        }
                        break;
                    case "ArrowUp":
                        if (e.altKey) {
                            this.close();
                        } else {
                            e.preventDefault();
                            this.highlightPrev();
                        }
                        break;
                    case "Escape":
                        this.close();
                        break;
                }
            };
            this.input.addEventListener('keydown', this.onSearchKeydownHandler);
            this.onFocusOutHandler = (e) => {
                if (this.contains(e.target)) return;
                if (!e.target.isConnected) return;
                this.close();
            }
            window.addEventListener('focusin', this.onFocusOutHandler);
            document.addEventListener('click', this.onFocusOutHandler);
        }
    }

    disconnectedCallback() {
        if (this.initialized) {
            document.removeEventListener('click', this.onFocusOutHandler);
            window.removeEventListener('focusin', this.onFocusOutHandler);
            this.onFocusOutHandler = null;
            this.input.removeEventListener('keydown', this.onSearchKeydownHandler);
            this.onSearchKeydownHandler = null;
        }
    }

    init() {
        this.initialized = true;

        this.input.setAttribute('spellcheck', 'false');
        this.input.setAttribute('autocomplete', 'false');
        this.input.setAttribute('role', 'combobox');

        this.options = document.createElement('ul');
        this.append(this.options);
        this.options.classList.add('options')
        this.options.setAttribute('role', 'listbox')
        this.options.dataset.hxOn = "htmx:afterSettle: this.parentNode.settled()";

        const indicator = document.createElement('img');
        this.append(indicator);
        indicator.classList.add('htmx-indicator');
        indicator.src = '/static/bars.svg';

        this.close();
    }

    settled() {
        this.open();
    }

    open() {
        this.options.removeAttribute('style');
        this.input.setAttribute('aria-expanded', 'true');
        const products = this.options.querySelectorAll('li[id]');
        if (products.length > 0) {
            this.highlight(this.currentHighlighted(), products.item(0));
        }
    }

    close() {
        this.input.removeAttribute('aria-activedescendant');
        this.input.setAttribute('aria-expanded', 'false');
        this.options.style.display = 'none';
    }

    highlightNext() {
        const current = this.currentHighlighted();
        if (!current) {
            return;
        }
        this.highlight(current, current.nextElementSibling);
    }

    highlightPrev() {
        const current = this.currentHighlighted();
        if (!current) {
            return;
        }
        this.highlight(current, current.previousElementSibling);
    }

    highlight(current, upcoming) {
        if (!upcoming) {
            return;
        }
        if (current) {
            current.classList.remove('highlight');
        }
        this.input.setAttribute('aria-activedescendant', upcoming.id);
        this.highlighted = upcoming.dataset.index;
        upcoming.classList.add('highlight');
    }

    selectHighlighted() {
        const highlighted = this.currentHighlighted();
        if (highlighted) {
            highlighted.click();
        }
    }

    currentHighlighted() {
        return document.getElementById(this.input.getAttribute('aria-activedescendant'));
    }
}

customElements.define('numerus-multiselect', Multiselect, {extends: 'div'});
customElements.define('numerus-tags', Tags, {extends: 'div'});
customElements.define('numerus-product-search', ProductSearch, {extends: 'div'});

let savedTitle = '';

htmx.onLoad((target) => {
    if (target.tagName === 'DIALOG') {
        const details = document.querySelectorAll('details[open]');
        for (const detail of details) {
            detail.removeAttribute('open');
        }
        savedTitle = document.title;
        document.title = target.querySelector('h2').textContent + ' — ' + savedTitle.substring(savedTitle.indexOf("—") + 1);
        target.showModal();
        const button = target.querySelector('.close-dialog');
        if (button) {
            button.addEventListener('click', (e) => {
                htmx.trigger(e.target, 'closeModal');
            }, {once: true});
        }
    }
})

htmx.on('htmx:configRequest', function (e) {
    const element = e.detail.elt;
    if (element && element.nodeName === 'FORM') {
        let submitter = e.detail.triggeringEvent.submitter;
        if (submitter) {
            const action = submitter.attributes['formaction'];
            if (action && action.value) {
                e.detail.path = action.value;
            }
        }
    }
})

htmx.on('closeModal', () => {
    const openDialog = document.querySelector('dialog[open]');
    if (!openDialog) {
        return;
    }
    openDialog.close();
    openDialog.remove();
    document.title = savedTitle;
});

htmx.on(document, 'alpine:init', () => {
    document.body.classList.remove('show-filters');
   
    Alpine.data('snackbar', () => ({
        show: false, toast: "", toasts: [], timeoutId: null, init() {
            htmx.on('htmx:error', (error) => {
                this.showError(error.detail.errorInfo.error);
            });
        }, showError(message) {
            this.toasts.push(message);
            this.popUp();
        }, popUp() {
            if (this.toasts.length === 0) {
                return;
            }
            if (this.show) {
                this.dismiss();
                return;
            }
            if (this.toast !== "") {
                // It will show after remove calls popUp again.
                return;
            }
            this.toast = this.toasts[0];
            this.show = true;
            this.timeoutId = setTimeout(this.dismiss.bind(this), 4000);
        }, dismiss() {
            if (!this.show) {
                // already dismissed
                return;
            }
            this.show = false;
            clearTimeout(this.timeoutId);
            this.timeoutId = setTimeout(this.remove.bind(this), 350);
        }, remove() {
            clearTimeout(this.timeoutId);
            this.toasts.splice(0, 1);
            this.toast = "";
            this.popUp();
        },
    }));
});
