/**  
 * @since Last modified: 2024-09-11 10:51:21
 * @class Infinite
 * @description Folding too long text and omittind.
 * @param {*} elem - target element
 * @param {Object} [options] - Parameters 
 * @see {@link https://codepen.io/axui/pen/ZEwvpNL|demo @ codepen}
 * @example
 * ax.Infinite('#id');
 */
import getEl from '../utils/getEl';
import isEmpty from '../utils/isEmpty';
import instance from '../utils/instance';
import breakpoints from '../utils/breakpoints';
import createEl from '../utils/createEl';
import deepClone from '../utils/deepClone';
import purifyHtml from '../utils/purifyHtml';
import PubBase from './PubBase';
import PubListen from './PubListen';
import PubUpdate from './PubUpdate';
import PubCommon from './PubCommon';
import pipe from '../utils/pipe';
import ax from '../ax';
import { T_infinite } from '../../../types/infinite';
import { T_el, T_fn, T_obj, T_required } from '../../../types/ax';
import optInfinite from '../options/optInfinite';
import elProps from '../utils/elProps';
import prefix from '../global/prefix';
import alias from '../global/alias';
import classes from '../utils/classes';
import toPixel from '../utils/toPixel';
import Observe from './Observe';
import getDataType from '../utils/getDataType';
import getContent from '../utils/getContent';
import tplToEls from '../utils/tplToEls';
import tplToEl from '../utils/tplToEl';
import Spy from './Spy';
import getEls from '../utils/getEls';
import requireTypes from '../utils/requireTypes';
import extend from '../utils/extend';
import { T_getcontent } from '../../../types/utils';
import debounce from '../utils/debounce';
import parseUrlArr from '../utils/parseUrlArr';
class Infinite extends pipe(PubListen, PubUpdate, PubCommon)(PubBase) {
    targetEl: any;
    options: T_required<T_infinite<0>> | T_obj = {};
    destroyed: any;
    plans: any;
    storKeys: any;
    storObs: any;
    storTmp: any;
    rawOpts: any;
    static hostType = 'node';
    constructor(elem: any, options?: T_infinite, initial: boolean = true) {
        super();
        this.ready({
            options,
            maps: optInfinite,
            host: elem,
            component: false,
            spread: ['spy']
        });
        this.content = [];
        this.index = 0;
        this.contReq = {
            contType: this.options.contType,
            contData: this.options.contData,
            ajax: {
                error: (resp: T_obj) => {
                    this.finish(true);
                    super.listen({ name: 'error', params: [resp] });
                },
                xhrName: 'contXhr',
                ...this.options.ajax,
            },
        }
        super.listen({ name: 'ready' });
        initial && this.init() ;
    }
    async init(cb?: T_fn): Promise<any> {
        super.listen({ name: 'launch' });
        this.options.b4Init && await this.options.b4Init.call(this);
        this.setAttrs();
        this.useTpl();
        this.getStatusEl();
        this.targetEl.appendChild(this.statusEl);
        await this.getInitContent();
        this.options.spy.enable && this.getListSpyIns();
        if (this.options.trigger === 'scroll') {
            this.getStatusSpyIns();
        } else if (this.options.trigger === 'click') {
            this.nextBtn.onclick = () => {
                this.getStatusSpyIns();
            }
        } else if (this.options.trigger === 'clicks') {
            this.nextBtn.onclick = () => {
                this.renderList(this.index);
            }
        }
        super.listen({ name: 'init', cb });
        return this;
    }
    setAttrs() {
        this.targetEl.classList.add(`${prefix}inf`);
        classes.add(this.targetEl, this.options.classes);
        this.targetEl.setAttribute('trigger', this.options.trigger);
    }
    finish(err: boolean = false) {
        this.statusEl.setAttribute('status', 'finish');
        this.tipsEl.innerHTML = err ? this.options.lang.error : this.options.lang.finish;
        this.statusSpyIns && this.statusSpyIns.destroy();
        super.listen({ name: 'finish' });
    }
    getListSpyIns() {
        this.listSpyIns = new Spy(null, extend({
            target: {
                repeat: false,
                visible: false,
                in: `${prefix}fadeIn`,
            },
            source: this.options.spy,
        }));
    }
    getStatusSpyIns() {
        this.statusSpyIns = new Spy(this.statusEl, {
            root: this.targetEl,
            onIn: debounce(() => {
                this.renderList(this.index);
            })
        },
        );
    }
    async renderList(index: number) {
        if (this.destroyed || index >= this.content.length) return;
        this.statusEl.setAttribute('status', 'loading');
        this.tipsEl.innerHTML = this.options.lang.loading;
        await this.getPageData(this.content[index], async (data: T_el[]) => {
            this.options.b4Append && await this.options.b4Append.call(this, data);
            this.statusEl.setAttribute('status', 'loaded');
            this.tipsEl.innerHTML = this.options.lang.loaded;
            let fragment = document.createDocumentFragment();
            for (let k of data) {
                k.classList.add(`${prefix}inf-item`);
                fragment.appendChild(k as any);
            };
            this.targetEl.insertBefore(fragment, this.statusEl);
            this.listSpyIns && this.listSpyIns.add(data);
            super.listen({ name: 'append', params: [data] });
            this.index++;
            (this.index >= this.content.length) && this.finish();
        });
    }
    getStatusEl() {
        this.statusEl = this.targetEl.querySelector(`.${prefix}inf-status`) || createEl('div', { class: `${prefix}inf-status`, status: 'preload' });
        this.spinEl = createEl('div', { [alias]: 'spin' }, this.options.spin);
        this.statusEl.appendChild(this.spinEl);
        let nextTmp = this.targetEl.querySelector(`[${alias}="next"]`)
        if (nextTmp) {
            this.nextEl = nextTmp;
        } else {
            this.nextEl = createEl('div', { [alias]: 'next' }, this.options.lang.next);
            ['click', 'clicks'].includes(this.options.trigger as string) && this.statusEl.appendChild(this.nextEl);
        }
        this.nextBtn = this.nextEl.firstElementChild;
        let tipsTmp = this.targetEl.querySelector(`[${alias}="tips"]`);
        if (tipsTmp) {
            this.tipsEl = tipsTmp;
        } else {
            this.tipsEl = createEl('div', { [alias]: 'tips' }, this.options.lang.preload);
            this.statusEl.appendChild(this.tipsEl);
        }
    }
    async getInitContent() {
        let result: string[] | T_el[] = [];
        if (this.options.content) {
            let contType = getDataType(this.options.content);
            if (contType === 'String') {
                result = parseUrlArr(this.options.content, { max: this.options.max });
            } else if (contType === 'Array' && this.options.content.length) {
                let firstType = getDataType(this.options.content[0]);
                if (firstType === 'Object') {
                    for (let k of this.options.content) {
                        let tmp = extend({ target: deepClone(this.contReq), source: k })
                        result.push(await getContent.call(this, tmp as T_getcontent));
                    }
                } else if (firstType === 'Array') {
                    for (let k of this.options.content) {
                        if (!k.length) continue;
                        let sonType = getDataType(k[0]);
                        if (sonType.includes('HTML')) {
                            result = [...this.options.content];
                        } else if (sonType === 'Object') {
                            let tmp = k.map((k: any) => tplToEl(super.getTplcont(k)));
                            result.push(tmp);
                        }
                    }
                } else {
                    result = this.options.content;
                }
            }
        } else {
            let pages = getEls(`[${alias}="page"]`, this.statusEl);
            result = pages.map((k: any) => k.href).filter(Boolean);
        }
        this.content = result.filter((k: any) => !isEmpty(k));
    }
    async getPageData(data: any, cb?: T_fn): Promise<any> {
        if (this.destroyed) return;
        let dataType = getDataType(data),
            result = [],
            contReq = deepClone(this.contReq);
        if (dataType === 'String') {
            result = this.options.contType === 'text' ? tplToEls(data) : await getContent.call(this, { content: data, ...contReq });
        } if (dataType.includes('HTML')) {
            result = [...data.children];
        } else if (dataType === 'Object') {
            let params = extend({
                target: contReq,
                source: data
            }), tmp;
            tmp = await getContent.call(this, params as T_getcontent);
            if (isEmpty(tmp)) {
                if (typeof tmp === 'string') {
                    result = tplToEls(data);
                } else if (Array.isArray(tmp) && getDataType(tmp[0].include('HTML'))) {
                    result = tmp;
                }
            }
        } else if (dataType === 'Array') {
            result = this.arr2Nodes(data);
        }
        let resultType = getDataType(result);
        if (resultType === 'String') {
            result = tplToEls(result)
        } else if (resultType === 'Array') {
            result = this.arr2Nodes(result);
        }
        result = result.filter(Boolean);
        cb && cb(result);
        return result;
    }
    arr2Nodes(data: any[]) {
        let result: any[] = [];
        if (!data.length) return result;
        let itemType = getDataType(data[0]);
        if (itemType.includes('HTML')) {
            result = data;
        } else if (itemType === 'Object') {
            result = data.map((k: any) => tplToEl(super.getTplcont(k)));
        }
        return result;
    }
    /**
     * @description Destroy instance.Prohibit use.
     * @param {fn_cb_destroy} cb - Call back after destroy.
     * @returns {*} Return the instance itself
     */
    destroy(cb?: any): any {
        if (this.destroyed) return this;
        this.nextBtn && (this.nextBtn.onclick = null);
        this.listSpyIns && this.listSpyIns.destroy();
        this.statusSpyIns && this.statusSpyIns.destroy();
        this.contXhr && this.contXhr.abort();
        this.destroyed = true;
        super.listen({ name: 'destroy', cb });
        return this;
    }
}
export default Infinite;
