/**
 * @since Last modified: 2024/04/29 18:34:43
 * @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|HTMLElement} options.selector  - Node selector.
 * @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 {string} [options.contType='']  - The content type of the request.
 * @param {object} [options.headers={}]  - The headers collection of the request.
 * @param {string} [options.responseType='']  - 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.loading='<i class="ax-loading"></i>']  - The loading effect before success or failure.
 * @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 {string|HTMLElement} [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 '../global/namespace';
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';
const ajax = (options, elem) => {
    if (isEmpty(options)) {
        throw new Error(`There is no options!`);
    }
    let label = createEl('span', { 'role': 'message' }), target = getEl(elem);
    target ? target.appendChild(label) : null;
    let dft = {
        url: '',
        selector: '',
        type: 'post',
        async: true,
        data: null,
        holdTime: 0,
        stopTime: 3600000,
        contType: '',
        headers: {},
        responseType: '',
        catchable: false,
        loading: `<i class="${ax.pfx}buff"></i>`,
        repeat: {
            index: 0,
            count: 0,
            keyword: '',
        },
        xhrFields: {},
        abort: (el, xhr) => {
            target ? label.innerHTML = ax.lang.ajax.abort : console.warn('The request has been suspended!');
        },
        timeout: (response) => {
            target ? label.innerHTML = ax.lang.ajax.timeout : console.warn('The request is out of time!');
        },
        opened: (response) => { },
        before: (response) => {
            target ? label.innerHTML = response.content : null;
        },
        downloading: (response) => { },
        uploading: (response) => { },
        success: (response) => {
            target ? target.innerHTML = response.content : null;
        },
        error: (response) => {
            target ? label.innerHTML = renderTpl(ax.lang.ajax.error, { status: response.status }) : console.error(`The current error state is:${response.status}`);
        },
    };
    extend(dft, options);
    let 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];
                }
                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.responseType;
    let timeoutFun, errorFun, successFun, abortFun = () => {
        timeoutFun ? xhr.removeEventListener('timeout', timeoutFun) : null;
        xhr.abort();
        target ? target.setAttribute('ajax-state', 'abort') : null;
        !isEmpty(dft.abort) ? dft.abort(target, xhr) : null;
    };
    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 + '%';
                cb && cb({ result, percent, current, total, ratio, time, target });
            }
        };
        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;
            });
        };
        /* xhr.onerror = (e: any) => {
            errorFun({ content: xhr.response, status: xhr.status, target, xhr });
        } */
        timeoutFun = () => {
            let response = { content: xhr.response, status: xhr.status, target, xhr };
            target ? target.setAttribute('ajax-state', 'timeout') : null;
            !isEmpty(dft.timeout) ? dft.timeout(response) : null;
            dft.catchable ? reject(response) : null;
        };
        errorFun = (response) => {
            target ? target.setAttribute('ajax-state', 'error') : null;
            !isEmpty(dft.error) ? dft.error(response) : null;
            dft.catchable ? reject(response) : null;
        };
        successFun = (response) => {
            if ((dft.repeat.max && dft.repeat.keyword) && (ax.ajaxStorage[dft.repeat.index] < dft.repeat.max) && (typeof response.content === 'string' && response.content.includes(dft.repeat.keyword))) {
                ajax(dft, target);
                ax.ajaxStorage[dft.repeat.index]++;
            }
            else {
                ax.ajaxStorage[dft.repeat.index] = 0;
                target ? target.setAttribute('ajax-state', 'success') : null;
                !isEmpty(dft.success) ? dft.success(response) : null;
                resolve(response);
            }
        };
        xhr.addEventListener('timeout', timeoutFun);
        xhr.onreadystatechange = function () {
            if (xhr.readyState < 4) {
                let response = { content: dft.loading, status: xhr.status, target, xhr, abort: abortFun };
                if (xhr.readyState === 1) {
                    !isEmpty(dft.opened) ? dft.opened(response) : null;
                }
                target ? target.setAttribute('ajax-state', 'before') : null;
                !isEmpty(dft.before) ? dft.before(response) : null;
            }
            else {
                let response = { status: xhr.status, target, xhr };
                if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
                    if (!dft.responseType || 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 }), selector = dft.selector || strEnd, childDom = getEl(selector, div);
                        if (childDom) {
                            content = childDom.innerHTML;
                        }
                        else if (selector && !childDom) {
                            console.warn(`The node of "${selector}" 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();
                            }
                        }
                        response.content = content;
                    }
                    else {
                        response.content = xhr.response;
                    }
                    if (dft.holdTime) {
                        delay({
                            duration: dft.holdTime,
                            done: () => {
                                successFun(response);
                            }
                        });
                    }
                    else {
                        successFun(response);
                    }
                }
                else {
                    response.content = xhr.response;
                    if (dft.holdTime) {
                        delay({
                            duration: dft.holdTime,
                            done: () => {
                                errorFun(response);
                            }
                        });
                    }
                    else {
                        errorFun(response);
                    }
                }
            }
        };
        let openParams = [], sendParams = null;
        if (dft.type.toLowerCase() === 'get') {
            openParams = ['get', dft.url + '?now=' + new Date().getTime() + '&' + 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;
