/**
 * @since Last modified: 2024/03/04 15:57:45
 * @constant form
 * @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 getEls from "./getEls";
import field from "./field";
import Message from '../modules/Message';
import ajax from './ajax';
import isEmpty from "./isEmpty";
import clampValue from "./clampValue";
import validTools from "./validTools";
import unique from "./unique";
interface Opt_submit {
    url: string;
    rel?: any,
    type?: 'post' | 'get';
    delay?: number;
    buffer?: 'loading' | 'text' | '';
    success?: any;
    before?: any;
    error?: any;
    opened?: any;
}
const form = {
    /**
     * @memberof form
     * @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.form.reset('#demo',true);
     * <!--return null-->
     */
    reset: (el: any, zero: boolean = false, cb?: T_fn): void => {
        let parentDom = getEl(el),
            fields = [...parentDom?.querySelectorAll('input:not([type="submit"],[type="reset"]),select,textarea')];
        if (fields.length > 0) {
            for (let k of fields) {
                let type = k.type;
                if (type?.includes('select')) {
                    [...k.options].forEach((i: any) => i.selected = zero ? i.defaultSelected : false);
                } else if (type === 'checkbox' || type === 'radio') {
                    k.checked = zero ? k.defaultChecked : false;
                } else if (['number', 'range'].includes(type)) {
                    k.value = zero ? k.defaultChecked : (k.min ? k.min : '0');
                } else {
                    k.value = zero ? k.defaultValue : '';
                }
            }
            cb && cb();
        }
    },
    /**
     * @memberof form
     * @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.Paramsters are "parent" and "fileds".
     * @returns {null} - Return null.
     * @see {@link https://codepen.io/axui/pen/XWGNgwN|demo @ codepen}
     * @example
     * ax.form.setValues('#demo');
     * <!--return null-->
     */
    setValues: function (el: any, data: any = {}, cb?: T_fn): void {
        let parentDom = getEl(el),
            fields = [...parentDom?.querySelectorAll('input:not([type="submit"],[type="reset"],[type="file"]),select,textarea')];
        if (!parent) { return }
        if (isEmpty(data)) { this.reset(parent) }
        for (let [k, v] of Object.entries(data)) {
            let item = fields.find((i: any) => i.name === k);
            if (item) {
                let type = item.type;
                if (['select-one', 'select-multiple', 'checkbox', 'radio'].includes(type)) {
                    field.setValues({
                        el: item,
                        val: v,
                        parent,
                    });
                } else if (['number', 'range'].includes(type)) {
                    item.value = clampValue({ val: (v as number | string), min: item.min, max: item.max });
                } else {
                    item.value = '';
                }
            }
        }
        cb && cb(parent, fields);
    },
    /**
     * @memberof form
     * @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.form.getValues({el:'#demo'});
     * <!--Return *-->
     */
    getValues: (el: any, format: 'str' | 'formdata' | 'json' | 'dft' = 'dft'): any => {
        let parentDom = getEl(el),
            none = format === 'str' ? [] : format === 'json' ? '' : null,
            condition = (k: any) => {
                return k.name && !k.name.includes(ax.nameSfx)
            },
            fields: any = [],
            inputs: any,
            items: any[] = [],
            output: any;
        if (parentDom) {
            fields = unique([...parentDom.querySelectorAll('input:not([type="submit"],[type="reset"]),select,textarea')].filter(condition), 'name');
            items = fields.map((k: any) => {
                return { name: k.name, type: k.type, values: [...field.getValues({ el: k, parent: parentDom,format:'arr' })] };
            });
        } else {
        }
        try {
            inputs = Array.isArray(el) ? getEls(el) : [...getEl(el).querySelectorAll('[name]')].filter(k => ['INPUT', 'SELECT', 'TEXTAREA'].includes(k.nodeName) && k.name && !k.name.includes(ax.nameSfx));
            inputs.forEach((i: any) => {
                let have = items.find((k: any) => k.name === i.name);
                if (have && (i.type === 'checkbox' || i.type === 'radio')) {
                    have.doms.push(i);
                } else {
                    items.push({ name: i.name, type: i.type, doms: [i] });
                }
            });
            items.forEach(k => {
                k.value = (k.type == 'file') ? field.getValues({ el: k.doms[0] }) :
                    k.type.includes('select') ? field.getValues({ el: k.doms[0] }) :
                        (k.type == 'checkbox' || k.type === 'radio') ? field.getValues({ el: k.doms[0], parent: parentDom }) : k.doms[0].value;
            });
        } catch {
        }
        if (format === 'string') {
            output = '';
            items.forEach((k, i) => {
                let symbol = i === 0 ? '' : '&';
                if (Array.isArray(k.value)) {
                    if (k.value.length > 0) {
                        k.value.forEach((x: any) => {
                            output += symbol + k.name + '=' + (k.type === 'file' ? x.name : x);
                        });
                    } else {
                        output += symbol + k.name + '=';
                    }
                } else {
                    output += symbol + k.name + '=' + (k.type === 'file' ? k.value?.name || '' : k.value);
                }
            });
        } else if (format === 'formdata') {
            output = new FormData();
            items.forEach(k => {
                if (Array.isArray(k.value)) {
                    if (k.value.length > 0) {
                        k.value.forEach((x: any) => {
                            output.append(k.name, x);
                        });
                    } else {
                        output.append(k.name, '');
                    }
                } else {
                    output.append(k.name, k.value);
                }
            });
        } else if (format === 'json') {
            output = {}
            items.forEach(k => {
                output[k.name] = k.value;
            });
        } else {
            output = items;
        }
        return output;
    },
    /**
    * @memberof form
    * @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.buffer - 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.form.submit('#demo');
    * <!--return null-->
    */
    submit: function (el: any, opt?: Opt_submit) {
        let formDom = getEl(el);
        if (!formDom) { return false; }
        if (opt && opt.hasOwnProperty('url')) {
            let {
                url = '',
                rel,
                type = 'post',
                delay = 1000,
                buffer = '',
                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(`${ax.pfx}ajax-overlay`);
                    if (relDom) {
                        if (buffer === 'loading' || buffer === 'text') {
                            relDom.removeAttribute('inert');
                            relDom.innerHTML = relText;
                        } else {
                            relDom.removeAttribute('cheking');
                        }
                    }
                };
            if (formDom.classList.contains(`${ax.pfx}ajax-overlay`)) { return false; }
            return ajax({
                url,
                data: this.values(formDom, 'formdata'),
                type,
                opened: (response: any) => {
                    opened && opened(response);
                },
                before: (response: any) => {
                    formDom.classList.add(`${ax.pfx}ajax-overlay`);
                    if (relDom) {
                        if (buffer === 'loading') {
                            relDom.innerHTML = response.content;
                            relDom.setAttribute('inert', '');
                        } else if (buffer === 'text') {
                            relDom.innerHTML = ax.lang.ajax.submit.btn;
                            relDom.setAttribute('inert', '');
                        } else {
                            relDom.setAttribute('checking', '');
                        }
                    }
                    before && before(response);
                },
                success: (response: any) => {
                    setTimeout(() => {
                        resume();
                        new Message({
                            content: ax.lang.ajax.submit.succ,
                            result: 'succ',
                            iconShow: true,
                        });
                        success && success(response);
                    }, delay);
                },
                error: (response: any) => {
                    resume();
                    new Message({
                        content: ax.lang.ajax.submit.fail,
                        result: '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.submit();
            }
        }
    }
};
export default form;
