// import { r, global, contains, isPositive, nullOrEmpty } from "../utility";
import './css/dropdown.scss';
import { r } from "../utility/lgres";
import { contains, nullOrEmpty } from "../utility/strings";
import { global, isPositive } from "../utility";
import { createElement } from "../functions";
import { createCheckbox } from "./checkbox";
import { createIcon } from "./icon"

const SymbolDropdown = Symbol.for('ui-dropdown');
const DropdownItemHeight = 30;

let dropdownGlobal = global[SymbolDropdown];

if (dropdownGlobal == null) {
    // init
    dropdownGlobal = {};
    Object.defineProperty(dropdownGlobal, 'clear', {
        writable: false,
        configurable: false,
        enumerable: false,
        value: function () {
            const panels = document.querySelectorAll('.ui-drop-box.active');
            for (let panel of [...panels]) {
                if (panel == null) {
                    continue;
                }
                panel.classList.remove('active');
                const dropId = panel.parentElement.dataset.dropId;
                if (dropId == null) {
                    continue;
                }
                const dropdown = this[dropId];
                if (dropdown?.multiSelect && typeof dropdown.onCollapsed === 'function') {
                    dropdown.onCollapsed();
                }
            }
        }
    })
    global[SymbolDropdown] = dropdownGlobal;

    document.addEventListener('mousedown', e => {
        let parent = e.target;
        while (parent != null) {
            if (parent.classList.contains('ui-drop-box')) {
                e.stopPropagation();
                return;
            }
            parent = parent.parentElement;
        }
        dropdownGlobal.clear();
    });
}

function selectItems(label, itemlist, htmlkey, textkey) {
    const htmls = itemlist.map(it => it[htmlkey]);
    if (htmls.some(it => it instanceof HTMLElement)) {
        label.replaceChildren(...htmls.filter(it => it != null).map(it => it.cloneNode(true)));
    } else {
        let text = itemlist.map(it => it[textkey]).join(', ');
        if (nullOrEmpty(text)) {
            text = r('noneItem', '( None )');
        }
        label.innerText = text;
    }
}

function filterSource(searchkeys, textkey, key, source) {
    if (!Array.isArray(searchkeys) || searchkeys.length === 0) {
        searchkeys = [textkey];
    }
    if (key.length > 0) {
        source = source.filter(it => {
            for (let k of searchkeys) {
                if (contains(it[k].toLowerCase(), key)) {
                    return true;
                }
            }
            return false;
        });
    }
    return source;
}

/**
 * 下拉列表参数对象
 * @typedef DropdownOptions
 * @property {string} [textKey=text] - 文本关键字
 * @property {string} [valueKey=value] - 值关键字
 * @property {string} [htmlKey=html] - 源码显示的关键字
 * @property {number} [maxLength=500] - 最大输入长度
 * @property {boolean} [multiSelect] - 是否允许多选
 * @property {string} [selected] - 选中值
 * @property {string[]} [selectedList] - 选中的数组
 * @property {boolean} [disabled] - 是否禁用
 * @property {boolean} [input] - 是否支持输入
 * @property {boolean} [search] - 是否支持搜索
 * @property {string[]} [searchKeys] - 搜索的关键字数组
 * @property {string} [searchPlaceholder] - 搜索提示文本，默认值取语言资源 `searchHolder` "Search..."
 * @property {number} [tabIndex] - 焦点索引
 * @property {string} [placeholder] - 输入框的提示文本
 * @property {boolean} [slideFixed] - 是否固定为向下展开
 * @property {HTMLElement} [wrapper] - 父元素，默认添加到头元素之后
 */

export class Dropdown {
    _var = {};
    // _var.options;

    // _var.wrapper;
    // _var.container;
    // _var.label;

    // _var.allChecked;
    // _var.source;
    // _var.lastSelected;
    // _var.selected;
    // _var.selectedList;

    sourceFilter;
    onSelectedList;
    onSelected;
    onExpanded;
    onCollapsed;

    constructor(options = {}) {
        options.searchPlaceholder ??= r('searchHolder', 'Search...');
        options.textKey ??= 'text';
        options.valueKey ??= 'value';
        options.htmlKey ??= 'html';
        options.maxLength ??= 500;
        this._var.options = options;
    }

    create() {
        const options = this._var.options;

        // wrapper
        const wrapper = createElement('div', 'ui-drop-wrapper');
        const dropId = String(Math.random()).substring(2);
        wrapper.dataset.dropId = dropId;
        dropdownGlobal[dropId] = this;
        this._var.wrapper = wrapper;

        // header
        const header = createElement('div', 'ui-drop-header');
        header.addEventListener('keypress', e => {
            if (e.key === ' ' || e.key === 'Enter') {
                header.dispatchEvent(new MouseEvent('click'));
            }
        });
        header.addEventListener('keydown', e => {
            const up = e.key === 'ArrowUp';
            const down = e.key === 'ArrowDown';
            if (up || down) {
                const source = this.source;
                const count = source.length;
                const valuekey = this._var.options.valueKey;
                let index = source?.indexOf(this._var.selected);
                if (isNaN(index) || index < -1) {
                    index = -1;
                } else if (index >= count) {
                    index = count - 1;
                }
                if (up) {
                    if (index > 0) {
                        index--;
                    } else {
                        index = 0;
                    }
                } else if (down) {
                    if (index < 0) {
                        index = 0;
                    } else if (index < count) {
                        index++;
                    } else {
                        index = count - 1;
                    }
                }
                const target = source[index]?.[valuekey];
                if (target != null) {
                    this.select(target);
                }
            } else if (e.key === 'Tab') {
                this._dropdown(false);
            }
        });
        header.addEventListener('click', () => {
            if (this.disabled) {
                return;
            }
            const active = this._expanded;
            const label = this._var.label;
            if (active && label.ownerDocument.activeElement === label) {
                return;
            }
            this._dropdown(!active);
            if (!active && typeof this.onExpanded === 'function') {
                setTimeout(() => this.onExpanded(), 120);
            }
        });

        // label or input
        let label;
        if (options.input) {
            label = createElement('input', 'ui-drop-text');
            label.setAttribute('type', 'text');
            options.placeholder && label.setAttribute('placeholder', options.placeholder);
            isPositive(options.maxLength) && label.setAttribute('maxlength', options.maxLength);
            isPositive(options.tabIndex) && label.setAttribute('tabindex', options.tabIndex);
            label.addEventListener('input', e => {
                const key = e.target.value.toLowerCase();
                const source = filterSource(options.searchKeys, options.textKey, key, this.source);
                this._filllist(source);
                this._var.container.classList.add('active');
            });
            label.addEventListener('blur', e => this.select(e.target.value));
            label.addEventListener('mousedown', e => this._expanded && e.stopPropagation());
        } else {
            isPositive(options.tabIndex) && header.setAttribute('tabindex', options.tabIndex);
            label = createElement('label', 'ui-drop-text');
        }
        this._var.label = label;
        if (options.multiSelect) {
            if (Array.isArray(options.selectedList)) {
                this.selectlist(options.selectedList, true);
            } else {
                this._var.allChecked = true;
                label.innerText = r('allItem', '( All )');
            }
        } else if (options.selected != null) {
            this.select(options.selected, true);
        }
        header.append(label, createElement('label', 'ui-drop-caret'));
        wrapper.appendChild(header);

        this.disabled = options.disabled || false;
        return wrapper;
    }

    get multiSelect() { return this._var.options.multiSelect }

    get disabled() { return this._var.wrapper == null || this._var.wrapper.querySelector('.ui-drop-header.disabled') != null }

    set disabled(flag) {
        if (this._var.wrapper == null) {
            return;
        }
        if (flag) {
            this._var.wrapper.querySelector('.ui-drop-header').classList.add('disabled');
        } else {
            this._var.wrapper.querySelector('.ui-drop-header').classList.remove('disabled');
        }
    }

    get source() {
        let source = this._var.source;
        if (source == null || !Array.isArray(source)) {
            if (typeof this.sourceFilter === 'function') {
                source = this.sourceFilter();
            }
            if (!Array.isArray(source)) {
                source = [];
            }
            this._var.source = source;
        }
        return source;
    }

    set source(list) {
        if (!Array.isArray(list)) {
            return;
        }
        this._var.source = list;
        if (this._expanded) {
            setTimeout(() => this._dropdown(), 120);
        }
    }

    get selected() { return this._var.selected }

    get selectedList() { return this._var.selectedList || [] }

    select(selected, silence) {
        if (typeof selected !== 'string') {
            selected = String(selected);
        }
        if (this._var.lastSelected === selected) {
            return false;
        }
        this._var.lastSelected = selected;
        const valuekey = this._var.options.valueKey;
        const textkey = this._var.options.textKey;
        const htmlkey = this._var.options.htmlKey;
        let item = this.source.find(it => String(it[valuekey]) === selected);
        if (this._var.options.input) {
            if (item == null) {
                item = { [valuekey]: selected };
            }
            this._var.label.value = selected;
        } else {
            const expanded = this._expanded;
            if (expanded) {
                this._var.container.querySelectorAll('li[data-value].selected').forEach(li => li.classList.remove('selected'));
            }
            if (item == null) {
                this._var.selected = null;
                this._var.label.innerText = ' ';
                return false;
            }
            const html = item[htmlkey];
            if (html instanceof HTMLElement) {
                this._var.label.replaceChildren(html.cloneNode(true));
            } else if (typeof html === 'string') {
                this._var.label.innerHTML = html;
            } else {
                let text = item[textkey];
                if (nullOrEmpty(text)) {
                    text = ' ';
                }
                this._var.label.innerText = text;
            }
            if (expanded) {
                const val = selected.replace(/"/g, '\\"');
                const li = this._var.container.querySelector(`li[data-value="${val}"]`);
                if (li != null) {
                    li.classList.add('selected');
                }
            }
        }
        this._var.selected = item;
        if (!silence && typeof this.onSelected === 'function') {
            this.onSelected(item);
        }
    }

    selectlist(selectedlist, silence) {
        const source = this.source;
        const valuekey = this._var.options.valueKey;
        const textkey = this._var.options.textKey;
        const htmlkey = this._var.options.htmlKey;
        const itemlist = selectedlist.map(a => {
            const v = typeof a === 'string' ? a : String(a);
            let item = source.find(it => String(it[valuekey]) === v);
            if (item == null) {
                item = {
                    [valuekey]: v,
                    [textkey]: v
                };
            }
            return item;
        });
        if (itemlist.length === 0) {
            this._var.selectedList = null;
            this._var.label.innerText = none;
            return false;
        }
        selectItems(this._var.label, itemlist, htmlkey, textkey);
        this._var.selectedList = itemlist;
        if (!silence && typeof this.onSelectedList === 'function') {
            this.onSelectedList(itemlist);
        }
    }

    get _expanded() { return this._var.container?.classList?.contains('active') }

    _dropdown(flag = true) {
        const options = this._var.options;
        let panel = this._var.container;
        if (panel == null) {
            panel = createElement('div', 'ui-drop-box');
            // search box
            if (!options.input && options.search) {
                const search = createElement('div', 'ui-drop-search');
                const input = createElement('input');
                input.setAttribute('type', 'text');
                isPositive(options.tabIndex) && input.setAttribute('tabindex', options.tabIndex);
                !nullOrEmpty(options.searchPlaceholder) && input.setAttribute('placeholder', options.searchPlaceholder);
                input.addEventListener('input', e => {
                    const key = e.target.value.toLowerCase();
                    const source = filterSource(options.searchKeys, options.textKey, key, this.source);
                    this._filllist(source);
                })
                search.append(input, createIcon('fa-light', 'search'));
                panel.appendChild(search);
            }
            // list
            const list = createElement('ul', 'ui-drop-list');
            if (!this.multiSelect) {
                list.addEventListener('click', e => {
                    let li = e.target;
                    while (li.tagName !== 'LI') {
                        li = li.parentElement;
                        if (li == null) {
                            return;
                        }
                    }
                    const value = li.dataset.value;
                    if (this.select(value) !== false) {
                        dropdownGlobal.clear();
                    }
                });
            }
            panel.appendChild(list);
            this._var.container = panel;
            if (options.wrapper instanceof HTMLElement) {
                options.wrapper.appendChild(panel);
            } else {
                this._var.wrapper.appendChild(panel);
            }
        }
        if (flag) {
            let source = this.source;
            if (!options.input && options.search) {
                const search = panel.querySelector('.ui-drop-search > input');
                if (!nullOrEmpty(search?.value)) {
                    source = filterSource(options.searchKeys, options.textKey, search.value, source);
                }
            }
            this._filllist(source);
            // slide direction
            if (!options.slideFixed) {
                const parent = options.wrapper ?? document.body;
                let p = this._var.wrapper;
                panel.style.minWidth = `${p.offsetWidth}px`;
                const headerHeight = p.offsetHeight;
                let top = p.offsetTop + headerHeight;
                let left = p.offsetLeft;
                if (p !== parent) {
                    while ((p = p.parentElement) != null && p !== parent) {
                        top -= p.scrollTop;
                        left -= p.scrollLeft;
                    }
                }
                p = this._var.wrapper;
                if (p !== parent) {
                    while ((p = p.offsetParent) != null && p !== parent) {
                        top += p.offsetTop;
                        left += p.offsetLeft;
                    }
                }
                const slideUp = top - parent.scrollTop + panel.offsetHeight >= parent.offsetHeight;
                if (options.wrapper instanceof HTMLElement) {
                    if (slideUp) {
                        panel.style.top = '';
                        panel.style.bottom = `${parent.offsetHeight - top + headerHeight - 4}px`;
                    } else {
                        panel.style.top = `${top}px`;
                        panel.style.bottom = '';
                    }
                    panel.style.left = `${left}px`;
                }
                if (slideUp) {
                    panel.classList.add('slide-up');
                } else {
                    panel.classList.remove('slide-up');
                }
            }
            panel.classList.add('active');
        } else {
            panel.classList.remove('active');
        }
    }

    _filllist(source) {
        const list = this._var.container.querySelector('.ui-drop-list');
        list.replaceChildren();
        const multiselect = this.multiSelect;
        const allchecked = this._var.allChecked;
        if (multiselect) {
            list.appendChild(
                createElement('li', null,
                    createCheckbox({
                        label: r('allItem', '( All )'),
                        checked: allchecked,
                        customAttributes: { 'isall': '1' },
                        onchange: e => this._triggerselect(e.target)
                    })
                )
            );
        }
        // TODO: virtual mode
        const valuekey = this._var.options.valueKey;
        const textkey = this._var.options.textKey;
        const htmlkey = this._var.options.htmlKey;
        const selected = this.selected;
        const selectedlist = this.selectedList;
        let scrolled;
        source.slice(0, 200).forEach((item, i) => {
            let val = item[valuekey];
            if (typeof val !== 'string') {
                val = String(val);
            }
            const li = createElement('li');
            li.dataset.value = val;
            li.setAttribute('title', item[textkey]);
            let label;
            const html = item[htmlkey];
            if (html instanceof HTMLElement) {
                label = html;
            } else if (typeof html === 'string') {
                label = createElement('span');
                label.innerHTML = html;
            }
            if (multiselect) {
                const selected = selectedlist.some(s => String(s[valuekey]) === val);
                if (label == null) {
                    label = createElement('span');
                    label.innerText = item[textkey];
                }
                const box = createCheckbox({
                    label,
                    checked: allchecked || selected,
                    customAttributes: {
                        'class': 'dataitem',
                        'data-value': val
                    },
                    onchange: e => this._triggerselect(e.target)
                });
                li.appendChild(box);
            } else {
                if (label == null) {
                    li.innerText = item[textkey];
                } else {
                    li.appendChild(label);
                }
                if (selected != null && String(selected[valuekey]) === val) {
                    scrolled = DropdownItemHeight * i;
                    li.classList.add('selected');
                }
            }
            list.appendChild(li);
        });
        if (scrolled != null) {
            setTimeout(() => list.scrollTop = scrolled, 10);
        }
    }

    _triggerselect(checkbox) {
        let list;
        const valuekey = this._var.options.valueKey;
        const textkey = this._var.options.textKey;
        const htmlkey = this._var.options.htmlKey;
        if (checkbox.getAttribute('isall') === '1') {
            const allchecked = this._var.allChecked = checkbox.checked;
            const boxes = this._var.container.querySelectorAll('input.dataitem');
            boxes.forEach(box => box.checked = allchecked);
            list = [];
        } else if (checkbox.checked) {
            if (this._var.container.querySelectorAll('input.dataitem:not(:checked)').length === 0) {
                this._var.allChecked = true;
                this._var.container.querySelector('input[isall="1"]').checked = true;
                list = [];
            } else {
                const source = this.source;
                list = [...this._var.container.querySelectorAll('input.dataitem:checked')]
                    .map(c => {
                        const v = c.dataset.value;
                        return source.find(it => String(it[valuekey]) === v);
                    })
                    .filter(it => it != null);
            }
        } else {
            const val = checkbox.dataset.value;
            if (this._var.allChecked) {
                this._var.allChecked = false;
                this._var.container.querySelector('input[isall="1"]').checked = false;
                list = this.source.filter(it => String(it[valuekey]) !== val);
            } else {
                list = this.selectedList.filter(it => String(it[valuekey]) !== val);
            }
        }
        if (this._var.allChecked) {
            this._var.label.innerText = r('allItem', '( All )');
        } else {
            selectItems(this._var.label, list, htmlkey, textkey);
        }
        this._var.selectedList = list;
        if (typeof this.onSelectedList === 'function') {
            this.onSelectedList(itemlist);
        }
    }

    static resolve(dom = document.body, trigger) {
        const selects = dom.querySelectorAll('select');
        for (let sel of selects) {
            const source = [...sel.children].map(it => {
                return { value: it.value, text: it.innerText }
            });
            const drop = new Dropdown({
                selected: sel.value,
                disabled: sel.disabled,
                tabIndex: sel.tabIndex
            });
            drop.source = source;
            if (typeof trigger === 'function') {
                drop.onSelected = item => trigger.call(drop, item);
            }
            sel.parentElement.replaceChild(drop.create(), sel);
        }
        return dom;
    }
}