/**
 * @since Last modified: 2024-09-17 00:21:02
 * @function ajax
 * @description Obtain the content by asynchronous requests.
 * @param {object} options  - Parameters of launching asynchronous requests.
 * @param {string} options.url  - Request URL.
 * @param {string} options.childSel  - Child node selector , e.g. "#demo" , ".demo" or "DIV".
 * @param {string} [options.type='post']  - Ajax type.
 * @param {boolean} [options.async=true]  - Synchronous request or asynchronous request.
 * @param {number} [options.holdTime=0]  - Waiting time in the state of success or failure.The unit is ms.
 * @param {number} [options.stopTime=6000]  - Stop the delay time of the request.The unit is ms.
 * @param {string} [options.contType='']  - The content type of the request.
 * @param {object} [options.headers={}]  - The headers collection of the request.
 * @param {string} [options.respType='']  - Back data type.The default is empty, that is, the "text" type.
 * @param {boolean} [options.catchable=false]  - Whether to capture the reject of promise.
 * @param {string} [options.spinStr='<i class="_spin"></i>']  - The spin string before success or failure.
 * @param {getEl} [options.spinSel='']  - Spin node selector.Which node to append the "loading" attribute to before "success".
 * @param {string} [options.xhrName='']  - Append the xhrName property to "this".e.g. ajax.call(this,{})
 * @param {object} [options.repeat]  - After the request is successful, the accident result is returned, whether the request repeated the request.
 * @param {number} [options.repeat.index=0]  - The location of the request number.The index of ax.ajaxStorage.
 * @param {number} [options.repeat.max=0]  - The maximum of repeat.
 * @param {string} [options.repeat.keyword='']  - Determine whether the return content contains this keyword.
 * @param {object} [options.xhrFields={}]  - Add additional fields to XHR.e.g."withCredentials".
 * @param {function} [options.abort]  - The cb of abort.
 * @param {function} [options.timeout]  - The cb of timeout.
 * @param {function} [options.opened]  - The cb of opened.
 * @param {function} [options.error]  - The cb of error.
 * @param {function} [options.success]  - The cb of success.
 * @param {function} [options.before]  - The cb of before.
 * @param {function} [options.uploading]  - The cb of uploading.
 * @param {function} [options.downloading]  - The cb of downloading.
 * @param {getEl} [elem]  - Node stored data.
 * @returns {Promise} Return a promise.
 * @see {@link https://codepen.io/axui/pen/abXaxbL|demo @ codepen}
 * @example
 * ax.ajax({url:''});
 * <!--return Promise-->
 */
'use strict';
import ax from '../ax';
import createEl from './createEl';
import getEl from './getEl';
import isEmpty from './isEmpty';
import getDataType from './getDataType';
import sliceStrEnd from './sliceStrEnd';
import renderTpl from './renderTpl';
import delay from './delay';
import extend from './extend';
import alias from '../global/alias';
import config from '../global/config';
import allToEls from './allToEls';
const ajax = (options) => {
    if (isEmpty(options)) {
        throw new Error(`There is no options!`);
    }
    let dft = {
        target: '',
        url: '',
        type: 'post',
        async: true,
        data: null,
        holdTime: 0,
        stopTime: 3600000,
        contType: '',
        headers: {},
        respType: '',
        catchable: false,
        spinStr: `<ax-spin></ax-spin>`,
        spinSel: '',
        xhrName: '',
        repeat: {
            index: 0,
            max: 0,
            keyword: '',
        },
        xhrFields: {},
        abort: (el, xhr) => { },
        timeout: (resp) => { },
        opened: (resp) => { },
        before: (resp) => { },
        downloading: (resp) => { },
        uploading: (resp) => { },
        complete: (resp) => { },
        success: (resp) => { },
        error: (resp) => { },
        cb: (resp) => { },
    };
    extend({ target: dft, source: options });
    !dft.type && (dft.type = 'post');
    let label = createEl('span', { [alias]: 'message' }), target = getEl(dft.target);
    target ? (target.innerHTML = '', target.appendChild(label)) : null;
    let dftAbort = () => {
        target ? label.innerHTML = config.lang.ajax.abort : console.warn('The request has been suspended!');
    }, dftTimeout = () => {
        target ? label.innerHTML = config.lang.ajax.timeout : console.warn('The request is out of time!');
    }, dftBefore = (res) => {
        target ? label.innerHTML = res.content : null;
    }, dftSuccess = (res) => {
        target ? target.innerHTML = res.content : null;
    }, dftError = (res) => {
        target ? label.innerHTML = renderTpl(config.lang.ajax.error, { status: res.status }) : console.error(`The current error state is:${res.status}`);
    };
    let spinEls = allToEls(dft.spinSel), xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"), params;
    if (!isEmpty(dft.data)) {
        let dataType = getDataType(dft.data);
        if (dataType === 'FormData') {
            params = dft.data;
        }
        else if (dataType === 'Object') {
            if (dft.contType?.includes('json')) {
                params = JSON.stringify(dft.data);
            }
            else {
                /* for (let k in dft.data) {
                    params += '&' + k + '=' + dft.data[k];
                } */
                params = new URLSearchParams(dft.data).toString();
                dft.contType = 'application/x-www-form-urlencoded';
            }
        }
        else {
            dft.data = dft.data.trim();
            params = dft.data.startsWith('&') || dft.data.startsWith('?') ? dft.data.slice(1) : dft.data;
            dft.contType = 'application/x-www-form-urlencoded';
        }
    }
    if (dft.contType) {
        dft.headers['Content-Type'] = dft.contType;
        if (!dft.contType.includes('urlencoded') && dft.type.toLowerCase() === 'get') {
            throw new Error(`Please request data with post!`);
        }
    }
    xhr.timeout = dft.stopTime;
    xhr.responseType = dft.respType;
    let dftResp = { target, label, xhr }, timeoutFun, errorFun, successFun, abortFun = () => {
        timeoutFun ? xhr.removeEventListener('timeout', timeoutFun) : null;
        xhr.abort();
        target ? target.setAttribute(config.attrs.ajaxState, 'abort') : null;
        !isEmpty(dft.abort) ? dft.abort(Object.assign({ status: xhr.status, content: xhr.response }, dftResp)) : dftAbort();
    };
    return new Promise((resolve, reject) => {
        let progress = (e, cb) => {
            let current = e.loaded, total = e.total, ratio = 0, percent = 0, result = '0%', time = (new Date(e.timeStamp)).getTime();
            if (e.lengthComputable) {
                ratio = current / total;
                percent = Math.round(ratio * 100);
                result = percent + '%';
                let data = Object.assign({ result, percent, current, total, ratio, time, status: xhr.status, abort: abortFun }, dftResp);
                cb && cb(data);
                percent >= 100 && dft.complete && dft.complete(data);
            }
        };
        xhr.upload.onprogress = (e) => {
            progress(e, (data) => {
                !isEmpty(dft.uploading) ? dft.uploading(data) : null;
            });
        };
        xhr.onprogress = (e) => {
            progress(e, (data) => {
                !isEmpty(dft.downloading) ? dft.downloading(data) : null;
            });
        };
        timeoutFun = () => {
            let resp = Object.assign({ status: xhr.status, content: xhr.response }, dftResp);
            target ? target.setAttribute(config.attrs.ajaxState, 'timeout') : null;
            !isEmpty(dft.timeout) ? dft.timeout(resp) : dftTimeout();
            dft.catchable ? reject(resp) : null;
            dft.cb && dft.cb(resp);
        };
        errorFun = (resp) => {
            target ? target.setAttribute(config.attrs.ajaxState, 'error') : null;
            !isEmpty(dft.error) ? dft.error(resp) : dftError(resp);
            dft.catchable ? reject(resp) : null;
            dft.cb && dft.cb(resp);
        };
        successFun = (resp) => {
            if ((dft.repeat.max && dft.repeat.keyword) && (ax.ajaxStorage[dft.repeat.index] < dft.repeat.max) && (typeof resp.content === 'string' && resp.content.includes(dft.repeat.keyword))) {
                ajax(dft);
                ax.ajaxStorage[dft.repeat.index]++;
            }
            else {
                ax.ajaxStorage[dft.repeat.index] = 0;
                if (spinEls.length > 0) {
                    spinEls.forEach((k) => {
                        k.removeAttribute(config.attrs.ajaxSpin, '');
                    });
                }
                target ? target.setAttribute(config.attrs.ajaxState, 'success') : null;
                !isEmpty(dft.success) ? dft.success(resp) : dftSuccess(resp);
                resolve(resp);
                dft.cb && dft.cb(resp);
            }
        };
        xhr.addEventListener('timeout', timeoutFun);
        xhr.onreadystatechange = function () {
            if (xhr.readyState < 4) {
                let resp = Object.assign({ status: xhr.status, content: dft.spinStr, abort: abortFun }, dftResp);
                if (xhr.readyState === 1) {
                    dft.xhrName ? this[dft.xhrName] = xhr : null;
                    !isEmpty(dft.opened) ? dft.opened(resp) : null;
                }
                if (spinEls.length > 0) {
                    spinEls.forEach((k) => {
                        k.setAttribute(config.attrs.ajaxSpin, '');
                    });
                }
                target ? target.setAttribute(config.attrs.ajaxState, 'before') : null;
                !isEmpty(dft.before) ? dft.before(resp) : dftBefore(resp);
            }
            else {
                let resp = Object.assign({ status: xhr.status }, dftResp);
                if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
                    if (!dft.respType || xhr.responseType === 'text') {
                        let div = createEl('div', '', xhr.responseText), content = '', reg = /(<head\b[^<]*(?:(?!<\/head>)<[^<]*)*<\/head>)|(<\/?html.*?>)|(<\!DOCTYPE.*?html.*?>)|(<\/?body.*?>)/gi, strEnd = sliceStrEnd({ str: dft.url }), childSel = (typeof dft.data === 'string' ? dft.data : strEnd), childDom = getEl(childSel, div);
                        if (childDom) {
                            content = childDom.innerHTML;
                        }
                        else if (!isEmpty(childSel) && !childDom) {
                            console.warn(`The node of "${childSel}" is not exist!`);
                            content = xhr.responseText.replace(reg, '').trim();
                        }
                        else {
                            if (xhr.responseText.trim().startsWith('{') || xhr.responseText.trim().startsWith('[')) {
                                content = JSON.parse(xhr.responseText.trim());
                            }
                            else {
                                content = xhr.responseText.replace(reg, '').trim();
                            }
                        }
                        resp.content = content;
                    }
                    else {
                        resp.content = xhr.response;
                    }
                    if (dft.holdTime) {
                        delay({
                            duration: dft.holdTime,
                            done: () => {
                                successFun(resp);
                            }
                        });
                    }
                    else {
                        successFun(resp);
                    }
                }
                else {
                    resp.content = xhr.response;
                    if (dft.holdTime) {
                        delay({
                            duration: dft.holdTime,
                            done: () => {
                                errorFun(resp);
                            }
                        });
                    }
                    else {
                        errorFun(resp);
                    }
                }
            }
        };
        let openParams = [], sendParams = null;
        if (dft.type.toLowerCase() === 'get') {
            openParams = ['get', dft.url + '?now=' + Date.now() + '&' + params, dft.async];
        }
        else if (dft.type.toLowerCase() === 'post') {
            openParams = ['post', dft.url, dft.async];
            sendParams = params;
        }
        for (let k in dft.xhrFields) {
            dft.xhrFields.hasOwnProperty(k) ? xhr[k] = dft.xhrFields[k] : null;
        }
        xhr.open(...openParams);
        for (let k in dft.headers) {
            dft.headers.hasOwnProperty(k) ? xhr.setRequestHeader(k, dft.headers[k]) : null;
        }
        xhr.send(sendParams);
    });
};
export default ajax;
