/**  
 * @since Last modified: 2024-08-13 18:28:23
 * @class PubCommon
 * @description The common utils of all modules
 * @example
 * class Demo extends pipe(PubCommon)(PubBase) {};
 */
import getDataType from '../utils/getDataType';
import getEl from '../utils/getEl';
import extend from '../utils/extend';
import ax from '../ax';
import deepClone from '../utils/deepClone';
import { T_el, T_fn, T_fn_node, T_fn_none, T_fn_obj, T_obj, T_opt, T_sel } from '../../../types/ax';
import isEmpty from '../utils/isEmpty';
import breakpoints from '../utils/breakpoints';
import spreadBool from '../utils/spreadBool';
import instance from '../utils/instance';
import elProps from '../utils/elProps';
import isNull from '../utils/isNull';
import attrJoinVal from '../utils/attrJoinVal';
import getEls from '../utils/getEls';
import renderTpl from '../utils/renderTpl';
const PubCommon = (parent: any) => {
    return class extends (parent || class Empty { }) {
        rawHtml: string = '';
        optMap: any[] = [];
        /**
         * @function PubCommon.getOptsFromMap
        * @description Get the default parameters from the map.
        * @returns {object} default parameters.
        */
        getOptsFromMap(map: T_obj[]): T_obj {
            let options: T_obj = {};
            map.forEach((k: any) => {
                options[k.prop] = k.value;
            });
            return options;
        }
        /**
        * @function PubCommon.isCmpt
        * @description Determine whether the target node is webcomponent.
        * @returns {boolean} True or false
        */
        isCmpt(elem: any): boolean {
            return elem.getAttribute('role') === 'slot-host';
        }
        /**
        * @function PubCommon.getElOptsMap
        * @description Get Node and attribute name with parameters.
        * @returns {object} target node and options attribure.
        */
        getElOptsMap(elem: any): T_obj {
            if (isEmpty(elem)) {
                return { el: null, attr: {} }
            } else {
                let isCmpt = this.isCmpt(elem),
                    el = isCmpt ? elem.parentNode : elem,
                    attr = isCmpt ? 'init' : `ax-${this.constructor.name.toLowerCase()}`;
                return { el, attr }
            }
        }
        /**
        * @function PubCommon.mergeOpts
        * @description Merge default parameters and user parameters.
        * @param {object} source - Participate in the merge parameters.
        * @returns {object} Parameters merged with the default parameters.
        */
        mergeOpts({
            source = {},
            map = [],
            el = null,
            component = false
        }: {
            source: any,
            map: T_obj[],
            el?: T_sel,
            component?: boolean
        }): T_obj {
            let target = getEl(el),
                moduleName = this.constructor.name.toLowerCase(),
                elMap: any = this.getElOptsMap(target),
                options = deepClone(this.getOptsFromMap(map));
            this.rawOpts = deepClone(options);
            !isEmpty(ax.lang[moduleName]) ? options.lang = Object.assign({ ...ax.lang[moduleName] }, options.lang) : null;
            extend({ target: options, source: ax.config[moduleName] || {} });
            extend({ target: options, source, host: elMap.el, attr: elMap.attr });
            component ? extend({ target: options, source: this.mergeCmptAttrs(target, map) }) : null;
            this.dftOpts = deepClone(options);
            return options;
        }
        /**
        * @function PubCommon.mergeCmptAttrs
        * @description From component attributes merging parameters.
        * @returns {object} Parameters merged with the component attributes.
        */
        mergeCmptAttrs(elem: any, map: T_obj[]): T_obj {
            let options: T_obj = {};
            if (this.isCmpt(elem)) {
                let target = elem.closest(`AX-${this.constructor.name.toUpperCase()}`);
                let attributes = target.attributes;
                for (let k of attributes) {
                    let item = map.find((i: any) => i.attr === k.name);
                    if (item) {
                        /* let value: any, valueType = getDataType(item.value);
                        if (!k.value) {
                            value = valueType === 'Boolean' ? true :
                                valueType === 'String' ? '' : null;
                        } else {
                            value = valueType === 'String' ? k.value : (new Function('return ' + k.value))();
                        } */
                        let keyVal = attrJoinVal(item.attr, k.value, [item]);
                        Object.assign(options, keyVal)
                    }
                }
            }
            return options;
        }
        /**
        * @function PubCommon.getOptsAttr
       * @description Get the attribute name of obtaining parameters from nodes.
       * @returns {string} Return the attribute name.
       */
        getOptsAttr(elem: any): string {
            return elem.nodeName === `AX-${this.constructor.name.toUpperCase()}` ? 'options' : `ax-${this.constructor.name.toLowerCase()}`;
        }
        /**
        * @function PubCommon.getStrFromContent
        * @description Fill in the real content from the content parameter.
        * @param {string} str - Selector or pure text string.
        * @returns {string} Return the text string.
        */
        getStrFromContent(str: string): string {
            let el = getEl(str);
            return el ? el.innerHTML : str;
        }
        /**
        * @function PubCommon.revertHtml
        * @description Revert node original state.
        * @returns null
        */
        revertHtml(elem: any) {
            this.rawHtml ? elem.innerHTML = this.rawHtml : null;
        }
        /**
        * @function PubCommon.updateOpts
        * @description Update options.
        * @returns null
        */
        updateOpts(spread?: T_obj) {
            this.optsMergeStorage && this.optsMergeStorage();
            !isEmpty(this.options.breakpoints) ? breakpoints(this.options, this.options.breakpoints) : null;
            if (Array.isArray(spread) && spread.length > 0) {
                let obj: T_obj = {}
                spread?.forEach((k: string) => {
                    obj[k] = this.rawOpts[k];
                });
                spreadBool(this.options, obj);
            }
        }
        /**
        * @function PubCommon.getStorageKeys
        * @description Get storage keys.
        * @returns A array of keys.
        */
        getStorageKeys() {
            let result = [];
            if (this.options.hasOwnProperty('storKeys')) {
                if ((this.options.storKeys as string[]).length > 0) {
                    result = this.options.storKeys;
                } else {
                    result = Object.keys(this.options).filter((k: string) => !['storKeys', 'insName', 'storName'].includes(k) && !/on[A-Z]/.test(k))
                }
            }
            return result;
        }
        /**
        * @function PubCommon.single2Els
        * @description Single value to element array.
        * @returns A element array.
        */
        single2Els(data: T_sel | T_el[], parent?: T_sel) {
            let result: T_el[] = [];
            if (data) {
                let dataType = getDataType(data);
                if (dataType === 'Array') {
                    result = data as T_el[];
                } else if (dataType.includes('HTML')) {
                    result.push(data as T_el);
                } else {
                    result = getEls(data, parent);
                }
            }
            return result.filter(Boolean);
        }
        /**
        * @function PubCommon.useTpl
        * @description Get `this.tplStr` and `this.tplEng`.
        * @returns null.
        */
        useTpl() {
            let tplEl = getEl(this.options.tplStr);
            this.tplEng = this.options.tplEng || renderTpl;
            this.tplStr = tplEl?.innerHTML || this.options.tplStr;
        }
        /**
        * @function PubCommon.getTemplate
        * @description Get template string.
        * @returns string.
        */
        /*  getStrFromTpl(data: T_obj | T_obj[], tplStr?: string, tplEng?: T_fn ) {
             let str = tplStr || this.tplStr,
                 eng = tplEng || this.tplEng;
             return eng.name === 'template' ? eng(str)(data) : eng(str, data);
         } */
        /**
        * @function PubCommon.ready
        * @description Executing after completing the constructor and only before initializing it.
        * @returns null
        */
        ready({
            options = {},
            maps = [],
            host,
            component = false,
            spread = [],
        }: {
            options: T_obj,
            maps: T_obj[],
            host?: any,
            component?: boolean,
            spread?: string[],
        }) {
            let hostType = getDataType(host),
                tmpType = '';
            if (!isNull(host)) {
                tmpType = (hostType === 'String' || hostType.includes('HTML')) ? 'el' : 'data';
            } else {
                tmpType = 'none';
            }
            if (tmpType === 'el') {
                this.targetEl = getEl(host);
                if (!this.targetEl) {
                    throw new Error('The target element is required!');
                };
                elProps(this.targetEl)?.add(this.moduleName);
                this.options = this.mergeOpts({
                    source: options,
                    map: maps,
                    el: this.targetEl,
                    component,
                });
                this.rawHtml = this.targetEl.innerHTML;
                this.cmptAttrs = [];
            } else {
                if (tmpType === 'data') {
                    this.targetData = host;
                    if (!this.targetData) {
                        throw new Error('The target data is required!');
                    };
                }
                this.options = this.mergeOpts({
                    source: options,
                    map: maps,
                });
            }
            this.storKeys = this.getStorageKeys();
            this.createProxy && this.createProxy();
            this.plans = {};
            instance.push(this, this.options.insName, this.constructor.name.toLowerCase());
            this.on('start', () => {
                this.destroyed = false;
                this.updateOpts(spread);
            });
            this.initCount = 0;
        }
        /**
        * @function PubCommon.reset
        * @description Re-initialization of old parameters.
        * @param {fn_cb_none} [cb] - Call back after reset settings.
        * @returns {*} Return the instance itself
        */
        reset(cb?: T_fn_none): any {
            this.options = deepClone(this.dftOpts);
            this.revertHtml && this.targetEl && this.revertHtml(this.targetEl as T_el);
            this.init();
            this.clearCache && this.clearCache();
            this.listen && this.listen({ name: 'reset', cb });
            return this;
        }
        /**
        * @function PubCommon.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
        */
        update(settings: Object, cb?: T_fn_obj): any {
            if (this.destroyed) return this;
            if (!isEmpty(settings)) {
                this.updateCache && this.updateCache(settings);
                extend({ target: this.options, source: settings });
                this.init();
            }
            this.listen && this.listen({ name: 'update', cb, params: [settings] });
            return this;
        }
        /**
        * @description Update content.
        * @param {string} data - The parameters of content.
        * @param {fn_cb_update_content} cb - Call back after update content.
        * @returns {*} Return the instance itself
        */
        updateCont(data: Object, cb?: T_fn): any {
            if (this.destroyed) return this;
            this.options.content = data;
            this.init();
            this.updateCache({ content: data });
            this.listen({ name: 'updateCont', cb, params: [data] });
            return this;
        }
    }
}
export default PubCommon;
