/**
 * @since Last modified: 2024-07-09 16:45:13
 * @class Menu
 * @description Horizontal or vertical menu.
 * @param {getEl} elem - Target element.
 * @param {object} [options] - Parameters
 *
 * @see {@link https://codepen.io/axui/pen/LYvEwMv|demo @ codepen}
 * @example
 * ax.Menu('#id');
 */
import Observe from "./Observe";
import optMenu from "../options/optMenu";
import createEl from "../utils/createEl";
import getDataType from "../utils/getDataType";
import isEmpty from "../utils/isEmpty";
import isNull from "../utils/isNull";
import pipe from "../utils/pipe";
import slideDown from "../utils/slideDown";
import slideUp from "../utils/slideUp";
import PubBase from "./PubBase";
import PubCommon from "./PubCommon";
import PubListen from "./PubListen";
import PubUpdate from "./PubUpdate";
import deepClone from "../utils/deepClone";
import elState from "../utils/elState";
import contains from "../utils/contains";
import getContent from "../utils/getContent";
import findItem from "../utils/findItem";
import treeTools from "../utils/treeTools";
import valToArr from "../utils/valToArr";
import prefix from "../global/prefix";
import classes from "../utils/classes";
import Hover from "./Hover";
import Drawer from "./Drawer";
import getEl from "../utils/getEl";
import unique from "../utils/unique";
class Menu extends pipe(PubListen, PubUpdate, PubCommon)(PubBase) {
    constructor(elem, options, inital = true) {
        super();
        this.options = {};
        this.ready({
            options,
            host: elem,
            maps: optMenu,
            component: true,
            spread: ['arrow', 'row', 'drawer']
        });
        /**
        * @name Menu.targetEl
        * @description Target element.
        * @type getEl
        */
        /**
        * @name Menu.rawOpts
        * @description Raw Parameters of the instance from map.
        * @type object
        */
        /**
         * @name Menu.dftOpts
         * @description Default Parameters of the instance from merging.The reset method requires this variable.
         * @type object
         */
        /**
         * @name Menu.options
         * @description Parameters of the instance.
         * @type object
         */
        /**
         * @name Menu.destroyed
         * @description Destruction state.
         * @type boolean
         */
        /**
        * @name Menu.plans
        * @description Plans for listening.
        * @type object
        */
        /**
        * @name Menu.storKeys
        * @description The set of parameter fields to save.
        * @type array
        */
        /**
         * @name Menu.storTmp
         * @description Cache object.The "updateCache" method will update the variable.
         * @type object
         */
        /**
        * @name Menu.storObs
        * @description Cache listener(proxy) that listens for "storTmp" changes and automatically updates the local storage.
        * @type any
        */
        /**
        * @name Menu.rawHtml
        * @description Raw innerHTML of the target node.
        * @type string
        */
        /**
        * @name Drawer.timestamp
        * @description Instance creation time.
        * @type number
        */
        /**
        * @function Menu.save
        * @description Storing settings locally.
        * @param {fn_cb_options} [cb] - Callback execution after saving local storage.
        * @returns {any} Return the instance itself
        */
        /**
        * @function Menu.reset
        * @description Re-initialization of old parameters.
        * @param {fn_cb_none} [cb] - Call back after reset settings.
        * @returns {*} Return the instance itself
        */
        let _this = this;
        this.expandEvent = function () {
            let attr = this.getAttribute('role') === 'arrow' ? 'arrowEl' : 'headEl', item = findItem(this, _this.flatData, attr);
            _this.toggleExpanded(item);
        };
        this.selectEvent = function () {
            _this.select(this);
        };
        this.pageEvent = function (event) {
            if (!contains(event.target, _this.targetEl)) {
                _this.collapseAll();
            }
        };
        this.lastExpanded = -1;
        this.treeDataOrig = [];
        super.listen({ name: 'ready' });
        inital ? this.init() : null;
    }
    /**
    * @description Initialization instance.
    * @param {fn_cb_none} [cb] - Callback execution after initialization.
    * @returns {*} Return the instance itself.
    */
    async init(cb) {
        super.listen({ name: 'start' });
        await this.getDataToRender();
        if (this.options.drawer.enable) {
            this.drawerHost = getEl(this.options.drawer.host);
            this.drawerIns = new Drawer(this.drawerHost, Object.assign({
                content: this.targetEl,
                contType: 'node',
            }, this.options.drawer));
        }
        this.setAttrs();
        if (this.initCount === 0) {
            this.active([...this.getActive(), ...valToArr(this.options.active)]);
        }
        else {
            this.active(valToArr(this.options.active));
        }
        this.disable(valToArr(this.options.disable));
        if (this.options.expandAll && this.options.multiple) {
            this.expandAll();
        }
        this.renderFinish();
        this.initCount++;
        super.listen({ name: 'init', cb });
        return this;
    }
    async getDataToRender() {
        let base = {
            rootStart: this.options.rootStart,
            idStart: this.options.idStart,
            floorStart: this.options.floorStart,
        }, promise;
        if (this.options.content) {
            promise = treeTools.allToTree({
                content: this.options.content,
                contType: this.options.contType,
                contData: this.options.contData,
                ajax: { spinSel: this.targetEl, xhrName: 'contXhr' },
                ins: this,
                ...base
            });
            await promise.then((res) => {
                /**
                * @name Menu.treeData
                * @description Multi-dimensional array.
                * @type object[]
                */
                this.treeDataOrig = res;
                this.targetEl.innerHTML = '';
                this.renderData(res);
            });
        }
        else {
            this.treeDataOrig = this.ulToArr();
            treeTools.addIdPath({ source: this.treeDataOrig });
        }
        this.getTreeFlat();
        super.listen({ name: 'render', params: [this.treeData, this.flatData] });
        return this;
    }
    getTreeFlat() {
        this.treeData = this.getObserver(this.treeDataOrig).proxy;
        /**
        * @name Menu.flatData
        * @description One-dimensional array.It is mainly used to find sub-items.
        * @type object[]
        */
        this.flatData = treeTools.toFlat(this.treeData);
    }
    getObserver(data) {
        this.observeIns = new Observe(data, {
            deep: true,
            onSet: (obj) => {
                if (obj.key === 'icon' && obj.target.iconEl) {
                    obj.target.iconEl.class = obj.value;
                }
                else if (obj.key === 'cube' && obj.target.cubeEl) {
                    obj.target.cubeEl.src = obj.value;
                }
                else if (obj.key === 'disk' && obj.target.diskEl) {
                    obj.target.diskEl.src = obj.value;
                }
                else if (obj.key === 'label') {
                    obj.target.labelEl.innerHTML = obj.value;
                }
                else if (obj.key === 'tips' && obj.target.tipsEl) {
                    obj.target.tipsEl.innerHTML = obj.value;
                }
                else if (obj.key === 'badge' && obj.target.badgeEl) {
                    obj.target.badgeEl.setAttribute('label', obj.value);
                }
                else if (obj.key === 'href' && obj.target.labelEl.nodeName === 'A') {
                    obj.target.labelEl.href = obj.value;
                }
                else if (obj.key === 'rel' && obj.target.labelEl.nodeName === 'A') {
                    obj.target.labelEl.rel = obj.value;
                }
                else if (obj.key === 'target' && obj.target.labelEl.nodeName === 'A') {
                    obj.target.labelEl.target = obj.value;
                }
                else if (obj.key === 'floor') {
                    obj.target.indentEl.innerHTML = `${'<i></i>'.repeat(obj.value * 2 + 1)}`;
                }
                else if (obj.key === 'expanded' && obj.target.children) {
                    obj.value === true ? obj.target.headEl.setAttribute('expanded', '') : obj.target.headEl.removeAttribute('expanded');
                }
                else if (obj.key === 'selected' && !obj.target.children) {
                    obj.value === true ? obj.target.headEl.setAttribute('selected', '') : obj.target.headEl.removeAttribute('selected');
                }
                else if (obj.key === 'disabled') {
                    obj.value === true ? obj.target.headEl.setAttribute('disabled', '') : obj.target.headEl.removeAttribute('disabled');
                }
                else if (obj.key === 'children') {
                    treeTools.childToParent(obj.target, true);
                }
            },
            onDelete: (obj) => {
                if (obj.key === 'children') {
                    treeTools.parentToChild(obj.target);
                }
            },
            onComplete: () => {
                let tmp = {
                    active: this.getActive().map((k) => k.id),
                    disabled: this.getDisabled().map((k) => k.id),
                    content: !isEmpty(this.options.content) ? deepClone(this.treeDataOrig) : '',
                };
                super.updateCache(tmp);
            }
        });
        return this.observeIns;
    }
    ulToArr() {
        /*  <ul>
             <li><div expanded selected disabled><i role="icon" class="_icon-global"></i><i role="label"></i><i role="tips"></i><i role="badge"></i><i role="arrow"></i></div>
                 <ul></ul>
             </li>
             <li><div><a role="label" href="" target=""></i><i role="arrow"></i></div>
                 <ul></ul>
             </li>
         </ul> */
        let data = [], setBoolean = (obj, props) => {
            let loop = (prop) => {
                let val = obj.headEl.hasAttribute(prop);
                if (val && val !== 'false' && val !== '0') {
                    obj[prop] = true;
                    obj.headEl.setAttribute(prop, '');
                }
                else {
                    obj[prop] = false;
                }
            };
            if (Array.isArray(props)) {
                props.forEach((k) => {
                    loop(k);
                });
            }
            else {
                loop(props);
            }
        }, getObj = (elem, floor) => {
            let obj = {};
            obj.wrapEl = elem;
            obj.headEl = elem.firstElementChild;
            obj.bodyEl = elem.querySelector('ul');
            obj.labelEl = obj.headEl.querySelector('[role="label"]') || obj.headEl;
            obj.label = obj.labelEl.textContent.trim();
            obj.value = isNull(obj.headEl.getAttribute('value')) ? obj.label : obj.headEl.getAttribute('value');
            obj.tipsEl = obj.headEl.querySelector('[role="tips"]');
            obj.tips = obj.tipsEl ? obj.tipsEl.textContent.trim() : '';
            obj.href = (obj.labelEl.nodeName === 'A' && obj.labelEl.href && !obj.labelEl.href.includes('##') && !obj.labelEl.href.includes('javascript:;')) ? obj.labelEl.href : '';
            console.log(obj.href);
            obj.target = (obj.labelEl.nodeName === 'A' && obj.labelEl.target) ? obj.labelEl.target : '';
            obj.rel = (obj.labelEl.nodeName === 'A' && obj.labelEl.rel) ? obj.labelEl.rel : '';
            obj.arrowEl = obj.headEl.querySelector('[role="arrow"]');
            obj.iconEl = obj.headEl.querySelector('[role="icon"]');
            obj.icon = obj.iconEl ? obj.iconEl.getAttribute('class') : '';
            obj.cubeEl = obj.headEl.querySelector('[role="cube"]');
            obj.cube = obj.cubeEl ? obj.cubeEl.src : '';
            obj.diskEl = obj.headEl.querySelector('[role="disk"]');
            obj.disk = obj.diskEl ? obj.diskEl.src : '';
            obj.badgeEl = obj.headEl.querySelector('[role="badge"],ax-badge');
            obj.badge = obj.badgeEl ? obj.badgeEl.getAttribute('label') || obj.badgeEl.textContent.trim() : '';
            setBoolean(obj, ['expanded', 'selected', 'disabled']);
            if (!obj.headEl.querySelector('[role="indent"]')) {
                obj.indentEl = createEl('span', { role: 'indent' });
                obj.indentEl.innerHTML = `${'<i></i>'.repeat(floor * 2 + 1)}`;
                obj.headEl.insertAdjacentElement('afterBegin', obj.indentEl);
            }
            else {
                obj.indentEl = obj.headEl.querySelector('[role="indent"]');
            }
            if (obj.bodyEl) {
                obj.children = [];
                [...obj.bodyEl.children].forEach(k => {
                    obj.children.push(getObj(k, floor + 1));
                });
            }
            return obj;
        };
        let getData = () => {
            [...this.targetEl.children].forEach(k => {
                data.push(getObj(k, 0));
            });
        };
        getData();
        return data;
    }
    renderData(data) {
        let outer = createEl('ul'), fragment = document.createDocumentFragment();
        let plantTree = (parent, data) => {
            let ul = createEl('ul');
            data.forEach((k) => {
                this.createHeadEl(k);
                if (k.hasOwnProperty('children')) {
                    k.bodyEl = plantTree(k.wrapEl, k.children);
                }
                ul.appendChild(k.wrapEl);
            });
            parent.appendChild(ul);
            return ul;
        };
        plantTree(outer, data);
        let list = outer.childNodes[0].childNodes;
        [...list].forEach(k => {
            fragment.appendChild(k);
        });
        this.targetEl.appendChild(fragment);
    }
    createHeadEl(item) {
        item.labelEl = createEl(item.hasOwnProperty('href') ? 'a' : 'i', { role: 'label' }, item.label);
        item.href ? item.labelEl.href = item.href : null;
        item.target ? item.labelEl.target = item.target : null;
        item.iconEl = item.icon ? createEl('i', { role: 'icon', class: item.icon }) : null;
        item.diskEl = item.disk ? createEl('img', { role: 'disk', src: item.disk }) : null;
        item.cubeEl = item.cube ? createEl('img', { role: 'cube', src: item.cube }) : null;
        item.arrowEl = this.options.arrow.enable && item.children ? createEl('i', { role: 'arrow' }) : null;
        item.arrowEl && this.options.arrow.icon ? item.arrowEl.classList.add(this.options.arrow.icon) : null;
        item.headEl = createEl('div');
        item.wrapEl = createEl('li');
        item.badgeEl = item.badge ? createEl('ax-badge', { role: 'badge' }, item?.badge?.trim()) : null;
        item.tipsEl = item.tips ? createEl('i', { role: 'tips' }, item.tips) : null;
        item.indentEl = createEl('span', { role: 'indent' });
        item.indentEl.innerHTML = `${'<i></i>'.repeat((item.floor - this.options.floorStart) * 2 + 1)}`;
        item.expanded ? item.headEl.setAttribute('expanded', '') : null;
        item.selected ? item.headEl.setAttribute('selected', '') : null;
        item.disabled ? item.headEl.setAttribute('disabled', '') : null;
        item.wrapEl.appendChild(item.headEl);
        item.headEl.appendChild(item.indentEl);
        item.iconEl ? item.headEl.appendChild(item.iconEl) : null;
        item.diskEl ? item.headEl.appendChild(item.disk) : null;
        item.cubeEl ? item.headEl.appendChild(item.cube) : null;
        item.headEl.appendChild(item.labelEl);
        item.tipsEl ? item.headEl.appendChild(item.tipsEl) : null;
        item.arrowEl ? item.headEl.appendChild(item.arrowEl) : null;
    }
    setAttrs() {
        this.targetEl.classList.add(`${prefix}menu`);
        this.options.classes ? classes.add(this.targetEl, this.options.classes) : null;
        this.targetEl.setAttribute('scheme', this.options.scheme);
        this.options.dispose ? this.dispose() : null;
        this.options.expose ? this.targetEl.setAttribute('expose', '') : null;
        this.options.spill && !this.options.row.enable ? this.targetEl.setAttribute('spill', '') : null;
        this.options.gapless ? this.targetEl.setAttribute('gapless', '') : null;
        this.options.lamp ? this.targetEl.setAttribute('lamp', this.options.lamp) : null;
        this.options.full && !this.options.row.enable ? this.targetEl.setAttribute('full', '') : null;
        this.options.zIndex ? this.targetEl.style.zIndex = this.options.zIndex : null;
        if (this.options.row.enable) {
            this.targetEl.setAttribute('row', '');
            this.options.row.align ? this.targetEl.setAttribute('align', this.options.row.align) : null;
            if (this.options.row.headWidth) {
                let firstObj = this.flatData.filter((k) => k.floor === this.options.floorStart);
                firstObj.forEach((k) => {
                    k.wrapEl.style.width = this.options.row.headWidth;
                });
            }
            if (this.options.row.bodyWidth) {
                this.flatData.forEach((k) => {
                    k.bodyEl ? k.bodyEl.style.width = this.options.row.bodyWidth : null;
                });
            }
            if (this.options.row.marginLeft) {
                this.flatData.filter((k) => k.floor === this.options.floorStart).forEach((k) => {
                    k.wrapEl.style.marginLeft = this.options.row.marginLeft;
                });
            }
            if (this.options.row.marginRight) {
                this.flatData.filter((k) => k.floor === this.options.floorStart).forEach((k) => {
                    k.wrapEl.style.marginRight = this.options.row.marginRight;
                });
            }
        }
    }
    renderFinish() {
        this.flatData.forEach((k) => {
            if (this.options.expose) {
                return this;
            }
            if (k.children) {
                if (k.disabled) {
                    return this;
                }
                let triggerEl = k.href && k.arrowEl ? k.arrowEl : k.headEl;
                if (this.options.trigger === 'click') {
                    triggerEl.removeEventListener('click', this.expandEvent);
                    triggerEl.addEventListener('click', this.expandEvent, false);
                }
                else if (this.options.trigger === 'hover') {
                    k.hoverIns ? k.hoverIns.destroy() : null;
                    k.hoverIns = new Hover(k.wrapEl, {
                        onMove: (isIn) => {
                            !isIn && !this.options.multiple ? this.collapse(k) : null;
                        },
                        onEnter: () => {
                            this.expand(k);
                        },
                        onLeave: () => {
                            this.collapse(k);
                        }
                    });
                }
            }
            else {
                if (k.disabled) {
                    return this;
                }
                k.headEl.removeEventListener('click', this.selectEvent);
                k.headEl.addEventListener('click', this.selectEvent, false);
            }
            k.action && k.action.call(this, k);
        });
        if (this.options.trigger === 'click' && this.options.pageClose) {
            document.body.removeEventListener('click', this.pageEvent);
            document.body.addEventListener('click', this.pageEvent, false);
        }
    }
    getDisabled() {
        return this.flatData.filter((k) => k.disabled);
    }
    getEnabled() {
        return this.flatData.filter((k) => !k.disabled);
    }
    getExpanded() {
        return this.flatData.filter((k) => k.expanded);
    }
    getActive() {
        return this.flatData.filter((k) => k.expanded || k.selected);
    }
    getSelected() {
        return this.flatData.find((k) => k.selected);
    }
    getTriggerEl(item) {
        return (item.href && item.arrowEl ? item.arrowEl : item.headEl);
    }
    addTrigger(item, target) {
        if (item.children) {
            if (!item.disabled) {
                let triggerEl = target || this.getTriggerEl(item);
                if (this.options.trigger === 'click') {
                    triggerEl.removeEventListener('click', this.expandEvent);
                    triggerEl.addEventListener('click', this.expandEvent, false);
                }
                else if (this.options.trigger === 'hover') {
                    this.hoverIns ? this.hoverIns.destroy() : null;
                    this.hoverIns = new Hover(item.wrapEl, {
                        onEnter: () => {
                            this.expand(item);
                            super.listen({ name: 'trigger', params: [item] });
                        },
                        onLeave: () => {
                            this.collapse(item);
                        }
                    });
                }
            }
        }
        else {
            if (!item.disabled) {
                item.headEl.removeEventListener('click', this.selectEvent);
                item.headEl.addEventListener('click', this.selectEvent, false);
            }
        }
    }
    removeTrigger(item, target) {
        if (item.children) {
            if (!item.disabled) {
                let triggerEl = target || this.getTriggerEl(item);
                if (this.options.trigger === 'click') {
                    triggerEl.removeEventListener('click', this.expandEvent);
                }
                else if (this.options.trigger === 'hover') {
                    this.hoverIns ? this.hoverIns.destroy() : null;
                }
            }
        }
        else {
            if (!item.disabled) {
                item.headEl.removeEventListener('click', this.selectEvent);
            }
        }
    }
    toggleDisabled(item, type = 'disable') {
        let triggerEl = item.href && item.arrowEl ? item.arrowEl : item.headEl;
        if (type === 'enable') {
            item.disabled = false;
            this.addTrigger(item, triggerEl);
        }
        else if (type === 'disable') {
            item.disabled = true;
            this.removeTrigger(item, triggerEl);
        }
    }
    toggleExpanded(data) {
        if (this.destroyed) {
            return this;
        }
        let item = findItem(data, this.flatData);
        if (!item || !item.children) {
            return this;
        }
        if (item.expanded) {
            this.collapse(item);
        }
        else {
            this.expand(item);
        }
        super.listen({ name: 'trigger', params: [item] });
    }
    async collapse(data, cb) {
        if (this.destroyed) {
            return this;
        }
        let item = findItem(data, this.flatData);
        if (!item || !item.children || !elState(item.bodyEl).isVisible || item.bodyEl.style.height) {
            return this;
        }
        this.options.b4Collapse && await this.options.b4Collapse.call(this, item);
        item.expanded = false;
        if (this.options.spill || this.options.row.enable) {
            item.wrapEl.style.cssText = item.wrapEl.style.cssText.replace('z-index:', '');
        }
        slideUp({
            el: item.bodyEl,
            duration: this.options.duration,
            done: () => {
                super.listen({ name: 'collapse', cb, params: [item] });
            }
        });
        return this;
    }
    async expand(data, cb) {
        if (this.destroyed) {
            return this;
        }
        let item = findItem(data, this.flatData);
        if (!item || !item.children || elState(item.bodyEl).isVisible) {
            return this;
        }
        this.options.b4Expand && await this.options.b4Expand.call(this, item);
        item.expanded = true;
        this.options.rise && this.expandParents(item);
        if (this.options.spill || this.options.row.enable) {
            item.wrapEl.style.zIndex = item.bodyEl.style.zIndex + 1;
        }
        this.lastExpanded = item.id;
        slideDown({
            el: item.bodyEl,
            duration: this.options.duration,
            done: () => {
                if (!this.options.multiple && this.lastExpanded !== item.id) {
                    this.collapse(item);
                }
                super.listen({ name: 'expand', cb, params: [item] });
            }
        });
        if (!this.options.multiple) {
            let others = this.flatData.filter((k) => (k !== item && k.expanded && k.floor === item.floor && k.children));
            others.forEach((k) => {
                this.collapse(k);
            });
        }
        return this;
    }
    collapseAll(cb) {
        if (this.destroyed) {
            return this;
        }
        this.flatData.filter((k) => k.expanded).forEach((k) => {
            this.collapse(k);
        });
        super.listen({ name: 'collapseAll', cb });
        return this;
    }
    expandAll(cb) {
        if (this.destroyed || !this.options.multiple) {
            return this;
        }
        this.flatData.forEach((k) => {
            this.expand(k);
        });
        super.listen({ name: 'expandAll', cb });
        return this;
    }
    expandParents(data) {
        let item = findItem(data, this.flatData);
        if (this.options.rise && item) {
            let parents = treeTools.getParentsFromPath({ path: item.path, flatData: this.flatData, prop: 'id', pathHyphen: this.options.pathHyphen }).parents;
            parents.pop();
            parents.forEach((k) => {
                this.expand(k);
            });
        }
    }
    dispose() {
        this.targetEl.setAttribute('inert', '');
        return this;
    }
    revive() {
        this.targetEl.removeAttribute('inert');
        return this;
    }
    disable(data, cb) {
        if (this.destroyed) {
            return this;
        }
        if (data !== 0 && isEmpty(data)) {
            this.enableAll();
        }
        let tmp = Array.isArray(data) ? data : [data], arr = tmp.map((k) => findItem(k, this.flatData)).filter(Boolean), fun = (k) => this.toggleDisabled(k, 'disable');
        arr.forEach(k => fun(k));
        super.listen({ name: 'disable', cb, params: [arr] });
        return this;
    }
    disableAll(cb) {
        if (this.destroyed) {
            return this;
        }
        this.disable(this.flatData);
        super.listen({ name: 'disableAll', cb });
        return this;
    }
    enable(data, cb) {
        if (this.destroyed || (data !== 0 && isEmpty(data))) {
            return this;
        }
        let tmp = Array.isArray(data) ? data : [data], arr = tmp.map((k) => findItem(k, this.flatData)).filter((k) => k && k.disabled), fun = (k) => this.toggleDisabled(k, 'enable');
        arr.forEach((k) => fun(k));
        super.listen({ name: 'enable', cb, params: [arr] });
        return this;
    }
    enableAll(cb) {
        if (this.destroyed) {
            return this;
        }
        this.enable(this.flatData);
        super.listen({ name: 'enableAll', cb });
        return this;
    }
    select(data, cb) {
        if (this.destroyed || isNull(data)) {
            return this;
        }
        let item = findItem(data, this.flatData);
        if (!item || item.children || item.selected) {
            return this;
        }
        item.selected = true;
        this.options.rise && this.expandParents(item);
        let others = this.flatData.filter((k) => k !== item && k.selected && !k.children);
        others.forEach((i) => {
            i.selected = false;
        });
        super.listen({ name: 'select', cb, params: [item] });
        return this;
    }
    unselect(data, cb) {
        if (this.destroyed || isNull(data)) {
            return this;
        }
        let item = findItem(data, this.flatData);
        if (!item || item.children || !item.selected) {
            return this;
        }
        item.selected = false;
        super.listen({ name: 'unselect', cb, params: [item] });
        return this;
    }
    active(data, cb) {
        if (this.destroyed) {
            return this;
        }
        if (data !== 0 && isEmpty(data)) {
            if (this.getExpanded().length > 0) {
                this.getExpanded().forEach((k) => {
                    this.collapse(k);
                });
            }
            this.getSelected() && this.unselect(this.getSelected());
        }
        let tmp = Array.isArray(data) ? data : [data], arr = unique(tmp.map((k) => findItem(k, this.flatData)).filter(Boolean), 'id'), expandedItems = arr.filter((k) => k.children), expendedItem = (!this.options.multiple) ? expandedItems.slice(-1) : expandedItems, selectedItems = arr.filter((k) => !k.children), selectedItem = selectedItems[selectedItems.length - 1], expands = [];
        expands = expendedItem.filter(Boolean).map((k) => treeTools.getParentsFromPath({ path: k.path, flatData: this.flatData }).parents).flat().filter(Boolean);
        expands.forEach((k) => {
            this.expand(k);
        });
        selectedItem ? this.select(selectedItem) : null;
        super.listen({ name: 'active', cb, params: selectedItem ? [...expands, selectedItem] : expands });
        return this;
    }
    async add({ data, target, isChild = true, brother, isFront = true, repeat = true, cb }) {
        if (this.destroyed || isNull(data)) {
            return this;
        }
        if (this.flatData.length >= this.options.max && this.options.max > 0) {
            throw new Error('The number of children exceeds the maximum!');
        }
        let type = getDataType(data), tmp = [], items = [], appendFun = (data) => {
            let obj = treeTools.createBranchObj({
                source: data,
                flatData: this.flatData,
                target,
                isChild
            });
            this.createHeadEl(obj);
            treeTools.addBranch({
                source: obj,
                rootEl: this.targetEl,
                flatData: this.flatData,
                treeData: this.treeData,
                brother: target && !isChild ? target : brother,
                isFront,
                repeat,
                cb: (item) => {
                    items.push(item);
                }
            });
        };
        tmp = (type === 'Array' && data.length > 0) ? data : [data];
        tmp.forEach((k) => appendFun(k));
        this.options.b4Add && await this.options.b4Add.call(this, items);
        if (items.length > 0) {
            if (this.options.addActive) {
                if (this.options.multiple) {
                    items.forEach(k => {
                        k.children ? this.expand(k) : null;
                    });
                }
                else {
                    let lastItem = items[items.length - 1];
                    lastItem.children ? this.expand(lastItem) : null;
                }
            }
            this.flatData = treeTools.toFlat(this.treeData);
            this.renderFinish();
        }
        super.listen({ name: 'add', cb, params: [items] });
        return this;
    }
    async remove(data, cb) {
        if (this.destroyed || isNull(data)) {
            return this;
        }
        if (this.flatData.length === 0) {
            console.warn('The source data is already empty!');
            return this;
        }
        let tmp = [], items = [], removeItem = (child) => {
            treeTools.removeBranch({
                source: child,
                flatData: this.flatData,
                treeData: this.treeData,
                remove: false,
                cb: (obj) => {
                    items.push(obj);
                }
            });
        };
        this.options.b4Remove && await this.options.b4Remove.call(this, items);
        tmp = Array.isArray(data) ? data : [data];
        tmp.forEach(k => {
            removeItem(k);
        });
        this.flatData = treeTools.toFlat(this.treeData);
        super.listen({ name: 'remove', cb, params: [items] });
        return this;
    }
    async edit({ item, data, cb }) {
        if (this.destroyed || isNull(item) || isEmpty(data)) {
            return this;
        }
        let source = findItem(item, this.flatData);
        if (!source) {
            return this;
        }
        this.options.b4Edit && await this.options.b4Edit.call(this, source);
        for (let k in data) {
            let val = data[k];
            if (k === 'action') {
                source[k] = val.call(source);
            }
            else if (['icon', 'disk', 'cube', 'href', 'target', 'rel', 'label', 'tips', 'badge', 'disabled', 'expanded', 'selected'].includes(k)) {
                source[k] = val;
            }
        }
        super.listen({ name: 'edit', cb, params: [source] });
        return this;
    }
    async graft({ source, target, isFront = true, isChild = true, cb }) {
        if (this.destroyed || isNull(source)) {
            return this;
        }
        let item = findItem(source, this.flatData);
        if (!item) {
            return this;
        }
        this.options.b4Graft && await this.options.b4Graft.call(this, item);
        treeTools.graftBranch({
            source: item,
            target,
            isFront,
            isChild,
            flatData: this.flatData,
            treeData: this.treeData,
            rootEl: this.targetEl,
            cb: (obj, refer) => {
                if (refer && refer.children) {
                    this.expand(refer);
                    this.unselect(refer);
                }
                super.listen({ name: 'graft', cb, params: [obj] });
                return this;
            }
        });
        this.flatData = treeTools.toFlat(this.treeData);
        this.renderFinish();
        return this;
    }
    /**
     * @description Update the contents of the POPUP, which will change "options.content".
     * @param {fn_cb_data} [cb] - Callback execution after updating contents.
     * @returns {Promise<any>} - Returns a proimise whose argument is the instance itself.
     */
    async updateCont(content, cb) {
        if (this.destroyed) {
            return this;
        }
        await getContent.call(this, {
            content,
            contType: this.options.contType,
            contData: this.options.contData,
            ajax: {
                xhrName: 'contXhr',
                ...this.options.ajax
            },
            request: (data) => {
                this.listen({ name: 'request', params: [data] });
            },
            cb: (data) => {
                if (Array.isArray(data)) {
                    this.treeDataOrig = deepClone(data);
                    treeTools.addIdPath({ source: this.treeDataOrig });
                    this.targetEl.innerHTML = '';
                    this.renderData(this.treeDataOrig);
                    this.getTreeFlat();
                    this.renderFinish();
                    super.updateCache({ content });
                    super.listen({ name: 'updateCont', cb, params: [this.treeDataOrig] });
                }
            }
        });
        return this;
    }
    /**
     * @description Destroy instance and prohibit use.
     * @param {fn_cb_none} [cb] - Callback execution after destruction.
     * @returns {any} - Return the instance itself.
     */
    destroy(cb) {
        if (this.destroyed) {
            return this;
        }
        this.flatData.forEach((k) => {
            if (this.options.trigger === 'click') {
                let triggerEl = k.hasOwnProperty('href') && k.arrowEl ? k.arrowEl : k.headEl;
                triggerEl.removeEventListener('click', this.expandEvent);
            }
            else if (this.options.trigger === 'hover') {
                this.hoverIns.destroy();
            }
            if (!k.children) {
                k.headEl.removeEventListener('click', this.selectEvent);
            }
        });
        document.body.removeEventListener('click', this.pageEvent);
        this.contXhr ? this.contXhr.abort() : null;
        this.destroyed = true;
        super.listen({ name: 'destroy', cb });
        return this;
    }
}
Menu.hostType = 'el';
export default Menu;
