/**  
 * @since Last modified: 2024-09-11 09:44:42
 * @class Bubble
 * @description Public methods, only the variables and methods about plan, can be extended.
 * @example
 * class Demo extends pipe(Bubble)(PubBase) {};
 */
import { T_el, T_fn, T_fn_obj, T_obj, T_sel } from '../../../types/ax';
import { T_btn_ft } from '../../../types/utils';
import prefix from '../global/prefix';
import alias from '../global/alias';
import bulletinTools from '../utils/bulletinTools';
import createFooter from '../utils/createFooter';
import createTools from '../utils/createTools';
import deepClone from '../utils/deepClone';
import extend from '../utils/extend';
import formTools from '../utils/formTools';
import getContent from '../utils/getContent';
import getDataType from '../utils/getDataType';
import getEl from '../utils/getEl';
import getEls from '../utils/getEls';
import isEmpty from '../utils/isEmpty';
import isNull from '../utils/isNull';
import plan from '../utils/plan';
import preventDft from '../utils/preventDft';
import renderTpl from '../utils/renderTpl';
import setContent from '../utils/setContent';
import startUpper from '../utils/startUpper';
import tplToEl from '../utils/tplToEl';
import validTools from '../utils/validTools';
const Bubble = (parent: any) => {
    return class extends (parent || class Empty { }) {
        static hostType = 'node';
        /**
        * @name Bubble.formEl
        * @description Form node.
        * @type HTMLElement
        */
        formEl: any = null;
        canTrigger: boolean = false;
        setEmpty() {
            if (this.bubbleType === 'popup') {
                this.positionIns && this.positionIns.destroy();
                this.hoverIns && this.hoverIns.destroy();
            }
            this.mainEl && this.mainEl.remove();
            /**
            * @name bubble.state
            * @description Instance state.There are three states: "hidden", "shown" and "ing".
            * @type string
            */
            this.state = 'hidden';
        }
        handleFooter() {
            if (this.footEl) {
                this.options.footer.children.forEach((k: T_btn_ft) => {
                    if (k.name === 'confirm') {
                        if (k.action) {
                            k.action.call(this, k);
                        } else {
                            if (!k?.attrs?.href) {
                                k.el.onclick = async (e: any) => {
                                    this.options.b4Confirm && await this.options.b4Confirm.call(this, k);
                                    if (k.event === 'submit') {
                                        if (this.formEl) {
                                            validTools.listenSubmit({
                                                item: this.formEl,
                                                cb: (resp: any) => {
                                                    this.listen({ name: 'valid', params: [resp] });
                                                }
                                            });
                                            formTools.submit(this.formEl);
                                        } else {
                                            throw new Error('Form node is required!');
                                        }
                                    } else if (k.event === 'async') {
                                        if (this.formEl && k.url) {
                                            validTools.validForm({
                                                el: this.formEl,
                                                succ: () => {
                                                    formTools.submit(this.formEl, {
                                                        url: k.url,
                                                        rel: k.el,
                                                        opened: (resp: any) => {
                                                            this.submitXhr = resp.xhr;
                                                        },
                                                        success: (resp: any) => {
                                                            this.listen({ name: 'valid', params: [resp.content] });
                                                        }
                                                    });
                                                }
                                            });
                                        } else {
                                            throw new Error('The request url and form node are required!');
                                        }
                                    } else if (k.event === 'prevent') {
                                        preventDft(e);
                                    } else {
                                        !this.options.keepShow && this.hide();
                                    }
                                    this.listen({ name: 'confirm', cb: k.cb, params: [k] });
                                };
                            }
                        }
                    } else if (k.name === 'cancel') {
                        if (k.action) {
                            k.action.call(this, k);
                        } else {
                            if (!k?.attrs?.href) {
                                k.el.onclick = async (e: any) => {
                                    this.options.b4Cancel && await this.options.b4Cancel.call(this, k);
                                    if (k.event === 'zero') {
                                        this.formEl && formTools.reset(this.formEl, true);
                                    } else if (k.event === 'reset') {
                                        this.formEl && this.formEl.reset();
                                    } else if (k.event === 'prevent') {
                                        preventDft(e);
                                    } else {
                                        !this.options.keepShow && this.hide();
                                    }
                                    this.listen({ name: 'cancel', cb: k.cb, params: [k] });
                                };
                            }
                        }
                    } else if (k.name === 'close') {
                        if (k.action) {
                            k.action.call(this, k);
                        } else {
                            k.el.onclick = () => {
                                this.hide();
                            };
                        }
                    } else {
                        k.action && k.action.call(this, k);
                    }
                });
            }
        }
        getListTpl() {
            let result = '';
            if (!this.options.tplStr) {
                result = bulletinTools.getListTpl(this.options.list);
            }
            return result;
        }
        async renderContent(data: any) {
            if (isEmpty(data)) {
                console.warn('No insertable content!');
            }
            if (this.options.b4Fill) {
                let resp = await this.options.b4Fill.call(this, data, this.contEl);
                resp && (data = resp);
            }
            let dataType = getDataType(data),
                tplEl = getEl(this.options.tplStr),
                template = tplEl?.innerHTML || this.options.tplStr || this.getListTpl();
            if (dataType.includes('HTML') && ['image', 'iframe', 'audio', 'video'].includes(this.options.contType)) {
                this.options.media.caption && data.insertAdjacentHTML('afterbegin', `<div ${alias}="caption">${this.options.media.caption}</div>`);
                this.options.media.brief && data.insertAdjacentHTML('beforeend', `<div ${alias}="brief">${this.options.media.brief}</div>`);
            }
            setContent({
                content: data,
                target: this.contEl,
                engine: this.options.tplEng,
                template,
            });
            if (this.options.list.type) {
                this.list = getEls(this.options.list.selector || `.${prefix}bulletin`, this.contEl);
                if (Array.isArray(this.data)) {
                    this.data.forEach((k: any, i) => {
                        if (this.list[i]) {
                            k.el = this.list[i];
                            k.action && k.action.call(this, k);
                        }
                    });
                }
            }
            this.formEl = this.contEl.querySelector('form');
        }
        renderBubble(name: 'popup' | 'dialog' | 'drawer' = 'popup') {
            /**
            * @name Bubble.template
            * @description Template string for the popup.
            * @type string
            */
            this.template = renderTpl(this.getTpl(name), this.options);
            /**
             * @name Bubble.mainEl
             * @description Bubble master node.
             * @type HTMLElement
             */
            this.mainEl = tplToEl(this.template);
            /**
            * @name Bubble.wrapEl
            * @description Bubble second level node.
            * @type HTMLElement
            */
            this.wrapEl = this.mainEl.querySelector(`.${prefix}${name}-wrap`);
            /**
            * @name Bubble.contEl
            * @description Bubble forth level node.
            * @type HTMLElement
            */
            this.contEl = this.mainEl.querySelector(`.${prefix}${name}-cont`);
            /**
            * @name Bubble.bodyEl
            * @description Bubble third level node.
            * @type HTMLElement
            */
            this.bodyEl = this.mainEl.querySelector(`.${prefix}${name}-body`);
            /**
           * @name Bubble.headEl
           * @description Bubble head node.
           * @type HTMLElement
           */
            this.headEl = this.mainEl.querySelector(`.${prefix}${name}-head`);
            /**
           * @name Bubble.footEl
           * @description Bubble footer node.
           * @type HTMLElement
           */
            this.footEl = this.mainEl.querySelector(`.${prefix}${name}-foot`);
            /**
           * @name Bubble.maskEl
           * @description Bubble mask node.
           * @type HTMLElement
           */
            this.maskEl = this.mainEl.querySelector(`.${prefix}${name}-mask`);
            if (this.options.tools.enable) {
                createTools(this.options.tools.children, this.wrapEl);
            }
            if (this.footEl) {
                createFooter(this.options.footer, this.footEl);
            }
        }
        getTpl(name: 'popup' | 'dialog' | 'drawer' = 'popup') {
            return `
                                {{let hasPadding = (this.padding  && !this.list.type) || (this.padding && this.list.type && this.list.cols>0 && this.list.lines==='fluid');/}}
                                <div class="${prefix}${name}"">
                                    {{ if(this?.mask?.enable){ /}}<div class="${prefix}${name}-mask"></div>{{ } /}}
                                    <div class="${prefix}${name}-wrap">
                                        {{ if(this.heading){ /}}
                                            <div class="${prefix}${name}-head">{{ this.heading }}</div>
                                            {{ } /}}
                                        {{ if(this.divider){ /}}
                                            <ax-line></ax-line>
                                            {{ } /}}
                                        <div class="${prefix}${name}-body">
                                            {{ if(typeof this.padding === 'string' && this.padding){ /}}
                                                <div class="{{ this.padding }} ${prefix}${name}-padding"> 
                                            {{ }else if(hasPadding){ /}}
                                                <div class="${prefix}${name}-padding"> 
                                            {{ } /}}
                                            <div class="${prefix}${name}-cont"></div>
                                            {{ if(hasPadding){ /}}
                                                </div>
                                                {{ } /}}
                                        </div>
                                        {{ if(this.footer.enable && this.list.type !=='custom' && !this.list.radioFill){ /}}
                                        <div class="${prefix}${name}-foot"></div>
                                        {{ } /}}
                                    </div>
                                </div>
                                `
        }
        /**
         * 
         * 
         * 
        */
        initList() {
            if (this.targetEl) {
                isNull(this.targetEl.readOnly) ? this.targetEl.setAttribute('readonly', '') : this.targetEl.readOnly = true;
            }
            this.list && (this.list = bulletinTools.getChildNodes(this.contEl) );
            let vals = this.getTargetVal();
            if (this.options.list.fillable) {
                bulletinTools.preset(vals, this.list);
                let fill2Hide = () => {
                    this.checkedVals = bulletinTools.getVals(this.list);
                    this.targetEl && bulletinTools.fillTarget(this.targetEl, this.checkedVals);
                    this.hide();
                }
                if (this.options.list.type.includes('select')) {
                    this.list.forEach((k: any) => {
                        k.onclick = () => {
                            bulletinTools.toggleChecked(k, this.list, this.options.list.type);
                            if (this.options.list.type === 'single-select' && this.options.list.radioFill) {
                                fill2Hide();
                            }
                        }
                    });
                }
                if (this.options.list.type === 'radios' && this.options.list.radioFill) {
                    this.list.forEach((k: any) => {
                        k.onclick = () => {
                            fill2Hide();
                        }
                    });
                }
                if (this.footEl) {
                    this.options.footer.children.forEach((k: any) => {
                        if (k.action) {
                            k.action.call(this, k);
                        } else {
                            if (k.name === 'confirm') {
                                k.el.onclick = () => {
                                    this.checkedVals = bulletinTools.getVals(this.list);
                                    this.targetEl && bulletinTools.fillTarget(this.targetEl, this.checkedVals);
                                    this.hide();
                                }
                            } else if (k.name === 'cancel') {
                                k.el.onclick = () => {
                                    this.clearCheckeds();
                                }
                            }
                        }
                    });
                }
            } else {
                /*  if (this.footEl) {
                     this.options.footer.children.forEach((k: any) => {
                         if (k.name === 'confirm' || k.name === 'cancel') {
                             k.el.onclick = () => {
                                 this.hide();
                             }
                         }
                     });
                 } */
            }
        }
        checkTargetChecked() {
            if (this.options.list.fillable) {
                let vals = this.getTargetVal();
                (vals !== this.checkedVals) && bulletinTools.preset(vals, this.list);
            }
        }
        getTargetVal() {
            if (this.targetEl) {
                return ['INPUT', 'TEXTAREA', 'AX-INPUT', 'AX-TEXTAREA'].includes(this.targetEl.nodeName) ? (this.targetEl?.value || this.targetEl.querySelector('input,textarea')?.value || '') : this.targetEl.textContent.trim();
            } else {
                return '';
            }
        }
        clearCheckeds() {
            bulletinTools.cancelChecked(this.list);
        }
        checkedAll() {
            if (['checkboxes', 'multi-select'].includes(this.options.list.type)) {
                this.list.forEach((k: any) => {
                    let input = k.querySelector('AX-CHECKBOX');
                    if (input) {
                        (!k.hasAttribute('disabled') && !input.hasAttribute('disabled')) && input.setAttribute('check', 'ed');
                    } else {
                        !k.hasAttribute('disabled') && k.setAttribute('checked', '');
                    }
                });
            }
        }
        /**
         * 
         * 
         * 
        */
        /**
        * @description Reinitialize the instance with default parameters(this.dftOpts).
        * @param {fn_cb_none} [cb] - Callback execution after reseting options.
        * @returns {Promise<any>} - Returns a proimise whose argument is the instance itself.
        */
        async reset(cb?: T_fn): Promise<any> {
            this.options = deepClone(this.dftOpts);
            await this.init();
            this.clearCache();
            this.listen && this.listen({ name: 'reset', cb });
            return this;
        }
        /**
        * @description Update settings.
        * @param {object} settings - The settings to be updated, in the format {name:'',age:''}.
        * @param {fn_cb_options} [cb] - Callback execution after updating settings.
        * @returns {Promise<any>} - Returns a proimise whose argument is the instance itself.
        */
        async update(settings: T_obj, cb?: T_fn_obj): Promise<any> {
            if (this.destroyed) {
                return this;
            }
            if (!isEmpty(settings)) {
                this.updateCache(settings);
                extend({ target: this.options, source: settings });
                if (this.state !== 'ing') {
                    let tmp = this.state;
                    await this.init().then((res: any) => {
                        tmp === 'shown' && res.show();
                    });
                }
            }
            this.listen({ name: 'update', cb, params: [settings] });
            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: any, cb?: T_fn): Promise<any> {
            if (this.destroyed) {
                return this;
            }
            await getContent.call(this, {
                content,
                contType: this.options.contType,
                contData: this.options.contData,
                ajax: {
                    xhrName: 'contXhr',
                    spinSel: this.contEl,
                    ...this.options.ajax
                },
                request: (data) => {
                    this.listen({ name: 'request', params: [data] });
                },
                cb: (data) => {
                    this.data = data;
                    this.renderContent(data);
                    this.updateCache({ content });
                    this.listen({ name: 'updateCont', cb, params: [data] });
                }
            });
            return this;
        }
        /**
         * @description Change bubble host.
         * @param {getEl} el - New host node selector, written as "getEl".
         * @param {fn_cb_node} [cb] - Callback execution after transfering bubble host.
         * @returns {any} - Returns the instance itself.
         */
        transfer(el: T_sel, cb?: T_fn): any {
            if (this.destroyed) {
                return this;
            }
            let host = getEl(el);
            if (host) {
                this.unbindTrigger('host');
                this.targetEl = host;
                this.bindTrigger();
                if (this.bubbleType === 'popup') {
                    this.positionIns.transfer(host, (target: T_el) => {
                        this.listen({ name: 'transfer', cb, params: [target] });
                    });
                } else {
                    this.listen({ name: 'transfer', cb, params: [host] });
                }
            }
            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?: T_fn): any {
            this.mainEl.remove();
            if (this.bubbleType === 'popup') {
                this.positionIns.destroy();
                this.hoverIns && this.hoverIns.destroy();
                this.toggleShow && document.removeEventListener('click', this.toggleShow);
            }
            if (this.options.trigger === 'click' || this.options.trigger === 'input') {
                this.targetEl.removeEventListener(this.options.trigger, this.triggerShow);
                this.wings.forEach((i: any) => {
                    i.removeEventListener(this.options.wing.trigger || this.options.trigger, this.triggerShow);
                });
            }
            if (this.maskEl && this.options.mask.closable) {
                this.maskEl.removeEventListener('click', this.triggerShow);
            }
            this.contEl.querySelectorAll(`[${alias}="closebubble"]`).forEach((k: any) => {
                k.removeEventListener('click', this.triggerClose);
            });
            this.canTrigger = false;
            this.contXhr && this.contXhr.abort();
            this.submitXhr && this.submitXhr.abort();
            this.destroyed = true;
            this.listen({ name: 'destroy', cb });
            return this;
        }
    }
}
export default Bubble;
