/**
 * @since Last modified: 2024/05/23 11:31:33
 * @function validate
 * @description Flat the nested function.Run function from left to right.
 * @param {array} args - The array of functions.
 * @returns  {function} - The flat function(the last function).
 * @see {@link https://codepen.io/axui/pen/vYbMgOb|demo @ codepen}
 * @example
 * ax.validate(fn1,fn2,fn3)(val);
 * <!--The same as : fn3(fn2(fn1(val)))-->
 * <!--ax.validate(fn1,fn2,fn3) will return the fn3-->
 */
'use strict';
import ax from "../ax";
import config from "../global/config";
import ajax from "./ajax";
import fieldTools from "./fieldTools";
import getDataType from "./getDataType";
import getEl from "./getEl";
import isDateStr from "./isDateStr";
import isEmpty from "./isEmpty";
import isSubset from "./isSubset";
import paramToJson from "./paramToJson";
import regExps from "./regExps";
import renderTpl from "./renderTpl";
import validTools from "./validTools";
const validate = async (val, opts = {}) => {
    let options = Object.assign({
        type: 'required',
        regLocal: config.valid.regLocal,
        regChars: config.valid.regChars,
        lengthStr: config.valid.lengthStr,
        lang: ax.lang.valid,
        separator: ',',
        succ: ax.lang.valid.succ,
        fail: ax.lang.valid.fail,
    }, opts), result = {}, valType = getDataType(val), value = valType === 'number' ? val + '' : val, parentEl = getEl(options.parent), dftRules = {}, userRules = {}, strength = validTools.getStrength(value, {
        chars: `[${options.regChars}]`,
        local: `[${options.regLocal}]`,
        length: options.lengthStr
    }), typeData = typeof options.type === 'string' ? paramToJson(options.type) : options.type;
    let testRegex = ({ regex, value, name, data }) => {
        let val = '';
        if (typeof value !== 'string') {
            console.warn('The value is not of string type, it has been converted to a string!');
            val = value + '';
        }
        let flag = validTools.test(val, regex), fail = renderTpl(options.lang[name], { label: options.label, name, value, data });
        ((name === 'length<' || name === 'length<=') && !value) && (flag = true);
        return flag ? { passed: true } : { passed: false, fail, type: name };
    }, loopTestRules = (types) => {
        let result = {};
        types.forEach((k) => {
            if (regExps.hasOwnProperty(k)) {
                result[k] = (data) => {
                    if (Array.isArray(data.value)) {
                        return { passed: false, fail: ax.lang.valid.strFormat, id: 'strFormat' };
                    }
                    return testRegex({ regex: regExps[k], value: data.value, name: k, data: typeData[k] });
                };
            }
        });
        return result;
    }, loopIncludeRules = (types) => {
        let result = {};
        types.forEach((k) => {
            result[k] = (data) => {
                if (!Array.isArray(typeData[k])) {
                    return { passed: false, fail: ax.lang.valid.arrFormat };
                }
                ;
                let child = Array.isArray(data.value) ? data.value : (data.value + '').split(options.separator), father = Array.isArray(typeData[k]) ? typeData[k] : (typeData[k] + '').split(options.separator), flag = isSubset(child, father), fail = renderTpl(options.lang[k], { label: data.label, name: data.name, value: data.value, data: typeData[k] });
                k === 'exclude' && (flag = !flag);
                return flag ? { passed: true } : { passed: false, fail, type: k };
            };
        });
        return result;
    }, loopSameRules = (types) => {
        let result = {};
        types.forEach((k) => {
            result[k] = (data) => {
                if (!Array.isArray(typeData[k])) {
                    return { passed: false, fail: ax.lang.valid.arrFormat };
                }
                ;
                let referField = parentEl?.querySelector(`[name="${typeData[k][0]}"]`), referValue = fieldTools.getVals({ el: referField, parent: parentEl, format: 'string' }), val = (value + ''), fail = renderTpl(options.lang[k], { label: data.label, name: data.name, value: val, data: typeData[k] }), condition = k === 'same' ? (referValue === val) : (referValue !== val);
                return condition ? { passed: true } : { passed: false, fail, type: k };
            };
        });
        return result;
    }, loopDateRules = (types) => {
        let result = {};
        types.forEach((k) => {
            result[k] = (data) => {
                if (Array.isArray(data.value)) {
                    return { passed: false, fail: ax.lang.valid.strFormat };
                }
                let flag = validTools.expired(data.value, typeData[k]).join(''), fail = renderTpl(options.lang[k], { label: data.label, value: data.value, data: typeData[k] }), condition = k === 'date=' ? (flag === '0-2') :
                    k === 'date<' ? (flag === '-1-2') :
                        k === 'date<=' ? (flag === '0-2' || flag === '-1-2') :
                            k === 'date>' ? (flag === '1-2') :
                                k === 'date>=' ? (flag === '1-2' || flag === '0-2') :
                                    k === 'date><' ? (flag === '1-1') :
                                        k === 'date><=' ? (flag === '1-1' || flag === '10') :
                                            k === 'date>=<' ? (flag === '1-1' || flag === '0-1') :
                                                k === 'date>=<=' ? (['1-1', '10', '0-1', '00'].includes(flag)) : true;
                return condition ? { passed: true } : { passed: false, fail, type: k };
            };
        });
        return result;
    }, loopThanRules = (types) => {
        let result = {};
        types.forEach((k) => {
            result[k] = (data) => {
                if (Array.isArray(data.value)) {
                    return { passed: false, fail: ax.lang.valid.strFormat };
                }
                let flag = validTools.than(data.value, types[k]).join(''), fail = renderTpl(options.lang[k], { label: data.label, value: data.value, data: typeData[k] }), condition = k === 'than>' ? (flag === '1-2') :
                    k === 'than>=' ? (flag === '1-2' || flag === '0-2') :
                        k === 'than<' ? (flag === '-1-2') :
                            k === 'than<=' ? (flag === '0-2' || flag === '-1-2') :
                                k === 'than=' ? (flag === '0-2') :
                                    k === 'than><' ? (flag === '1-1') :
                                        k === 'than><=' ? (flag === '1-1' || flag === '10') :
                                            k === 'than>=<' ? (flag === '1-1' || flag === '0-1') :
                                                k === 'than>=<=' ? (['1-1', '10', '0-1', '00'].includes(flag)) : true;
                return condition ? { passed: true } : { passed: false, fail, type: k };
            };
        });
        return result;
    }, loopLengthRules = (types) => {
        let result = {};
        types.forEach((k) => {
            result[k] = (data) => {
                if (Array.isArray(data.value)) {
                    return { passed: false, fail: ax.lang.valid.strFormat, id: 'strFormat' };
                }
                let pattern = k === 'length=' ? typeData[k] :
                    k === 'length>' ? `${typeData[k] + 1},` :
                        k === 'length>=' ? `${typeData[k]},` :
                            k === 'length<' ? `0,${typeData[k] - 1}` :
                                k === 'length<=' ? `0,${typeData[k]}` :
                                    k === 'length><' ? `${typeData[k][0] + 1},${typeData[k][1] - 1}` :
                                        k === 'length><=' ? `${types[k][0] + 1},${typeData[k][1]}` :
                                            k === 'length>=<' ? `${types[k][0]},${typeData[k][1] - 1}` :
                                                k === 'length>=<=' ? `${types[k][0]},${typeData[k][1]}` : '', regex = new RegExp(`^.{${pattern}}$`);
                return testRegex({ regex, value: data.value, name: k });
            };
        });
        return result;
    }, loopValuesRules = (types) => {
        let result = {};
        types.forEach((k) => {
            result[k] = (data) => {
                let value = (Array.isArray(data.value)) ? data.value : data.value.split(options.separator).filter(Boolean), fail = renderTpl(options.lang[k], { label: data.label, name: data.name, ins: this, value: value.length, data: typeData[k] }), condition = k === 'count>=<=' ? (value.length >= types[k][0] && value.length <= types[k][1]) :
                    k === 'count><=' ? (value.length > types[k][0] && value.length <= types[k][1]) :
                        k === 'count>=<' ? (value.length >= types[k][0] && value.length < types[k][1]) :
                            k === 'count><' ? (value.length > types[k][0] && value.length < types[k][1]) :
                                k === 'count=' ? `${value.length} === ${types[k]}` : `${value.length} ${k.replace('count', '')} ${types[k]}`;
                return new Function(`return ${condition}`)() ? { passed: true } : { passed: false, fail, type: k };
            };
        });
        return result;
    };
    dftRules = {
        /*
        *required validation
        */
        'required': (data) => {
            let fail = renderTpl(options.lang.required, { label: data.label });
            return !isEmpty(data.value) ? { passed: true } : { passed: false, fail, type: 'required' };
        },
        /*
         *Commonly used regExps validation
         */
        ...loopTestRules(['email', 'cellphone', 'landline', 'ip', 'id', 'zip', 'url', 'locale', 'password', 'letter', 'string', 'ymdhms', 'ymd', 'hms', 'ym', 'y', 'm', 'd', 'integer', 'number']),
        'plate': (data) => {
            let regex = data.value.length === 8 ? regExps.plate.nev : regExps.plate.oil;
            return testRegex({ regex, value: data.value, name: 'plate' });
        },
        'date': (data) => {
            let flag = isDateStr(data.value), fail = renderTpl(options.lang['date'], { label: data.label, value: data.value });
            return flag ? { passed: true } : { passed: false, fail, type: 'date' };
        },
        /*
         *date range validation
         */
        ...loopDateRules(['date=', 'date<', 'date<=', 'date>', 'date>=', 'date><', 'date><=', 'date>=<', 'date>=<=']),
        /*
         * number range validation
         */
        ...loopThanRules(['than=', 'than>', 'than>=', 'than<', 'than<=', 'than><', 'than><=', 'than>=<', 'than>=<=',]),
        /*
         *string length validation
         */
        ...loopLengthRules(['length=', 'length>', 'length>=', 'length<', 'length<=', 'length><', 'length><=', 'length>=<', 'length>=<=']),
        /**
         * include and exclude validation
         */
        ...loopIncludeRules(['include', 'exclude']),
        /**
         * value total validation
         */
        ...loopValuesRules(['count=', 'count>', 'count>=', 'count<', 'count<=', 'count><', 'count><=', 'count>=<', 'count>=<=']),
        /**
        * same value validation
        */
        ...loopSameRules(['same', 'different']),
        /**
         * strength validation (0~6)
         */
        'strength': (data) => {
            if (typeof data.value !== 'string') {
                return { passed: false, fail: ax.lang.valid.strFormat };
            }
            let fail = renderTpl(options.lang['strength'], { label: data.label, name: data.name, value: strength, data: typeData['strength'] });
            return strength >= typeData['strength'] ? { passed: true } : { passed: false, fail, type: 'strength' };
        },
        /**
         * specific validation
         */
        'specific': (data) => {
            if (typeof data.value !== 'string') {
                return { passed: false, fail: ax.lang.valid.strFormat };
            }
            let parse = validTools.parseSpecific(typeData['specific'], { label: data.label, name: data.name, value: data.value, tpl: options.lang['specific'] }), regExp = parse.regex, regText = parse.text;
            return validTools.test(data.value, regExp) ? { passed: true } : { passed: false, fail: regText, type: 'specific' };
        },
        /**
        * combine validation
        */
        'combine': (data) => {
            if (typeof data.value !== 'string') {
                return { passed: false, fail: ax.lang.valid.strFormat };
            }
            let parse = validTools.parseCombine(typeData['combine'], { label: data.label, name: data.name, value: data.value, tpl: options.lang['combine'] }), regExp = parse.regex, regText = parse.text;
            return validTools.test(data.value, regExp) ? { passed: true } : { passed: false, fail: regText, type: 'combine' };
        },
        /**
         * async validation,data={label,name,value,strength}
         */
        'async': (data) => {
            return ajax.call(this, Object.assign({
                url: typeData['async'],
                data: {
                    label: data.label,
                    name: data.name,
                    value: data.value,
                    strength,
                },
            }, options.ajax));
        },
        ...options.extend
    };
    for (let k in typeData) {
        if (typeData.hasOwnProperty(k) && dftRules.hasOwnProperty(k)) {
            userRules[k] = dftRules[k];
            userRules[k].data = typeData[k];
        }
    }
    await validTools.factory({
        value,
        name: options.name,
        label: options.label,
        rules: userRules,
        text: {
            succ: options.succ,
            fail: options.fail,
        },
        cb: (resp) => {
            result = { ...resp, value, strength };
        }
    });
    return result;
};
export default validate;
