import * as React from 'react';
import {BasicConfig, BasicContainer, BasicContainerPropsInterface} from '../../render/core/Container/types';
import componentLoader from '../../render/util/componentLoader';
import {Form} from '@native-ads/antd';
import {createChild} from '../../render/util/createChild';
import {ColProps} from '@native-ads/antd/lib/grid/col';
import {applyRule} from './validate';
import {SET_FORM_ITEM_PAYLOAD} from '../../render/core/Form/actions';
import {compileExpressionString, isExpression, parseExpressionString} from '../../render/util/vm';
import {request} from '../../render/services/api';

export interface ValidateRules {
    /**
     * 字段长度
     */
    len?: number;

    /**
     * 最大值
     */
    max?: number;

    /**
     * 最大长度
     */
    maxLength?: number;

    /**
     * 最小值
     */
    min?: number;

    /**
     * 最小长度
     */
    minLength?: number;

    /**
     * 校验文案
     */
    message?: string;

    /**
     * 正则表达式
     */
    pattern?: string;

    /**
     * 是否必须
     */
    required?: boolean;

    /**
     * 必须时，空格是否会被认为是错误
     */
    whitespace?: boolean;
}

type ApiRule = {
    /**
     * 地址
     */
    url: string;
    /**
     * 请求方法
     */
    method: string;
    /**
     * 请求的数据
     */
    data: string;

    /**
     * 使用ExpressionString， 验证接口返回值是否合格
     */
    validate: string;

    /**
     * 错误信息
     */
    errmsg: string;
};

class FormItemConfig extends BasicConfig {
    /**
     * 表单文字
     */
    label?: string;

    /**
     * label 标签布局
     */
    labelCol?: ColProps;

    /**
     * 需要为输入控件设置布局样式时，使用该属性，用法同 labelCol
     */
    wrapperCol?: ColProps;

    /**
     * 使用内置的验证规则
     */
    rules?: ValidateRules[];

    /**
     * 使用ExpressionString来验证
     */
    filterRule?: string;

    /**
     * 使用ExpressionString验证失败的错误信息
     */
    filterErrMsg?: string;

    /**
     * 使用API来验证输入
     */
    apiRule?: ApiRule;

    /**
     * 显示反馈
     */
    feedback?: boolean;

    /**
     * 是否必须
     */
    required?: boolean;

    /**
     * 是否有反馈提示
     */
    hasFeedBack?: boolean;

    /**
     * 控制的表单元素
     */
    control: BasicConfig;

    /**
     * 额外的说明文字
     */
    extra?: string;
}

class FormItemPropsInterface extends BasicContainerPropsInterface {
    info: FormItemConfig;

    /**
     * 更新表单元素的属性
     */
    $setFormItem: (payload: Partial<SET_FORM_ITEM_PAYLOAD>) => void;

    /**
     * 删除表单元素的验证信息
     */
    $deleteFormItem: (formItemName: string) => void;
}

class AbstractFormItem extends BasicContainer<FormItemPropsInterface, {}> {
    private infoBlackList: string[];
    private isApiValidate: boolean;

    constructor(props: FormItemPropsInterface) {
        super(props);

        this.handleChange = this.handleChange.bind(this);
        this.handleDelete = this.handleDelete.bind(this);
        this.handleBlur = this.handleBlur.bind(this);
        this.eventHandle = this.eventHandle.bind(this);
        this.validateFormItem = this.validateFormItem.bind(this);
        this.apiValidate = this.apiValidate.bind(this);

        this.infoBlackList = ['filterRule', 'filterErrMsg'];
        this.isApiValidate = false;
    }

    componentWillMount() {
        let info = this.getPropsInfo(this.props.info, this.props, this.infoBlackList);
        let isHidden = info.hidden === true || info.show === false;

        if (info.control.name && this.props.$setFormItem && !isHidden) {
            let formItemName = info.control.name;
            let valid = !info.required;

            if (valid && info.rules) {
                valid = info.rules.some(rule => !rule.required);
            }

            this.props.$setFormItem({
                formItemName: formItemName,
                rules: info.rules,
                required: info.required,
                valid: valid
            });
        }
    }

    componentWillReceiveProps(nextProps: FormItemPropsInterface) {
        let info = this.getPropsInfo(nextProps.info, nextProps, this.infoBlackList);
        let name = info.control.name;
        if (nextProps.$data && name) {
            let nextValue = this.getValueFromDataStore(name, nextProps.$data);

            if (nextValue) {
                this.validateFormItem(name, nextValue);
            }

            if (nextProps.$form && nextProps.$form.control[name]) {
                let itemInfo = nextProps.$form.control[name];
                let shouldValidate = itemInfo.$validate;
                if (shouldValidate) {
                    this.validateFormItem(name, nextValue);
                    this.props.$setFormItem({
                        formItemName: name,
                        $validate: false
                    });
                }
            }
        }
    }

    shouldComponentUpdate() {
        return true;
    }

    private async apiValidate(apiRule: ApiRule, formItemName: string, value: any) {
        let runTime = this.getRuntimeContext();
        if (!apiRule.url) {
            console.error('至少提供一个请求的地址');
            return;
        }

        if (!apiRule.validate || !isExpression(apiRule.validate)) {
            console.error('请提供验证所需的ExpressionString');
            return;
        }

        let data;

        if (typeof apiRule.data === 'string') {
            data = parseExpressionString(apiRule.data, {
                ...runTime,
                $args: {
                    value: value
                }
            });
        } else {
            data = compileExpressionString(apiRule.data, {
                ...runTime,
                $args: {
                    value: value
                }
            });
        }

        let ret = await request(apiRule.url, {
            url: apiRule.url,
            method: apiRule.method,
            data: data
        }, runTime.$global.proxy);

        let isValid = parseExpressionString(apiRule.validate, {
            ...runTime,
            $args: {
                value: value
            },
            $output: ret.data
        });

        if (isValid) {
            this.props.$setFormItem({
                formItemName: formItemName,
                valid: true,
                status: 'success'
            });
        } else {
            let errmsg = apiRule.errmsg;
            if (isExpression(errmsg)) {
                errmsg = parseExpressionString(errmsg, {
                    ...runTime,
                    $args: {
                        value: value
                    },
                    $output: ret.data
                });
            }

            this.props.$setFormItem({
                formItemName: formItemName,
                valid: false,
                errorMsg: errmsg,
                status: 'error'
            });
        }

        this.forceUpdate();
        this.isApiValidate = false;
    }

    private validateFormItem(formItemName: string, data: any) {
        let info = this.getPropsInfo(this.props.info, this.props, this.infoBlackList);
        let rules = info.rules || [];
        let required = info.required;
        let filterRule = info.filterRule;
        let apiRule = info.apiRule;
        let runTime = this.getRuntimeContext();
        let isValid = true;
        let errmsg = '';

        if (required) {
            rules.push({
                required: true
            });
        }

        if (apiRule) {
            if (!this.isApiValidate) {
                this.apiValidate(apiRule, formItemName, data);
                this.isApiValidate = true;
                this.props.$setFormItem({
                    formItemName: formItemName,
                    valid: true,
                    status: 'validating'
                });
            }

            return;
        }

        if (typeof filterRule === 'string' && isExpression(filterRule)) {
            isValid = parseExpressionString(filterRule, {
                ...runTime,
                $args: {
                    value: data
                }
            });
            let filterErrMsg = info.filterErrMsg || '';
            if (typeof filterErrMsg === 'string' && isExpression(filterErrMsg)) {
                filterErrMsg = parseExpressionString(filterErrMsg, {
                    ...runTime,
                    $args: {
                        value: data
                    }
                });
            }
            errmsg = filterErrMsg;
        } else if (rules.length > 0) {
            let result = {
                message: '',
                valid: false
            };

            isValid = rules.every(rule => {
                result = applyRule(rule, data);

                return result.valid;
            });

            if (!isValid) {
                errmsg = result.message;
            }
        }

        if (isValid) {
            this.props.$setFormItem({
                formItemName: formItemName,
                valid: true,
                status: 'success'
            });
        } else {
            this.props.$setFormItem({
                formItemName: formItemName,
                valid: false,
                status: 'error',
                errorMsg: errmsg
            });
        }
    }

    private handleChange(name: string, data: any) {
        // 输入值改变，需要重置API请求锁
        this.isApiValidate = false;

        this.validateFormItem(name, data);

        if (this.props.$setData) {
            this.props.$setData(name, data);
        }
    }

    private handleDelete(name: string) {
        this.props.$deleteFormItem(name);

        if (this.props.$deleteData) {
            this.props.$deleteData(name);
        }
    }

    private handleBlur(args: Object) {
        let info = this.getPropsInfo(this.props.info, this.props, this.infoBlackList);
        let formItemName = info.control.name;

        if (formItemName) {
            let data = this.getValueFromDataStore(formItemName);
            this.validateFormItem(formItemName, data);
            this.forceUpdate();
        }
    }

    private eventHandle(eventName: string, args: Object) {
        switch (eventName) {
            case 'onBlur':
                this.handleBlur(args);
                break;
            default:
                break;
        }
    }

    componentDidUpdate() {
        let info = this.getPropsInfo(this.props.info, this.props, this.infoBlackList);

        if (this.props.$form && info.control.name) {
            let itemState = this.props.$form.control[info.control.name];
            if ((info.hidden === true || info.show === false) && itemState) {
                this.props.$deleteFormItem(info.control.name);
            }
        }
    }

    render() {
        let info = this.getPropsInfo(this.props.info, this.props, this.infoBlackList);

        if (!this.props.$form) {
            console.error('FormItem 组件必须在Form组件内部');
            return <div/>;
        }

        if (!info.control) {
            return this.errorReport('control property is required for FormGroup', 'div');
        }

        let control = info.control;
        let formItemChildren;
        let formItemName = info.control.name;
        let validateStatus: 'success' | 'warning' | 'error' | 'validating' | undefined;
        let validateErrMsg: string | undefined;
        let required = info.required || (info.rules && info.rules.some(rule => !!rule.required));

        if (formItemName && this.props.$form.control && this.props.$form.control[formItemName]) {
            let itemInfo = this.props.$form.control[formItemName];
            validateStatus = itemInfo.status;
            if (!itemInfo.valid) {
                validateErrMsg = itemInfo.errorMsg;
            }
        }

        if (control instanceof Array) {
            formItemChildren = control.map((child, index) => createChild(child, {
                ...this.props,
                info: child,
                key: `form_item_child_${child.name}`,
                $setData: this.handleChange,
                $deleteData: this.handleDelete,
                eventHandle: this.eventHandle
            }));
        } else {
            formItemChildren = createChild(control, {
                ...this.props,
                info: control,
                $setData: this.handleChange,
                $deleteData: this.handleDelete,
                eventHandle: this.eventHandle
            });
        }

        let labelCol: undefined | ColProps;
        let wrapperCol: undefined | ColProps;

        if (this.props.$form.layout === 'horizontal') {
            labelCol = info.labelCol || {span: 4};
            wrapperCol = info.wrapperCol || {span: 24 - labelCol.span!};
        }

        let hasFeedBack: boolean = true;
        if (info.hasFeedBack === false) {
            hasFeedBack = false;
        }

        const childElement = (
            <Form.Item
                label={info.label}
                className={info.className}
                style={{
                    width: '100%',
                    ...info.style
                }}
                required={required}
                labelCol={labelCol}
                wrapperCol={wrapperCol}
                hasFeedback={hasFeedBack}
                validateStatus={validateStatus}
                help={validateErrMsg}
                extra={info.extra}
            >
                {formItemChildren}
            </Form.Item>
        );

        return this.renderChildren(info, childElement);
    }
}

componentLoader.addComponent('formItem', AbstractFormItem, FormItemPropsInterface);
