/**
 * @since Last modified: 2024-09-04 20:04:21
 * @constant formTools
 * @description Collection of operation methods for forms.
 * @see {@link https://codepen.io/axui/pen/abXwOXm|demo @ codepen}
 */
'use strict';
import ax from "../ax";
import getEl from "./getEl";
import fieldTools from "./fieldTools";
import Message from '../modules/Message';
import ajax from './ajax';
import isEmpty from "./isEmpty";
import validTools from "./validTools";
import unique from "./unique";
import { T_el, T_fn, T_obj, T_sel } from "../../../types/ax";
import prefix from "../global/prefix";
import config from "../global/config";
type T_form = {
    fixName: any;
    getHosts: any;
    reset: any;
    getVals: any;
    setVals: any;
    submit: any;
}
type T_opt_submit = {
    url: string;
    rel?: any,
    type?: 'post' | 'get';
    delay?: number;
    spin?: 'loading' | 'text' | '';
    success?: any;
    before?: any;
    error?: any;
    opened?: any;
}
const formTools: T_form = {
    /**
     * @memberof form
     * @description Correct the name of the output value.
     * @param {getEl} item - The item of field,that contains name , type and value properties.
     * @returns {string} - Return a new name.
     * @see {@link https://codepen.io/axui/pen/XWGNgwN|demo @ codepen}
     * @example
     * ax.formTools.getHosts('#demo');
     * <!--return array-->
     */
    fixName: (item: T_el): string => {
        let types = ['file', 'select-multiple', 'checkbox'];
        return (types.includes(item.type) && !item.name.endsWith('[]')) ? `${item.name}[]` : item.name;
    },
    /**
     * @memberof formTools
     * @description Based on the name attribute, obtain the host form field from the form.
     * @param {getEl} el - Form node.
     * @returns {null} - Return a array of host fields.
     * @see {@link https://codepen.io/axui/pen/XWGNgwN|demo @ codepen}
     * @example
     * ax.formTools.getHosts('#demo');
     * <!--return array-->
     */
    getHosts: (el: T_el): T_el[] => {
        let condition = (k: any) => k.name && !k.name.includes(ax.nameSfx);
        return unique([...el?.querySelectorAll('input:not([type="submit"],[type="reset"]),select,textarea')]?.filter(condition), 'name');
    },
    /**
     * @memberof formTools
     * @description Clear the value of the form to zero.
     * @param {getEl} el - Form node.
     * @param {boolean} [zero=false] - Whether to return to zero directly.
     * @param {function} [cb] - The call back of reset.No paramsters.
     * @returns {null} - Return null.
     * @see {@link https://codepen.io/axui/pen/XWGNgwN|demo @ codepen}
     * @example
     * ax.formTools.reset('#demo',true);
     * <!--return null-->
     */
    reset: function (el: T_sel, zero: boolean = false, cb?: T_fn): void {
        let parentDom = getEl(el),
            fields = this.getHosts(parentDom);
        if (fields.length > 0) {
            for (let k of fields) {
                fieldTools.reset({ el: k, parent: parentDom, zero });
            }
            cb && cb();
        }
    },
    /**
     * @memberof formTools
     * @description Update form based on data, type=file is invalid.
     * @param {getEl} el - Form node.
     * @param {object} [data={}] - The data of updated.If it is a null value, use the reset method.
     * @param {function} [cb] - The call back of update.No paramsters.
     * @returns {null} - Return null.
     * @see {@link https://codepen.io/axui/pen/XWGNgwN|demo @ codepen}
     * @example
     * ax.formTools.setVals('#demo');
     * <!--return null-->
     */
    setVals: function (el: T_sel, data: T_obj = {}, cb?: T_fn): void {
        let parentDom = getEl(el),
            fields = this.getHosts(parentDom);
        if (!parent || fields.length === 0) { return }
        if (isEmpty(data)) { this.reset(parent, true) }
        for (let [k, val] of Object.entries(data)) {
            let item = fields.find((i: any) => i.name === k);
            item ? fieldTools.setVals({ el: item, val, parent }) : null;
        }
        cb && cb();
    },
    /**
     * @memberof formTools
     * @description Serialize a form and get the values of all form fields.
     * @param {object} obj  - Parameters.
     * @param {getEl|getEls} obj.el - Form dom or inputs array.
     * @param {"dft"|"string"|"formdata"|"json"} [obj.format='dft'] - Output value format (array)."string"=>`?name=wang&age=18&like[]=game&name[]=boll`;"formdata"=>return formData format;""=>array data;"json"=>{name:'',age:''...}.
     * @returns  {*} - The output values.
     * @see {@link https://codepen.io/axui/pen/yLwYwdE|demo @ codepen}
     * @example
     * ax.formTools.getVals({el:'#demo'});
     * <!--Return *-->
     */
    getVals: function (el: T_sel, format: 'string' | 'formdata' | 'json' | '' = ''): any {
        let parentDom = getEl(el),
            none = format === 'string' ? '' : format === 'json' ? {} : format === 'formdata' ? new FormData() : [],
            fields: any = [],
            items: any[] = [],
            fieldName = '',
            output: any;
        try {
            if (parentDom) {
                fields = this.getHosts(parentDom);
                items = fields.map((k: any) => {
                    return { name: k.name, type: k.type, value: fieldTools.getVals({ el: k, parent: parentDom }) };
                });
                if (format === 'string') {
                    output = '';
                    items.forEach((k, i) => {
                        fieldName = this.fixName(k);
                        let symbol = i === 0 ? '' : '&';
                        if (Array.isArray(k.value)) {
                            if (k.value.length > 0) {
                                k.value.forEach((x: any) => {
                                    output += symbol + fieldName + '=' + (k.type === 'file' ? x.name : x);
                                });
                            } else {
                                output += symbol + fieldName + '=';
                            }
                        } else {
                            output += symbol + fieldName + '=' + k.value;
                        }
                    });
                } else if (format === 'formdata') {
                    output = new FormData();
                    items.forEach(k => {
                        fieldName = this.fixName(k);
                        if (Array.isArray(k.value)) {
                            if (k.value.length > 0) {
                                k.value.forEach((x: any) => {
                                    output.append(fieldName, x);
                                });
                            }
                        } else {
                            output.append(fieldName, k.value);
                        }
                    });
                } else if (format === 'json') {
                    output = {}
                    items.forEach(k => {
                        fieldName = this.fixName(k);
                        output[fieldName] = k.value;
                    });
                } else {
                    output = items;
                }
                return output;
            } else {
                return none;
            }
        } catch {
            return none;
        }
    },
    /**
    * @memberof formTools
    * @description Submit form.
    * @param {getEl} el  - The node of form.
    * @param {false|null|object} opt - The parameters.If it is an object containing the url property, the form will be submitted async by building formdata.
    * @param {string} opt.url - The url of async request.
    * @param {getEl} opt.rel - The related node.usually a button.
    * @param {'post' | 'get'} opt.type - The type of async request.
    * @param {number} opt.delay - The delay after success submit.
    * @param {'loading' | 'text' | ''} opt.spin - The related node loading mode.
    * @param {function} opt.success - The call back of success.
    * @param {function} opt.before - The call back before success.
    * @param {function} opt.error - The call back of error.
    * @param {function} opt.opened - The call back of xhr opened.
    * @returns null.
    * @see {@link https://codepen.io/axui/pen/PoLbxJe|demo @ codepen}
    * @example
    * ax.formTools.submit('#demo');
    * <!--return null-->
    */
    submit: function (el: any, opt?: T_opt_submit) {
        let formDom = getEl(el);
        if (!formDom) { return false; }
        if (opt && opt.hasOwnProperty('url')) {
            let {
                url = '',
                rel,
                type = 'post',
                delay = 1000,
                spin = '',
                success,
                before,
                error,
                opened,
            } = opt;
            if (!url || !parent) { throw new Error('The request url and form node are required!'); }
            let relDom = getEl(rel),
                relText = relDom ? relDom.innerHTML : '',
                resume = () => {
                    formDom?.classList.remove(`${prefix}ajax-overlay`);
                    if (relDom) {
                        if (spin === 'loading' || spin === 'text') {
                            relDom.removeAttribute('inert');
                            relDom.innerHTML = relText;
                        } else {
                            relDom.removeAttribute('cheking');
                        }
                    }
                };
            if (formDom.classList.contains(`${prefix}ajax-overlay`)) { return false; }
            return ajax({
                url,
                data: this.getVals(formDom, 'formdata'),
                type,
                opened: (response: any) => {
                    opened && opened(response);
                },
                before: (response: any) => {
                    formDom?.classList.add(`${prefix}ajax-overlay`);
                    if (relDom) {
                        if (spin === 'loading') {
                            relDom.innerHTML = response.content;
                            relDom.setAttribute('inert', '');
                        } else if (spin === 'text') {
                            relDom.innerHTML = config.lang.ajax.submit.btn;
                            relDom.setAttribute('inert', '');
                        } else {
                            relDom.setAttribute('checking', '');
                        }
                    }
                    before && before(response);
                },
                success: (response: any) => {
                    setTimeout(() => {
                        resume();
                        new Message({
                            content: config.lang.ajax.submit.succ,
                            status: 'succ',
                            iconShow: true,
                        });
                        success && success(response);
                    }, delay);
                },
                error: (response: any) => {
                    resume();
                    new Message({
                        content: config.lang.ajax.submit.fail,
                        status: 'error',
                        iconShow: true,
                    });
                    error && error(response);
                }
            });
        } else {
            let validItem = ax.valids.find((k: any) => k.parent === formDom);
            if (validItem) {
                validTools.listenSubmit({ item: formDom });
                formDom.dispatchEvent(new Event('submit'));
            } else {
                formDom.nodeName === 'form' ? formDom.submit() : null;
            }
        }
    }
};
export default formTools;
