/**
 * @since Last modified: 2024-08-12 21:19:04
 * @class Tab
 * @description Horizontal or vertical tab.
 * @param {getEl} elem - Target element.
 * @param {object} [options] - Parameters
 *
 * @see {@link https://codepen.io/axui/pen/LYvEwMv|demo @ codepen}
 * @example
 * ax.Tab('#id');
 */
import Observe from "./Observe";
import optTab from "../options/optTab";
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 PubBase from "./PubBase";
import PubCommon from "./PubCommon";
import PubListen from "./PubListen";
import PubUpdate from "./PubUpdate";
import deepClone from "../utils/deepClone";
import getContent from "../utils/getContent";
import dlToArr from "../utils/dlToArr";
import prefix from "../global/prefix";
import findItem from "../utils/findItem";
import valToArr from "../utils/valToArr";
import moveItem from "../utils/moveItem";
import setContent from "../utils/setContent";
import classes from "../utils/classes";
class Tab extends pipe(PubListen, PubUpdate, PubCommon)(PubBase) {
    constructor(elem, options) {
        super();
        this.options = {};
        this.ready({
            options,
            host: elem,
            maps: optTab,
            component: false,
            spread: ['slider', 'col', 'menu', 'tools']
        });
        /**
        * @name Tab.targetEl
        * @description Target element.
        * @type getEl
        */
        /**
        * @name Tab.rawOpts
        * @description Raw Parameters of the instance from map.
        * @type object
        */
        /**
         * @name Tab.dftOpts
         * @description Default Parameters of the instance from merging.The reset method requires this variable.
         * @type object
         */
        /**
         * @name Tab.options
         * @description Parameters of the instance.
         * @type object
         */
        /**
         * @name Tab.destroyed
         * @description Destruction state.
         * @type boolean
         */
        /**
        * @name Tab.plans
        * @description Plans for listening.
        * @type object
        */
        /**
        * @name Tab.storKeys
        * @description The set of parameter fields to save.
        * @type array
        */
        /**
         * @name Tab.storTmp
         * @description Cache object.The "updateCache" method will update the variable.
         * @type object
         */
        /**
        * @name Tab.storObs
        * @description Cache listener(proxy) that listens for "storTmp" changes and automatically updates the local storage.
        * @type any
        */
        /**
        * @name Tab.rawHtml
        * @description Raw innerHTML of the target node.
        * @type string
        */
        /**
        * @name Tab.timestamp
        * @description Instance creation time.
        * @type number
        */
        /**
        * @function Tab.save
        * @description Storing settings locally.
        * @param {fn_cb_options} [cb] - Callback execution after saving local storage.
        * @returns {any} Return the instance itself
        */
        /**
        * @function Tab.reset
        * @description Re-initialization of old parameters.
        * @param {fn_cb_none} [cb] - Call back after reset settings.
        * @returns {*} Return the instance itself
        */
        /**
        * @function PubUpdate.update
        * @description Update settings.
        * @param {object} settings - The parameters to be updated.Format : settings={name:'',age:''}
        * @param {fn_cb_options} [cb] - Call back after update settings.
        * @returns {*} Return the instance itself
        */
        this.timestamp = new Date().getTime();
        let _this = this;
        this.activeEvent = function () {
            let attr = this.getAttribute('role') === 'label' ? 'labelEl' : 'headEl', item = findItem(this, _this.treeData, attr);
            if (item.disabled) {
                return false;
            }
            _this.active(item);
        };
        this.trigger = this.options.trigger === 'hover' ? 'mouseenter' : 'click';
        this.dataOriginal = [];
        super.listen({ name: 'ready' });
        this.init();
    }
    /**
    * @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();
        this.setAttrs();
        this.active(this.options.active || 0);
        this.disable(valToArr(this.options.disabled));
        this.renderFinish();
        super.listen({ name: 'init', cb });
        return this;
    }
    async getDataToRender() {
        if (!isEmpty(this.options.content)) {
            await getContent.call(this, {
                content: this.options.content,
                contType: this.options.contType,
                contData: this.options.contData,
                ajax: {
                    xhrName: 'contXhr',
                    spinSel: this.targetEl,
                },
                cb: (data) => {
                    let dataType = getDataType(data);
                    if (dataType === 'Array') {
                        this.dataOriginal = deepClone(data);
                    }
                    else if (dataType.includes('HTML')) {
                        let dts = data.querySelectorAll('dt'), dds = data.querySelectorAll('dd');
                        dts && dds && dts.length === dds.length && (this.dataOriginal = dlToArr(data));
                    }
                    else if (dataType === 'String') {
                        let tmp = createEl('div', '', data), dts = tmp.querySelectorAll('dt'), dds = tmp.querySelectorAll('dd');
                        if (dts && dds && dts.length === dds.length) {
                            this.dataOriginal = dlToArr(tmp);
                            tmp.remove();
                        }
                    }
                }
            });
            this.targetEl.innerHTML = '';
            this.renderData(this.dataOriginal);
        }
        else {
            this.dataOriginal = this.htmlToArr();
        }
        this.treeData = this.getObserver(this.dataOriginal).proxy;
        super.listen({ name: 'rendered', params: [this.treeData] });
        return this;
    }
    getObserver(data) {
        this.observeIns ? this.observeIns.destroy() : null;
        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 === 'selected') {
                    if (obj.value === true) {
                        obj.target.headEl.setAttribute('selected', '');
                        obj.target.bodyEl.setAttribute('selected', '');
                        !obj.target.bodyEl.innerHTML ? this.fillContent(obj.target) : null;
                    }
                    else {
                        obj.target.headEl.removeAttribute('selected');
                        obj.target.bodyEl.removeAttribute('selected');
                    }
                }
                else if (obj.key === 'disabled') {
                    obj.value === true ? obj.target.headEl.setAttribute('disabled', '') : obj.target.headEl.removeAttribute('disabled');
                }
            },
            onComplete: () => {
                let activeItem = this.getActive(), tmp = {
                    active: activeItem ? (activeItem.hasOwnProperty('id') ? activeItem.id : this.treeData.indexOf(activeItem)) : 0,
                    disabled: this.getDisabled().map((k, i) => k.hasOwnProperty('id') ? k.id : i),
                    content: !isEmpty(this.options.content) ? deepClone(this.dataOriginal) : {},
                };
                super.updateCache(tmp);
            }
        });
        return this.observeIns;
    }
    htmlToArr() {
        /*  <div>
                <ul role="_tab_head">
                    <li role="head" active><i role="label"></i></li>
                    <li role="head"></li>
                </ul>
                <ul role="_tab_body">
                    <li role="body"></li>
                    <li role="body"></li>
                </ul>
         </div> */
        let data = [];
        this.headsEl = this.targetEl.querySelector(`.${prefix}tab-head`);
        this.bodysEl = this.targetEl.querySelector(`.${prefix}tab-body`);
        if (!this.headsEl || !this.bodysEl) {
            return data;
        }
        let headsArr = [...this.headsEl.children], bodysArr = [...this.bodysEl.children];
        if (headsArr.length !== bodysArr.length) {
            console.warn('Inconsistent number of heads and bodys!');
            return data;
        }
        let 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);
            }
        }, getPropsFromHead = (elem) => {
            let obj = {}, tmpLabel = elem.querySelector('[role="label"]');
            obj.headEl = elem;
            obj.labelEl = tmpLabel || elem;
            obj.label = obj.labelEl.textContent.trim();
            obj.tipsEl = elem.querySelector('[role="tips"]');
            obj.tips = obj.tipsEl ? obj.tipsEl.textContent.trim() : '';
            obj.iconEl = elem.querySelector('[role="icon"]');
            obj.icon = obj.iconEl ? obj.iconEl.getAttribute('class') : '';
            obj.cubeEl = elem.querySelector('[role="cube"]');
            obj.cube = obj.cubeEl ? obj.cubeEl.src : '';
            obj.diskEl = elem.querySelector('[role="disk"]');
            obj.disk = obj.diskEl ? obj.diskEl.src : '';
            obj.badgeEl = elem.querySelector('[role="badge"],ax-badge');
            obj.badge = obj.badgeEl ? obj.badgeEl.getAttribute('label') || obj.badgeEl.textContent.trim() : '';
            setBoolean(obj, ['selected', 'disabled']);
            return obj;
        };
        data = headsArr.map((k, i) => {
            return { ...getPropsFromHead(k), bodyEl: bodysArr[i], content: bodysArr[i].innerHTML };
        });
        return data;
    }
    renderData(data) {
        this.headsEl = createEl(this.options.render.head);
        this.bodysEl = createEl(this.options.render.body);
        for (let k of data) {
            if (!k.hasOwnProperty('label') || !k.hasOwnProperty('content')) {
                continue;
            }
            else {
                this.createItemEl(k);
                this.headsEl.appendChild(k.headEl);
                this.bodysEl.appendChild(k.bodyEl);
            }
        }
        this.targetEl.appendChild(this.headsEl);
        this.targetEl.appendChild(this.bodysEl);
    }
    createItemEl(item) {
        item.labelEl = createEl('i', { role: 'label' }, item.label);
        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.headEl = createEl(this.options.render.headChild);
        item.bodyEl = createEl(this.options.render.bodyChild, '');
        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.selected ? (item.headEl.setAttribute('selected', '') && item.bodyEl.setAttribute('selected', '')) : null;
        item.disabled ? item.headEl.setAttribute('disabled', '') : null;
        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;
    }
    async fillContent(obj, cb) {
        await getContent.call(this, {
            content: obj.content,
            contType: obj.contType,
            contData: obj.contData,
            ajax: {
                ajaxType: obj.ajaxType,
                xhrName: 'contXhr',
                spinSel: obj.bodyEl,
            },
            cb: (data) => {
                setContent({
                    content: data,
                    target: obj.bodyEl,
                    engine: obj.engine,
                    template: obj.template
                });
                cb && cb(data);
            }
        });
    }
    setAttrs() {
        this.targetEl.classList.add(`${prefix}tab`);
        this.options.classes ? classes.add(this.targetEl, this.options.classes) : null;
        this.options.dispose ? this.dispose() : null;
        if (this.options.layout) {
            this.headsEl.classList.add(`${prefix}group-${this.options.layout}`);
        }
        this.options.embed ? this.targetEl.setAttribute('embed', '') : null;
        this.options.col.enable ? this.targetEl.setAttribute('col', this.options.col.position) : null;
    }
    renderFinish() {
        this.treeData.forEach((k) => {
            k.headEl.removeEventListener(this.trigger, this.activeEvent);
            k.headEl.addEventListener(this.trigger, this.activeEvent, false);
            k.action && k.action.call(this, k);
        });
    }
    getDisabled() {
        return this.treeData.filter((k) => k.disabled);
    }
    getEnabled() {
        return this.treeData.filter((k) => !k.disabled);
    }
    getActive() {
        return this.treeData.find((k) => k.selected);
    }
    addTrigger(item) {
        if (!item.disabled) {
            item.headEl.removeEventListener(this.trigger, this.activeEvent);
            item.headEl.addEventListener(this.trigger, this.activeEvent, false);
        }
    }
    removeTrigger(item) {
        if (!item.disabled) {
            item.headEl.removeEventListener(this.trigger, this.activeEvent);
        }
    }
    toggleDisabled(item, type = 'disable') {
        if (type === 'enable') {
            item.disabled = false;
            this.addTrigger(item);
        }
        else if (type === 'disable') {
            item.disabled = true;
            this.removeTrigger(item);
        }
    }
    dispose() {
        this.targetEl.setAttribute('inert', '');
    }
    revive() {
        this.targetEl.removeAttribute('inert');
    }
    disable(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.treeData)).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.treeData);
        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.treeData)).filter(Boolean), 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.treeData);
        super.listen({ name: 'enableAll', cb });
        return this;
    }
    active(data, cb) {
        if (this.destroyed || isNull(data)) {
            return this;
        }
        let item = findItem(data, this.treeData), other = this.treeData.find((k) => k !== item && k.selected);
        if (!item || item.selected) {
            return this;
        }
        other ? other.selected = false : null;
        item.selected = true;
        item.disabled = false;
        super.listen({ name: 'active', cb, params: [item] });
        return this;
    }
    add({ data, target, isFront = true, repeat = true, cb }) {
        if (this.destroyed || isNull(data)) {
            return this;
        }
        if (this.treeData.length >= this.options.max && this.options.max > 0) {
            throw new Error('The number of children exceeds the maximum!');
        }
        let type = getDataType(data), targetObj = findItem(target, this.treeData), items = type === 'Array' ? data : type === 'Object' ? [data] : [];
        items = items.filter((k) => k.hasOwnProperty('label') && k.hasOwnProperty('content'));
        if (!repeat) {
            let labels = this.treeData.map((k) => k.label);
            items = items.filter((k) => !labels.includes(k.label));
        }
        if (items.length === 0) {
            return this;
        }
        for (let k of items) {
            if (!k.hasOwnProperty('label') || !k.hasOwnProperty('content')) {
                continue;
            }
            else {
                this.createItemEl(k);
            }
        }
        if (targetObj) {
            let index = this.treeData.indexOf(targetObj), i = isFront ? index : index + 1, position = isFront ? 'beforeBegin' : 'afterEnd';
            this.treeData.splice(i, 0, ...items);
            !isFront ? items.reverse() : null;
            items.forEach((k) => {
                targetObj.headEl.insertAdjacentElement(position, k.headEl);
                targetObj.bodyEl.insertAdjacentElement(position, k.bodyEl);
            });
        }
        else {
            if (isFront) {
                this.treeData.unshift(...items);
                items.reverse().forEach((k) => {
                    this.headsEl.insertAdjacentElement('afterBegin', k.headEl);
                    this.bodysEl.insertAdjacentElement('afterBegin', k.bodyEl);
                });
            }
            else {
                this.treeData.push(...items);
                items.forEach((k) => {
                    this.headsEl.appendChild(k.headEl);
                    this.bodysEl.appendChild(k.bodyEl);
                });
            }
        }
        if (this.options.addActive) {
            this.active(items[items.length - 1]);
        }
        this.renderFinish();
        super.listen({ name: 'add', cb, params: [items] });
        return this;
    }
    remove(data, cb) {
        if (this.destroyed || isNull(data)) {
            return this;
        }
        if (this.treeData.length === 0) {
            console.warn('The source data is already empty!');
            return this;
        }
        let tmp = Array.isArray(data) ? data : [data], items = tmp.map((k) => findItem(k, this.treeData)).filter(Boolean), removeItem = (obj) => {
            let index = this.treeData.indexOf(obj);
            this.treeData.splice(index, 1);
            obj.headEl.remove();
            obj.bodyEl.remove();
        };
        items.forEach(k => {
            removeItem(k);
        });
        super.listen({ name: 'remove', cb, params: [items] });
        return this;
    }
    edit({ source, target, cb }) {
        if (this.destroyed || isNull(source) || isEmpty(target)) {
            return this;
        }
        let item = findItem(source, this.treeData);
        if (!item) {
            return this;
        }
        for (let k in target) {
            let val = target[k];
            if (k === 'action') {
                item[k] = val.call(item);
            }
            else if (['icon', 'disk', 'cube', 'label', 'tips', 'badge', 'disabled'].includes(k)) {
                item[k] = val;
            }
            else if (k === 'selected') {
                val === true ? this.active(item) : item[k] = val;
            }
        }
        super.listen({ name: 'edit', cb, params: [item] });
        return this;
    }
    move({ source, target, isFront = true, cb }) {
        if (this.destroyed || isNull(source)) {
            return this;
        }
        let sourceObj = findItem(source, this.treeData), targetObj = findItem(target, this.treeData);
        if (!sourceObj) {
            return this;
        }
        if (targetObj) {
            let position = isFront ? 'beforeBegin' : 'afterEnd';
            targetObj.headEl.insertAdjacentElement(position, sourceObj.headEl);
            targetObj.bodyEl.insertAdjacentElement(position, sourceObj.bodyEl);
            moveItem({ source: sourceObj, target: targetObj, data: this.treeData, isFront });
        }
        else {
            let position = isFront ? 'afterBegin' : 'beforeEnd';
            this.headsEl.insertAdjacentElement(position, sourceObj.headEl);
            this.bodysEl.insertAdjacentElement('beforeEnd', sourceObj.bodyEl);
            moveItem({ source: sourceObj, data: this.treeData, isFront });
        }
        super.listen({ name: 'move', cb, params: [sourceObj, targetObj] });
        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 updateItemContent({ content, contType, contData, ajaxType, target, cb }) {
        if (this.destroyed) {
            return this;
        }
        let targetObj = findItem(target, this.treeData);
        if (isEmpty(content) || !targetObj) {
            return this;
        }
        Object.assign(targetObj, {
            content,
            contType,
            contData,
            ajaxType,
        });
        await this.fillContent(targetObj);
        super.listen({ name: 'updateItemContent', cb, params: [targetObj] });
        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: {
                ajaxType: this.options.ajaxType,
                xhrName: 'contXhr',
            },
            cb: (data) => {
                this.targetEl.innerHTML = '';
                this.renderData(data);
                super.updateCache({ content });
                super.listen({ name: 'updateCont', cb, params: [data] });
            }
        });
        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.treeData.forEach((k) => {
            k.headEl.removeEventListener(this.trigger, this.activeEvent);
        });
        this.contXhr ? this.contXhr.abort() : null;
        this.destroyed = true;
        super.listen({ name: 'destroy', cb });
        return this;
    }
}
Tab.hostType = 'el';
export default Tab;
